1 /***************************************************************************
2                          testprocesinggui.cpp
3                          ---------------------------
4     begin                : April 2018
5     copyright            : (C) 2018 by Matthias Kuhn
6     email                : matthias@opengis.ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include <QObject>
19 #include <QLabel>
20 #include <QCheckBox>
21 #include <QComboBox>
22 #include <QStackedWidget>
23 #include <QToolButton>
24 #include <QLineEdit>
25 #include <QPushButton>
26 #include <QPlainTextEdit>
27 #include <QStandardItemModel>
28 #include <QSignalSpy>
29 
30 #include "qgstest.h"
31 #include "qgsconfig.h"
32 #include "qgsgui.h"
33 #include "qgsprocessingguiregistry.h"
34 #include "qgsprocessingregistry.h"
35 #include "qgsprocessingalgorithm.h"
36 #include "qgsprocessingalgorithmconfigurationwidget.h"
37 #include "qgsprocessingwidgetwrapper.h"
38 #include "qgsprocessingwidgetwrapperimpl.h"
39 #include "qgsprocessingmodelerparameterwidget.h"
40 #include "qgsprocessingparameters.h"
41 #include "qgsmodelundocommand.h"
42 #include "qgsprocessingmaplayercombobox.h"
43 #include "qgsnativealgorithms.h"
44 #include "processing/models/qgsprocessingmodelalgorithm.h"
45 #include "processing/models/qgsprocessingmodelgroupbox.h"
46 #include "qgsxmlutils.h"
47 #include "qgspropertyoverridebutton.h"
48 #include "qgsprojectionselectionwidget.h"
49 #include "qgsdoublespinbox.h"
50 #include "qgsspinbox.h"
51 #include "qgsmapcanvas.h"
52 #include "qgsauthconfigselect.h"
53 #include "qgsauthmanager.h"
54 #include "qgsprocessingmatrixparameterdialog.h"
55 #include "models/qgsprocessingmodelalgorithm.h"
56 #include "qgsfilewidget.h"
57 #include "qgsexpressionlineedit.h"
58 #include "qgsfieldexpressionwidget.h"
59 #include "qgsprocessingmultipleselectiondialog.h"
60 #include "qgsprintlayout.h"
61 #include "qgslayoutmanager.h"
62 #include "qgslayoutcombobox.h"
63 #include "qgslayoutitemcombobox.h"
64 #include "qgslayoutitemlabel.h"
65 #include "qgsscalewidget.h"
66 #include "mesh/qgsmeshlayer.h"
67 #include "mesh/qgsmeshdataprovider.h"
68 #include "qgscolorbutton.h"
69 #include "qgsprocessingparameterdefinitionwidget.h"
70 #include "qgscoordinateoperationwidget.h"
71 #include "qgsmessagebar.h"
72 #include "qgsfieldcombobox.h"
73 #include "qgsmapthemecollection.h"
74 #include "qgsdatetimeedit.h"
75 #include "qgsproviderregistry.h"
76 #include "qgsprovidermetadata.h"
77 #include "qgsproviderconnectioncombobox.h"
78 #include "qgsdatabaseschemacombobox.h"
79 #include "qgsdatabasetablecombobox.h"
80 #include "qgsprocessingoutputdestinationwidget.h"
81 #include "qgssettings.h"
82 #include "qgsprocessingfeaturesourceoptionswidget.h"
83 #include "qgsextentwidget.h"
84 #include "qgsrasterbandcombobox.h"
85 #include "qgsmeshlayertemporalproperties.h"
86 #include "qgsmodelgraphicsscene.h"
87 #include "qgsmodelgraphicsview.h"
88 #include "qgsmodelcomponentgraphicitem.h"
89 #include "qgsprocessingfieldmapwidgetwrapper.h"
90 #include "qgsprocessingparameterfieldmap.h"
91 #include "qgsprocessingaggregatewidgetwrapper.h"
92 #include "qgsprocessingparameteraggregate.h"
93 #include "qgsprocessingparametertininputlayers.h"
94 #include "qgsprocessingtininputlayerswidget.h"
95 #include "qgsprocessingparameterdxflayers.h"
96 #include "qgsprocessingdxflayerswidgetwrapper.h"
97 #include "qgsprocessingmeshdatasetwidget.h"
98 #include "qgsabstractdatabaseproviderconnection.h"
99 #include "qgspluginlayer.h"
100 #include "qgspointcloudlayer.h"
101 #include "qgsannotationlayer.h"
102 
103 
104 class TestParamType : public QgsProcessingParameterDefinition
105 {
106   public:
107 
TestParamType(const QString & type,const QString & name,const QVariant & defaultValue=QVariant ())108     TestParamType( const QString &type, const QString &name, const QVariant &defaultValue = QVariant() )
109       : QgsProcessingParameterDefinition( name, name, defaultValue )
110       , mType( type )
111     {}
112 
113     QString mType;
114 
clone() const115     QgsProcessingParameterDefinition *clone() const override
116     {
117       return new TestParamType( mType, name() );
118     }
119 
type() const120     QString type() const override { return mType; }
valueAsPythonString(const QVariant &,QgsProcessingContext &) const121     QString valueAsPythonString( const QVariant &, QgsProcessingContext & ) const override { return QString(); }
asScriptCode() const122     QString asScriptCode() const override { return QString(); }
123 
124 };
125 
126 class TestWidgetWrapper : public QgsAbstractProcessingParameterWidgetWrapper // clazy:exclude=missing-qobject-macro
127 {
128   public:
129 
TestWidgetWrapper(const QgsProcessingParameterDefinition * parameter=nullptr,QgsProcessingGui::WidgetType type=QgsProcessingGui::Standard)130     TestWidgetWrapper( const QgsProcessingParameterDefinition *parameter = nullptr,
131                        QgsProcessingGui::WidgetType type = QgsProcessingGui::Standard )
132       : QgsAbstractProcessingParameterWidgetWrapper( parameter, type )
133     {}
134 
createWidget()135     QWidget *createWidget() override
136     {
137       return nullptr;
138     }
139 
createLabel()140     QLabel *createLabel() override
141     {
142       return nullptr;
143     }
144 
setWidgetValue(const QVariant &,QgsProcessingContext &)145     void setWidgetValue( const QVariant &, QgsProcessingContext & ) override
146     {
147     }
148 
widgetValue() const149     QVariant widgetValue() const override
150     {
151       return QVariant();
152     }
153 
154 };
155 
156 class TestWidgetFactory : public QgsProcessingParameterWidgetFactoryInterface
157 {
158 
159   public:
160 
TestWidgetFactory(const QString & type)161     TestWidgetFactory( const QString &type )
162       : type( type )
163     {}
164 
165     QString type;
166 
parameterType() const167     QString parameterType() const override
168     {
169       return type;
170     }
171 
createWidgetWrapper(const QgsProcessingParameterDefinition * parameter,QgsProcessingGui::WidgetType type)172     QgsAbstractProcessingParameterWidgetWrapper *createWidgetWrapper( const QgsProcessingParameterDefinition *parameter,
173         QgsProcessingGui::WidgetType type ) override
174     {
175       return new TestWidgetWrapper( parameter, type );
176     }
177 
178 
compatibleParameterTypes() const179     QStringList compatibleParameterTypes() const override { return QStringList(); }
180 
compatibleOutputTypes() const181     QStringList compatibleOutputTypes() const override { return QStringList(); }
182 
183 };
184 
185 class DummyPluginLayer: public QgsPluginLayer
186 {
187   public:
188 
DummyPluginLayer(const QString & layerType,const QString & layerName)189     DummyPluginLayer( const QString &layerType, const QString &layerName ): QgsPluginLayer( layerType, layerName )
190     {
191       mValid = true;
192     };
193 
clone() const194     DummyPluginLayer *clone() const override { return new DummyPluginLayer( "dummylayer", "test" ); };
195 
createMapRenderer(QgsRenderContext & rendererContext)196     QgsMapLayerRenderer *createMapRenderer( QgsRenderContext &rendererContext ) override
197     {
198       Q_UNUSED( rendererContext );
199       return nullptr;
200     };
201 
writeXml(QDomNode & layerNode,QDomDocument & doc,const QgsReadWriteContext & context) const202     bool writeXml( QDomNode &layerNode, QDomDocument &doc, const QgsReadWriteContext &context ) const override
203     {
204       Q_UNUSED( layerNode );
205       Q_UNUSED( doc );
206       Q_UNUSED( context );
207       return true;
208     };
readSymbology(const QDomNode & node,QString & errorMessage,QgsReadWriteContext & context,StyleCategories categories=AllStyleCategories)209     bool readSymbology( const QDomNode &node, QString &errorMessage,
210                         QgsReadWriteContext &context, StyleCategories categories = AllStyleCategories ) override
211     {
212       Q_UNUSED( node );
213       Q_UNUSED( errorMessage );
214       Q_UNUSED( context );
215       Q_UNUSED( categories );
216       return true;
217     };
writeSymbology(QDomNode & node,QDomDocument & doc,QString & errorMessage,const QgsReadWriteContext & context,StyleCategories categories=AllStyleCategories) const218     bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context,
219                          StyleCategories categories = AllStyleCategories ) const override
220     {
221       Q_UNUSED( node );
222       Q_UNUSED( doc );
223       Q_UNUSED( errorMessage );
224       Q_UNUSED( context );
225       Q_UNUSED( categories );
226       return true;
227     };
228 
setTransformContext(const QgsCoordinateTransformContext & transformContext)229     void setTransformContext( const QgsCoordinateTransformContext &transformContext ) override { Q_UNUSED( transformContext ); };
230 };
231 
232 class TestProcessingGui : public QObject
233 {
234     Q_OBJECT
235   public:
236     TestProcessingGui() = default;
237 
238   private slots:
239     void initTestCase();// will be called before the first testfunction is executed.
240     void cleanupTestCase();// will be called after the last testfunction was executed.
241     void init();// will be called before each testfunction is executed.
242     void cleanup();// will be called after every testfunction.
243     void testModelUndo();
244     void testSetGetConfig();
245     void testFilterAlgorithmConfig();
246     void testWrapperFactoryRegistry();
247     void testWrapperGeneral();
248     void testWrapperDynamic();
249     void testModelerWrapper();
250     void testHiddenWrapper();
251     void testBooleanWrapper();
252     void testStringWrapper();
253     void testFileWrapper();
254     void testAuthCfgWrapper();
255     void testCrsWrapper();
256     void testNumericWrapperDouble();
257     void testNumericWrapperInt();
258     void testDistanceWrapper();
259     void testDurationWrapper();
260     void testScaleWrapper();
261     void testRangeWrapper();
262     void testMatrixDialog();
263     void testMatrixWrapper();
264     void testExpressionWrapper();
265     void testFieldSelectionPanel();
266     void testFieldWrapper();
267     void testMultipleSelectionDialog();
268     void testMultipleFileSelectionDialog();
269     void testRasterBandSelectionPanel();
270     void testBandWrapper();
271     void testMultipleInputWrapper();
272     void testEnumSelectionPanel();
273     void testEnumCheckboxPanel();
274     void testEnumWrapper();
275     void testLayoutWrapper();
276     void testLayoutItemWrapper();
277     void testPointPanel();
278     void testPointWrapper();
279     void testGeometryWrapper();
280     void testExtentWrapper();
281     void testColorWrapper();
282     void testCoordinateOperationWrapper();
283     void mapLayerComboBox();
284     void testMapLayerWrapper();
285     void testRasterLayerWrapper();
286     void testVectorLayerWrapper();
287     void testFeatureSourceWrapper();
288     void testMeshLayerWrapper();
289     void paramConfigWidget();
290     void testMapThemeWrapper();
291     void testDateTimeWrapper();
292     void testProviderConnectionWrapper();
293     void testDatabaseSchemaWrapper();
294     void testDatabaseTableWrapper();
295     void testPointCloudLayerWrapper();
296     void testAnnotationLayerWrapper();
297     void testFieldMapWidget();
298     void testFieldMapWrapper();
299     void testAggregateWidget();
300     void testAggregateWrapper();
301     void testOutputDefinitionWidget();
302     void testOutputDefinitionWidgetVectorOut();
303     void testOutputDefinitionWidgetRasterOut();
304     void testOutputDefinitionWidgetFolder();
305     void testOutputDefinitionWidgetFileOut();
306     void testFeatureSourceOptionsWidget();
307     void testVectorOutWrapper();
308     void testSinkWrapper();
309     void testRasterOutWrapper();
310     void testFileOutWrapper();
311     void testFolderOutWrapper();
312     void testTinInputLayerWrapper();
313     void testDxfLayersWrapper();
314     void testMeshDatasetWrapperLayerInProject();
315     void testMeshDatasetWrapperLayerOutsideProject();
316     void testModelGraphicsView();
317 
318   private:
319 
320     QString mTempDir;
321     const char *mPass = "pass";
322 
323     void cleanupTempDir();
324 };
325 
326 
initTestCase()327 void TestProcessingGui::initTestCase()
328 {
329   mTempDir = QDir::tempPath() + "/auth_proc";
330   // setup a temporary auth db:
331   cleanupTempDir();
332 
333   // make QGIS_AUTH_DB_DIR_PATH temp dir for qgis - auth.db and master password file
334   QDir tmpDir = QDir::temp();
335   QVERIFY2( tmpDir.mkpath( mTempDir ), "Couldn't make temp directory" );
336   qputenv( "QGIS_AUTH_DB_DIR_PATH", mTempDir.toLatin1() );
337 
338   // init app and auth manager
339   QgsApplication::init();
340   QgsApplication::initQgis();
341   QVERIFY2( !QgsApplication::authManager()->isDisabled(),
342             "Authentication system is DISABLED" );
343 
344   // verify QGIS_AUTH_DB_DIR_PATH (temp auth db path) worked
345   QString db1( QFileInfo( QgsApplication::authManager()->authenticationDatabasePath() ).canonicalFilePath() );
346   QString db2( QFileInfo( mTempDir + "/qgis-auth.db" ).canonicalFilePath() );
347   QVERIFY2( db1 == db2, "Auth db temp path does not match db path of manager" );
348 
349   // verify master pass can be set manually
350   // (this also creates a fresh password hash in the new temp database)
351   QVERIFY2( QgsApplication::authManager()->setMasterPassword( mPass, true ),
352             "Master password could not be set" );
353   QVERIFY2( QgsApplication::authManager()->masterPasswordIsSet(),
354             "Auth master password not set from passed string" );
355 
356   // create QGIS_AUTH_PASSWORD_FILE file
357   QString passfilepath = mTempDir + "/passfile";
358   QFile passfile( passfilepath );
359   if ( passfile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
360   {
361     QTextStream fout( &passfile );
362     fout << QString( mPass ) << "\r\n";
363     passfile.close();
364     qputenv( "QGIS_AUTH_PASSWORD_FILE", passfilepath.toLatin1() );
365   }
366 
367   // re-init app and auth manager
368   QgsApplication::quit();
369   QgsApplication::init();
370   QgsApplication::initQgis();
371   QVERIFY2( !QgsApplication::authManager()->isDisabled(),
372             "Authentication system is DISABLED" );
373 
374   // verify QGIS_AUTH_PASSWORD_FILE worked, when compared against hash in db
375   QVERIFY2( QgsApplication::authManager()->masterPasswordIsSet(),
376             "Auth master password not set from QGIS_AUTH_PASSWORD_FILE" );
377 
378   QgsApplication::processingRegistry()->addProvider( new QgsNativeAlgorithms( QgsApplication::processingRegistry() ) );
379 }
380 
cleanupTestCase()381 void TestProcessingGui::cleanupTestCase()
382 {
383   QgsApplication::exitQgis();
384 }
init()385 void TestProcessingGui::init()
386 {
387 
388 }
389 
cleanup()390 void TestProcessingGui::cleanup()
391 {
392 
393 }
394 
testModelUndo()395 void TestProcessingGui::testModelUndo()
396 {
397   // make a little model
398   QgsProcessingModelAlgorithm model( QStringLiteral( "test" ), QStringLiteral( "testGroup" ) );
399   QMap<QString, QgsProcessingModelChildAlgorithm> algs;
400   QgsProcessingModelChildAlgorithm a1( "native:buffer" );
401   a1.setDescription( QStringLiteral( "alg1" ) );
402   a1.setChildId( QStringLiteral( "alg1" ) );
403   QgsProcessingModelChildAlgorithm a2;
404   a2.setDescription( QStringLiteral( "alg2" ) );
405   a2.setChildId( QStringLiteral( "alg2" ) );
406   QgsProcessingModelChildAlgorithm a3( QStringLiteral( "native:buffer" ) );
407   a3.setDescription( QStringLiteral( "alg3" ) );
408   a3.setChildId( QStringLiteral( "alg3" ) );
409   algs.insert( QStringLiteral( "alg1" ), a1 );
410   algs.insert( QStringLiteral( "alg2" ), a2 );
411   algs.insert( QStringLiteral( "alg3" ), a3 );
412   model.setChildAlgorithms( algs );
413 
414   QgsModelUndoCommand command( &model, QStringLiteral( "undo" ) );
415   model.childAlgorithm( QStringLiteral( "alg1" ) ).setDescription( QStringLiteral( "new desc" ) );
416   command.saveAfterState();
417 
418   QCOMPARE( model.childAlgorithm( QStringLiteral( "alg1" ) ).description(), QStringLiteral( "new desc" ) );
419   command.undo();
420   QCOMPARE( model.childAlgorithm( QStringLiteral( "alg1" ) ).description(), QStringLiteral( "alg1" ) );
421 
422   // first redo should have no effect -- we ignore it, since it's automatically triggered when the
423   // command is added to the stack (yet we apply the initial change to the models outside of the undo stack)
424   command.redo();
425   QCOMPARE( model.childAlgorithm( QStringLiteral( "alg1" ) ).description(), QStringLiteral( "alg1" ) );
426   command.redo();
427   QCOMPARE( model.childAlgorithm( QStringLiteral( "alg1" ) ).description(), QStringLiteral( "new desc" ) );
428 
429   // the last used parameter values setting should not be affected by undo stack changes
430   QVariantMap params;
431   params.insert( QStringLiteral( "a" ), 1 );
432   model.setDesignerParameterValues( params );
433   command.undo();
434   QCOMPARE( model.designerParameterValues(), params );
435   command.redo();
436   QCOMPARE( model.designerParameterValues(), params );
437 }
438 
testSetGetConfig()439 void TestProcessingGui::testSetGetConfig()
440 {
441   const QList< const QgsProcessingAlgorithm * > algorithms = QgsApplication::instance()->processingRegistry()->algorithms();
442 
443   // Find all defined widgets for native algorithms
444   // and get the default configuration (that is, we create a widget
445   // and get the configuration it returns without being modified in any way)
446   // We then set and get this configuration and validate that it matches the original one.
447   for ( const QgsProcessingAlgorithm *algorithm : algorithms )
448   {
449     std::unique_ptr<QgsProcessingAlgorithmConfigurationWidget> configWidget( QgsGui::instance()->processingGuiRegistry()->algorithmConfigurationWidget( algorithm ) );
450     if ( configWidget )
451     {
452       QCOMPARE( configWidget->algorithm(), algorithm );
453       const QVariantMap defaultConfig = configWidget->configuration();
454       configWidget->setConfiguration( defaultConfig );
455       const QVariantMap defaultControlConfig = configWidget->configuration();
456       QDomDocument defaultConfigDoc;
457       QDomDocument defaultConfigControlDoc;
458       QgsXmlUtils::writeVariant( defaultConfig, defaultConfigDoc );
459       QgsXmlUtils::writeVariant( defaultControlConfig, defaultConfigControlDoc );
460       QCOMPARE( defaultConfigDoc.toString(), defaultConfigControlDoc.toString() );
461     }
462   }
463 }
464 
testFilterAlgorithmConfig()465 void TestProcessingGui::testFilterAlgorithmConfig()
466 {
467   const QgsProcessingAlgorithm *algorithm = QgsApplication::instance()->processingRegistry()->algorithmById( QStringLiteral( "native:filter" ) );
468   std::unique_ptr<QgsProcessingAlgorithmConfigurationWidget> configWidget( QgsGui::instance()->processingGuiRegistry()->algorithmConfigurationWidget( algorithm ) );
469 
470   QVariantMap config;
471   QVariantList outputs;
472   QVariantMap output;
473   output.insert( QStringLiteral( "name" ), QStringLiteral( "test" ) );
474   output.insert( QStringLiteral( "expression" ), QStringLiteral( "I am an expression" ) );
475   output.insert( QStringLiteral( "isModelOutput" ), true );
476   outputs.append( output );
477   config.insert( QStringLiteral( "outputs" ), outputs );
478   configWidget->setConfiguration( config );
479 
480   QVariantMap configControl = configWidget->configuration();
481 
482   QDomDocument configDoc;
483   QDomDocument configControlDoc;
484   QgsXmlUtils::writeVariant( config, configDoc );
485   QgsXmlUtils::writeVariant( configControl, configControlDoc );
486   QCOMPARE( configDoc.toString(), configControlDoc.toString() );
487 }
488 
testWrapperFactoryRegistry()489 void TestProcessingGui::testWrapperFactoryRegistry()
490 {
491   QgsProcessingGuiRegistry registry;
492 
493   TestParamType numParam( QStringLiteral( "num" ), QStringLiteral( "num" ) );
494   TestParamType stringParam( QStringLiteral( "str" ), QStringLiteral( "str" ) );
495 
496   QVERIFY( !registry.createParameterWidgetWrapper( &numParam, QgsProcessingGui::Standard ) );
497 
498   TestWidgetFactory *factory = new TestWidgetFactory( QStringLiteral( "str" ) );
499   QVERIFY( registry.addParameterWidgetFactory( factory ) );
500 
501   // duplicate type not allowed
502   TestWidgetFactory *factory2 = new TestWidgetFactory( QStringLiteral( "str" ) );
503   QVERIFY( !registry.addParameterWidgetFactory( factory2 ) );
504   delete factory2;
505 
506   QgsAbstractProcessingParameterWidgetWrapper *wrapper = registry.createParameterWidgetWrapper( &numParam, QgsProcessingGui::Standard );
507   QVERIFY( !wrapper );
508   wrapper = registry.createParameterWidgetWrapper( &stringParam, QgsProcessingGui::Standard );
509   QVERIFY( wrapper );
510   QCOMPARE( wrapper->parameterDefinition()->type(), QStringLiteral( "str" ) );
511   delete wrapper;
512 
513   TestWidgetFactory *factory3 = new TestWidgetFactory( QStringLiteral( "num" ) );
514   QVERIFY( registry.addParameterWidgetFactory( factory3 ) );
515 
516   wrapper = registry.createParameterWidgetWrapper( &numParam, QgsProcessingGui::Standard );
517   QVERIFY( wrapper );
518   QCOMPARE( wrapper->parameterDefinition()->type(), QStringLiteral( "num" ) );
519   delete wrapper;
520 
521   // removing
522   registry.removeParameterWidgetFactory( nullptr );
523   TestWidgetFactory *factory4 = new TestWidgetFactory( QStringLiteral( "xxxx" ) );
524   registry.removeParameterWidgetFactory( factory4 );
525   registry.removeParameterWidgetFactory( factory );
526   wrapper = registry.createParameterWidgetWrapper( &stringParam, QgsProcessingGui::Standard );
527   QVERIFY( !wrapper );
528 
529   wrapper = registry.createParameterWidgetWrapper( &numParam, QgsProcessingGui::Standard );
530   QVERIFY( wrapper );
531   QCOMPARE( wrapper->parameterDefinition()->type(), QStringLiteral( "num" ) );
532   delete wrapper;
533 }
534 
testWrapperGeneral()535 void TestProcessingGui::testWrapperGeneral()
536 {
537   TestParamType param( QStringLiteral( "boolean" ), QStringLiteral( "bool" ) );
538   param.setAdditionalExpressionContextVariables( QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
539   QgsProcessingBooleanWidgetWrapper wrapper( &param );
540   QCOMPARE( wrapper.type(), QgsProcessingGui::Standard );
541 
542   QgsExpressionContext expContext = wrapper.createExpressionContext();
543   QVERIFY( expContext.hasVariable( QStringLiteral( "a" ) ) );
544   QVERIFY( expContext.hasVariable( QStringLiteral( "b" ) ) );
545   QCOMPARE( expContext.highlightedVariables(), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
546 
547   QgsProcessingBooleanWidgetWrapper wrapper2( &param, QgsProcessingGui::Batch );
548   QCOMPARE( wrapper2.type(), QgsProcessingGui::Batch );
549   QCOMPARE( wrapper2.parameterDefinition()->name(), QStringLiteral( "bool" ) );
550 
551   QgsProcessingBooleanWidgetWrapper wrapperModeler( &param, QgsProcessingGui::Modeler );
552   QCOMPARE( wrapperModeler.type(), QgsProcessingGui::Modeler );
553 
554   QgsProcessingContext context;
555   QVERIFY( !wrapper2.wrappedWidget() );
556   QWidget *w = wrapper2.createWrappedWidget( context );
557   QVERIFY( w );
558   QCOMPARE( wrapper2.wrappedWidget(), w );
559   delete w;
560   QVERIFY( !wrapper2.wrappedLabel() );
561   QLabel *l = wrapper2.createWrappedLabel();
562   QCOMPARE( wrapper2.wrappedLabel(), l );
563   delete l;
564   l = wrapperModeler.createWrappedLabel();
565   QVERIFY( l );
566   QCOMPARE( wrapperModeler.wrappedLabel(), l );
567   delete l;
568 
569   // check that created widget starts with default value
570   param = TestParamType( QStringLiteral( "boolean" ), QStringLiteral( "bool" ), true );
571   QgsProcessingBooleanWidgetWrapper trueDefault( &param );
572   w = trueDefault.createWrappedWidget( context );
573   QVERIFY( trueDefault.widgetValue().toBool() );
574   delete w;
575   param = TestParamType( QStringLiteral( "boolean" ), QStringLiteral( "bool" ), false );
576   QgsProcessingBooleanWidgetWrapper falseDefault( &param );
577   w = falseDefault.createWrappedWidget( context );
578   QVERIFY( !falseDefault.widgetValue().toBool() );
579   delete w;
580 
581   std::unique_ptr< QgsMapCanvas > mc = std::make_unique< QgsMapCanvas >();
582   QgsProcessingParameterWidgetContext widgetContext;
583   widgetContext.setMapCanvas( mc.get() );
584   QCOMPARE( widgetContext.mapCanvas(), mc.get() );
585 
586   std::unique_ptr< QgsMessageBar > mb = std::make_unique< QgsMessageBar >();
587   widgetContext.setMessageBar( mb.get() );
588   QCOMPARE( widgetContext.messageBar(), mb.get() );
589 
590   QgsProject p;
591   widgetContext.setProject( &p );
592   QCOMPARE( widgetContext.project(), &p );
593   std::unique_ptr< QgsProcessingModelAlgorithm > model = std::make_unique< QgsProcessingModelAlgorithm >();
594   widgetContext.setModel( model.get() );
595   QCOMPARE( widgetContext.model(), model.get() );
596   widgetContext.setModelChildAlgorithmId( QStringLiteral( "xx" ) );
597   QCOMPARE( widgetContext.modelChildAlgorithmId(), QStringLiteral( "xx" ) );
598 
599   wrapper.setWidgetContext( widgetContext );
600   QCOMPARE( wrapper.widgetContext().mapCanvas(), mc.get() );
601   QCOMPARE( wrapper.widgetContext().messageBar(), mb.get() );
602   QCOMPARE( wrapper.widgetContext().model(), model.get() );
603   QCOMPARE( wrapper.widgetContext().modelChildAlgorithmId(), QStringLiteral( "xx" ) );
604 }
605 
606 class TestProcessingContextGenerator : public QgsProcessingContextGenerator
607 {
608   public:
609 
TestProcessingContextGenerator(QgsProcessingContext & context)610     TestProcessingContextGenerator( QgsProcessingContext &context )
611       : mContext( context )
612     {}
613 
processingContext()614     QgsProcessingContext *processingContext() override
615     {
616       return &mContext;
617     }
618 
619     QgsProcessingContext &mContext;
620 };
621 
622 
623 class TestLayerWrapper : public QgsAbstractProcessingParameterWidgetWrapper // clazy:exclude=missing-qobject-macro
624 {
625   public:
TestLayerWrapper(const QgsProcessingParameterDefinition * parameter=nullptr)626     TestLayerWrapper( const QgsProcessingParameterDefinition *parameter = nullptr )
627       : QgsAbstractProcessingParameterWidgetWrapper( parameter )
628     {}
createWidget()629     QWidget *createWidget() override { return nullptr; }
setWidgetValue(const QVariant & val,QgsProcessingContext &)630     void setWidgetValue( const QVariant &val, QgsProcessingContext & ) override { v = val;}
widgetValue() const631     QVariant widgetValue() const override { return v; }
632 
633     QVariant v;
634 };
635 
testWrapperDynamic()636 void TestProcessingGui::testWrapperDynamic()
637 {
638   const QgsProcessingAlgorithm *centroidAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:centroids" ) );
639   const QgsProcessingParameterDefinition *layerDef = centroidAlg->parameterDefinition( QStringLiteral( "INPUT" ) );
640   const QgsProcessingParameterDefinition *allPartsDef = centroidAlg->parameterDefinition( QStringLiteral( "ALL_PARTS" ) );
641 
642   QgsProcessingBooleanWidgetWrapper inputWrapper( layerDef, QgsProcessingGui::Standard );
643   QgsProcessingBooleanWidgetWrapper allPartsWrapper( allPartsDef, QgsProcessingGui::Standard );
644 
645   QgsProcessingContext context;
646 
647   std::unique_ptr< QWidget > allPartsWidget( allPartsWrapper.createWrappedWidget( context ) );
648   // dynamic parameter, so property button should be created
649   QVERIFY( allPartsWrapper.mPropertyButton.data() != nullptr );
650 
651   std::unique_ptr< QWidget > inputWidget( inputWrapper.createWrappedWidget( context ) );
652   // not dynamic parameter, so property button should be NOT created
653   QVERIFY( inputWrapper.mPropertyButton.data() == nullptr );
654 
655   // set dynamic parameter to dynamic value
656   allPartsWrapper.setParameterValue( QgsProperty::fromExpression( QStringLiteral( "1+2" ) ), context );
657   QCOMPARE( allPartsWrapper.parameterValue().value< QgsProperty >().expressionString(), QStringLiteral( "1+2" ) );
658   // not dynamic value
659   allPartsWrapper.setParameterValue( true, context );
660   QCOMPARE( allPartsWrapper.parameterValue().toBool(), true );
661   allPartsWrapper.setParameterValue( false, context );
662   QCOMPARE( allPartsWrapper.parameterValue().toBool(), false );
663 
664   // project layer
665   QgsProject p;
666   QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
667   p.addMapLayer( vl );
668 
669   TestLayerWrapper layerWrapper( layerDef );
670 
671   QVERIFY( !allPartsWrapper.mPropertyButton->vectorLayer() );
672   layerWrapper.setWidgetValue( QVariant::fromValue( vl ), context );
673   allPartsWrapper.setDynamicParentLayerParameter( &layerWrapper );
674   QCOMPARE( allPartsWrapper.mPropertyButton->vectorLayer(), vl );
675   // should not be owned by wrapper
676   QVERIFY( !allPartsWrapper.mDynamicLayer.get() );
677   layerWrapper.setWidgetValue( QVariant(), context );
678   allPartsWrapper.setDynamicParentLayerParameter( &layerWrapper );
679   QVERIFY( !allPartsWrapper.mPropertyButton->vectorLayer() );
680 
681   layerWrapper.setWidgetValue( vl->id(), context );
682   allPartsWrapper.setDynamicParentLayerParameter( &layerWrapper );
683   QVERIFY( !allPartsWrapper.mPropertyButton->vectorLayer() );
684   QVERIFY( !allPartsWrapper.mDynamicLayer.get() );
685 
686   // with project layer
687   context.setProject( &p );
688   TestProcessingContextGenerator generator( context );
689   allPartsWrapper.registerProcessingContextGenerator( &generator );
690 
691   layerWrapper.setWidgetValue( vl->id(), context );
692   allPartsWrapper.setDynamicParentLayerParameter( &layerWrapper );
693   QCOMPARE( allPartsWrapper.mPropertyButton->vectorLayer(), vl );
694   QVERIFY( !allPartsWrapper.mDynamicLayer.get() );
695 
696   // non-project layer
697   QString pointFileName = TEST_DATA_DIR + QStringLiteral( "/points.shp" );
698   layerWrapper.setWidgetValue( pointFileName, context );
699   allPartsWrapper.setDynamicParentLayerParameter( &layerWrapper );
700   QCOMPARE( allPartsWrapper.mPropertyButton->vectorLayer()->publicSource(), pointFileName );
701   // must be owned by wrapper, or layer may be deleted while still required by wrapper
702   QCOMPARE( allPartsWrapper.mDynamicLayer->publicSource(), pointFileName );
703 }
704 
testModelerWrapper()705 void TestProcessingGui::testModelerWrapper()
706 {
707   // make a little model
708   QgsProcessingModelAlgorithm model( QStringLiteral( "test" ), QStringLiteral( "testGroup" ) );
709   QMap<QString, QgsProcessingModelChildAlgorithm> algs;
710   QgsProcessingModelChildAlgorithm a1( "native:buffer" );
711   a1.setDescription( QStringLiteral( "alg1" ) );
712   a1.setChildId( QStringLiteral( "alg1" ) );
713   QgsProcessingModelChildAlgorithm a2;
714   a2.setDescription( QStringLiteral( "alg2" ) );
715   a2.setChildId( QStringLiteral( "alg2" ) );
716   QgsProcessingModelChildAlgorithm a3( QStringLiteral( "native:buffer" ) );
717   a3.setDescription( QStringLiteral( "alg3" ) );
718   a3.setChildId( QStringLiteral( "alg3" ) );
719   QgsProcessingModelChildAlgorithm a4( QStringLiteral( "native:package" ) );
720   a4.setDescription( QStringLiteral( "alg4" ) );
721   a4.setChildId( QStringLiteral( "alg4" ) );
722   algs.insert( QStringLiteral( "alg1" ), a1 );
723   algs.insert( QStringLiteral( "alg2" ), a2 );
724   algs.insert( QStringLiteral( "alg3" ), a3 );
725   algs.insert( QStringLiteral( "alg4" ), a4 );
726   model.setChildAlgorithms( algs );
727 
728   QMap<QString, QgsProcessingModelParameter> pComponents;
729   QgsProcessingModelParameter pc1;
730   pc1.setParameterName( QStringLiteral( "my_param" ) );
731   pComponents.insert( QStringLiteral( "my_param" ), pc1 );
732   model.setParameterComponents( pComponents );
733 
734   QgsProcessingModelParameter bool1( "p1" );
735   model.addModelParameter( new QgsProcessingParameterBoolean( "p1", "desc" ), bool1 );
736   QgsProcessingModelParameter testParam( "p2" );
737   model.addModelParameter( new TestParamType( "test_type", "p2" ), testParam );
738   QgsProcessingModelParameter testDestParam( "p3" );
739   model.addModelParameter( new QgsProcessingParameterFileDestination( "test_dest", "p3" ), testDestParam );
740   // try to create a parameter widget, no factories registered
741   QgsProcessingGuiRegistry registry;
742   QgsProcessingContext context;
743   QVERIFY( !registry.createModelerParameterWidget( &model, QStringLiteral( "a" ), model.parameterDefinition( "p2" ), context ) );
744 
745   // register factory
746   TestWidgetFactory *factory = new TestWidgetFactory( QStringLiteral( "test_type" ) );
747   QVERIFY( registry.addParameterWidgetFactory( factory ) );
748   QgsProcessingModelerParameterWidget *w = registry.createModelerParameterWidget( &model, QStringLiteral( "a" ), model.parameterDefinition( "p2" ), context );
749   QVERIFY( w );
750   delete w;
751 
752 
753   // widget tests
754   w = new QgsProcessingModelerParameterWidget( &model, "alg1", model.parameterDefinition( "p1" ), context );
755   QCOMPARE( w->parameterDefinition()->name(), QStringLiteral( "p1" ) );
756   QLabel *l = w->createLabel();
757   QVERIFY( l );
758   QCOMPARE( l->text(), QStringLiteral( "desc" ) );
759   QCOMPARE( l->toolTip(), w->parameterDefinition()->toolTip() );
760   delete l;
761 
762   // static value
763   w->setWidgetValue( QgsProcessingModelChildParameterSource::fromStaticValue( true ) );
764   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::StaticValue );
765   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().staticValue().toBool(), true );
766   w->setWidgetValue( QgsProcessingModelChildParameterSource::fromStaticValue( false ) );
767   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::StaticValue );
768   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().staticValue().toBool(), false );
769   QCOMPARE( w->mStackedWidget->currentIndex(), 0 );
770   QCOMPARE( w->mSourceButton->toolTip(), QStringLiteral( "Value" ) );
771 
772   // expression value
773   w->setWidgetValue( QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "1+2" ) ) );
774   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::Expression );
775   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().expression(), QStringLiteral( "1+2" ) );
776   QCOMPARE( w->mStackedWidget->currentIndex(), 1 );
777   QCOMPARE( w->mSourceButton->toolTip(), QStringLiteral( "Pre-calculated Value" ) );
778 
779   // model input - should fail, because we haven't populated sources yet, and so have no compatible sources
780   w->setWidgetValue( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "p1" ) ) );
781   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::ModelParameter );
782   QVERIFY( w->value().value< QgsProcessingModelChildParameterSource>().parameterName().isEmpty() );
783   QCOMPARE( w->mStackedWidget->currentIndex(), 2 );
784   QCOMPARE( w->mSourceButton->toolTip(), QStringLiteral( "Model Input" ) );
785 
786   // alg output  - should fail, because we haven't populated sources yet, and so have no compatible sources
787   w->setWidgetValue( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg3" ), QStringLiteral( "OUTPUT" ) ) );
788   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::ChildOutput );
789   QVERIFY( w->value().value< QgsProcessingModelChildParameterSource>().outputChildId().isEmpty() );
790   QCOMPARE( w->mStackedWidget->currentIndex(), 3 );
791   QCOMPARE( w->mSourceButton->toolTip(), QStringLiteral( "Algorithm Output" ) );
792 
793   // populate sources and re-try
794   w->populateSources( QStringList() << QStringLiteral( "boolean" ), QStringList() << QStringLiteral( "outputVector" ), QList<int>() );
795 
796   // model input
797   w->setWidgetValue( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "p1" ) ) );
798   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::ModelParameter );
799   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().parameterName(), QStringLiteral( "p1" ) );
800 
801   // alg output
802   w->setWidgetValue( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg3" ), QStringLiteral( "OUTPUT" ) ) );
803   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::ChildOutput );
804   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().outputChildId(), QStringLiteral( "alg3" ) );
805   QCOMPARE( w->value().value< QgsProcessingModelChildParameterSource>().outputName(), QStringLiteral( "OUTPUT" ) );
806 
807   // model output
808   delete w;
809   w = new QgsProcessingModelerParameterWidget( &model, "alg1", model.parameterDefinition( "test_dest" ), context );
810   QCOMPARE( w->parameterDefinition()->name(), QStringLiteral( "test_dest" ) );
811   // should default to being a model output for destination parameters, but with no value
812   QVERIFY( w->isModelOutput() );
813   QCOMPARE( w->modelOutputName(), QString() );
814   // set it to something else
815   w->setWidgetValue( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg3" ), QStringLiteral( "OUTPUT" ) ) );
816   QVERIFY( !w->isModelOutput() );
817   // and back
818   w->setToModelOutput( QStringLiteral( "out" ) );
819   QVERIFY( w->isModelOutput() );
820   QCOMPARE( w->modelOutputName(), QStringLiteral( "out" ) );
821   w->setWidgetValue( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg3" ), QStringLiteral( "OUTPUT" ) ) );
822   w->setToModelOutput( QString() );
823   QVERIFY( w->isModelOutput() );
824   QCOMPARE( w->modelOutputName(), QString() );
825 
826   // multi-source input
827   delete w;
828   const QgsProcessingAlgorithm *packageAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:package" ) );
829   const QgsProcessingParameterDefinition *layerDef = packageAlg->parameterDefinition( QStringLiteral( "LAYERS" ) );
830 
831   w = new QgsProcessingModelerParameterWidget( &model, "alg4", layerDef, context );
832 
833   w->setWidgetValue( QList< QgsProcessingModelChildParameterSource>()
834                      << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg3" ), QStringLiteral( "OUTPUT" ) )
835                      << QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "p1" ) )
836                      << QgsProcessingModelChildParameterSource::fromStaticValue( QStringLiteral( "something" ) ) );
837   QCOMPARE( w->value().toList().count(), 3 );
838 
839   QCOMPARE( w->value().toList().at( 0 ).value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::ChildOutput );
840   QCOMPARE( w->value().toList().at( 0 ).value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::ChildOutput );
841   QCOMPARE( w->value().toList().at( 0 ).value< QgsProcessingModelChildParameterSource>().outputChildId(), QStringLiteral( "alg3" ) );
842   QCOMPARE( w->value().toList().at( 0 ).value< QgsProcessingModelChildParameterSource>().outputName(), QStringLiteral( "OUTPUT" ) );
843   QCOMPARE( w->value().toList().at( 1 ).value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::ModelParameter );
844   QCOMPARE( w->value().toList().at( 1 ).value< QgsProcessingModelChildParameterSource>().parameterName(), QStringLiteral( "p1" ) );
845   QCOMPARE( w->value().toList().at( 2 ).value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::StaticValue );
846   QCOMPARE( w->value().toList().at( 2 ).value< QgsProcessingModelChildParameterSource>().staticValue().toString(), QStringLiteral( "something" ) );
847   delete w;
848 
849 }
850 
testHiddenWrapper()851 void TestProcessingGui::testHiddenWrapper()
852 {
853   TestParamType param( QStringLiteral( "boolean" ), QStringLiteral( "bool" ) );
854 
855   QgsProcessingHiddenWidgetWrapper wrapper( &param );
856   QSignalSpy spy( &wrapper, &QgsProcessingHiddenWidgetWrapper::widgetValueHasChanged );
857 
858   QgsProcessingContext context;
859   wrapper.setWidgetValue( 1, context );
860   QCOMPARE( spy.count(), 1 );
861   QCOMPARE( wrapper.widgetValue().toInt(), 1 );
862   wrapper.setWidgetValue( 1, context );
863   QCOMPARE( spy.count(), 1 );
864   QCOMPARE( wrapper.widgetValue().toInt(), 1 );
865   wrapper.setWidgetValue( 2, context );
866   QCOMPARE( spy.count(), 2 );
867   QCOMPARE( wrapper.widgetValue().toInt(), 2 );
868 
869   QVERIFY( !wrapper.createWrappedWidget( context ) );
870   QVERIFY( !wrapper.createWrappedLabel() );
871 
872   std::unique_ptr< QgsVectorLayer > vl = std::make_unique< QgsVectorLayer >( QStringLiteral( "Polygon?crs=epsg:3111&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
873   QVERIFY( !wrapper.linkedVectorLayer() );
874   wrapper.setLinkedVectorLayer( vl.get() );
875   QCOMPARE( wrapper.linkedVectorLayer(), vl.get() );
876 }
877 
testBooleanWrapper()878 void TestProcessingGui::testBooleanWrapper()
879 {
880   TestParamType param( QStringLiteral( "boolean" ), QStringLiteral( "bool" ) );
881 
882   // standard wrapper
883   QgsProcessingBooleanWidgetWrapper wrapper( &param );
884   QSignalSpy spy( &wrapper, &QgsProcessingBooleanWidgetWrapper::widgetValueHasChanged );
885 
886   QgsProcessingContext context;
887   QWidget *w = wrapper.createWrappedWidget( context );
888   wrapper.setWidgetValue( true, context );
889   QCOMPARE( spy.count(), 1 );
890   QVERIFY( wrapper.widgetValue().toBool() );
891   QVERIFY( static_cast< QCheckBox * >( wrapper.wrappedWidget() )->isChecked() );
892   wrapper.setWidgetValue( false, context );
893   QCOMPARE( spy.count(), 2 );
894   QVERIFY( !wrapper.widgetValue().toBool() );
895   QVERIFY( !static_cast< QCheckBox * >( wrapper.wrappedWidget() )->isChecked() );
896 
897   // should be no label in standard mode
898   QVERIFY( !wrapper.createWrappedLabel() );
899   QCOMPARE( static_cast< QCheckBox * >( wrapper.wrappedWidget() )->text(), QStringLiteral( "bool" ) );
900 
901   // check signal
902   static_cast< QCheckBox * >( wrapper.wrappedWidget() )->setChecked( true );
903   QCOMPARE( spy.count(), 3 );
904   static_cast< QCheckBox * >( wrapper.wrappedWidget() )->setChecked( false );
905   QCOMPARE( spy.count(), 4 );
906 
907   delete w;
908 
909   // batch wrapper
910   QgsProcessingBooleanWidgetWrapper wrapperB( &param, QgsProcessingGui::Batch );
911 
912   w = wrapperB.createWrappedWidget( context );
913   QSignalSpy spy2( &wrapperB, &QgsProcessingBooleanWidgetWrapper::widgetValueHasChanged );
914   wrapperB.setWidgetValue( true, context );
915   QCOMPARE( spy2.count(), 1 );
916   QVERIFY( wrapperB.widgetValue().toBool() );
917   QVERIFY( static_cast< QComboBox * >( wrapperB.wrappedWidget() )->currentData().toBool() );
918   wrapperB.setWidgetValue( false, context );
919   QCOMPARE( spy2.count(), 2 );
920   QVERIFY( !wrapperB.widgetValue().toBool() );
921   QVERIFY( !static_cast< QComboBox * >( wrapperB.wrappedWidget() )->currentData().toBool() );
922 
923   // check signal
924   static_cast< QComboBox * >( w )->setCurrentIndex( 0 );
925   QCOMPARE( spy2.count(), 3 );
926   static_cast< QComboBox * >( w )->setCurrentIndex( 1 );
927   QCOMPARE( spy2.count(), 4 );
928 
929   // should be no label in batch mode
930   QVERIFY( !wrapperB.createWrappedLabel() );
931   delete w;
932 
933   // modeler wrapper
934   QgsProcessingBooleanWidgetWrapper wrapperM( &param, QgsProcessingGui::Modeler );
935 
936   w = wrapperM.createWrappedWidget( context );
937   QSignalSpy spy3( &wrapperM, &QgsProcessingBooleanWidgetWrapper::widgetValueHasChanged );
938   wrapperM.setWidgetValue( true, context );
939   QVERIFY( wrapperM.widgetValue().toBool() );
940   QCOMPARE( spy3.count(), 1 );
941   QVERIFY( static_cast< QComboBox * >( wrapperM.wrappedWidget() )->currentData().toBool() );
942   wrapperM.setWidgetValue( false, context );
943   QVERIFY( !wrapperM.widgetValue().toBool() );
944   QCOMPARE( spy3.count(), 2 );
945   QVERIFY( !static_cast< QComboBox * >( wrapperM.wrappedWidget() )->currentData().toBool() );
946 
947   // check signal
948   static_cast< QComboBox * >( w )->setCurrentIndex( 0 );
949   QCOMPARE( spy3.count(), 3 );
950   static_cast< QComboBox * >( w )->setCurrentIndex( 1 );
951   QCOMPARE( spy3.count(), 4 );
952 
953   // should be a label in modeler mode
954   QLabel *l = wrapperM.createWrappedLabel();
955   QVERIFY( l );
956   QCOMPARE( l->text(), QStringLiteral( "bool" ) );
957   QCOMPARE( l->toolTip(), param.toolTip() );
958   delete w;
959   delete l;
960 
961   // config widget
962   QgsProcessingParameterWidgetContext widgetContext;
963   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "boolean" ), context, widgetContext );
964   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
965   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
966   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
967   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
968 
969   // using a parameter definition as initial values
970   QgsProcessingParameterBoolean boolParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), true, false );
971   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "boolean" ), context, widgetContext, &boolParam );
972   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
973   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
974   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
975   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
976   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
977   QVERIFY( static_cast< QgsProcessingParameterBoolean * >( def.get() )->defaultValue().toBool() );
978   boolParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
979   boolParam.setDefaultValue( false );
980   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "boolean" ), context, widgetContext, &boolParam );
981   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
982   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
983   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
984   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
985   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
986   QVERIFY( !static_cast< QgsProcessingParameterBoolean * >( def.get() )->defaultValue().toBool() );
987 }
988 
testStringWrapper()989 void TestProcessingGui::testStringWrapper()
990 {
991   QgsProcessingParameterString param( QStringLiteral( "string" ), QStringLiteral( "string" ) );
992 
993   // standard wrapper
994   QgsProcessingStringWidgetWrapper wrapper( &param );
995 
996   QgsProcessingContext context;
997   QWidget *w = wrapper.createWrappedWidget( context );
998 
999   QSignalSpy spy( &wrapper, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged );
1000   wrapper.setWidgetValue( QStringLiteral( "a" ), context );
1001   QCOMPARE( spy.count(), 1 );
1002   QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "a" ) );
1003   QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text(), QStringLiteral( "a" ) );
1004   wrapper.setWidgetValue( QString(), context );
1005   QCOMPARE( spy.count(), 2 );
1006   QVERIFY( wrapper.widgetValue().toString().isEmpty() );
1007   QVERIFY( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text().isEmpty() );
1008 
1009   QLabel *l = wrapper.createWrappedLabel();
1010   QVERIFY( l );
1011   QCOMPARE( l->text(), QStringLiteral( "string" ) );
1012   QCOMPARE( l->toolTip(), param.toolTip() );
1013   delete l;
1014 
1015   // check signal
1016   static_cast< QLineEdit * >( wrapper.wrappedWidget() )->setText( QStringLiteral( "b" ) );
1017   QCOMPARE( spy.count(), 3 );
1018   static_cast< QLineEdit * >( wrapper.wrappedWidget() )->clear();
1019   QCOMPARE( spy.count(), 4 );
1020 
1021   delete w;
1022 
1023   // batch wrapper
1024   QgsProcessingStringWidgetWrapper wrapperB( &param, QgsProcessingGui::Batch );
1025 
1026   w = wrapperB.createWrappedWidget( context );
1027   QSignalSpy spy2( &wrapperB, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged );
1028   wrapperB.setWidgetValue( QStringLiteral( "a" ), context );
1029   QCOMPARE( spy2.count(), 1 );
1030   QCOMPARE( wrapperB.widgetValue().toString(), QStringLiteral( "a" ) );
1031   QCOMPARE( static_cast< QLineEdit * >( wrapperB.wrappedWidget() )->text(), QStringLiteral( "a" ) );
1032   wrapperB.setWidgetValue( QString(), context );
1033   QCOMPARE( spy2.count(), 2 );
1034   QVERIFY( wrapperB.widgetValue().toString().isEmpty() );
1035   QVERIFY( static_cast< QLineEdit * >( wrapperB.wrappedWidget() )->text().isEmpty() );
1036 
1037   // check signal
1038   static_cast< QLineEdit * >( w )->setText( QStringLiteral( "x" ) );
1039   QCOMPARE( spy2.count(), 3 );
1040   static_cast< QLineEdit * >( w )->clear();
1041   QCOMPARE( spy2.count(), 4 );
1042 
1043   // should be no label in batch mode
1044   QVERIFY( !wrapperB.createWrappedLabel() );
1045   delete w;
1046 
1047   // modeler wrapper
1048   QgsProcessingStringWidgetWrapper wrapperM( &param, QgsProcessingGui::Modeler );
1049 
1050   w = wrapperM.createWrappedWidget( context );
1051   QSignalSpy spy3( &wrapperM, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged );
1052   wrapperM.setWidgetValue( QStringLiteral( "a" ), context );
1053   QCOMPARE( wrapperM.widgetValue().toString(), QStringLiteral( "a" ) );
1054   QCOMPARE( spy3.count(), 1 );
1055   QCOMPARE( static_cast< QLineEdit * >( wrapperM.wrappedWidget() )->text(), QStringLiteral( "a" ) );
1056   wrapperM.setWidgetValue( QString(), context );
1057   QVERIFY( wrapperM.widgetValue().toString().isEmpty() );
1058   QCOMPARE( spy3.count(), 2 );
1059   QVERIFY( static_cast< QLineEdit * >( wrapperM.wrappedWidget() )->text().isEmpty() );
1060 
1061   // check signal
1062   static_cast< QLineEdit * >( w )->setText( QStringLiteral( "x" ) );
1063   QCOMPARE( spy3.count(), 3 );
1064   static_cast< QLineEdit * >( w )->clear();
1065   QCOMPARE( spy3.count(), 4 );
1066 
1067   // should be a label in modeler mode
1068   l = wrapperM.createWrappedLabel();
1069   QVERIFY( l );
1070   QCOMPARE( l->text(), QStringLiteral( "string" ) );
1071   QCOMPARE( l->toolTip(), param.toolTip() );
1072   delete w;
1073   delete l;
1074 
1075   //
1076   // multiline parameter
1077   //
1078   param = QgsProcessingParameterString( QStringLiteral( "string" ), QStringLiteral( "string" ), QVariant(), true );
1079 
1080   // standard wrapper
1081   QgsProcessingStringWidgetWrapper wrapperMultiLine( &param );
1082 
1083   w = wrapperMultiLine.createWrappedWidget( context );
1084 
1085   QSignalSpy spy4( &wrapperMultiLine, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged );
1086   wrapperMultiLine.setWidgetValue( QStringLiteral( "a" ), context );
1087   QCOMPARE( spy4.count(), 1 );
1088   QCOMPARE( wrapperMultiLine.widgetValue().toString(), QStringLiteral( "a" ) );
1089   QCOMPARE( static_cast< QPlainTextEdit * >( wrapperMultiLine.wrappedWidget() )->toPlainText(), QStringLiteral( "a" ) );
1090   wrapperMultiLine.setWidgetValue( QString(), context );
1091   QCOMPARE( spy4.count(), 2 );
1092   QVERIFY( wrapperMultiLine.widgetValue().toString().isEmpty() );
1093   QVERIFY( static_cast< QPlainTextEdit * >( wrapperMultiLine.wrappedWidget() )->toPlainText().isEmpty() );
1094 
1095   l = wrapper.createWrappedLabel();
1096   QVERIFY( l );
1097   QCOMPARE( l->text(), QStringLiteral( "string" ) );
1098   QCOMPARE( l->toolTip(), param.toolTip() );
1099   delete l;
1100 
1101   // check signal
1102   static_cast< QPlainTextEdit * >( wrapperMultiLine.wrappedWidget() )->setPlainText( QStringLiteral( "b" ) );
1103   QCOMPARE( spy4.count(), 3 );
1104   static_cast< QPlainTextEdit * >( wrapperMultiLine.wrappedWidget() )->clear();
1105   QCOMPARE( spy4.count(), 4 );
1106 
1107   delete w;
1108 
1109   // batch wrapper - should still be a line edit
1110   QgsProcessingStringWidgetWrapper wrapperMultiLineB( &param, QgsProcessingGui::Batch );
1111 
1112   w = wrapperMultiLineB.createWrappedWidget( context );
1113   QSignalSpy spy5( &wrapperMultiLineB, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged );
1114   wrapperMultiLineB.setWidgetValue( QStringLiteral( "a" ), context );
1115   QCOMPARE( spy5.count(), 1 );
1116   QCOMPARE( wrapperMultiLineB.widgetValue().toString(), QStringLiteral( "a" ) );
1117   QCOMPARE( static_cast< QLineEdit * >( wrapperMultiLineB.wrappedWidget() )->text(), QStringLiteral( "a" ) );
1118   wrapperMultiLineB.setWidgetValue( QString(), context );
1119   QCOMPARE( spy5.count(), 2 );
1120   QVERIFY( wrapperMultiLineB.widgetValue().toString().isEmpty() );
1121   QVERIFY( static_cast< QLineEdit * >( wrapperMultiLineB.wrappedWidget() )->text().isEmpty() );
1122 
1123   // check signal
1124   static_cast< QLineEdit * >( w )->setText( QStringLiteral( "x" ) );
1125   QCOMPARE( spy5.count(), 3 );
1126   static_cast< QLineEdit * >( w )->clear();
1127   QCOMPARE( spy5.count(), 4 );
1128 
1129   // should be no label in batch mode
1130   QVERIFY( !wrapperB.createWrappedLabel() );
1131   delete w;
1132 
1133   // modeler wrapper
1134   QgsProcessingStringWidgetWrapper wrapperMultiLineM( &param, QgsProcessingGui::Modeler );
1135 
1136   w = wrapperMultiLineM.createWrappedWidget( context );
1137   QSignalSpy spy6( &wrapperMultiLineM, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged );
1138   wrapperMultiLineM.setWidgetValue( QStringLiteral( "a" ), context );
1139   QCOMPARE( wrapperMultiLineM.widgetValue().toString(), QStringLiteral( "a" ) );
1140   QCOMPARE( spy6.count(), 1 );
1141   QCOMPARE( static_cast< QPlainTextEdit * >( wrapperMultiLineM.wrappedWidget() )->toPlainText(), QStringLiteral( "a" ) );
1142   wrapperMultiLineM.setWidgetValue( QString(), context );
1143   QVERIFY( wrapperMultiLineM.widgetValue().toString().isEmpty() );
1144   QCOMPARE( spy6.count(), 2 );
1145   QVERIFY( static_cast< QPlainTextEdit * >( wrapperMultiLineM.wrappedWidget() )->toPlainText().isEmpty() );
1146 
1147   // check signal
1148   static_cast< QPlainTextEdit * >( w )->setPlainText( QStringLiteral( "x" ) );
1149   QCOMPARE( spy6.count(), 3 );
1150   static_cast< QPlainTextEdit * >( w )->clear();
1151   QCOMPARE( spy6.count(), 4 );
1152 
1153   // should be a label in modeler mode
1154   l = wrapperMultiLineM.createWrappedLabel();
1155   QVERIFY( l );
1156   QCOMPARE( l->text(), QStringLiteral( "string" ) );
1157   QCOMPARE( l->toolTip(), param.toolTip() );
1158   delete w;
1159   delete l;
1160 
1161 
1162   //
1163   // with value hints
1164   //
1165   param = QgsProcessingParameterString( QStringLiteral( "string" ), QStringLiteral( "string" ), QVariant() );
1166   param.setMetadata( { {
1167       QStringLiteral( "widget_wrapper" ),
1168       QVariantMap(
1169       { {
1170           QStringLiteral( "value_hints" ),
1171           QStringList() << "value 1" << "value 2" << "value 3"
1172         }
1173       }
1174       )
1175     }
1176   } );
1177 
1178   QgsProcessingStringWidgetWrapper wrapperHints( &param );
1179 
1180   w = wrapperHints.createWrappedWidget( context );
1181 
1182   QSignalSpy spy7( &wrapperHints, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged );
1183   wrapperHints.setWidgetValue( QStringLiteral( "value 2" ), context );
1184   QCOMPARE( spy7.count(), 1 );
1185   QCOMPARE( wrapperHints.widgetValue().toString(), QStringLiteral( "value 2" ) );
1186   QCOMPARE( qgis::down_cast< QComboBox * >( wrapperHints.wrappedWidget() )->currentText(), QStringLiteral( "value 2" ) );
1187   wrapperHints.setWidgetValue( QStringLiteral( "value 3" ), context );
1188   QCOMPARE( spy7.count(), 2 );
1189   QCOMPARE( wrapperHints.widgetValue().toString(), QStringLiteral( "value 3" ) );
1190   QCOMPARE( qgis::down_cast< QComboBox * >( wrapperHints.wrappedWidget() )->currentText(), QStringLiteral( "value 3" ) );
1191 
1192   // set to value which is not present -- should fallback to first value
1193   wrapperHints.setWidgetValue( QStringLiteral( "value 4" ), context );
1194   QCOMPARE( spy7.count(), 3 );
1195   QCOMPARE( wrapperHints.widgetValue().toString(), QStringLiteral( "value 1" ) );
1196   QCOMPARE( qgis::down_cast< QComboBox * >( wrapperHints.wrappedWidget() )->currentText(), QStringLiteral( "value 1" ) );
1197 
1198   l = wrapperHints.createWrappedLabel();
1199   QVERIFY( l );
1200   QCOMPARE( l->text(), QStringLiteral( "string" ) );
1201   QCOMPARE( l->toolTip(), param.toolTip() );
1202   delete l;
1203 
1204   // check signal
1205   qgis::down_cast< QComboBox * >( wrapperHints.wrappedWidget() )->setCurrentIndex( 1 );
1206   QCOMPARE( spy7.count(), 4 );
1207   qgis::down_cast< QComboBox * >( wrapperHints.wrappedWidget() )->setCurrentIndex( 2 );
1208   QCOMPARE( spy7.count(), 5 );
1209 
1210   delete w;
1211 
1212   // with value hints, optional param
1213   param = QgsProcessingParameterString( QStringLiteral( "string" ), QStringLiteral( "string" ), QVariant(), false, true );
1214   param.setMetadata( { {
1215       QStringLiteral( "widget_wrapper" ),
1216       QVariantMap(
1217       { {
1218           QStringLiteral( "value_hints" ),
1219           QStringList() << "value 1" << "value 2" << "value 3"
1220         }
1221       }
1222       )
1223     }
1224   } );
1225 
1226   QgsProcessingStringWidgetWrapper wrapperHintsOptional( &param );
1227 
1228   w = wrapperHintsOptional.createWrappedWidget( context );
1229 
1230   QSignalSpy spy8( &wrapperHintsOptional, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged );
1231   wrapperHintsOptional.setWidgetValue( QStringLiteral( "value 2" ), context );
1232   QCOMPARE( spy8.count(), 1 );
1233   QCOMPARE( wrapperHintsOptional.widgetValue().toString(), QStringLiteral( "value 2" ) );
1234   QCOMPARE( qgis::down_cast< QComboBox * >( wrapperHintsOptional.wrappedWidget() )->currentText(), QStringLiteral( "value 2" ) );
1235   wrapperHintsOptional.setWidgetValue( QVariant(), context );
1236   QCOMPARE( spy8.count(), 2 );
1237   QVERIFY( !wrapperHintsOptional.widgetValue().isValid() );
1238   QCOMPARE( qgis::down_cast< QComboBox * >( wrapperHintsOptional.wrappedWidget() )->currentText(), QString() );
1239   wrapperHintsOptional.setWidgetValue( QStringLiteral( "value 3" ), context );
1240   QCOMPARE( spy8.count(), 3 );
1241   QCOMPARE( wrapperHintsOptional.widgetValue().toString(), QStringLiteral( "value 3" ) );
1242   QCOMPARE( qgis::down_cast< QComboBox * >( wrapperHintsOptional.wrappedWidget() )->currentText(), QStringLiteral( "value 3" ) );
1243 
1244   // set to value which is not present -- should fallback to first value ("not set")
1245   wrapperHintsOptional.setWidgetValue( QStringLiteral( "value 4" ), context );
1246   QCOMPARE( spy8.count(), 4 );
1247   QVERIFY( !wrapperHintsOptional.widgetValue().isValid() );
1248   QCOMPARE( qgis::down_cast< QComboBox * >( wrapperHintsOptional.wrappedWidget() )->currentText(), QString() );
1249 
1250   l = wrapperHintsOptional.createWrappedLabel();
1251   QVERIFY( l );
1252   QCOMPARE( l->text(), QStringLiteral( "string [optional]" ) );
1253   QCOMPARE( l->toolTip(), param.toolTip() );
1254   delete l;
1255 
1256   // check signal
1257   qgis::down_cast< QComboBox * >( wrapperHintsOptional.wrappedWidget() )->setCurrentIndex( 1 );
1258   QCOMPARE( spy8.count(), 5 );
1259   qgis::down_cast< QComboBox * >( wrapperHintsOptional.wrappedWidget() )->setCurrentIndex( 2 );
1260   QCOMPARE( spy8.count(), 6 );
1261 
1262   delete w;
1263 
1264 
1265   // config widget
1266   QgsProcessingParameterWidgetContext widgetContext;
1267   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "string" ), context, widgetContext );
1268   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
1269   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1270   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
1271   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
1272   QVERIFY( !static_cast< QgsProcessingParameterString * >( def.get() )->multiLine() );
1273 
1274   // using a parameter definition as initial values
1275   QgsProcessingParameterString stringParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "aaa" ), true );
1276   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "string" ), context, widgetContext, &stringParam );
1277   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
1278   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1279   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
1280   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
1281   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
1282   QVERIFY( static_cast< QgsProcessingParameterString * >( def.get() )->multiLine() );
1283   QCOMPARE( static_cast< QgsProcessingParameterString * >( def.get() )->defaultValue().toString(), QStringLiteral( "aaa" ) );
1284   stringParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
1285   stringParam.setMultiLine( false );
1286   stringParam.setDefaultValue( QString() );
1287   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "string" ), context, widgetContext, &stringParam );
1288   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
1289   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1290   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
1291   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
1292   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
1293   QVERIFY( static_cast< QgsProcessingParameterString * >( def.get() )->defaultValue().toString().isEmpty() );
1294   QVERIFY( !static_cast< QgsProcessingParameterString * >( def.get() )->multiLine() );
1295 }
1296 
testFileWrapper()1297 void TestProcessingGui::testFileWrapper()
1298 {
1299   auto testWrapper = []( QgsProcessingGui::WidgetType type )
1300   {
1301     QgsProcessingParameterFile param( QStringLiteral( "file" ), QStringLiteral( "file" ) );
1302 
1303     QgsProcessingFileWidgetWrapper wrapper( &param, type );
1304 
1305     QgsProcessingContext context;
1306     QWidget *w = wrapper.createWrappedWidget( context );
1307 
1308     QSignalSpy spy( &wrapper, &QgsProcessingFileWidgetWrapper::widgetValueHasChanged );
1309     wrapper.setWidgetValue( QString( TEST_DATA_DIR + QStringLiteral( "/points.shp" ) ), context );
1310     QCOMPARE( spy.count(), 1 );
1311     QCOMPARE( wrapper.widgetValue().toString(), QString( TEST_DATA_DIR + QStringLiteral( "/points.shp" ) ) );
1312     QCOMPARE( static_cast< QgsFileWidget * >( wrapper.wrappedWidget() )->filePath(), QString( TEST_DATA_DIR + QStringLiteral( "/points.shp" ) ) );
1313     QCOMPARE( static_cast< QgsFileWidget * >( wrapper.wrappedWidget() )->filter(), QStringLiteral( "All files (*.*)" ) );
1314     QCOMPARE( static_cast< QgsFileWidget * >( wrapper.wrappedWidget() )->storageMode(),  QgsFileWidget::GetFile );
1315     wrapper.setWidgetValue( QString(), context );
1316     QCOMPARE( spy.count(), 2 );
1317     QVERIFY( wrapper.widgetValue().toString().isEmpty() );
1318     QVERIFY( static_cast< QgsFileWidget * >( wrapper.wrappedWidget() )->filePath().isEmpty() );
1319 
1320     QLabel *l = wrapper.createWrappedLabel();
1321     if ( wrapper.type() != QgsProcessingGui::Batch )
1322     {
1323       QVERIFY( l );
1324       QCOMPARE( l->text(), QStringLiteral( "file" ) );
1325       QCOMPARE( l->toolTip(), param.toolTip() );
1326       delete l;
1327     }
1328     else
1329     {
1330       QVERIFY( !l );
1331     }
1332 
1333     // check signal
1334     static_cast< QgsFileWidget * >( wrapper.wrappedWidget() )->setFilePath( TEST_DATA_DIR + QStringLiteral( "/polys.shp" ) );
1335     QCOMPARE( spy.count(), 3 );
1336 
1337     delete w;
1338 
1339     // with extension
1340     QgsProcessingParameterFile param2( QStringLiteral( "file" ), QStringLiteral( "file" ), QgsProcessingParameterFile::File, QStringLiteral( "qml" ) );
1341 
1342     QgsProcessingFileWidgetWrapper wrapper2( &param2, type );
1343     w = wrapper2.createWrappedWidget( context );
1344     QCOMPARE( static_cast< QgsFileWidget * >( wrapper2.wrappedWidget() )->filter(), QStringLiteral( "QML files (*.qml)" ) );
1345     QCOMPARE( static_cast< QgsFileWidget * >( wrapper2.wrappedWidget() )->storageMode(),  QgsFileWidget::GetFile );
1346 
1347     // with filter
1348     QgsProcessingParameterFile param3( QStringLiteral( "file" ), QStringLiteral( "file" ), QgsProcessingParameterFile::File, QString(), QVariant(), false, QStringLiteral( "Project files (*.qgs *.qgz)" ) );
1349 
1350     QgsProcessingFileWidgetWrapper wrapper3( & param3, type );
1351     w = wrapper3.createWrappedWidget( context );
1352     QCOMPARE( static_cast< QgsFileWidget * >( wrapper3.wrappedWidget() )->filter(), QStringLiteral( "Project files (*.qgs *.qgz)" ) );
1353     QCOMPARE( static_cast< QgsFileWidget * >( wrapper3.wrappedWidget() )->storageMode(),  QgsFileWidget::GetFile );
1354 
1355     // folder mode
1356     QgsProcessingParameterFile param4( QStringLiteral( "folder" ), QStringLiteral( "folder" ), QgsProcessingParameterFile::Folder );
1357 
1358     QgsProcessingFileWidgetWrapper wrapper4( &param4, type );
1359     w = wrapper4.createWrappedWidget( context );
1360     QCOMPARE( static_cast< QgsFileWidget * >( wrapper4.wrappedWidget() )->storageMode(),  QgsFileWidget::GetDirectory );
1361   };
1362 
1363   // standard wrapper
1364   testWrapper( QgsProcessingGui::Standard );
1365 
1366   // batch wrapper
1367   testWrapper( QgsProcessingGui::Batch );
1368 
1369   // modeler wrapper
1370   testWrapper( QgsProcessingGui::Modeler );
1371 
1372 
1373   // config widget
1374   QgsProcessingParameterWidgetContext widgetContext;
1375   QgsProcessingContext context;
1376   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "file" ), context, widgetContext );
1377   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
1378   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1379   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
1380   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
1381 
1382   // using a parameter definition as initial values
1383   QgsProcessingParameterFile fileParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QgsProcessingParameterFile::File );
1384   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "file" ), context, widgetContext, &fileParam );
1385   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
1386   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1387   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
1388   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
1389   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
1390   QCOMPARE( static_cast< QgsProcessingParameterFile * >( def.get() )->behavior(), QgsProcessingParameterFile::File );
1391   QVERIFY( !static_cast< QgsProcessingParameterFile * >( def.get() )->defaultValue().isValid() );
1392   QCOMPARE( static_cast< QgsProcessingParameterFile * >( def.get() )->fileFilter(), QStringLiteral( "All files (*.*)" ) );
1393   fileParam.setFileFilter( QStringLiteral( "TAB files (*.tab)" ) );
1394   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "file" ), context, widgetContext, &fileParam );
1395   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
1396   QCOMPARE( static_cast< QgsProcessingParameterFile * >( def.get() )->fileFilter(), QStringLiteral( "TAB files (*.tab)" ) );
1397 
1398   fileParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
1399   fileParam.setBehavior( QgsProcessingParameterFile::Folder );
1400   fileParam.setDefaultValue( QStringLiteral( "my path" ) );
1401   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "file" ), context, widgetContext, &fileParam );
1402   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
1403   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1404   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
1405   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
1406   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
1407   QCOMPARE( static_cast< QgsProcessingParameterFile * >( def.get() )->behavior(), QgsProcessingParameterFile::Folder );
1408   QCOMPARE( static_cast< QgsProcessingParameterFile * >( def.get() )->defaultValue().toString(), QStringLiteral( "my path" ) );
1409 }
1410 
testAuthCfgWrapper()1411 void TestProcessingGui::testAuthCfgWrapper()
1412 {
1413   QList<QgsAuthMethodConfig> configs;
1414 
1415   // Basic
1416   QgsAuthMethodConfig b_config;
1417   b_config.setId( QStringLiteral( "aaaaaaa" ) );
1418   b_config.setName( QStringLiteral( "Basic" ) );
1419   b_config.setMethod( QStringLiteral( "Basic" ) );
1420   b_config.setUri( QStringLiteral( "http://example.com" ) );
1421   b_config.setConfig( QStringLiteral( "username" ), QStringLiteral( "username" ) );
1422   b_config.setConfig( QStringLiteral( "password" ), QStringLiteral( "password" ) );
1423   b_config.setConfig( QStringLiteral( "realm" ), QStringLiteral( "Realm" ) );
1424   configs << b_config;
1425 
1426   QgsAuthMethodConfig b_config2;
1427   b_config2.setId( QStringLiteral( "bbbbbbb" ) );
1428   b_config2.setName( QStringLiteral( "Basic2" ) );
1429   b_config2.setMethod( QStringLiteral( "Basic" ) );
1430   b_config2.setUri( QStringLiteral( "http://example.com" ) );
1431   b_config2.setConfig( QStringLiteral( "username" ), QStringLiteral( "username" ) );
1432   b_config2.setConfig( QStringLiteral( "password" ), QStringLiteral( "password" ) );
1433   b_config2.setConfig( QStringLiteral( "realm" ), QStringLiteral( "Realm" ) );
1434   configs << b_config2;
1435 
1436   QgsAuthManager *authm = QgsApplication::authManager();
1437   QStringList authIds;
1438   for ( QgsAuthMethodConfig config : std::as_const( configs ) )
1439   {
1440     QVERIFY( config.isValid() );
1441 
1442     QVERIFY( authm->storeAuthenticationConfig( config ) );
1443 
1444     // config should now have a valid, unique ID
1445     authIds << config.id();
1446   }
1447 
1448   QCOMPARE( authIds.count(), 2 );
1449 
1450   QgsProcessingParameterAuthConfig param( QStringLiteral( "authcfg" ), QStringLiteral( "authcfg" ) );
1451 
1452   // standard wrapper
1453   QgsProcessingAuthConfigWidgetWrapper wrapper( &param );
1454 
1455   QgsProcessingContext context;
1456   QWidget *w = wrapper.createWrappedWidget( context );
1457 
1458   QSignalSpy spy( &wrapper, &QgsProcessingAuthConfigWidgetWrapper::widgetValueHasChanged );
1459   wrapper.setWidgetValue( authIds.at( 0 ), context );
1460   QCOMPARE( spy.count(), 1 );
1461   QCOMPARE( wrapper.widgetValue().toString(), authIds.at( 0 ) );
1462   QCOMPARE( static_cast< QgsAuthConfigSelect * >( wrapper.wrappedWidget() )->configId(), authIds.at( 0 ) );
1463   wrapper.setWidgetValue( authIds.at( 1 ), context );
1464   QCOMPARE( spy.count(), 2 );
1465   QCOMPARE( wrapper.widgetValue().toString(), authIds.at( 1 ) );
1466   QCOMPARE( static_cast< QgsAuthConfigSelect * >( wrapper.wrappedWidget() )->configId(), authIds.at( 1 ) );
1467   wrapper.setWidgetValue( QString(), context );
1468   QCOMPARE( spy.count(), 3 );
1469   QVERIFY( wrapper.widgetValue().toString().isEmpty() );
1470   QVERIFY( static_cast< QgsAuthConfigSelect * >( wrapper.wrappedWidget() )->configId().isEmpty() );
1471 
1472   QLabel *l = wrapper.createWrappedLabel();
1473   QVERIFY( l );
1474   QCOMPARE( l->text(), QStringLiteral( "authcfg" ) );
1475   QCOMPARE( l->toolTip(), param.toolTip() );
1476   delete l;
1477 
1478   // check signal
1479   static_cast< QgsAuthConfigSelect * >( wrapper.wrappedWidget() )->setConfigId( authIds.at( 0 ) );
1480   QCOMPARE( spy.count(), 4 );
1481 
1482   delete w;
1483 
1484   // batch wrapper
1485   QgsProcessingAuthConfigWidgetWrapper wrapperB( &param, QgsProcessingGui::Batch );
1486 
1487   w = wrapperB.createWrappedWidget( context );
1488   QSignalSpy spy2( &wrapperB, &QgsProcessingAuthConfigWidgetWrapper::widgetValueHasChanged );
1489   wrapperB.setWidgetValue( authIds.at( 0 ), context );
1490   QCOMPARE( spy2.count(), 1 );
1491   QCOMPARE( wrapperB.widgetValue().toString(), authIds.at( 0 ) );
1492   QCOMPARE( static_cast< QgsAuthConfigSelect * >( wrapperB.wrappedWidget() )->configId(), authIds.at( 0 ) );
1493   wrapperB.setWidgetValue( QString(), context );
1494   QCOMPARE( spy2.count(), 2 );
1495   QVERIFY( wrapperB.widgetValue().toString().isEmpty() );
1496   QVERIFY( static_cast< QgsAuthConfigSelect * >( wrapperB.wrappedWidget() )->configId().isEmpty() );
1497 
1498   // check signal
1499   static_cast< QgsAuthConfigSelect * >( w )->setConfigId( authIds.at( 0 ) );
1500   QCOMPARE( spy2.count(), 3 );
1501 
1502   // should be no label in batch mode
1503   QVERIFY( !wrapperB.createWrappedLabel() );
1504   delete w;
1505 
1506   // modeler wrapper
1507   QgsProcessingAuthConfigWidgetWrapper wrapperM( &param, QgsProcessingGui::Modeler );
1508 
1509   w = wrapperM.createWrappedWidget( context );
1510   QSignalSpy spy3( &wrapperM, &QgsProcessingAuthConfigWidgetWrapper::widgetValueHasChanged );
1511   wrapperM.setWidgetValue( authIds.at( 0 ), context );
1512   QCOMPARE( wrapperM.widgetValue().toString(), authIds.at( 0 ) );
1513   QCOMPARE( spy3.count(), 1 );
1514   QCOMPARE( static_cast< QgsAuthConfigSelect * >( wrapperM.wrappedWidget() )->configId(), authIds.at( 0 ) );
1515   wrapperM.setWidgetValue( QString(), context );
1516   QVERIFY( wrapperM.widgetValue().toString().isEmpty() );
1517   QCOMPARE( spy3.count(), 2 );
1518   QVERIFY( static_cast< QgsAuthConfigSelect * >( wrapperM.wrappedWidget() )->configId().isEmpty() );
1519 
1520   // check signal
1521   static_cast< QgsAuthConfigSelect * >( w )->setConfigId( authIds.at( 0 ) );
1522   QCOMPARE( spy3.count(), 3 );
1523 
1524   // should be a label in modeler mode
1525   l = wrapperM.createWrappedLabel();
1526   QVERIFY( l );
1527   QCOMPARE( l->text(), QStringLiteral( "authcfg" ) );
1528   QCOMPARE( l->toolTip(), param.toolTip() );
1529   delete w;
1530   delete l;
1531 }
1532 
testCrsWrapper()1533 void TestProcessingGui::testCrsWrapper()
1534 {
1535   QgsProcessingParameterCrs param( QStringLiteral( "crs" ), QStringLiteral( "crs" ) );
1536 
1537   // standard wrapper
1538   QgsProcessingCrsWidgetWrapper wrapper( &param );
1539 
1540   QgsProcessingContext context;
1541   QWidget *w = wrapper.createWrappedWidget( context );
1542 
1543   QSignalSpy spy( &wrapper, &QgsProcessingCrsWidgetWrapper::widgetValueHasChanged );
1544   wrapper.setWidgetValue( QStringLiteral( "epsg:3111" ), context );
1545   QCOMPARE( spy.count(), 1 );
1546   QCOMPARE( wrapper.widgetValue().value< QgsCoordinateReferenceSystem >().authid(), QStringLiteral( "EPSG:3111" ) );
1547   QCOMPARE( static_cast< QgsProjectionSelectionWidget * >( wrapper.wrappedWidget() )->crs().authid(), QStringLiteral( "EPSG:3111" ) );
1548   wrapper.setWidgetValue( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ), context );
1549   QCOMPARE( spy.count(), 2 );
1550   QCOMPARE( wrapper.widgetValue().value< QgsCoordinateReferenceSystem >().authid(), QStringLiteral( "EPSG:28356" ) );
1551   QCOMPARE( static_cast< QgsProjectionSelectionWidget * >( wrapper.wrappedWidget() )->crs().authid(), QStringLiteral( "EPSG:28356" ) );
1552   wrapper.setWidgetValue( QString(), context );
1553   QCOMPARE( spy.count(), 3 );
1554   QVERIFY( !wrapper.widgetValue().value< QgsCoordinateReferenceSystem >().isValid() );
1555   QVERIFY( !static_cast< QgsProjectionSelectionWidget * >( wrapper.wrappedWidget() )->crs().isValid() );
1556 
1557   QLabel *l = wrapper.createWrappedLabel();
1558   QVERIFY( l );
1559   QCOMPARE( l->text(), QStringLiteral( "crs" ) );
1560   QCOMPARE( l->toolTip(), param.toolTip() );
1561   delete l;
1562 
1563   // check signal
1564   static_cast< QgsProjectionSelectionWidget * >( wrapper.wrappedWidget() )->setCrs( QgsCoordinateReferenceSystem( "EPSG:3857" ) );
1565   QCOMPARE( spy.count(), 4 );
1566   static_cast< QgsProjectionSelectionWidget * >( wrapper.wrappedWidget() )->setCrs( QgsCoordinateReferenceSystem() );
1567   QCOMPARE( spy.count(), 5 );
1568 
1569   delete w;
1570 
1571   // batch wrapper
1572   QgsProcessingCrsWidgetWrapper wrapperB( &param, QgsProcessingGui::Batch );
1573 
1574   w = wrapperB.createWrappedWidget( context );
1575   QSignalSpy spy2( &wrapperB, &QgsProcessingCrsWidgetWrapper::widgetValueHasChanged );
1576   wrapperB.setWidgetValue( QStringLiteral( "epsg:3111" ), context );
1577   QCOMPARE( spy2.count(), 1 );
1578   QCOMPARE( wrapperB.widgetValue().value< QgsCoordinateReferenceSystem >().authid(), QStringLiteral( "EPSG:3111" ) );
1579   QCOMPARE( static_cast< QgsProjectionSelectionWidget * >( wrapperB.wrappedWidget() )->crs().authid(), QStringLiteral( "EPSG:3111" ) );
1580   wrapperB.setWidgetValue( QgsCoordinateReferenceSystem(), context );
1581   QCOMPARE( spy2.count(), 2 );
1582   QVERIFY( !wrapperB.widgetValue().value< QgsCoordinateReferenceSystem >().isValid() );
1583   QVERIFY( !static_cast< QgsProjectionSelectionWidget * >( wrapperB.wrappedWidget() )->crs().isValid() );
1584 
1585   // check signal
1586   static_cast< QgsProjectionSelectionWidget * >( w )->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ) );
1587   QCOMPARE( spy2.count(), 3 );
1588   static_cast< QgsProjectionSelectionWidget * >( w )->setCrs( QgsCoordinateReferenceSystem() );
1589   QCOMPARE( spy2.count(), 4 );
1590 
1591   // should be no label in batch mode
1592   QVERIFY( !wrapperB.createWrappedLabel() );
1593   delete w;
1594 
1595   // modeler wrapper
1596   QgsProcessingCrsWidgetWrapper wrapperM( &param, QgsProcessingGui::Modeler );
1597 
1598   w = wrapperM.createWrappedWidget( context );
1599   QSignalSpy spy3( &wrapperM, &QgsProcessingCrsWidgetWrapper::widgetValueHasChanged );
1600   wrapperM.setWidgetValue( QStringLiteral( "epsg:3111" ), context );
1601   QCOMPARE( wrapperM.widgetValue().value< QgsCoordinateReferenceSystem >().authid(), QStringLiteral( "EPSG:3111" ) );
1602   QCOMPARE( spy3.count(), 1 );
1603   QCOMPARE( wrapperM.mProjectionSelectionWidget->crs().authid(), QStringLiteral( "EPSG:3111" ) );
1604   QVERIFY( !wrapperM.mUseProjectCrsCheckBox->isChecked() );
1605   wrapperM.setWidgetValue( QgsCoordinateReferenceSystem(), context );
1606   QVERIFY( !wrapperM.widgetValue().value< QgsCoordinateReferenceSystem >().isValid() );
1607   QCOMPARE( spy3.count(), 2 );
1608   QVERIFY( !wrapperM.mProjectionSelectionWidget->crs().isValid() );
1609   QVERIFY( !wrapperM.mUseProjectCrsCheckBox->isChecked() );
1610   wrapperM.setWidgetValue( QStringLiteral( "ProjectCrs" ), context );
1611   QCOMPARE( wrapperM.widgetValue().toString(), QStringLiteral( "ProjectCrs" ) );
1612   QCOMPARE( spy3.count(), 3 );
1613   QVERIFY( wrapperM.mUseProjectCrsCheckBox->isChecked() );
1614 
1615   // check signal
1616   wrapperM.mUseProjectCrsCheckBox->setChecked( false );
1617   QCOMPARE( spy3.count(), 4 );
1618   wrapperM.mProjectionSelectionWidget->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28355" ) ) );
1619   QCOMPARE( spy3.count(), 5 );
1620   wrapperM.mProjectionSelectionWidget->setCrs( QgsCoordinateReferenceSystem() );
1621   QCOMPARE( spy3.count(), 6 );
1622 
1623   // should be a label in modeler mode
1624   l = wrapperM.createWrappedLabel();
1625   QVERIFY( l );
1626   QCOMPARE( l->text(), QStringLiteral( "crs" ) );
1627   QCOMPARE( l->toolTip(), param.toolTip() );
1628   delete w;
1629   delete l;
1630 
1631   // config widget
1632   QgsProcessingParameterWidgetContext widgetContext;
1633   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "crs" ), context, widgetContext );
1634   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
1635   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1636   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
1637   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
1638 
1639   // using a parameter definition as initial values
1640   QgsProcessingParameterCrs crsParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "EPSG:4326" ) );
1641   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "crs" ), context, widgetContext, &crsParam );
1642   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
1643   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1644   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
1645   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
1646   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
1647   QCOMPARE( static_cast< QgsProcessingParameterCrs * >( def.get() )->defaultValue().toString(), QStringLiteral( "EPSG:4326" ) );
1648   crsParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
1649   crsParam.setDefaultValue( QStringLiteral( "EPSG:3111" ) );
1650   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "crs" ), context, widgetContext, &crsParam );
1651   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
1652   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1653   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
1654   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
1655   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
1656   QCOMPARE( static_cast< QgsProcessingParameterCrs * >( def.get() )->defaultValue().toString(), QStringLiteral( "EPSG:3111" ) );
1657 }
1658 
testNumericWrapperDouble()1659 void TestProcessingGui::testNumericWrapperDouble()
1660 {
1661   auto testWrapper = []( QgsProcessingGui::WidgetType type )
1662   {
1663     QgsProcessingContext context;
1664 
1665     QgsProcessingParameterNumber param( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Double );
1666     QgsProcessingNumericWidgetWrapper wrapper( &param, type );
1667 
1668     QWidget *w = wrapper.createWrappedWidget( context );
1669     QVERIFY( static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->expressionsEnabled() );
1670     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->decimals(), 6 ); // you can change this, if it's an intentional change!
1671     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->singleStep(), 1.0 );
1672     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->minimum(), -999999999.0 );
1673     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->maximum(), 999999999.0 );
1674     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->clearValue(), 0.0 );
1675 
1676     QSignalSpy spy( &wrapper, &QgsProcessingNumericWidgetWrapper::widgetValueHasChanged );
1677     wrapper.setWidgetValue( 5, context );
1678     QCOMPARE( spy.count(), 1 );
1679     QCOMPARE( wrapper.widgetValue().toDouble(), 5.0 );
1680     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->value(), 5.0 );
1681     wrapper.setWidgetValue( QStringLiteral( "28356" ), context );
1682     QCOMPARE( spy.count(), 2 );
1683     QCOMPARE( wrapper.widgetValue().toDouble(), 28356.0 );
1684     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->value(), 28356.0 );
1685     wrapper.setWidgetValue( QVariant(), context ); // not optional, so shouldn't work
1686     QCOMPARE( spy.count(), 3 );
1687     QCOMPARE( wrapper.widgetValue().toDouble(), 0.0 );
1688     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->value(), 0.0 );
1689 
1690     QLabel *l = wrapper.createWrappedLabel();
1691     if ( wrapper.type() != QgsProcessingGui::Batch )
1692     {
1693       QVERIFY( l );
1694       QCOMPARE( l->text(), QStringLiteral( "num" ) );
1695       QCOMPARE( l->toolTip(), param.toolTip() );
1696       delete l;
1697     }
1698     else
1699     {
1700       QVERIFY( !l );
1701     }
1702 
1703     // check signal
1704     static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->setValue( 37.0 );
1705     QCOMPARE( spy.count(), 4 );
1706     static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->clear();
1707     QCOMPARE( spy.count(), 5 );
1708     QCOMPARE( wrapper.widgetValue().toDouble(), 0.0 );
1709     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapper.wrappedWidget() )->value(), 0.0 );
1710 
1711     delete w;
1712 
1713     // with min value
1714     QgsProcessingParameterNumber paramMin( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Double );
1715     paramMin.setMinimum( -5 );
1716 
1717     QgsProcessingNumericWidgetWrapper wrapperMin( &paramMin, type );
1718 
1719     w = wrapperMin.createWrappedWidget( context );
1720     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMin.wrappedWidget() )->singleStep(), 1.0 );
1721     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMin.wrappedWidget() )->minimum(), -5.0 );
1722     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMin.wrappedWidget() )->maximum(), 999999999.0 );
1723     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMin.wrappedWidget() )->clearValue(), -5.0 );
1724     QCOMPARE( wrapperMin.parameterValue().toDouble(), 0.0 );
1725     delete w;
1726 
1727     // with max value
1728     QgsProcessingParameterNumber paramMax( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Double );
1729     paramMax.setMaximum( 5 );
1730 
1731     QgsProcessingNumericWidgetWrapper wrapperMax( &paramMax, type );
1732 
1733     w = wrapperMax.createWrappedWidget( context );
1734     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMax.wrappedWidget() )->singleStep(), 1.0 );
1735     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMax.wrappedWidget() )->minimum(), -999999999.0 );
1736     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMax.wrappedWidget() )->maximum(), 5.0 );
1737     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMax.wrappedWidget() )->clearValue(), 0.0 );
1738     QCOMPARE( wrapperMax.parameterValue().toDouble(), 0.0 );
1739     delete w;
1740 
1741     // with min and max value
1742     QgsProcessingParameterNumber paramMinMax( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Double );
1743     paramMinMax.setMinimum( -.1 );
1744     paramMinMax.setMaximum( .1 );
1745 
1746     QgsProcessingNumericWidgetWrapper wrapperMinMax( &paramMinMax, type );
1747 
1748     w = wrapperMinMax.createWrappedWidget( context );
1749     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMinMax.wrappedWidget() )->singleStep(), 0.02 );
1750     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMinMax.wrappedWidget() )->minimum(), -.1 );
1751     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMinMax.wrappedWidget() )->maximum(), .1 );
1752     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperMinMax.wrappedWidget() )->clearValue(), -.1 );
1753     QCOMPARE( wrapperMinMax.parameterValue().toDouble(), 0.0 );
1754     delete w;
1755 
1756     // with default value
1757     QgsProcessingParameterNumber paramDefault( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Double );
1758     paramDefault.setDefaultValue( 55 );
1759 
1760     QgsProcessingNumericWidgetWrapper wrapperDefault( &paramDefault, type );
1761 
1762     w = wrapperDefault.createWrappedWidget( context );
1763     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperDefault.wrappedWidget() )->clearValue(), 55.0 );
1764     QCOMPARE( wrapperDefault.parameterValue().toDouble(), 55.0 );
1765     delete w;
1766 
1767     // optional, no default
1768     QgsProcessingParameterNumber paramOptional( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Double, QVariant(), true );
1769 
1770     QgsProcessingNumericWidgetWrapper wrapperOptional( &paramOptional, type );
1771 
1772     w = wrapperOptional.createWrappedWidget( context );
1773     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperOptional.wrappedWidget() )->clearValue(), -1000000000.0 );
1774     QVERIFY( !wrapperOptional.parameterValue().isValid() );
1775     wrapperOptional.setParameterValue( 5, context );
1776     QCOMPARE( wrapperOptional.parameterValue().toDouble(), 5.0 );
1777     wrapperOptional.setParameterValue( QVariant(), context );
1778     QVERIFY( !wrapperOptional.parameterValue().isValid() );
1779     wrapperOptional.setParameterValue( 5, context );
1780     static_cast< QgsDoubleSpinBox * >( wrapperOptional.wrappedWidget() )->clear();
1781     QVERIFY( !wrapperOptional.parameterValue().isValid() );
1782 
1783     // optional, with default
1784     paramOptional.setDefaultValue( 3 );
1785     QgsProcessingNumericWidgetWrapper wrapperOptionalDefault( &paramOptional, type );
1786 
1787     w = wrapperOptionalDefault.createWrappedWidget( context );
1788     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperOptionalDefault.wrappedWidget() )->clearValue(), -1000000000.0 );
1789     QCOMPARE( wrapperOptionalDefault.parameterValue().toDouble(), 3.0 );
1790     wrapperOptionalDefault.setParameterValue( 5, context );
1791     QCOMPARE( wrapperOptionalDefault.parameterValue().toDouble(), 5.0 );
1792     wrapperOptionalDefault.setParameterValue( QVariant(), context );
1793     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperOptionalDefault.wrappedWidget() )->value(), -1000000000.0 );
1794     QVERIFY( !wrapperOptionalDefault.parameterValue().isValid() );
1795     wrapperOptionalDefault.setParameterValue( 5, context );
1796     QCOMPARE( wrapperOptionalDefault.parameterValue().toDouble(), 5.0 );
1797     static_cast< QgsDoubleSpinBox * >( wrapperOptionalDefault.wrappedWidget() )->clear();
1798     QVERIFY( !wrapperOptionalDefault.parameterValue().isValid() );
1799     wrapperOptionalDefault.setParameterValue( 5, context );
1800     QCOMPARE( wrapperOptionalDefault.parameterValue().toDouble(), 5.0 );
1801 
1802     delete w;
1803 
1804     // with decimals
1805     QgsProcessingParameterNumber paramDecimals( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Double, QVariant(), true, 1, 1.02 );
1806     QVariantMap metadata;
1807     QVariantMap wrapperMetadata;
1808     wrapperMetadata.insert( QStringLiteral( "decimals" ), 2 );
1809     metadata.insert( QStringLiteral( "widget_wrapper" ), wrapperMetadata );
1810     paramDecimals.setMetadata( metadata );
1811     QgsProcessingNumericWidgetWrapper wrapperDecimals( &paramDecimals, type );
1812     w = wrapperDecimals.createWrappedWidget( context );
1813     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperDecimals.wrappedWidget() )->decimals(), 2 );
1814     QCOMPARE( static_cast< QgsDoubleSpinBox * >( wrapperDecimals.wrappedWidget() )->singleStep(), 0.01 ); // single step should never be less than set number of decimals
1815     delete w;
1816   };
1817 
1818   // standard wrapper
1819   testWrapper( QgsProcessingGui::Standard );
1820 
1821   // batch wrapper
1822   testWrapper( QgsProcessingGui::Batch );
1823 
1824   // modeler wrapper
1825   testWrapper( QgsProcessingGui::Modeler );
1826 
1827   // config widget
1828   QgsProcessingParameterWidgetContext widgetContext;
1829   QgsProcessingContext context;
1830   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "number" ), context, widgetContext );
1831   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
1832   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1833   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
1834   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
1835 
1836   // using a parameter definition as initial values
1837   QgsProcessingParameterNumber numParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QgsProcessingParameterNumber::Double, 1.0 );
1838   numParam.setMinimum( 0 );
1839   numParam.setMaximum( 10 );
1840   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "number" ), context, widgetContext, &numParam );
1841   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
1842   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1843   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
1844   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
1845   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
1846   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->defaultValue().toDouble(), 1.0 );
1847   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->dataType(), QgsProcessingParameterNumber::Double );
1848   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->minimum(), 0.0 );
1849   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->maximum(), 10.0 );
1850   numParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
1851   numParam.setDataType( QgsProcessingParameterNumber::Integer );
1852   numParam.setMinimum( -1 );
1853   numParam.setMaximum( 1 );
1854   numParam.setDefaultValue( 0 );
1855   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "number" ), context, widgetContext, &numParam );
1856   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
1857   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
1858   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
1859   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
1860   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
1861   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->defaultValue().toInt(), 0 );
1862   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->dataType(), QgsProcessingParameterNumber::Integer );
1863   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->minimum(), -1.0 );
1864   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->maximum(), 1.0 );
1865 }
1866 
testNumericWrapperInt()1867 void TestProcessingGui::testNumericWrapperInt()
1868 {
1869   auto testWrapper = []( QgsProcessingGui::WidgetType type )
1870   {
1871     QgsProcessingContext context;
1872 
1873     QgsProcessingParameterNumber param( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Integer );
1874     QgsProcessingNumericWidgetWrapper wrapper( &param, type );
1875 
1876     QWidget *w = wrapper.createWrappedWidget( context );
1877     QVERIFY( static_cast< QgsSpinBox * >( wrapper.wrappedWidget() )->expressionsEnabled() );
1878     QCOMPARE( static_cast< QgsSpinBox * >( wrapper.wrappedWidget() )->minimum(), -999999999 );
1879     QCOMPARE( static_cast< QgsSpinBox * >( wrapper.wrappedWidget() )->maximum(), 999999999 );
1880     QCOMPARE( static_cast< QgsSpinBox * >( wrapper.wrappedWidget() )->clearValue(), 0 );
1881 
1882     QSignalSpy spy( &wrapper, &QgsProcessingNumericWidgetWrapper::widgetValueHasChanged );
1883     wrapper.setWidgetValue( 5, context );
1884     QCOMPARE( spy.count(), 1 );
1885     QCOMPARE( wrapper.widgetValue().toInt(), 5 );
1886     QCOMPARE( static_cast< QgsSpinBox * >( wrapper.wrappedWidget() )->value(), 5 );
1887     wrapper.setWidgetValue( QStringLiteral( "28356" ), context );
1888     QCOMPARE( spy.count(), 2 );
1889     QCOMPARE( wrapper.widgetValue().toInt(), 28356 );
1890     QCOMPARE( static_cast< QgsSpinBox * >( wrapper.wrappedWidget() )->value(), 28356 );
1891     wrapper.setWidgetValue( QVariant(), context ); // not optional, so shouldn't work
1892     QCOMPARE( spy.count(), 3 );
1893     QCOMPARE( wrapper.widgetValue().toInt(), 0 );
1894     QCOMPARE( static_cast< QgsSpinBox * >( wrapper.wrappedWidget() )->value(), 0 );
1895 
1896     QLabel *l = wrapper.createWrappedLabel();
1897     if ( wrapper.type() != QgsProcessingGui::Batch )
1898     {
1899       QVERIFY( l );
1900       QCOMPARE( l->text(), QStringLiteral( "num" ) );
1901       QCOMPARE( l->toolTip(), param.toolTip() );
1902       delete l;
1903     }
1904     else
1905     {
1906       QVERIFY( !l );
1907     }
1908 
1909     // check signal
1910     static_cast< QgsSpinBox * >( wrapper.wrappedWidget() )->setValue( 37 );
1911     QCOMPARE( spy.count(), 4 );
1912     static_cast< QgsSpinBox * >( wrapper.wrappedWidget() )->clear();
1913     QCOMPARE( spy.count(), 5 );
1914     QCOMPARE( wrapper.widgetValue().toInt(), 0 );
1915     QCOMPARE( static_cast< QgsSpinBox * >( wrapper.wrappedWidget() )->value(), 0 );
1916 
1917     delete w;
1918 
1919     // with min value
1920     QgsProcessingParameterNumber paramMin( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Integer );
1921     paramMin.setMinimum( -5 );
1922 
1923     QgsProcessingNumericWidgetWrapper wrapperMin( &paramMin, type );
1924 
1925     w = wrapperMin.createWrappedWidget( context );
1926     QCOMPARE( static_cast< QgsSpinBox * >( wrapperMin.wrappedWidget() )->minimum(), -5 );
1927     QCOMPARE( static_cast< QgsSpinBox * >( wrapperMin.wrappedWidget() )->maximum(), 999999999 );
1928     QCOMPARE( static_cast< QgsSpinBox * >( wrapperMin.wrappedWidget() )->clearValue(), -5 );
1929     QCOMPARE( wrapperMin.parameterValue().toInt(), 0 );
1930     delete w;
1931 
1932     // with max value
1933     QgsProcessingParameterNumber paramMax( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Integer );
1934     paramMax.setMaximum( 5 );
1935 
1936     QgsProcessingNumericWidgetWrapper wrapperMax( &paramMax, type );
1937 
1938     w = wrapperMax.createWrappedWidget( context );
1939     QCOMPARE( static_cast< QgsSpinBox * >( wrapperMax.wrappedWidget() )->minimum(), -999999999 );
1940     QCOMPARE( static_cast< QgsSpinBox * >( wrapperMax.wrappedWidget() )->maximum(), 5 );
1941     QCOMPARE( static_cast< QgsSpinBox * >( wrapperMax.wrappedWidget() )->clearValue(), 0 );
1942     QCOMPARE( wrapperMax.parameterValue().toInt(), 0 );
1943     delete w;
1944 
1945     // with min and max value
1946     QgsProcessingParameterNumber paramMinMax( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Integer );
1947     paramMinMax.setMinimum( -1 );
1948     paramMinMax.setMaximum( 1 );
1949 
1950     QgsProcessingNumericWidgetWrapper wrapperMinMax( &paramMinMax, type );
1951 
1952     w = wrapperMinMax.createWrappedWidget( context );
1953     QCOMPARE( static_cast< QgsSpinBox * >( wrapperMinMax.wrappedWidget() )->minimum(), -1 );
1954     QCOMPARE( static_cast< QgsSpinBox * >( wrapperMinMax.wrappedWidget() )->maximum(), 1 );
1955     QCOMPARE( static_cast< QgsSpinBox * >( wrapperMinMax.wrappedWidget() )->clearValue(), -1 );
1956     QCOMPARE( wrapperMinMax.parameterValue().toInt(), 0 );
1957     delete w;
1958 
1959     // with default value
1960     QgsProcessingParameterNumber paramDefault( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Integer );
1961     paramDefault.setDefaultValue( 55 );
1962 
1963     QgsProcessingNumericWidgetWrapper wrapperDefault( &paramDefault, type );
1964 
1965     w = wrapperDefault.createWrappedWidget( context );
1966     QCOMPARE( static_cast< QgsSpinBox * >( wrapperDefault.wrappedWidget() )->clearValue(), 55 );
1967     QCOMPARE( wrapperDefault.parameterValue().toInt(), 55 );
1968     delete w;
1969 
1970     // optional, no default
1971     QgsProcessingParameterNumber paramOptional( QStringLiteral( "num" ), QStringLiteral( "num" ), QgsProcessingParameterNumber::Integer, QVariant(), true );
1972 
1973     QgsProcessingNumericWidgetWrapper wrapperOptional( &paramOptional, type );
1974 
1975     w = wrapperOptional.createWrappedWidget( context );
1976     QCOMPARE( static_cast< QgsSpinBox * >( wrapperOptional.wrappedWidget() )->clearValue(), -1000000000 );
1977     QVERIFY( !wrapperOptional.parameterValue().isValid() );
1978     wrapperOptional.setParameterValue( 5, context );
1979     QCOMPARE( wrapperOptional.parameterValue().toInt(), 5 );
1980     wrapperOptional.setParameterValue( QVariant(), context );
1981     QVERIFY( !wrapperOptional.parameterValue().isValid() );
1982     wrapperOptional.setParameterValue( 5, context );
1983     static_cast< QgsSpinBox * >( wrapperOptional.wrappedWidget() )->clear();
1984     QVERIFY( !wrapperOptional.parameterValue().isValid() );
1985 
1986     // optional, with default
1987     paramOptional.setDefaultValue( 3 );
1988     QgsProcessingNumericWidgetWrapper wrapperOptionalDefault( &paramOptional, type );
1989 
1990     w = wrapperOptionalDefault.createWrappedWidget( context );
1991     QCOMPARE( static_cast< QgsSpinBox * >( wrapperOptionalDefault.wrappedWidget() )->clearValue(), -1000000000 );
1992     QCOMPARE( wrapperOptionalDefault.parameterValue().toInt(), 3 );
1993     wrapperOptionalDefault.setParameterValue( 5, context );
1994     QCOMPARE( wrapperOptionalDefault.parameterValue().toInt(), 5 );
1995     wrapperOptionalDefault.setParameterValue( QVariant(), context );
1996     QCOMPARE( static_cast< QgsSpinBox * >( wrapperOptionalDefault.wrappedWidget() )->value(), -1000000000 );
1997     QVERIFY( !wrapperOptionalDefault.parameterValue().isValid() );
1998     wrapperOptionalDefault.setParameterValue( 5, context );
1999     QCOMPARE( wrapperOptionalDefault.parameterValue().toInt(), 5 );
2000     static_cast< QgsSpinBox * >( wrapperOptionalDefault.wrappedWidget() )->clear();
2001     QVERIFY( !wrapperOptionalDefault.parameterValue().isValid() );
2002     wrapperOptionalDefault.setParameterValue( 5, context );
2003     QCOMPARE( wrapperOptionalDefault.parameterValue().toInt(), 5 );
2004 
2005     delete w;
2006   };
2007 
2008   // standard wrapper
2009   testWrapper( QgsProcessingGui::Standard );
2010 
2011   // batch wrapper
2012   testWrapper( QgsProcessingGui::Batch );
2013 
2014   // modeler wrapper
2015   testWrapper( QgsProcessingGui::Modeler );
2016 
2017   // config widget
2018   QgsProcessingParameterWidgetContext widgetContext;
2019   QgsProcessingContext context;
2020   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "number" ), context, widgetContext );
2021   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
2022   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2023   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
2024   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2025 
2026   // using a parameter definition as initial values
2027   QgsProcessingParameterNumber numParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QgsProcessingParameterNumber::Integer, 1 );
2028   numParam.setMinimum( 0 );
2029   numParam.setMaximum( 10 );
2030   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "number" ), context, widgetContext, &numParam );
2031   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2032   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2033   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2034   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
2035   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2036   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->defaultValue().toDouble(), 1.0 );
2037   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->dataType(), QgsProcessingParameterNumber::Integer );
2038   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->minimum(), 0.0 );
2039   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->maximum(), 10.0 );
2040   numParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
2041   numParam.setDataType( QgsProcessingParameterNumber::Double );
2042   numParam.setMinimum( -2.5 );
2043   numParam.setMaximum( 2.5 );
2044   numParam.setDefaultValue( 0.5 );
2045   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "number" ), context, widgetContext, &numParam );
2046   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2047   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2048   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2049   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
2050   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
2051   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->defaultValue().toDouble(), 0.5 );
2052   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->dataType(), QgsProcessingParameterNumber::Double );
2053   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->minimum(), -2.5 );
2054   QCOMPARE( static_cast< QgsProcessingParameterNumber * >( def.get() )->maximum(), 2.5 );
2055 }
2056 
testDistanceWrapper()2057 void TestProcessingGui::testDistanceWrapper()
2058 {
2059   QgsProcessingParameterDistance param( QStringLiteral( "distance" ), QStringLiteral( "distance" ) );
2060 
2061   // standard wrapper
2062   QgsProcessingDistanceWidgetWrapper wrapper( &param );
2063 
2064   QgsProcessingContext context;
2065   QWidget *w = wrapper.createWrappedWidget( context );
2066 
2067   QSignalSpy spy( &wrapper, &QgsProcessingDistanceWidgetWrapper::widgetValueHasChanged );
2068   wrapper.setWidgetValue( 55.5, context );
2069   QCOMPARE( spy.count(), 1 );
2070   QCOMPARE( wrapper.widgetValue().toDouble(), 55.5 );
2071   QCOMPARE( wrapper.mDoubleSpinBox->value(), 55.5 );
2072   wrapper.setWidgetValue( -34.0, context );
2073   QCOMPARE( spy.count(), 2 );
2074   QCOMPARE( wrapper.widgetValue().toDouble(), -34.0 );
2075   QCOMPARE( wrapper.mDoubleSpinBox->value(), -34.0 );
2076 
2077   QLabel *l = wrapper.createWrappedLabel();
2078   QVERIFY( l );
2079   QCOMPARE( l->text(), QStringLiteral( "distance" ) );
2080   QCOMPARE( l->toolTip(), param.toolTip() );
2081   delete l;
2082 
2083   // check signal
2084   wrapper.mDoubleSpinBox->setValue( 43.0 );
2085   QCOMPARE( spy.count(), 3 );
2086 
2087   // test unit handling
2088   w->show();
2089 
2090   QCOMPARE( wrapper.mLabel->text(), QStringLiteral( "<unknown>" ) );
2091 
2092   // crs values
2093   wrapper.setUnitParameterValue( QStringLiteral( "EPSG:3111" ) );
2094   QCOMPARE( wrapper.mLabel->text(), QStringLiteral( "meters" ) );
2095   QVERIFY( !wrapper.mWarningLabel->isVisible() );
2096   QVERIFY( wrapper.mUnitsCombo->isVisible() );
2097   QVERIFY( !wrapper.mLabel->isVisible() );
2098   QCOMPARE( wrapper.mUnitsCombo->currentData().toInt(), static_cast< int >( QgsUnitTypes::DistanceMeters ) );
2099 
2100   wrapper.setUnitParameterValue( QStringLiteral( "EPSG:4326" ) );
2101   QCOMPARE( wrapper.mLabel->text(), QStringLiteral( "degrees" ) );
2102   QVERIFY( wrapper.mWarningLabel->isVisible() );
2103   QVERIFY( !wrapper.mUnitsCombo->isVisible() );
2104   QVERIFY( wrapper.mLabel->isVisible() );
2105 
2106   wrapper.setUnitParameterValue( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ) );
2107   QCOMPARE( wrapper.mLabel->text(), QStringLiteral( "meters" ) );
2108   QVERIFY( !wrapper.mWarningLabel->isVisible() );
2109   QVERIFY( wrapper.mUnitsCombo->isVisible() );
2110   QVERIFY( !wrapper.mLabel->isVisible() );
2111   QCOMPARE( wrapper.mUnitsCombo->currentData().toInt(), static_cast< int >( QgsUnitTypes::DistanceMeters ) );
2112 
2113   wrapper.setUnitParameterValue( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
2114   QCOMPARE( wrapper.mLabel->text(), QStringLiteral( "degrees" ) );
2115   QVERIFY( wrapper.mWarningLabel->isVisible() );
2116   QVERIFY( !wrapper.mUnitsCombo->isVisible() );
2117   QVERIFY( wrapper.mLabel->isVisible() );
2118 
2119   // layer values
2120   std::unique_ptr< QgsVectorLayer > vl = std::make_unique< QgsVectorLayer >( QStringLiteral( "Polygon?crs=epsg:3111&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
2121   wrapper.setUnitParameterValue( QVariant::fromValue( vl.get() ) );
2122   QCOMPARE( wrapper.mLabel->text(), QStringLiteral( "meters" ) );
2123   QVERIFY( !wrapper.mWarningLabel->isVisible() );
2124   QVERIFY( wrapper.mUnitsCombo->isVisible() );
2125   QVERIFY( !wrapper.mLabel->isVisible() );
2126   QCOMPARE( wrapper.mUnitsCombo->currentData().toInt(), static_cast< int >( QgsUnitTypes::DistanceMeters ) );
2127 
2128   std::unique_ptr< QgsVectorLayer > vl2 = std::make_unique< QgsVectorLayer >( QStringLiteral( "Polygon?crs=epsg:4326&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
2129   wrapper.setUnitParameterValue( QVariant::fromValue( vl2.get() ) );
2130   QCOMPARE( wrapper.mLabel->text(), QStringLiteral( "degrees" ) );
2131   QVERIFY( wrapper.mWarningLabel->isVisible() );
2132   QVERIFY( !wrapper.mUnitsCombo->isVisible() );
2133   QVERIFY( wrapper.mLabel->isVisible() );
2134 
2135   // unresolvable values
2136   wrapper.setUnitParameterValue( QStringLiteral( "blah" ) );
2137   QCOMPARE( wrapper.mLabel->text(), QStringLiteral( "<unknown>" ) );
2138   QVERIFY( !wrapper.mWarningLabel->isVisible() );
2139   QVERIFY( !wrapper.mUnitsCombo->isVisible() );
2140   QVERIFY( wrapper.mLabel->isVisible() );
2141 
2142   // resolvable text value
2143   const QString id = vl->id();
2144   QgsProject::instance()->addMapLayer( vl.release() );
2145   context.setProject( QgsProject::instance() );
2146 
2147   TestProcessingContextGenerator generator( context );
2148   wrapper.registerProcessingContextGenerator( &generator );
2149   wrapper.setUnitParameterValue( id );
2150   QCOMPARE( wrapper.mLabel->text(), QStringLiteral( "meters" ) );
2151   QVERIFY( !wrapper.mWarningLabel->isVisible() );
2152   QVERIFY( wrapper.mUnitsCombo->isVisible() );
2153   QVERIFY( !wrapper.mLabel->isVisible() );
2154   QCOMPARE( wrapper.mUnitsCombo->currentData().toInt(), static_cast< int >( QgsUnitTypes::DistanceMeters ) );
2155 
2156   // using unit choice
2157   wrapper.setParameterValue( 5, context );
2158   QCOMPARE( wrapper.parameterValue().toDouble(), 5.0 );
2159   wrapper.mUnitsCombo->setCurrentIndex( wrapper.mUnitsCombo->findData( QgsUnitTypes::DistanceKilometers ) );
2160   QCOMPARE( wrapper.parameterValue().toDouble(), 5000.0 );
2161   wrapper.setParameterValue( 2, context );
2162   QCOMPARE( wrapper.parameterValue().toDouble(), 2000.0 );
2163 
2164   wrapper.setUnitParameterValue( id );
2165   QCOMPARE( wrapper.parameterValue().toDouble(), 2.0 );
2166   wrapper.setParameterValue( 5, context );
2167   QCOMPARE( wrapper.parameterValue().toDouble(), 5.0 );
2168 
2169   delete w;
2170 
2171   // with default unit
2172   QgsProcessingParameterDistance paramDefaultUnit( QStringLiteral( "num" ), QStringLiteral( "num" ) );
2173   paramDefaultUnit.setDefaultUnit( QgsUnitTypes::DistanceFeet );
2174   QgsProcessingDistanceWidgetWrapper wrapperDefaultUnit( &paramDefaultUnit, QgsProcessingGui::Standard );
2175   w = wrapperDefaultUnit.createWrappedWidget( context );
2176   w->show();
2177   QCOMPARE( wrapperDefaultUnit.mLabel->text(), QStringLiteral( "feet" ) );
2178   delete w;
2179 
2180   // with decimals
2181   QgsProcessingParameterDistance paramDecimals( QStringLiteral( "num" ), QStringLiteral( "num" ), QVariant(), QString(), true, 1, 1.02 );
2182   QVariantMap metadata;
2183   QVariantMap wrapperMetadata;
2184   wrapperMetadata.insert( QStringLiteral( "decimals" ), 2 );
2185   metadata.insert( QStringLiteral( "widget_wrapper" ), wrapperMetadata );
2186   paramDecimals.setMetadata( metadata );
2187   QgsProcessingDistanceWidgetWrapper wrapperDecimals( &paramDecimals, QgsProcessingGui::Standard );
2188   w = wrapperDecimals.createWrappedWidget( context );
2189   QCOMPARE( wrapperDecimals.mDoubleSpinBox->decimals(), 2 );
2190   QCOMPARE( wrapperDecimals.mDoubleSpinBox->singleStep(), 0.01 ); // single step should never be less than set number of decimals
2191   delete w;
2192 
2193   // batch wrapper
2194   QgsProcessingDistanceWidgetWrapper wrapperB( &param, QgsProcessingGui::Batch );
2195 
2196   w = wrapperB.createWrappedWidget( context );
2197   QSignalSpy spy2( &wrapperB, &QgsProcessingDistanceWidgetWrapper::widgetValueHasChanged );
2198   wrapperB.setWidgetValue( 34, context );
2199   QCOMPARE( spy2.count(), 1 );
2200   QCOMPARE( wrapperB.widgetValue().toDouble(), 34.0 );
2201   QCOMPARE( wrapperB.mDoubleSpinBox->value(), 34.0 );
2202   wrapperB.setWidgetValue( -57, context );
2203   QCOMPARE( spy2.count(), 2 );
2204   QCOMPARE( wrapperB.widgetValue().toDouble(), -57.0 );
2205   QCOMPARE( wrapperB.mDoubleSpinBox->value(), -57.0 );
2206 
2207   // check signal
2208   static_cast< QgsDoubleSpinBox * >( w )->setValue( 29 );
2209   QCOMPARE( spy2.count(), 3 );
2210 
2211   // should be no label in batch mode
2212   QVERIFY( !wrapperB.createWrappedLabel() );
2213   delete w;
2214 
2215   // modeler wrapper
2216   QgsProcessingDistanceWidgetWrapper wrapperM( &param, QgsProcessingGui::Modeler );
2217 
2218   w = wrapperM.createWrappedWidget( context );
2219   QSignalSpy spy3( &wrapperM, &QgsProcessingDistanceWidgetWrapper::widgetValueHasChanged );
2220   wrapperM.setWidgetValue( 29, context );
2221   QCOMPARE( wrapperM.widgetValue().toDouble(), 29.0 );
2222   QCOMPARE( spy3.count(), 1 );
2223   QCOMPARE( wrapperM.mDoubleSpinBox->value(), 29.0 );
2224   wrapperM.setWidgetValue( -29, context );
2225   QCOMPARE( wrapperM.widgetValue().toDouble(), -29.0 );
2226   QCOMPARE( spy3.count(), 2 );
2227   QCOMPARE( wrapperM.mDoubleSpinBox->value(), -29.0 );
2228 
2229   // check signal
2230   wrapperM.mDoubleSpinBox->setValue( 33 );
2231   QCOMPARE( spy3.count(), 3 );
2232 
2233   // should be a label in modeler mode
2234   l = wrapperM.createWrappedLabel();
2235   QVERIFY( l );
2236   QCOMPARE( l->text(), QStringLiteral( "distance" ) );
2237   QCOMPARE( l->toolTip(), param.toolTip() );
2238   delete w;
2239   delete l;
2240 
2241   // config widget
2242   QgsProcessingParameterWidgetContext widgetContext;
2243   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "distance" ), context, widgetContext );
2244   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
2245   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2246   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
2247   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2248 
2249   // using a parameter definition as initial values
2250   QgsProcessingParameterDistance distParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), 1, QStringLiteral( "parent" ) );
2251   distParam.setMinimum( 1 );
2252   distParam.setMaximum( 100 );
2253   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "distance" ), context, widgetContext, &distParam );
2254   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2255   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2256   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2257   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
2258   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2259   QCOMPARE( static_cast< QgsProcessingParameterDistance * >( def.get() )->defaultValue().toDouble(), 1.0 );
2260   QCOMPARE( static_cast< QgsProcessingParameterDistance * >( def.get() )->minimum(), 1.0 );
2261   QCOMPARE( static_cast< QgsProcessingParameterDistance * >( def.get() )->maximum(), 100.0 );
2262   QCOMPARE( static_cast< QgsProcessingParameterDistance * >( def.get() )->parentParameterName(), QStringLiteral( "parent" ) );
2263   distParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
2264   distParam.setParentParameterName( QString() );
2265   distParam.setMinimum( 10 );
2266   distParam.setMaximum( 12 );
2267   distParam.setDefaultValue( 11.5 );
2268   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "distance" ), context, widgetContext, &distParam );
2269   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2270   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2271   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2272   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
2273   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
2274   QCOMPARE( static_cast< QgsProcessingParameterDistance * >( def.get() )->defaultValue().toDouble(), 11.5 );
2275   QCOMPARE( static_cast< QgsProcessingParameterDistance * >( def.get() )->minimum(), 10.0 );
2276   QCOMPARE( static_cast< QgsProcessingParameterDistance * >( def.get() )->maximum(), 12.0 );
2277   QVERIFY( static_cast< QgsProcessingParameterDistance * >( def.get() )->parentParameterName().isEmpty() );
2278 }
2279 
testDurationWrapper()2280 void TestProcessingGui::testDurationWrapper()
2281 {
2282   QgsProcessingParameterDuration param( QStringLiteral( "duration" ), QStringLiteral( "duration" ) );
2283 
2284   // standard wrapper
2285   QgsProcessingDurationWidgetWrapper wrapper( &param );
2286 
2287   QgsProcessingContext context;
2288   QWidget *w = wrapper.createWrappedWidget( context );
2289 
2290   QSignalSpy spy( &wrapper, &QgsProcessingDurationWidgetWrapper::widgetValueHasChanged );
2291   wrapper.setWidgetValue( 55.5, context );
2292   QCOMPARE( spy.count(), 1 );
2293   QCOMPARE( wrapper.widgetValue().toDouble(), 55.5 );
2294   QCOMPARE( wrapper.mDoubleSpinBox->value(), 55.5 );
2295   wrapper.setWidgetValue( -34.0, context );
2296   QCOMPARE( spy.count(), 2 );
2297   QCOMPARE( wrapper.widgetValue().toDouble(), -34.0 );
2298   QCOMPARE( wrapper.mDoubleSpinBox->value(), -34.0 );
2299 
2300   QLabel *l = wrapper.createWrappedLabel();
2301   QVERIFY( l );
2302   QCOMPARE( l->text(), QStringLiteral( "duration" ) );
2303   QCOMPARE( l->toolTip(), param.toolTip() );
2304   delete l;
2305 
2306   // check signal
2307   wrapper.mDoubleSpinBox->setValue( 43.0 );
2308   QCOMPARE( spy.count(), 3 );
2309 
2310   // with default unit
2311   QgsProcessingParameterDuration paramDefaultUnit( QStringLiteral( "dur" ), QStringLiteral( "dur" ) );
2312   paramDefaultUnit.setDefaultUnit( QgsUnitTypes::TemporalDays );
2313   QgsProcessingDurationWidgetWrapper wrapperDefaultUnit( &paramDefaultUnit, QgsProcessingGui::Standard );
2314   w = wrapperDefaultUnit.createWrappedWidget( context );
2315   w->show();
2316   QCOMPARE( wrapperDefaultUnit.mUnitsCombo->currentText(), QgsUnitTypes::toString( QgsUnitTypes::TemporalDays ) );
2317   delete w;
2318 
2319   // with decimals
2320   QgsProcessingParameterDuration paramDecimals( QStringLiteral( "num" ), QStringLiteral( "num" ), QVariant(), true, 1, 1.02 );
2321   QVariantMap metadata;
2322   QVariantMap wrapperMetadata;
2323   wrapperMetadata.insert( QStringLiteral( "decimals" ), 2 );
2324   metadata.insert( QStringLiteral( "widget_wrapper" ), wrapperMetadata );
2325   paramDecimals.setMetadata( metadata );
2326   QgsProcessingDurationWidgetWrapper wrapperDecimals( &paramDecimals, QgsProcessingGui::Standard );
2327   w = wrapperDecimals.createWrappedWidget( context );
2328   QCOMPARE( wrapperDecimals.mDoubleSpinBox->decimals(), 2 );
2329   QCOMPARE( wrapperDecimals.mDoubleSpinBox->singleStep(), 0.01 ); // single step should never be less than set number of decimals
2330   delete w;
2331 
2332   // batch wrapper
2333   QgsProcessingDurationWidgetWrapper wrapperB( &param, QgsProcessingGui::Batch );
2334 
2335   w = wrapperB.createWrappedWidget( context );
2336   QSignalSpy spy2( &wrapperB, &QgsProcessingDurationWidgetWrapper::widgetValueHasChanged );
2337   wrapperB.setWidgetValue( 34, context );
2338   QCOMPARE( spy2.count(), 1 );
2339   QCOMPARE( wrapperB.widgetValue().toDouble(), 34.0 );
2340   QCOMPARE( wrapperB.mDoubleSpinBox->value(), 34.0 );
2341   wrapperB.setWidgetValue( -57, context );
2342   QCOMPARE( spy2.count(), 2 );
2343   QCOMPARE( wrapperB.widgetValue().toDouble(), -57.0 );
2344   QCOMPARE( wrapperB.mDoubleSpinBox->value(), -57.0 );
2345 
2346   // check signal
2347   static_cast< QgsDoubleSpinBox * >( w )->setValue( 29 );
2348   QCOMPARE( spy2.count(), 3 );
2349 
2350   // should be no label in batch mode
2351   QVERIFY( !wrapperB.createWrappedLabel() );
2352   delete w;
2353 
2354   // modeler wrapper
2355   QgsProcessingDurationWidgetWrapper wrapperM( &param, QgsProcessingGui::Modeler );
2356 
2357   w = wrapperM.createWrappedWidget( context );
2358   QSignalSpy spy3( &wrapperM, &QgsProcessingDurationWidgetWrapper::widgetValueHasChanged );
2359   wrapperM.setWidgetValue( 29, context );
2360   QCOMPARE( wrapperM.widgetValue().toDouble(), 29.0 );
2361   QCOMPARE( spy3.count(), 1 );
2362   QCOMPARE( wrapperM.mDoubleSpinBox->value(), 29.0 );
2363   wrapperM.setWidgetValue( -29, context );
2364   QCOMPARE( wrapperM.widgetValue().toDouble(), -29.0 );
2365   QCOMPARE( spy3.count(), 2 );
2366   QCOMPARE( wrapperM.mDoubleSpinBox->value(), -29.0 );
2367 
2368   // check signal
2369   wrapperM.mDoubleSpinBox->setValue( 33 );
2370   QCOMPARE( spy3.count(), 3 );
2371 
2372   // should be a label in modeler mode which includes a unit type string
2373   l = wrapperM.createWrappedLabel();
2374   QVERIFY( l );
2375   QCOMPARE( l->text(), QStringLiteral( "duration [milliseconds]" ) );
2376   QCOMPARE( l->toolTip(), param.toolTip() );
2377   delete w;
2378   delete l;
2379 
2380   // config widget
2381   QgsProcessingParameterWidgetContext widgetContext;
2382   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "duration" ), context, widgetContext );
2383   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
2384   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2385   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
2386   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2387 
2388   // using a parameter definition as initial values
2389   QgsProcessingParameterDuration durParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), 1 );
2390   durParam.setMinimum( 1 );
2391   durParam.setMaximum( 100 );
2392   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "duration" ), context, widgetContext, &durParam );
2393   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2394   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2395   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2396   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
2397   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2398   QCOMPARE( static_cast< QgsProcessingParameterDuration * >( def.get() )->defaultValue().toDouble(), 1.0 );
2399   QCOMPARE( static_cast< QgsProcessingParameterDuration * >( def.get() )->minimum(), 1.0 );
2400   QCOMPARE( static_cast< QgsProcessingParameterDuration * >( def.get() )->maximum(), 100.0 );
2401   durParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
2402   durParam.setMinimum( 10 );
2403   durParam.setMaximum( 12 );
2404   durParam.setDefaultValue( 11.5 );
2405   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "duration" ), context, widgetContext, &durParam );
2406   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2407   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2408   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2409   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
2410   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
2411   QCOMPARE( static_cast< QgsProcessingParameterDuration * >( def.get() )->defaultValue().toDouble(), 11.5 );
2412   QCOMPARE( static_cast< QgsProcessingParameterDuration * >( def.get() )->minimum(), 10.0 );
2413   QCOMPARE( static_cast< QgsProcessingParameterDuration * >( def.get() )->maximum(), 12.0 );
2414 }
2415 
testScaleWrapper()2416 void TestProcessingGui::testScaleWrapper()
2417 {
2418   auto testWrapper = []( QgsProcessingGui::WidgetType type )
2419   {
2420     QgsProcessingContext context;
2421 
2422     QgsProcessingParameterScale param( QStringLiteral( "num" ), QStringLiteral( "num" ) );
2423     QgsProcessingScaleWidgetWrapper wrapper( &param, type );
2424 
2425     QWidget *w = wrapper.createWrappedWidget( context );
2426     QSignalSpy spy( &wrapper, &QgsProcessingNumericWidgetWrapper::widgetValueHasChanged );
2427     wrapper.setWidgetValue( 5, context );
2428     QCOMPARE( spy.count(), 1 );
2429     QCOMPARE( wrapper.widgetValue().toDouble(), 5.0 );
2430     QCOMPARE( static_cast< QgsScaleWidget * >( wrapper.wrappedWidget() )->scale(), 5.0 );
2431     wrapper.setWidgetValue( QStringLiteral( "28356" ), context );
2432     QCOMPARE( spy.count(), 2 );
2433     QCOMPARE( wrapper.widgetValue().toDouble(), 28356.0 );
2434     QCOMPARE( static_cast< QgsScaleWidget * >( wrapper.wrappedWidget() )->scale(), 28356.0 );
2435     wrapper.setWidgetValue( QVariant(), context ); // not optional, so shouldn't work
2436     QCOMPARE( spy.count(), 3 );
2437     QCOMPARE( wrapper.widgetValue().toDouble(), 0.0 );
2438     QCOMPARE( static_cast< QgsScaleWidget * >( wrapper.wrappedWidget() )->scale(), 0.0 );
2439 
2440     QLabel *l = wrapper.createWrappedLabel();
2441     if ( wrapper.type() != QgsProcessingGui::Batch )
2442     {
2443       QVERIFY( l );
2444       QCOMPARE( l->text(), QStringLiteral( "num" ) );
2445       QCOMPARE( l->toolTip(), param.toolTip() );
2446       delete l;
2447     }
2448     else
2449     {
2450       QVERIFY( !l );
2451     }
2452 
2453     // check signal
2454     static_cast< QgsScaleWidget * >( wrapper.wrappedWidget() )->setScale( 37.0 );
2455     QCOMPARE( spy.count(), 4 );
2456 
2457     delete w;
2458 
2459     // optional, no default
2460     QgsProcessingParameterScale paramOptional( QStringLiteral( "num" ), QStringLiteral( "num" ), QVariant(), true );
2461 
2462     QgsProcessingScaleWidgetWrapper wrapperOptional( &paramOptional, type );
2463 
2464     w = wrapperOptional.createWrappedWidget( context );
2465     QVERIFY( !wrapperOptional.parameterValue().isValid() );
2466     wrapperOptional.setParameterValue( 5, context );
2467     QCOMPARE( wrapperOptional.parameterValue().toDouble(), 5.0 );
2468     wrapperOptional.setParameterValue( QVariant(), context );
2469     QVERIFY( !wrapperOptional.parameterValue().isValid() );
2470     wrapperOptional.setParameterValue( 5, context );
2471     static_cast< QgsScaleWidget * >( wrapperOptional.wrappedWidget() )->setScale( std::numeric_limits< double >::quiet_NaN() );
2472     QVERIFY( !wrapperOptional.parameterValue().isValid() );
2473 
2474     // optional, with default
2475     paramOptional.setDefaultValue( 3 );
2476     QgsProcessingScaleWidgetWrapper wrapperOptionalDefault( &paramOptional, type );
2477 
2478     w = wrapperOptionalDefault.createWrappedWidget( context );
2479     QCOMPARE( wrapperOptionalDefault.parameterValue().toDouble(), 3.0 );
2480     wrapperOptionalDefault.setParameterValue( 5, context );
2481     QCOMPARE( wrapperOptionalDefault.parameterValue().toDouble(), 5.0 );
2482     wrapperOptionalDefault.setParameterValue( QVariant(), context );
2483     QVERIFY( std::isnan( static_cast< QgsScaleWidget * >( wrapperOptionalDefault.wrappedWidget() )->scale() ) );
2484     QVERIFY( !wrapperOptionalDefault.parameterValue().isValid() );
2485     wrapperOptionalDefault.setParameterValue( 5, context );
2486     QCOMPARE( wrapperOptionalDefault.parameterValue().toDouble(), 5.0 );
2487     static_cast< QgsScaleWidget * >( wrapperOptionalDefault.wrappedWidget() )->setScale( std::numeric_limits< double >::quiet_NaN() );
2488     QVERIFY( !wrapperOptionalDefault.parameterValue().isValid() );
2489     wrapperOptionalDefault.setParameterValue( 5, context );
2490     QCOMPARE( wrapperOptionalDefault.parameterValue().toDouble(), 5.0 );
2491 
2492     delete w;
2493   };
2494 
2495   // standard wrapper
2496   testWrapper( QgsProcessingGui::Standard );
2497 
2498   // batch wrapper
2499   testWrapper( QgsProcessingGui::Batch );
2500 
2501   // modeler wrapper
2502   testWrapper( QgsProcessingGui::Modeler );
2503 
2504   // config widget
2505   QgsProcessingParameterWidgetContext widgetContext;
2506   QgsProcessingContext context;
2507   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "scale" ), context, widgetContext );
2508   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
2509   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2510   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
2511   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2512 
2513   // using a parameter definition as initial values
2514   QgsProcessingParameterScale scaleParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), 1000 );
2515   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "scale" ), context, widgetContext, &scaleParam );
2516   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2517   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2518   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2519   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
2520   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2521   QCOMPARE( static_cast< QgsProcessingParameterScale * >( def.get() )->defaultValue().toDouble(), 1000.0 );
2522   scaleParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
2523   scaleParam.setDefaultValue( 28356 );
2524   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "scale" ), context, widgetContext, &scaleParam );
2525   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2526   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2527   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2528   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
2529   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
2530   QCOMPARE( static_cast< QgsProcessingParameterScale * >( def.get() )->defaultValue().toDouble(), 28356.0 );
2531 }
2532 
testRangeWrapper()2533 void TestProcessingGui::testRangeWrapper()
2534 {
2535   auto testWrapper = []( QgsProcessingGui::WidgetType type )
2536   {
2537     QgsProcessingContext context;
2538 
2539     QgsProcessingParameterRange param( QStringLiteral( "range" ), QStringLiteral( "range" ), QgsProcessingParameterNumber::Double );
2540     param.setDefaultValue( QStringLiteral( "0.0,100.0" ) );
2541     QgsProcessingRangeWidgetWrapper wrapper( &param, type );
2542 
2543     QWidget *w = wrapper.createWrappedWidget( context );
2544     QVERIFY( w );
2545 
2546     // initial value
2547     QCOMPARE( wrapper.parameterValue().toString(), QStringLiteral( "0,100" ) );
2548 
2549     QVERIFY( wrapper.mMinSpinBox->expressionsEnabled() );
2550     QVERIFY( wrapper.mMaxSpinBox->expressionsEnabled() );
2551     QCOMPARE( wrapper.mMinSpinBox->decimals(), 6 ); // you can change this, if it's an intentional change!
2552     QCOMPARE( wrapper.mMaxSpinBox->decimals(), 6 ); // you can change this, if it's an intentional change!
2553     QGSCOMPARENEAR( wrapper.mMinSpinBox->minimum(), -99999999.999999, 1 );
2554     QGSCOMPARENEAR( wrapper.mMaxSpinBox->minimum(), -99999999.999999, 1 );
2555     QGSCOMPARENEAR( wrapper.mMinSpinBox->maximum(), 99999999.999999, 1 );
2556     QGSCOMPARENEAR( wrapper.mMaxSpinBox->maximum(), 99999999.999999, 1 );
2557 
2558     QSignalSpy spy( &wrapper, &QgsProcessingRangeWidgetWrapper::widgetValueHasChanged );
2559     wrapper.setWidgetValue( QVariantList() << 5 << 7, context );
2560     QCOMPARE( spy.count(), 1 );
2561     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "5,7" ) );
2562     QCOMPARE( wrapper.mMinSpinBox->value(), 5.0 );
2563     QCOMPARE( wrapper.mMaxSpinBox->value(), 7.0 );
2564     wrapper.setWidgetValue( QStringLiteral( "28.1,36.5" ), context );
2565     QCOMPARE( spy.count(), 2 );
2566     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "28.1,36.5" ) );
2567     QCOMPARE( wrapper.mMinSpinBox->value(), 28.1 );
2568     QCOMPARE( wrapper.mMaxSpinBox->value(), 36.5 );
2569 
2570     QLabel *l = wrapper.createWrappedLabel();
2571     if ( wrapper.type() != QgsProcessingGui::Batch )
2572     {
2573       QVERIFY( l );
2574       QCOMPARE( l->text(), QStringLiteral( "range" ) );
2575       QCOMPARE( l->toolTip(), param.toolTip() );
2576       delete l;
2577     }
2578     else
2579     {
2580       QVERIFY( !l );
2581     }
2582 
2583     // check signal
2584     wrapper.mMinSpinBox->setValue( 7.0 );
2585     QCOMPARE( spy.count(), 3 );
2586     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "7,36.5" ) );
2587     wrapper.mMaxSpinBox->setValue( 9.0 );
2588     QCOMPARE( spy.count(), 4 );
2589     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "7,9" ) );
2590 
2591     // check that min/max are mutually adapted
2592     wrapper.setParameterValue( QStringLiteral( "200.0,100.0" ), context );
2593     QCOMPARE( wrapper.parameterValue().toString(), QStringLiteral( "100,100" ) );
2594 
2595     wrapper.mMaxSpinBox->setValue( 50 );
2596     QCOMPARE( wrapper.parameterValue().toString(), QStringLiteral( "50,50" ) );
2597     wrapper.mMinSpinBox->setValue( 100 );
2598     QCOMPARE( wrapper.parameterValue().toString(), QStringLiteral( "100,100" ) );
2599 
2600     delete w;
2601 
2602     // ints
2603     QgsProcessingParameterRange param2( QStringLiteral( "range" ), QStringLiteral( "range" ), QgsProcessingParameterNumber::Integer );
2604     param2.setDefaultValue( QStringLiteral( "0.1,100.1" ) );
2605 
2606     QgsProcessingRangeWidgetWrapper wrapper2( &param2, type );
2607 
2608     w = wrapper2.createWrappedWidget( context );
2609     QVERIFY( w );
2610     QCOMPARE( wrapper2.mMinSpinBox->decimals(), 0 );
2611     QCOMPARE( wrapper2.mMaxSpinBox->decimals(), 0 ); // you can't change this, vampire worms will bite you at night if you do
2612 
2613     // check initial value
2614     QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "0,100" ) );
2615     // check rounding
2616     wrapper2.setParameterValue( QStringLiteral( "100.1,200.1" ), context );
2617     QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "100,200" ) );
2618     wrapper2.setParameterValue( QStringLiteral( "100.6,200.6" ), context );
2619     QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "101,201" ) );
2620     // check set/get
2621     wrapper2.setParameterValue( QStringLiteral( "100.1,200.1" ), context );
2622     QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "100,200" ) );
2623     // check that min/max are mutually adapted
2624     wrapper2.setParameterValue( QStringLiteral( "200.1,100.1" ), context );
2625     QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "100,100" ) );
2626     wrapper2.mMaxSpinBox->setValue( 50.1 );
2627     QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "50,50" ) );
2628     wrapper2.mMinSpinBox->setValue( 100.1 );
2629     QCOMPARE( wrapper2.parameterValue().toString(), QStringLiteral( "100,100" ) );
2630 
2631     delete w;
2632 
2633     // optional
2634     QgsProcessingParameterRange paramOptional( QStringLiteral( "range" ), QStringLiteral( "range" ), QgsProcessingParameterNumber::Double, QVariant(), true );
2635 
2636     QgsProcessingRangeWidgetWrapper wrapperOptional( &paramOptional, type );
2637 
2638     w = wrapperOptional.createWrappedWidget( context );
2639     QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "None,None" ) );
2640     wrapperOptional.setParameterValue( QStringLiteral( "1,100" ), context );
2641     QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "1,100" ) );
2642     wrapperOptional.setParameterValue( QStringLiteral( "None,100" ), context );
2643     QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "None,100" ) );
2644     wrapperOptional.setParameterValue( QStringLiteral( "1,None" ), context );
2645     QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "1,None" ) );
2646     wrapperOptional.setParameterValue( QStringLiteral( "None,None" ), context );
2647     QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "None,None" ) );
2648     wrapperOptional.setParameterValue( QStringLiteral( "None" ), context );
2649     QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "None,None" ) );
2650     wrapperOptional.setParameterValue( QVariant(), context );
2651     QCOMPARE( wrapperOptional.parameterValue().toString(), QStringLiteral( "None,None" ) );
2652 
2653     delete w;
2654   };
2655 
2656   // standard wrapper
2657   testWrapper( QgsProcessingGui::Standard );
2658 
2659   // batch wrapper
2660   testWrapper( QgsProcessingGui::Batch );
2661 
2662   // modeler wrapper
2663   testWrapper( QgsProcessingGui::Modeler );
2664 
2665   // config widget
2666   QgsProcessingParameterWidgetContext widgetContext;
2667   QgsProcessingContext context;
2668   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "range" ), context, widgetContext );
2669   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
2670   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2671   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
2672   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2673 
2674   // using a parameter definition as initial values
2675   QgsProcessingParameterRange rangeParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QgsProcessingParameterNumber::Integer, QStringLiteral( "0,255" ) );
2676   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "range" ), context, widgetContext, &rangeParam );
2677   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2678   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2679   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2680   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
2681   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2682   QCOMPARE( static_cast< QgsProcessingParameterRange * >( def.get() )->defaultValue().toString(), QStringLiteral( "0,255" ) );
2683   QCOMPARE( static_cast< QgsProcessingParameterRange * >( def.get() )->dataType(), QgsProcessingParameterNumber::Integer );
2684   rangeParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
2685   rangeParam.setDataType( QgsProcessingParameterNumber::Double );
2686   rangeParam.setDefaultValue( QStringLiteral( "0,1" ) );
2687   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "range" ), context, widgetContext, &rangeParam );
2688   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2689   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2690   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2691   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
2692   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
2693   QCOMPARE( static_cast< QgsProcessingParameterRange * >( def.get() )->defaultValue().toString(), QStringLiteral( "0,1" ) );
2694   QCOMPARE( static_cast< QgsProcessingParameterRange * >( def.get() )->dataType(), QgsProcessingParameterNumber::Double );
2695 }
2696 
testMatrixDialog()2697 void TestProcessingGui::testMatrixDialog()
2698 {
2699   QgsProcessingParameterMatrix matrixParam( QString(), QString(), 3, false, QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
2700   std::unique_ptr< QgsProcessingMatrixParameterPanelWidget > dlg = std::make_unique< QgsProcessingMatrixParameterPanelWidget>( nullptr, &matrixParam );
2701   // variable length table
2702   QVERIFY( dlg->mButtonAdd->isEnabled() );
2703   QVERIFY( dlg->mButtonRemove->isEnabled() );
2704   QVERIFY( dlg->mButtonRemoveAll->isEnabled() );
2705 
2706   QCOMPARE( dlg->table(), QVariantList() );
2707 
2708   dlg = std::make_unique< QgsProcessingMatrixParameterPanelWidget >( nullptr, &matrixParam, QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ) << QStringLiteral( "d" ) << QStringLiteral( "e" ) << QStringLiteral( "f" ) );
2709   QCOMPARE( dlg->table(), QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ) << QStringLiteral( "d" ) << QStringLiteral( "e" ) << QStringLiteral( "f" ) );
2710   dlg->addRow();
2711   QCOMPARE( dlg->table(), QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ) << QStringLiteral( "d" ) << QStringLiteral( "e" ) << QStringLiteral( "f" ) << QString() << QString() );
2712   dlg->deleteAllRows();
2713   QCOMPARE( dlg->table(), QVariantList() );
2714 
2715   QgsProcessingParameterMatrix matrixParam2( QString(), QString(), 3, true, QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
2716   dlg = std::make_unique< QgsProcessingMatrixParameterPanelWidget >( nullptr, &matrixParam2, QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ) << QStringLiteral( "d" ) << QStringLiteral( "e" ) << QStringLiteral( "f" ) );
2717   QVERIFY( !dlg->mButtonAdd->isEnabled() );
2718   QVERIFY( !dlg->mButtonRemove->isEnabled() );
2719   QVERIFY( !dlg->mButtonRemoveAll->isEnabled() );
2720 }
2721 
testMatrixWrapper()2722 void TestProcessingGui::testMatrixWrapper()
2723 {
2724   auto testWrapper = []( QgsProcessingGui::WidgetType type )
2725   {
2726     QgsProcessingContext context;
2727 
2728     QgsProcessingParameterMatrix param( QStringLiteral( "matrix" ), QStringLiteral( "matrix" ), 3, false, QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
2729     param.setDefaultValue( QStringLiteral( "0.0,100.0,150.0,250.0" ) );
2730     QgsProcessingMatrixWidgetWrapper wrapper( &param, type );
2731 
2732     QWidget *w = wrapper.createWrappedWidget( context );
2733     QVERIFY( w );
2734 
2735     // initial value
2736     QCOMPARE( wrapper.parameterValue().toList(), QVariantList() << QStringLiteral( "0.0" ) << QStringLiteral( "100.0" ) << QStringLiteral( "150.0" ) << QStringLiteral( "250.0" ) );
2737 
2738     QSignalSpy spy( &wrapper, &QgsProcessingMatrixWidgetWrapper::widgetValueHasChanged );
2739     wrapper.setWidgetValue( QVariantList() << 5 << 7, context );
2740     QCOMPARE( spy.count(), 1 );
2741     QCOMPARE( wrapper.widgetValue().toList(), QVariantList() << QStringLiteral( "5" ) << QStringLiteral( "7" ) );
2742     QCOMPARE( wrapper.mMatrixWidget->value(), QVariantList() << QStringLiteral( "5" ) << QStringLiteral( "7" ) );
2743     wrapper.setWidgetValue( QStringLiteral( "28.1,36.5,5.5,8.9" ), context );
2744     QCOMPARE( spy.count(), 2 );
2745     QCOMPARE( wrapper.widgetValue().toList(), QVariantList() << QStringLiteral( "28.1" ) << QStringLiteral( "36.5" ) << QStringLiteral( "5.5" ) << QStringLiteral( "8.9" ) );
2746     QCOMPARE( wrapper.mMatrixWidget->value(), QVariantList() << QStringLiteral( "28.1" ) << QStringLiteral( "36.5" ) << QStringLiteral( "5.5" ) << QStringLiteral( "8.9" ) );
2747 
2748     QLabel *l = wrapper.createWrappedLabel();
2749     if ( wrapper.type() != QgsProcessingGui::Batch )
2750     {
2751       QVERIFY( l );
2752       QCOMPARE( l->text(), QStringLiteral( "matrix" ) );
2753       QCOMPARE( l->toolTip(), param.toolTip() );
2754       delete l;
2755     }
2756     else
2757     {
2758       QVERIFY( !l );
2759     }
2760 
2761     // check signal
2762     wrapper.mMatrixWidget->setValue( QVariantList() << QStringLiteral( "7" ) << QStringLiteral( "9" ) );
2763     QCOMPARE( spy.count(), 3 );
2764     QCOMPARE( wrapper.widgetValue().toList(), QVariantList() << QStringLiteral( "7" ) << QStringLiteral( "9" ) );
2765 
2766     delete w;
2767   };
2768 
2769   // standard wrapper
2770   testWrapper( QgsProcessingGui::Standard );
2771 
2772   // batch wrapper
2773   testWrapper( QgsProcessingGui::Batch );
2774 
2775   // modeler wrapper
2776   testWrapper( QgsProcessingGui::Modeler );
2777 
2778   // config widget
2779   QgsProcessingParameterWidgetContext widgetContext;
2780   QgsProcessingContext context;
2781   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "matrix" ), context, widgetContext );
2782   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
2783   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2784   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
2785   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2786 
2787   // using a parameter definition as initial values
2788   QgsProcessingParameterMatrix matrixParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), 1, false, QStringList() << "A" << "B" << "C", QVariantList() << 0 << 0 << 0 );
2789   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "matrix" ), context, widgetContext, &matrixParam );
2790   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2791   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2792   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2793   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
2794   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2795   QCOMPARE( static_cast< QgsProcessingParameterMatrix * >( def.get() )->headers(), QStringList() << "A" << "B" << "C" );
2796   QCOMPARE( static_cast< QgsProcessingParameterMatrix * >( def.get() )->defaultValue().toStringList(), QStringList() << "0" << "0" << "0" );
2797   QVERIFY( !static_cast< QgsProcessingParameterMatrix * >( def.get() )->hasFixedNumberRows() );
2798   matrixParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
2799   matrixParam.setHasFixedNumberRows( true );
2800   matrixParam.setDefaultValue( QVariantList() << 1 << 2 << 3 );
2801   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "matrix" ), context, widgetContext, &matrixParam );
2802   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2803   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2804   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2805   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
2806   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
2807   QCOMPARE( static_cast< QgsProcessingParameterMatrix * >( def.get() )->headers(), QStringList() << "A" << "B" << "C" );
2808   QCOMPARE( static_cast< QgsProcessingParameterMatrix * >( def.get() )->defaultValue().toStringList(), QStringList() << "1" << "2" << "3" );
2809   QVERIFY( static_cast< QgsProcessingParameterMatrix * >( def.get() )->hasFixedNumberRows() );
2810 }
2811 
testExpressionWrapper()2812 void TestProcessingGui::testExpressionWrapper()
2813 {
2814   const QgsProcessingAlgorithm *centroidAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:centroids" ) );
2815   const QgsProcessingParameterDefinition *layerDef = centroidAlg->parameterDefinition( QStringLiteral( "INPUT" ) );
2816 
2817   auto testWrapper = [layerDef]( QgsProcessingGui::WidgetType type )
2818   {
2819     QgsProcessingParameterExpression param( QStringLiteral( "expression" ), QStringLiteral( "expression" ) );
2820 
2821     QgsProcessingExpressionWidgetWrapper wrapper( &param, type );
2822 
2823     QgsProcessingContext context;
2824     QWidget *w = wrapper.createWrappedWidget( context );
2825 
2826     QSignalSpy spy( &wrapper, &QgsProcessingExpressionWidgetWrapper::widgetValueHasChanged );
2827     wrapper.setWidgetValue( QStringLiteral( "1+2" ), context );
2828     QCOMPARE( spy.count(), 1 );
2829     QCOMPARE( wrapper.widgetValue().toString(),  QStringLiteral( "1+2" ) );
2830     QCOMPARE( static_cast< QgsExpressionLineEdit * >( wrapper.wrappedWidget() )->expression(),  QStringLiteral( "1+2" ) );
2831     wrapper.setWidgetValue( QString(), context );
2832     QCOMPARE( spy.count(), 2 );
2833     QVERIFY( wrapper.widgetValue().toString().isEmpty() );
2834     QVERIFY( static_cast< QgsExpressionLineEdit * >( wrapper.wrappedWidget() )->expression().isEmpty() );
2835 
2836     QLabel *l = wrapper.createWrappedLabel();
2837     if ( wrapper.type() != QgsProcessingGui::Batch )
2838     {
2839       QVERIFY( l );
2840       QCOMPARE( l->text(), QStringLiteral( "expression" ) );
2841       QCOMPARE( l->toolTip(), param.toolTip() );
2842       delete l;
2843     }
2844     else
2845     {
2846       QVERIFY( !l );
2847     }
2848 
2849     // check signal
2850     static_cast< QgsExpressionLineEdit * >( wrapper.wrappedWidget() )->setExpression( QStringLiteral( "3+4" ) );
2851     QCOMPARE( spy.count(), 3 );
2852 
2853     delete w;
2854 
2855     // with layer
2856     param.setParentLayerParameterName( QStringLiteral( "other" ) );
2857     QgsProcessingExpressionWidgetWrapper wrapper2( &param, type );
2858     w = wrapper2.createWrappedWidget( context );
2859 
2860     QSignalSpy spy2( &wrapper2, &QgsProcessingExpressionWidgetWrapper::widgetValueHasChanged );
2861     wrapper2.setWidgetValue( QStringLiteral( "11+12" ), context );
2862     QCOMPARE( spy2.count(), 1 );
2863     QCOMPARE( wrapper2.widgetValue().toString(),  QStringLiteral( "11+12" ) );
2864     QCOMPARE( static_cast< QgsFieldExpressionWidget * >( wrapper2.wrappedWidget() )->expression(),  QStringLiteral( "11+12" ) );
2865 
2866     wrapper2.setWidgetValue( QString(), context );
2867     QCOMPARE( spy2.count(), 2 );
2868     QVERIFY( wrapper2.widgetValue().toString().isEmpty() );
2869     QVERIFY( static_cast< QgsFieldExpressionWidget * >( wrapper2.wrappedWidget() )->expression().isEmpty() );
2870 
2871     static_cast< QgsFieldExpressionWidget * >( wrapper2.wrappedWidget() )->setExpression( QStringLiteral( "3+4" ) );
2872     QCOMPARE( spy2.count(), 3 );
2873 
2874     TestLayerWrapper layerWrapper( layerDef );
2875     QgsProject p;
2876     QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
2877     p.addMapLayer( vl );
2878 
2879     QVERIFY( !wrapper2.mFieldExpWidget->layer() );
2880     layerWrapper.setWidgetValue( QVariant::fromValue( vl ), context );
2881     wrapper2.setParentLayerWrapperValue( &layerWrapper );
2882     QCOMPARE( wrapper2.mFieldExpWidget->layer(), vl );
2883 
2884     // should not be owned by wrapper
2885     QVERIFY( !wrapper2.mParentLayer.get() );
2886     layerWrapper.setWidgetValue( QVariant(), context );
2887     wrapper2.setParentLayerWrapperValue( &layerWrapper );
2888     QVERIFY( !wrapper2.mFieldExpWidget->layer() );
2889 
2890     layerWrapper.setWidgetValue( vl->id(), context );
2891     wrapper2.setParentLayerWrapperValue( &layerWrapper );
2892     QVERIFY( !wrapper2.mFieldExpWidget->layer() );
2893     QVERIFY( !wrapper2.mParentLayer.get() );
2894 
2895     // with project layer
2896     context.setProject( &p );
2897     TestProcessingContextGenerator generator( context );
2898     wrapper2.registerProcessingContextGenerator( &generator );
2899 
2900     layerWrapper.setWidgetValue( vl->id(), context );
2901     wrapper2.setParentLayerWrapperValue( &layerWrapper );
2902     QCOMPARE( wrapper2.mFieldExpWidget->layer(), vl );
2903     QVERIFY( !wrapper2.mParentLayer.get() );
2904 
2905     // non-project layer
2906     QString pointFileName = TEST_DATA_DIR + QStringLiteral( "/points.shp" );
2907     layerWrapper.setWidgetValue( pointFileName, context );
2908     wrapper2.setParentLayerWrapperValue( &layerWrapper );
2909     QCOMPARE( wrapper2.mFieldExpWidget->layer()->publicSource(), pointFileName );
2910     // must be owned by wrapper, or layer may be deleted while still required by wrapper
2911     QCOMPARE( wrapper2.mParentLayer->publicSource(), pointFileName );
2912   };
2913 
2914   // standard wrapper
2915   testWrapper( QgsProcessingGui::Standard );
2916 
2917   // batch wrapper
2918   testWrapper( QgsProcessingGui::Batch );
2919 
2920   // modeler wrapper
2921   testWrapper( QgsProcessingGui::Modeler );
2922 
2923   // config widget
2924   QgsProcessingParameterWidgetContext widgetContext;
2925   QgsProcessingContext context;
2926   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "expression" ), context, widgetContext );
2927   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
2928   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2929   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
2930   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2931 
2932   // using a parameter definition as initial values
2933   QgsProcessingParameterExpression exprParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QVariant(), QStringLiteral( "parent" ) );
2934   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "expression" ), context, widgetContext, &exprParam );
2935   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2936   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2937   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2938   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
2939   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
2940   QCOMPARE( static_cast< QgsProcessingParameterExpression * >( def.get() )->parentLayerParameterName(), QStringLiteral( "parent" ) );
2941   exprParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
2942   exprParam.setParentLayerParameterName( QString() );
2943   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "expression" ), context, widgetContext, &exprParam );
2944   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
2945   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
2946   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
2947   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
2948   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
2949   QVERIFY( static_cast< QgsProcessingParameterExpression * >( def.get() )->parentLayerParameterName().isEmpty() );
2950 }
2951 
testFieldSelectionPanel()2952 void TestProcessingGui::testFieldSelectionPanel()
2953 {
2954   QgsProcessingParameterField fieldParam( QString(), QString(), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true );
2955   QgsProcessingFieldPanelWidget w( nullptr, &fieldParam );
2956   QSignalSpy spy( &w, &QgsProcessingFieldPanelWidget::changed );
2957 
2958   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "0 options selected" ) );
2959   w.setValue( QStringLiteral( "aa" ) );
2960   QCOMPARE( spy.count(), 1 );
2961   QCOMPARE( w.value().toList(), QVariantList() << QStringLiteral( "aa" ) );
2962   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "1 options selected" ) );
2963 
2964   w.setValue( QVariantList() << QStringLiteral( "bb" ) << QStringLiteral( "aa" ) );
2965   QCOMPARE( spy.count(), 2 );
2966   QCOMPARE( w.value().toList(), QVariantList() << QStringLiteral( "bb" ) << QStringLiteral( "aa" ) );
2967   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "2 options selected" ) );
2968 
2969   w.setValue( QVariant() );
2970   QCOMPARE( spy.count(), 3 );
2971   QCOMPARE( w.value().toList(), QVariantList() );
2972   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "0 options selected" ) );
2973 
2974 }
2975 
testFieldWrapper()2976 void TestProcessingGui::testFieldWrapper()
2977 {
2978   const QgsProcessingAlgorithm *centroidAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:centroids" ) );
2979   const QgsProcessingParameterDefinition *layerDef = centroidAlg->parameterDefinition( QStringLiteral( "INPUT" ) );
2980 
2981   auto testWrapper = [layerDef]( QgsProcessingGui::WidgetType type )
2982   {
2983     TestLayerWrapper layerWrapper( layerDef );
2984     QgsProject p;
2985     QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString?field=aaa:int&field=bbb:string" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
2986     p.addMapLayer( vl );
2987 
2988     QgsProcessingParameterField param( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ) );
2989 
2990     QgsProcessingFieldWidgetWrapper wrapper( &param, type );
2991 
2992     QgsProcessingContext context;
2993 
2994     QWidget *w = wrapper.createWrappedWidget( context );
2995     ( void )w;
2996     layerWrapper.setWidgetValue( QVariant::fromValue( vl ), context );
2997     wrapper.setParentLayerWrapperValue( &layerWrapper );
2998 
2999     QSignalSpy spy( &wrapper, &QgsProcessingFieldWidgetWrapper::widgetValueHasChanged );
3000     wrapper.setWidgetValue( QStringLiteral( "bbb" ), context );
3001     QCOMPARE( spy.count(), 1 );
3002     QCOMPARE( wrapper.widgetValue().toString(),  QStringLiteral( "bbb" ) );
3003 
3004     switch ( type )
3005     {
3006       case QgsProcessingGui::Standard:
3007       case QgsProcessingGui::Batch:
3008         QCOMPARE( static_cast< QgsFieldComboBox * >( wrapper.wrappedWidget() )->currentField(),  QStringLiteral( "bbb" ) );
3009         break;
3010 
3011       case QgsProcessingGui::Modeler:
3012         QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text(),  QStringLiteral( "bbb" ) );
3013         break;
3014     }
3015 
3016     wrapper.setWidgetValue( QString(), context );
3017     QCOMPARE( spy.count(), 2 );
3018     QVERIFY( wrapper.widgetValue().toString().isEmpty() );
3019 
3020     delete w;
3021 
3022     // optional
3023     param = QgsProcessingParameterField( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, false, true );
3024 
3025     QgsProcessingFieldWidgetWrapper wrapper2( &param, type );
3026 
3027     w = wrapper2.createWrappedWidget( context );
3028     layerWrapper.setWidgetValue( QVariant::fromValue( vl ), context );
3029     wrapper2.setParentLayerWrapperValue( &layerWrapper );
3030     QSignalSpy spy2( &wrapper2, &QgsProcessingFieldWidgetWrapper::widgetValueHasChanged );
3031     wrapper2.setWidgetValue( QStringLiteral( "aaa" ), context );
3032     QCOMPARE( spy2.count(), 1 );
3033     QCOMPARE( wrapper2.widgetValue().toString(),  QStringLiteral( "aaa" ) );
3034 
3035     wrapper2.setWidgetValue( QString(), context );
3036     QCOMPARE( spy2.count(), 2 );
3037     QVERIFY( wrapper2.widgetValue().toString().isEmpty() );
3038 
3039     switch ( type )
3040     {
3041       case QgsProcessingGui::Standard:
3042       case QgsProcessingGui::Batch:
3043         QCOMPARE( static_cast< QgsFieldComboBox * >( wrapper2.wrappedWidget() )->currentField(), QString() );
3044         break;
3045 
3046       case QgsProcessingGui::Modeler:
3047         QCOMPARE( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text(),  QString() );
3048         break;
3049     }
3050 
3051     QLabel *l = wrapper.createWrappedLabel();
3052     if ( wrapper.type() != QgsProcessingGui::Batch )
3053     {
3054       QVERIFY( l );
3055       QCOMPARE( l->text(), QStringLiteral( "field [optional]" ) );
3056       QCOMPARE( l->toolTip(), param.toolTip() );
3057       delete l;
3058     }
3059     else
3060     {
3061       QVERIFY( !l );
3062     }
3063 
3064     // check signal
3065     switch ( type )
3066     {
3067       case QgsProcessingGui::Standard:
3068       case QgsProcessingGui::Batch:
3069         static_cast< QgsFieldComboBox * >( wrapper2.wrappedWidget() )->setField( QStringLiteral( "bbb" ) );
3070         break;
3071 
3072       case QgsProcessingGui::Modeler:
3073         static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->setText( QStringLiteral( "bbb" ) );
3074         break;
3075     }
3076 
3077     QCOMPARE( spy2.count(), 3 );
3078 
3079     switch ( type )
3080     {
3081       case QgsProcessingGui::Standard:
3082       case QgsProcessingGui::Batch:
3083         QCOMPARE( wrapper2.mComboBox->layer(), vl );
3084         break;
3085 
3086       case QgsProcessingGui::Modeler:
3087         break;
3088     }
3089 
3090     // should not be owned by wrapper
3091     QVERIFY( !wrapper2.mParentLayer.get() );
3092     layerWrapper.setWidgetValue( QVariant(), context );
3093     wrapper2.setParentLayerWrapperValue( &layerWrapper );
3094 
3095     switch ( type )
3096     {
3097       case QgsProcessingGui::Standard:
3098       case QgsProcessingGui::Batch:
3099         QVERIFY( !wrapper2.mComboBox->layer() );
3100         break;
3101 
3102       case QgsProcessingGui::Modeler:
3103         break;
3104     }
3105 
3106     layerWrapper.setWidgetValue( vl->id(), context );
3107     wrapper2.setParentLayerWrapperValue( &layerWrapper );
3108     switch ( type )
3109     {
3110       case QgsProcessingGui::Standard:
3111       case QgsProcessingGui::Batch:
3112         QVERIFY( !wrapper2.mComboBox->layer() );
3113         break;
3114 
3115       case QgsProcessingGui::Modeler:
3116         break;
3117     }
3118     QVERIFY( !wrapper2.mParentLayer.get() );
3119 
3120     // with project layer
3121     context.setProject( &p );
3122     TestProcessingContextGenerator generator( context );
3123     wrapper2.registerProcessingContextGenerator( &generator );
3124 
3125     layerWrapper.setWidgetValue( vl->id(), context );
3126     wrapper2.setParentLayerWrapperValue( &layerWrapper );
3127     switch ( type )
3128     {
3129       case QgsProcessingGui::Standard:
3130       case QgsProcessingGui::Batch:
3131         QCOMPARE( wrapper2.mComboBox->layer(), vl );
3132         break;
3133 
3134       case QgsProcessingGui::Modeler:
3135         break;
3136     }
3137     QVERIFY( !wrapper2.mParentLayer.get() );
3138 
3139     // non-project layer
3140     QString pointFileName = TEST_DATA_DIR + QStringLiteral( "/points.shp" );
3141     layerWrapper.setWidgetValue( pointFileName, context );
3142     wrapper2.setParentLayerWrapperValue( &layerWrapper );
3143     switch ( type )
3144     {
3145       case QgsProcessingGui::Standard:
3146       case QgsProcessingGui::Batch:
3147         QCOMPARE( wrapper2.mComboBox->layer()->publicSource(), pointFileName );
3148         break;
3149 
3150       case QgsProcessingGui::Modeler:
3151         break;
3152     }
3153 
3154     // must be owned by wrapper, or layer may be deleted while still required by wrapper
3155     QCOMPARE( wrapper2.mParentLayer->publicSource(), pointFileName );
3156 
3157     delete w;
3158 
3159     // multiple
3160     param = QgsProcessingParameterField( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true, true );
3161 
3162     QgsProcessingFieldWidgetWrapper wrapper3( &param, type );
3163 
3164     w = wrapper3.createWrappedWidget( context );
3165     layerWrapper.setWidgetValue( QVariant::fromValue( vl ), context );
3166     wrapper3.setParentLayerWrapperValue( &layerWrapper );
3167     QSignalSpy spy3( &wrapper3, &QgsProcessingFieldWidgetWrapper::widgetValueHasChanged );
3168     wrapper3.setWidgetValue( QStringLiteral( "aaa" ), context );
3169     QCOMPARE( spy3.count(), 1 );
3170     QCOMPARE( wrapper3.widgetValue().toStringList(), QStringList() << QStringLiteral( "aaa" ) );
3171 
3172     wrapper3.setWidgetValue( QString(), context );
3173     QCOMPARE( spy3.count(), 2 );
3174     QVERIFY( wrapper3.widgetValue().toString().isEmpty() );
3175 
3176     wrapper3.setWidgetValue( QStringLiteral( "aaa;bbb" ), context );
3177     QCOMPARE( spy3.count(), 3 );
3178     QCOMPARE( wrapper3.widgetValue().toStringList(), QStringList() << QStringLiteral( "aaa" ) << QStringLiteral( "bbb" ) );
3179 
3180     delete w;
3181 
3182     // filtering fields
3183     QgsFields f;
3184     f.append( QgsField( QStringLiteral( "string" ), QVariant::String ) );
3185     f.append( QgsField( QStringLiteral( "double" ), QVariant::Double ) );
3186     f.append( QgsField( QStringLiteral( "int" ), QVariant::Int ) );
3187     f.append( QgsField( QStringLiteral( "date" ), QVariant::Date ) );
3188     f.append( QgsField( QStringLiteral( "time" ), QVariant::Time ) );
3189     f.append( QgsField( QStringLiteral( "datetime" ), QVariant::DateTime ) );
3190 
3191     QgsFields f2 = wrapper3.filterFields( f );
3192     QCOMPARE( f2, f );
3193 
3194     // string fields
3195     param = QgsProcessingParameterField( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::String, false, true );
3196     QgsProcessingFieldWidgetWrapper wrapper4( &param, type );
3197     w = wrapper4.createWrappedWidget( context );
3198     switch ( type )
3199     {
3200       case QgsProcessingGui::Standard:
3201       case QgsProcessingGui::Batch:
3202         QCOMPARE( static_cast< QgsFieldComboBox * >( wrapper4.wrappedWidget() )->filters(), QgsFieldProxyModel::String );
3203         break;
3204 
3205       case QgsProcessingGui::Modeler:
3206         break;
3207     }
3208     f2 = wrapper4.filterFields( f );
3209     QCOMPARE( f2.size(), 1 );
3210     QCOMPARE( f2.at( 0 ).name(), QStringLiteral( "string" ) );
3211     delete w;
3212 
3213     // string, multiple
3214     param = QgsProcessingParameterField( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::String, true, true );
3215     QgsProcessingFieldWidgetWrapper wrapper4a( &param, type );
3216     w = wrapper4a.createWrappedWidget( context );
3217     wrapper4a.setParentLayerWrapperValue( &layerWrapper );
3218     switch ( type )
3219     {
3220       case QgsProcessingGui::Standard:
3221       case QgsProcessingGui::Batch:
3222         QCOMPARE( wrapper4a.mPanel->fields().count(), 1 );
3223         QCOMPARE( wrapper4a.mPanel->fields().at( 0 ).name(), QStringLiteral( "bbb" ) );
3224         break;
3225 
3226       case QgsProcessingGui::Modeler:
3227         break;
3228     }
3229     delete w;
3230 
3231     // numeric fields
3232     param = QgsProcessingParameterField( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Numeric, false, true );
3233     QgsProcessingFieldWidgetWrapper wrapper5( &param, type );
3234     w = wrapper5.createWrappedWidget( context );
3235     switch ( type )
3236     {
3237       case QgsProcessingGui::Standard:
3238       case QgsProcessingGui::Batch:
3239         QCOMPARE( static_cast< QgsFieldComboBox * >( wrapper5.wrappedWidget() )->filters(), QgsFieldProxyModel::Numeric );
3240         break;
3241 
3242       case QgsProcessingGui::Modeler:
3243         break;
3244     }
3245     f2 = wrapper5.filterFields( f );
3246     QCOMPARE( f2.size(), 2 );
3247     QCOMPARE( f2.at( 0 ).name(), QStringLiteral( "double" ) );
3248     QCOMPARE( f2.at( 1 ).name(), QStringLiteral( "int" ) );
3249 
3250     delete w;
3251 
3252     // numeric, multiple
3253     param = QgsProcessingParameterField( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Numeric, true, true );
3254     QgsProcessingFieldWidgetWrapper wrapper5a( &param, type );
3255     w = wrapper5a.createWrappedWidget( context );
3256     wrapper5a.setParentLayerWrapperValue( &layerWrapper );
3257     switch ( type )
3258     {
3259       case QgsProcessingGui::Standard:
3260       case QgsProcessingGui::Batch:
3261         QCOMPARE( wrapper5a.mPanel->fields().count(), 1 );
3262         QCOMPARE( wrapper5a.mPanel->fields().at( 0 ).name(), QStringLiteral( "aaa" ) );
3263         break;
3264 
3265       case QgsProcessingGui::Modeler:
3266         break;
3267     }
3268     delete w;
3269 
3270     // datetime fields
3271     param = QgsProcessingParameterField( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::DateTime, false, true );
3272     QgsProcessingFieldWidgetWrapper wrapper6( &param, type );
3273     w = wrapper6.createWrappedWidget( context );
3274     switch ( type )
3275     {
3276       case QgsProcessingGui::Standard:
3277       case QgsProcessingGui::Batch:
3278         QCOMPARE( static_cast< QgsFieldComboBox * >( wrapper6.wrappedWidget() )->filters(), QgsFieldProxyModel::Date | QgsFieldProxyModel::Time | QgsFieldProxyModel::DateTime );
3279         break;
3280 
3281       case QgsProcessingGui::Modeler:
3282         break;
3283     }
3284     f2 = wrapper6.filterFields( f );
3285     QCOMPARE( f2.size(), 3 );
3286     QCOMPARE( f2.at( 0 ).name(), QStringLiteral( "date" ) );
3287     QCOMPARE( f2.at( 1 ).name(), QStringLiteral( "time" ) );
3288     QCOMPARE( f2.at( 2 ).name(), QStringLiteral( "datetime" ) );
3289 
3290 
3291     // default to all fields
3292     param = QgsProcessingParameterField( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true, true );
3293     param.setDefaultToAllFields( true );
3294     QgsProcessingFieldWidgetWrapper wrapper7( &param, type );
3295     w = wrapper7.createWrappedWidget( context );
3296     wrapper7.setParentLayerWrapperValue( &layerWrapper );
3297     switch ( type )
3298     {
3299       case QgsProcessingGui::Standard:
3300       case QgsProcessingGui::Batch:
3301         QCOMPARE( wrapper7.widgetValue().toList(), QVariantList() << QStringLiteral( "aaa" ) << QStringLiteral( "bbb" ) );
3302         break;
3303 
3304       case QgsProcessingGui::Modeler:
3305         break;
3306     }
3307     delete w;
3308 
3309     // MultipleLayers as parent layer
3310     QgsVectorLayer *vl2 = new QgsVectorLayer( QStringLiteral( "LineString?field=bbb:string" ), QStringLiteral( "y" ), QStringLiteral( "memory" ) );
3311     p.addMapLayer( vl2 );
3312 
3313     QgsProcessingFieldWidgetWrapper wrapper8( &param, type );
3314     wrapper8.registerProcessingContextGenerator( &generator );
3315     w = wrapper8.createWrappedWidget( context );
3316     layerWrapper.setWidgetValue( QVariantList() << vl->id() << vl2->id(), context );
3317     wrapper8.setParentLayerWrapperValue( &layerWrapper );
3318 
3319     switch ( type )
3320     {
3321       case QgsProcessingGui::Standard:
3322       case QgsProcessingGui::Batch:
3323         QCOMPARE( wrapper8.widgetValue().toList(), QVariantList() << QStringLiteral( "bbb" ) );
3324         break;
3325 
3326       case QgsProcessingGui::Modeler:
3327         break;
3328     }
3329     delete w;
3330   };
3331 
3332   // standard wrapper
3333   testWrapper( QgsProcessingGui::Standard );
3334 
3335   // batch wrapper
3336   testWrapper( QgsProcessingGui::Batch );
3337 
3338   // modeler wrapper
3339   testWrapper( QgsProcessingGui::Modeler );
3340 
3341   // config widget
3342   QgsProcessingParameterWidgetContext widgetContext;
3343   QgsProcessingContext context;
3344   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "field" ), context, widgetContext );
3345   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
3346   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
3347   QVERIFY( !def->defaultValue().isValid() );
3348   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
3349   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
3350 
3351   // using a parameter definition as initial values
3352   QgsProcessingParameterField fieldParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "field_name" ), QStringLiteral( "parent" ) );
3353   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "field" ), context, widgetContext, &fieldParam );
3354   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
3355   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
3356   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
3357   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
3358   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
3359   QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->defaultValue().toString(), QStringLiteral( "field_name" ) );
3360   QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->parentLayerParameterName(), QStringLiteral( "parent" ) );
3361   QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->dataType(), QgsProcessingParameterField::Any );
3362   QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->allowMultiple(), false );
3363   QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->defaultToAllFields(), false );
3364   fieldParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
3365   fieldParam.setParentLayerParameterName( QString() );
3366   fieldParam.setAllowMultiple( true );
3367   fieldParam.setDefaultToAllFields( true );
3368   fieldParam.setDataType( QgsProcessingParameterField::String );
3369   fieldParam.setDefaultValue( QStringLiteral( "field_1;field_2" ) );
3370   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "field" ), context, widgetContext, &fieldParam );
3371   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
3372   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
3373   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
3374   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
3375   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
3376   QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->defaultValue().toString(), QStringLiteral( "field_1;field_2" ) );
3377   QVERIFY( static_cast< QgsProcessingParameterBand * >( def.get() )->parentLayerParameterName().isEmpty() );
3378   QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->dataType(), QgsProcessingParameterField::String );
3379   QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->allowMultiple(), true );
3380   QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->defaultToAllFields(), true );
3381 }
3382 
testMultipleSelectionDialog()3383 void TestProcessingGui::testMultipleSelectionDialog()
3384 {
3385   QVariantList availableOptions;
3386   QVariantList selectedOptions;
3387   std::unique_ptr< QgsProcessingMultipleSelectionPanelWidget > dlg = std::make_unique< QgsProcessingMultipleSelectionPanelWidget >( availableOptions, selectedOptions );
3388   QVERIFY( dlg->selectedOptions().isEmpty() );
3389   QCOMPARE( dlg->mModel->rowCount(), 0 );
3390 
3391   std::unique_ptr< QgsVectorLayer > vl = std::make_unique< QgsVectorLayer >( QStringLiteral( "LineString" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
3392   availableOptions << QVariant( "aa" ) << QVariant( 15 ) << QVariant::fromValue( vl.get() );
3393   dlg = std::make_unique< QgsProcessingMultipleSelectionPanelWidget >( availableOptions, selectedOptions );
3394   QVERIFY( dlg->selectedOptions().isEmpty() );
3395   QCOMPARE( dlg->mModel->rowCount(), 3 );
3396   dlg->selectAll( true );
3397   QCOMPARE( dlg->selectedOptions(), availableOptions );
3398   dlg->mModel->item( 1 )->setCheckState( Qt::Unchecked );
3399   QCOMPARE( dlg->selectedOptions(), QVariantList() << "aa" << QVariant::fromValue( vl.get() ) );
3400 
3401   // reorder rows
3402   QList<QStandardItem *> itemList = dlg->mModel->takeRow( 2 );
3403   dlg->mModel->insertRow( 0, itemList );
3404   QCOMPARE( dlg->selectedOptions(), QVariantList() << QVariant::fromValue( vl.get() ) << "aa" );
3405 
3406   // additional options
3407   availableOptions.clear();
3408   selectedOptions << QVariant( "bb" ) << QVariant( 6.6 );
3409   dlg = std::make_unique< QgsProcessingMultipleSelectionPanelWidget >( availableOptions, selectedOptions );
3410   QCOMPARE( dlg->mModel->rowCount(), 2 );
3411   QCOMPARE( dlg->selectedOptions(), selectedOptions );
3412   dlg->mModel->item( 1 )->setCheckState( Qt::Unchecked );
3413   QCOMPARE( dlg->selectedOptions(), QVariantList() << "bb" );
3414 
3415   // mix of standard and additional options
3416   availableOptions << QVariant( 6.6 ) << QVariant( "aa" );
3417   dlg = std::make_unique< QgsProcessingMultipleSelectionPanelWidget >( availableOptions, selectedOptions );
3418   QCOMPARE( dlg->mModel->rowCount(), 3 );
3419   QCOMPARE( dlg->selectedOptions(), selectedOptions ); // order must be maintained!
3420   dlg->mModel->item( 2 )->setCheckState( Qt::Checked );
3421   QCOMPARE( dlg->selectedOptions(), QVariantList() << "bb" << QVariant( 6.6 ) << QVariant( "aa" ) );
3422 
3423   // selection buttons
3424   selectedOptions.clear();
3425   availableOptions = QVariantList() << QVariant( "a" ) << QVariant( "b" ) << QVariant( "c" );
3426   dlg = std::make_unique< QgsProcessingMultipleSelectionPanelWidget >( availableOptions, selectedOptions );
3427   QVERIFY( dlg->selectedOptions().isEmpty() );
3428   dlg->mSelectionList->selectionModel()->select( dlg->mModel->index( 1, 0 ), QItemSelectionModel::ClearAndSelect );
3429   // without a multi-selection, select all/toggle options should affect all items
3430   dlg->selectAll( true );
3431   QCOMPARE( dlg->selectedOptions(), availableOptions );
3432   dlg->selectAll( false );
3433   QVERIFY( dlg->selectedOptions().isEmpty() );
3434   dlg->toggleSelection();
3435   QCOMPARE( dlg->selectedOptions(), availableOptions );
3436   dlg->toggleSelection();
3437   QVERIFY( dlg->selectedOptions().isEmpty() );
3438   // with multi-selection, actions should only affected selected rows
3439   dlg->mSelectionList->selectionModel()->select( dlg->mModel->index( 2, 0 ), QItemSelectionModel::Select );
3440   dlg->selectAll( true );
3441   QCOMPARE( dlg->selectedOptions(), QVariantList() << "b" << "c" );
3442   dlg->selectAll( false );
3443   QVERIFY( dlg->selectedOptions().isEmpty() );
3444   dlg->selectAll( true );
3445   QCOMPARE( dlg->selectedOptions(), QVariantList() << "b" << "c" );
3446   dlg->mModel->item( 0 )->setCheckState( Qt::Checked );
3447   dlg->selectAll( false );
3448   QCOMPARE( dlg->selectedOptions(), QVariantList() << "a" );
3449   dlg->toggleSelection();
3450   QCOMPARE( dlg->selectedOptions(), QVariantList() << "a" << "b" << "c" );
3451   dlg->toggleSelection();
3452   QCOMPARE( dlg->selectedOptions(), QVariantList() << "a" );
3453 
3454   // text format
3455   availableOptions = QVariantList() << QVariant( "a" ) << 6 << 6.2;
3456   dlg = std::make_unique< QgsProcessingMultipleSelectionPanelWidget >( availableOptions, selectedOptions );
3457   QCOMPARE( dlg->mModel->item( 0 )->text(), QStringLiteral( "a" ) );
3458   QCOMPARE( dlg->mModel->item( 1 )->text(), QStringLiteral( "6" ) );
3459   QCOMPARE( dlg->mModel->item( 2 )->text(), QStringLiteral( "6.2" ) );
3460   dlg->setValueFormatter( []( const QVariant & v )-> QString
3461   {
3462     return v.toString() + '_';
3463   } );
3464   QCOMPARE( dlg->mModel->item( 0 )->text(), QStringLiteral( "a_" ) );
3465   QCOMPARE( dlg->mModel->item( 1 )->text(), QStringLiteral( "6_" ) );
3466   QCOMPARE( dlg->mModel->item( 2 )->text(), QStringLiteral( "6.2_" ) );
3467 
3468   // mix of fixed + model choices
3469   availableOptions = QVariantList() << QVariant( "a" ) << 6 << 6.2
3470                      << QVariant::fromValue( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) )
3471                      << QVariant::fromValue( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "input" ) ) );
3472   dlg = std::make_unique< QgsProcessingMultipleSelectionPanelWidget >( availableOptions, QVariantList() << 6
3473         << QVariant::fromValue( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) )
3474         << QVariant::fromValue( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "input" ) ) ) );
3475 
3476   // when any selected option is a model child parameter source, then we require that all options are upgraded in place to model child parameter sources
3477   QVariantList res = dlg->selectedOptions();
3478   QCOMPARE( res.size(), 3 );
3479   QCOMPARE( res.at( 0 ).value< QgsProcessingModelChildParameterSource >(), QgsProcessingModelChildParameterSource::fromStaticValue( 6 ) );
3480   QCOMPARE( res.at( 1 ).value< QgsProcessingModelChildParameterSource >(), QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) );
3481   QCOMPARE( res.at( 2 ).value< QgsProcessingModelChildParameterSource >(), QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "input" ) ) );
3482   dlg->selectAll( true );
3483   res = dlg->selectedOptions();
3484   QCOMPARE( res.size(), 5 );
3485   QCOMPARE( res.at( 0 ).value< QgsProcessingModelChildParameterSource >(), QgsProcessingModelChildParameterSource::fromStaticValue( 6 ) );
3486   QCOMPARE( res.at( 1 ).value< QgsProcessingModelChildParameterSource >(), QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg" ), QStringLiteral( "out" ) ) );
3487   QCOMPARE( res.at( 2 ).value< QgsProcessingModelChildParameterSource >(), QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "input" ) ) );
3488   QCOMPARE( res.at( 3 ).value< QgsProcessingModelChildParameterSource >(), QgsProcessingModelChildParameterSource::fromStaticValue( QStringLiteral( "a" ) ) );
3489   QCOMPARE( res.at( 4 ).value< QgsProcessingModelChildParameterSource >(), QgsProcessingModelChildParameterSource::fromStaticValue( 6.2 ) );
3490 }
3491 
testMultipleFileSelectionDialog()3492 void TestProcessingGui::testMultipleFileSelectionDialog()
3493 {
3494   std::unique_ptr< QgsProcessingParameterMultipleLayers > param = std::make_unique< QgsProcessingParameterMultipleLayers >( QString(), QString(), QgsProcessing::TypeRaster );
3495   QVariantList selectedOptions;
3496   std::unique_ptr< QgsProcessingMultipleInputPanelWidget > dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), selectedOptions, QList<QgsProcessingModelChildParameterSource >() );
3497   QVERIFY( dlg->selectedOptions().isEmpty() );
3498   QCOMPARE( dlg->mModel->rowCount(), 0 );
3499 
3500   QgsProject::instance()->removeAllMapLayers();
3501   QgsVectorLayer *point = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "point" ), QStringLiteral( "memory" ) );
3502   QgsProject::instance()->addMapLayer( point );
3503   QgsVectorLayer *line = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "line" ), QStringLiteral( "memory" ) );
3504   QgsProject::instance()->addMapLayer( line );
3505   QgsVectorLayer *polygon = new QgsVectorLayer( QStringLiteral( "Polygon" ), QStringLiteral( "polygon" ), QStringLiteral( "memory" ) );
3506   QgsProject::instance()->addMapLayer( polygon );
3507   QgsVectorLayer *noGeom = new QgsVectorLayer( QStringLiteral( "None" ), QStringLiteral( "nogeom" ), QStringLiteral( "memory" ) );
3508   QgsProject::instance()->addMapLayer( noGeom );
3509   QgsMeshLayer *mesh = new QgsMeshLayer( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle.2dm", QStringLiteral( "mesh" ), QStringLiteral( "mdal" ) );
3510   mesh->dataProvider()->addDataset( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle_vertex_scalar_with_inactive_face.dat" );
3511   QVERIFY( mesh->isValid() );
3512   QgsProject::instance()->addMapLayer( mesh );
3513   QgsRasterLayer *raster = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/raster/band1_byte_ct_epsg4326.tif", QStringLiteral( "raster" ) );
3514   QgsProject::instance()->addMapLayer( raster );
3515   DummyPluginLayer *plugin = new DummyPluginLayer( "dummylayer", "plugin" );
3516   QgsProject::instance()->addMapLayer( plugin );
3517 
3518 #ifdef HAVE_EPT
3519   QgsPointCloudLayer *pointCloud = new QgsPointCloudLayer( QStringLiteral( TEST_DATA_DIR ) + "/point_clouds/ept/sunshine-coast/ept.json", QStringLiteral( "pointcloud" ), QStringLiteral( "ept" ) );
3520   QgsProject::instance()->addMapLayer( pointCloud );
3521 #endif
3522 
3523   QgsAnnotationLayer *annotationLayer = new QgsAnnotationLayer( QStringLiteral( "secondary annotations" ), QgsAnnotationLayer::LayerOptions( QgsProject::instance()->transformContext() ) );
3524   QgsProject::instance()->addMapLayer( annotationLayer );
3525 
3526   dlg->setProject( QgsProject::instance() );
3527   // should be filtered to raster layers only
3528   QCOMPARE( dlg->mModel->rowCount(), 1 );
3529   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QStringLiteral( "raster [EPSG:4326]" ) );
3530   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), raster->id() );
3531   QVERIFY( dlg->selectedOptions().isEmpty() );
3532   // existing value using layer id should match to project layer
3533   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList() << raster->id(), QList<QgsProcessingModelChildParameterSource >() );
3534   dlg->setProject( QgsProject::instance() );
3535   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QStringLiteral( "raster [EPSG:4326]" ) );
3536   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), raster->id() );
3537   QCOMPARE( dlg->selectedOptions().size(), 1 );
3538   QCOMPARE( dlg->selectedOptions().at( 0 ).toString(), raster->id() );
3539   // existing value using layer source should also match to project layer
3540   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList() << raster->source(), QList<QgsProcessingModelChildParameterSource >() );
3541   dlg->setProject( QgsProject::instance() );
3542   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QStringLiteral( "raster [EPSG:4326]" ) );
3543   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), raster->source() );
3544   QCOMPARE( dlg->selectedOptions().size(), 1 );
3545   QCOMPARE( dlg->selectedOptions().at( 0 ).toString(), raster->source() );
3546   // existing value using full layer path not matching a project layer should work
3547   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList() << raster->source() << QString( QStringLiteral( TEST_DATA_DIR ) + "/landsat.tif" ), QList<QgsProcessingModelChildParameterSource >() );
3548   dlg->setProject( QgsProject::instance() );
3549   QCOMPARE( dlg->mModel->rowCount(), 2 );
3550   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QStringLiteral( "raster [EPSG:4326]" ) );
3551   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), raster->source() );
3552   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 1, 0 ) ).toString(), QString( QStringLiteral( TEST_DATA_DIR ) + "/landsat.tif" ) );
3553   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 1, 0 ), Qt::UserRole ).toString(), QString( QStringLiteral( TEST_DATA_DIR ) + "/landsat.tif" ) );
3554   QCOMPARE( dlg->selectedOptions().size(), 2 );
3555   QCOMPARE( dlg->selectedOptions().at( 0 ).toString(), raster->source() );
3556   QCOMPARE( dlg->selectedOptions().at( 1 ).toString(), QString( QStringLiteral( TEST_DATA_DIR ) + "/landsat.tif" ) );
3557 
3558   // should remember layer order
3559   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList()  << QString( QStringLiteral( TEST_DATA_DIR ) + "/landsat.tif" ) << raster->source(), QList<QgsProcessingModelChildParameterSource >() );
3560   dlg->setProject( QgsProject::instance() );
3561   QCOMPARE( dlg->mModel->rowCount(), 2 );
3562   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QString( QStringLiteral( TEST_DATA_DIR ) + "/landsat.tif" ) );
3563   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), QString( QStringLiteral( TEST_DATA_DIR ) + "/landsat.tif" ) );
3564   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 1, 0 ) ).toString(), QStringLiteral( "raster [EPSG:4326]" ) );
3565   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 1, 0 ), Qt::UserRole ).toString(), raster->source() );
3566   QCOMPARE( dlg->selectedOptions().size(), 2 );
3567   QCOMPARE( dlg->selectedOptions().at( 0 ).toString(), QString( QStringLiteral( TEST_DATA_DIR ) + "/landsat.tif" ) );
3568   QCOMPARE( dlg->selectedOptions().at( 1 ).toString(), raster->source() );
3569 
3570   // mesh
3571   param = std::make_unique< QgsProcessingParameterMultipleLayers >( QString(), QString(), QgsProcessing::TypeMesh );
3572   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList(), QList<QgsProcessingModelChildParameterSource >() );
3573   dlg->setProject( QgsProject::instance() );
3574   QCOMPARE( dlg->mModel->rowCount(), 1 );
3575   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QStringLiteral( "mesh" ) );
3576   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), mesh->id() );
3577 
3578   // plugin
3579   param = std::make_unique< QgsProcessingParameterMultipleLayers >( QString(), QString(), QgsProcessing::TypePlugin );
3580   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList(), QList<QgsProcessingModelChildParameterSource >() );
3581   dlg->setProject( QgsProject::instance() );
3582   QCOMPARE( dlg->mModel->rowCount(), 1 );
3583   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QStringLiteral( "plugin" ) );
3584   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), plugin->id() );
3585 
3586 #ifdef HAVE_EPT
3587   // point cloud
3588   param = std::make_unique< QgsProcessingParameterMultipleLayers >( QString(), QString(), QgsProcessing::TypePointCloud );
3589   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList(), QList<QgsProcessingModelChildParameterSource >() );
3590   dlg->setProject( QgsProject::instance() );
3591   QCOMPARE( dlg->mModel->rowCount(), 1 );
3592   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QStringLiteral( "pointcloud [EPSG:28356]" ) );
3593   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), pointCloud->id() );
3594 #endif
3595 
3596   // annotation
3597   param = std::make_unique< QgsProcessingParameterMultipleLayers >( QString(), QString(), QgsProcessing::TypeAnnotation );
3598   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList(), QList<QgsProcessingModelChildParameterSource >() );
3599   dlg->setProject( QgsProject::instance() );
3600   QCOMPARE( dlg->mModel->rowCount(), 2 );
3601   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QStringLiteral( "secondary annotations" ) );
3602   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), annotationLayer->id() );
3603   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 1, 0 ) ).toString(), QStringLiteral( "Annotations" ) );
3604   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 1, 0 ), Qt::UserRole ).toString(), QStringLiteral( "main" ) );
3605 
3606   // vector points
3607   param = std::make_unique< QgsProcessingParameterMultipleLayers >( QString(), QString(), QgsProcessing::TypeVectorPoint );
3608   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList(), QList<QgsProcessingModelChildParameterSource >() );
3609   dlg->setProject( QgsProject::instance() );
3610   QCOMPARE( dlg->mModel->rowCount(), 1 );
3611   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QStringLiteral( "point [EPSG:4326]" ) );
3612   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), point->id() );
3613 
3614   // vector lines
3615   param = std::make_unique< QgsProcessingParameterMultipleLayers >( QString(), QString(), QgsProcessing::TypeVectorLine );
3616   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList(), QList<QgsProcessingModelChildParameterSource >() );
3617   dlg->setProject( QgsProject::instance() );
3618   QCOMPARE( dlg->mModel->rowCount(), 1 );
3619   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QStringLiteral( "line [EPSG:4326]" ) );
3620   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), line->id() );
3621 
3622   // vector polygons
3623   param = std::make_unique< QgsProcessingParameterMultipleLayers >( QString(), QString(), QgsProcessing::TypeVectorPolygon );
3624   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList(), QList<QgsProcessingModelChildParameterSource >() );
3625   dlg->setProject( QgsProject::instance() );
3626   QCOMPARE( dlg->mModel->rowCount(), 1 );
3627   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ) ).toString(), QStringLiteral( "polygon [EPSG:4326]" ) );
3628   QCOMPARE( dlg->mModel->data( dlg->mModel->index( 0, 0 ), Qt::UserRole ).toString(), polygon->id() );
3629 
3630   // vector any type
3631   param = std::make_unique< QgsProcessingParameterMultipleLayers >( QString(), QString(), QgsProcessing::TypeVector );
3632   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList(), QList<QgsProcessingModelChildParameterSource >() );
3633   dlg->setProject( QgsProject::instance() );
3634   QCOMPARE( dlg->mModel->rowCount(), 4 );
3635   QSet< QString > titles;
3636   for ( int i = 0; i < dlg->mModel->rowCount(); ++i )
3637     titles << dlg->mModel->data( dlg->mModel->index( i, 0 ) ).toString();
3638   QCOMPARE( titles, QSet<QString>() << QStringLiteral( "polygon [EPSG:4326]" ) << QStringLiteral( "point [EPSG:4326]" ) << QStringLiteral( "line [EPSG:4326]" ) << QStringLiteral( "nogeom" ) );
3639 
3640   // any type
3641   param = std::make_unique< QgsProcessingParameterMultipleLayers >( QString(), QString(), QgsProcessing::TypeMapLayer );
3642   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList(), QList<QgsProcessingModelChildParameterSource >() );
3643   dlg->setProject( QgsProject::instance() );
3644 #ifdef HAVE_EPT
3645   QCOMPARE( dlg->mModel->rowCount(), 10 );
3646 #else
3647   QCOMPARE( dlg->mModel->rowCount(), 9 );
3648 #endif
3649 
3650   titles.clear();
3651   for ( int i = 0; i < dlg->mModel->rowCount(); ++i )
3652     titles << dlg->mModel->data( dlg->mModel->index( i, 0 ) ).toString();
3653 #ifdef HAVE_EPT
3654   QCOMPARE( titles, QSet<QString>() << QStringLiteral( "polygon [EPSG:4326]" ) << QStringLiteral( "point [EPSG:4326]" ) << QStringLiteral( "line [EPSG:4326]" )
3655             << QStringLiteral( "nogeom" ) << QStringLiteral( "raster [EPSG:4326]" ) << QStringLiteral( "mesh" ) << QStringLiteral( "plugin" )
3656             << QStringLiteral( "pointcloud [EPSG:28356]" ) << QStringLiteral( "secondary annotations" ) << QStringLiteral( "Annotations" ) );
3657 #else
3658   QCOMPARE( titles, QSet<QString>() << QStringLiteral( "polygon [EPSG:4326]" ) << QStringLiteral( "point [EPSG:4326]" ) << QStringLiteral( "line [EPSG:4326]" )
3659             << QStringLiteral( "nogeom" ) << QStringLiteral( "raster [EPSG:4326]" ) << QStringLiteral( "mesh" ) << QStringLiteral( "plugin" )
3660             << QStringLiteral( "secondary annotations" ) << QStringLiteral( "Annotations" ) );
3661 #endif
3662 
3663   // files
3664   param = std::make_unique< QgsProcessingParameterMultipleLayers >( QString(), QString(), QgsProcessing::TypeFile );
3665   dlg = std::make_unique< QgsProcessingMultipleInputPanelWidget >( param.get(), QVariantList(), QList<QgsProcessingModelChildParameterSource >() );
3666   dlg->setProject( QgsProject::instance() );
3667   QCOMPARE( dlg->mModel->rowCount(), 0 );
3668 }
3669 
testRasterBandSelectionPanel()3670 void TestProcessingGui::testRasterBandSelectionPanel()
3671 {
3672   QgsProcessingParameterBand bandParam( QString(), QString(), QVariant(), QStringLiteral( "INPUT" ), false, true );
3673   QgsProcessingRasterBandPanelWidget w( nullptr, &bandParam );
3674   QSignalSpy spy( &w, &QgsProcessingRasterBandPanelWidget::changed );
3675 
3676   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "0 bands selected" ) );
3677   w.setValue( QStringLiteral( "1" ) );
3678   QCOMPARE( spy.count(), 1 );
3679   QCOMPARE( w.value().toList(), QVariantList() << QStringLiteral( "1" ) );
3680   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "1 bands selected" ) );
3681 
3682   w.setValue( QVariantList() << QStringLiteral( "2" ) << QStringLiteral( "1" ) );
3683   QCOMPARE( spy.count(), 2 );
3684   QCOMPARE( w.value().toList(), QVariantList() << QStringLiteral( "2" ) << QStringLiteral( "1" ) );
3685   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "2 bands selected" ) );
3686 
3687   w.setValue( QVariantList() << 3 << 5 << 1 );
3688   QCOMPARE( spy.count(), 3 );
3689   QCOMPARE( w.value().toList(), QVariantList() << 3 << 5 << 1 );
3690   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "3 bands selected" ) );
3691 
3692   w.setValue( QVariant() );
3693   QCOMPARE( spy.count(), 4 );
3694   QCOMPARE( w.value().toList(), QVariantList() );
3695   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "0 bands selected" ) );
3696 }
3697 
testBandWrapper()3698 void TestProcessingGui::testBandWrapper()
3699 {
3700   const QgsProcessingAlgorithm *statsAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:rasterlayerstatistics" ) );
3701   const QgsProcessingParameterDefinition *layerDef = statsAlg->parameterDefinition( QStringLiteral( "INPUT" ) );
3702 
3703   auto testWrapper = [layerDef]( QgsProcessingGui::WidgetType type )
3704   {
3705     TestLayerWrapper layerWrapper( layerDef );
3706     QgsProject p;
3707     QgsRasterLayer *rl = new QgsRasterLayer( TEST_DATA_DIR + QStringLiteral( "/landsat.tif" ), QStringLiteral( "x" ), QStringLiteral( "gdal" ) );
3708     p.addMapLayer( rl );
3709 
3710     QgsProcessingParameterBand param( QStringLiteral( "band" ), QStringLiteral( "band" ), QVariant(), QStringLiteral( "INPUT" ) );
3711 
3712     QgsProcessingBandWidgetWrapper wrapper( &param, type );
3713 
3714     QgsProcessingContext context;
3715 
3716     QWidget *w = wrapper.createWrappedWidget( context );
3717     ( void )w;
3718     layerWrapper.setWidgetValue( QVariant::fromValue( rl ), context );
3719     wrapper.setParentLayerWrapperValue( &layerWrapper );
3720 
3721     QSignalSpy spy( &wrapper, &QgsProcessingBandWidgetWrapper::widgetValueHasChanged );
3722     wrapper.setWidgetValue( 3, context );
3723     QCOMPARE( spy.count(), 1 );
3724     QCOMPARE( wrapper.widgetValue().toInt(), 3 );
3725 
3726     switch ( type )
3727     {
3728       case QgsProcessingGui::Standard:
3729       case QgsProcessingGui::Batch:
3730         QCOMPARE( static_cast< QgsRasterBandComboBox * >( wrapper.wrappedWidget() )->currentBand(), 3 );
3731         break;
3732 
3733       case QgsProcessingGui::Modeler:
3734         QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text(),  QStringLiteral( "3" ) );
3735         break;
3736     }
3737 
3738     wrapper.setWidgetValue( QStringLiteral( "1" ), context );
3739     QCOMPARE( spy.count(), 2 );
3740     QCOMPARE( wrapper.widgetValue().toInt(), 1 );
3741 
3742     switch ( type )
3743     {
3744       case QgsProcessingGui::Standard:
3745       case QgsProcessingGui::Batch:
3746         QCOMPARE( static_cast< QgsRasterBandComboBox * >( wrapper.wrappedWidget() )->currentBand(), 1 );
3747         break;
3748 
3749       case QgsProcessingGui::Modeler:
3750         QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text(),  QStringLiteral( "1" ) );
3751         break;
3752     }
3753 
3754     delete w;
3755 
3756     // optional
3757     param = QgsProcessingParameterBand( QStringLiteral( "band" ), QStringLiteral( "band" ), QVariant(), QStringLiteral( "INPUT" ), true, false );
3758 
3759     QgsProcessingBandWidgetWrapper wrapper2( &param, type );
3760 
3761     w = wrapper2.createWrappedWidget( context );
3762     layerWrapper.setWidgetValue( QVariant::fromValue( rl ), context );
3763     wrapper2.setParentLayerWrapperValue( &layerWrapper );
3764     QSignalSpy spy2( &wrapper2, &QgsProcessingBandWidgetWrapper::widgetValueHasChanged );
3765     wrapper2.setWidgetValue( QStringLiteral( "4" ), context );
3766     QCOMPARE( spy2.count(), 1 );
3767     QCOMPARE( wrapper2.widgetValue().toInt(),  4 );
3768 
3769     wrapper2.setWidgetValue( QVariant(), context );
3770     QCOMPARE( spy2.count(), 2 );
3771     QVERIFY( !wrapper2.widgetValue().isValid() );
3772 
3773     switch ( type )
3774     {
3775       case QgsProcessingGui::Standard:
3776       case QgsProcessingGui::Batch:
3777         QCOMPARE( static_cast< QgsRasterBandComboBox * >( wrapper2.wrappedWidget() )->currentBand(), -1 );
3778         break;
3779 
3780       case QgsProcessingGui::Modeler:
3781         QCOMPARE( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text(),  QString() );
3782         break;
3783     }
3784 
3785     QLabel *l = wrapper.createWrappedLabel();
3786     if ( wrapper.type() != QgsProcessingGui::Batch )
3787     {
3788       QVERIFY( l );
3789       QCOMPARE( l->text(), QStringLiteral( "band [optional]" ) );
3790       QCOMPARE( l->toolTip(), param.toolTip() );
3791       delete l;
3792     }
3793     else
3794     {
3795       QVERIFY( !l );
3796     }
3797 
3798     // check signal
3799     switch ( type )
3800     {
3801       case QgsProcessingGui::Standard:
3802       case QgsProcessingGui::Batch:
3803         static_cast< QgsRasterBandComboBox * >( wrapper2.wrappedWidget() )->setBand( 6 );
3804         break;
3805 
3806       case QgsProcessingGui::Modeler:
3807         static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->setText( QStringLiteral( "6" ) );
3808         break;
3809     }
3810 
3811     QCOMPARE( spy2.count(), 3 );
3812 
3813     switch ( type )
3814     {
3815       case QgsProcessingGui::Standard:
3816       case QgsProcessingGui::Batch:
3817         QCOMPARE( wrapper2.mComboBox->layer(), rl );
3818         break;
3819 
3820       case QgsProcessingGui::Modeler:
3821         break;
3822     }
3823 
3824     // should not be owned by wrapper
3825     QVERIFY( !wrapper2.mParentLayer.get() );
3826     layerWrapper.setWidgetValue( QVariant(), context );
3827     wrapper2.setParentLayerWrapperValue( &layerWrapper );
3828 
3829     switch ( type )
3830     {
3831       case QgsProcessingGui::Standard:
3832       case QgsProcessingGui::Batch:
3833         QVERIFY( !wrapper2.mComboBox->layer() );
3834         break;
3835 
3836       case QgsProcessingGui::Modeler:
3837         break;
3838     }
3839 
3840     layerWrapper.setWidgetValue( rl->id(), context );
3841     wrapper2.setParentLayerWrapperValue( &layerWrapper );
3842     switch ( type )
3843     {
3844       case QgsProcessingGui::Standard:
3845       case QgsProcessingGui::Batch:
3846         QVERIFY( !wrapper2.mComboBox->layer() );
3847         break;
3848 
3849       case QgsProcessingGui::Modeler:
3850         break;
3851     }
3852     QVERIFY( !wrapper2.mParentLayer.get() );
3853 
3854     // with project layer
3855     context.setProject( &p );
3856     TestProcessingContextGenerator generator( context );
3857     wrapper2.registerProcessingContextGenerator( &generator );
3858 
3859     layerWrapper.setWidgetValue( rl->id(), context );
3860     wrapper2.setParentLayerWrapperValue( &layerWrapper );
3861     switch ( type )
3862     {
3863       case QgsProcessingGui::Standard:
3864       case QgsProcessingGui::Batch:
3865         QCOMPARE( wrapper2.mComboBox->layer(), rl );
3866         break;
3867 
3868       case QgsProcessingGui::Modeler:
3869         break;
3870     }
3871     QVERIFY( !wrapper2.mParentLayer.get() );
3872 
3873     // non-project layer
3874     QString rasterFileName = TEST_DATA_DIR + QStringLiteral( "/landsat-f32-b1.tif" );
3875     layerWrapper.setWidgetValue( rasterFileName, context );
3876     wrapper2.setParentLayerWrapperValue( &layerWrapper );
3877     switch ( type )
3878     {
3879       case QgsProcessingGui::Standard:
3880       case QgsProcessingGui::Batch:
3881         QCOMPARE( wrapper2.mComboBox->layer()->publicSource(), rasterFileName );
3882         break;
3883 
3884       case QgsProcessingGui::Modeler:
3885         break;
3886     }
3887 
3888     // must be owned by wrapper, or layer may be deleted while still required by wrapper
3889     QCOMPARE( wrapper2.mParentLayer->publicSource(), rasterFileName );
3890 
3891     delete w;
3892 
3893     // multiple
3894     param = QgsProcessingParameterBand( QStringLiteral( "band" ), QStringLiteral( "band" ), QVariant(), QStringLiteral( "INPUT" ), true, true );
3895 
3896     QgsProcessingBandWidgetWrapper wrapper3( &param, type );
3897 
3898     w = wrapper3.createWrappedWidget( context );
3899     layerWrapper.setWidgetValue( QVariant::fromValue( rl ), context );
3900     wrapper3.setParentLayerWrapperValue( &layerWrapper );
3901     QSignalSpy spy3( &wrapper3, &QgsProcessingBandWidgetWrapper::widgetValueHasChanged );
3902     wrapper3.setWidgetValue( QStringLiteral( "5" ), context );
3903     QCOMPARE( spy3.count(), 1 );
3904     QCOMPARE( wrapper3.widgetValue().toList(), QVariantList() << 5 );
3905 
3906     wrapper3.setWidgetValue( QString(), context );
3907     QCOMPARE( spy3.count(), 2 );
3908     QVERIFY( wrapper3.widgetValue().toString().isEmpty() );
3909 
3910     wrapper3.setWidgetValue( QStringLiteral( "3;4" ), context );
3911     QCOMPARE( spy3.count(), 3 );
3912     QCOMPARE( wrapper3.widgetValue().toStringList(), QStringList() << QStringLiteral( "3" ) << QStringLiteral( "4" ) );
3913 
3914     wrapper3.setWidgetValue( QVariantList() << 5 << 6 << 7, context );
3915     QCOMPARE( spy3.count(), 4 );
3916     QCOMPARE( wrapper3.widgetValue().toList(), QVariantList() << 5 << 6 << 7 );
3917 
3918     wrapper3.setWidgetValue( QVariant(), context );
3919     QCOMPARE( spy3.count(), 5 );
3920     QVERIFY( !wrapper3.widgetValue().isValid() );
3921 
3922 
3923     // multiple non-optional
3924     param = QgsProcessingParameterBand( QStringLiteral( "band" ), QStringLiteral( "band" ), QVariant(), QStringLiteral( "INPUT" ), false, true );
3925 
3926     QgsProcessingBandWidgetWrapper wrapper4( &param, type );
3927 
3928     w = wrapper4.createWrappedWidget( context );
3929     layerWrapper.setWidgetValue( QVariant::fromValue( rl ), context );
3930     wrapper4.setParentLayerWrapperValue( &layerWrapper );
3931     QSignalSpy spy4( &wrapper4, &QgsProcessingBandWidgetWrapper::widgetValueHasChanged );
3932     wrapper4.setWidgetValue( QStringLiteral( "5" ), context );
3933     QCOMPARE( spy4.count(), 1 );
3934     QCOMPARE( wrapper4.widgetValue().toList(), QVariantList() << 5 );
3935 
3936     wrapper4.setWidgetValue( QString(), context );
3937     QCOMPARE( spy4.count(), 2 );
3938     QVERIFY( wrapper4.widgetValue().toString().isEmpty() );
3939 
3940     wrapper4.setWidgetValue( QStringLiteral( "3;4" ), context );
3941     QCOMPARE( spy4.count(), 3 );
3942     QCOMPARE( wrapper4.widgetValue().toStringList(), QStringList() << QStringLiteral( "3" ) << QStringLiteral( "4" ) );
3943 
3944     wrapper4.setWidgetValue( QVariantList() << 5 << 6 << 7, context );
3945     QCOMPARE( spy4.count(), 4 );
3946     QCOMPARE( wrapper4.widgetValue().toList(), QVariantList() << 5 << 6 << 7 );
3947 
3948     delete w;
3949   };
3950 
3951   // standard wrapper
3952   testWrapper( QgsProcessingGui::Standard );
3953 
3954   // batch wrapper
3955   testWrapper( QgsProcessingGui::Batch );
3956 
3957   // modeler wrapper
3958   testWrapper( QgsProcessingGui::Modeler );
3959 
3960   // config widget
3961   QgsProcessingParameterWidgetContext widgetContext;
3962   QgsProcessingContext context;
3963   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "band" ), context, widgetContext );
3964   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
3965   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
3966   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
3967   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
3968 
3969   // using a parameter definition as initial values
3970   QgsProcessingParameterBand bandParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), 1, QStringLiteral( "parent" ) );
3971   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "band" ), context, widgetContext, &bandParam );
3972   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
3973   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
3974   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
3975   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
3976   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
3977   QCOMPARE( static_cast< QgsProcessingParameterBand * >( def.get() )->defaultValue().toString(), QStringLiteral( "1" ) );
3978   QCOMPARE( static_cast< QgsProcessingParameterBand * >( def.get() )->allowMultiple(), false );
3979   QCOMPARE( static_cast< QgsProcessingParameterBand * >( def.get() )->parentLayerParameterName(), QStringLiteral( "parent" ) );
3980   bandParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
3981   bandParam.setParentLayerParameterName( QString() );
3982   bandParam.setAllowMultiple( true );
3983   bandParam.setDefaultValue( QVariantList() << 2 << 3 );
3984   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "band" ), context, widgetContext, &bandParam );
3985   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
3986   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
3987   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
3988   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
3989   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
3990   QCOMPARE( static_cast< QgsProcessingParameterBand * >( def.get() )->defaultValue().toStringList(), QStringList() << "2" << "3" );
3991   QCOMPARE( static_cast< QgsProcessingParameterBand * >( def.get() )->allowMultiple(), true );
3992   QVERIFY( static_cast< QgsProcessingParameterBand * >( def.get() )->parentLayerParameterName().isEmpty() );
3993 }
3994 
testMultipleInputWrapper()3995 void TestProcessingGui::testMultipleInputWrapper()
3996 {
3997   QString path1 = TEST_DATA_DIR + QStringLiteral( "/landsat-f32-b1.tif" );
3998   QString path2 = TEST_DATA_DIR + QStringLiteral( "/landsat.tif" );
3999 
4000   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
4001   {
4002     QgsProcessingParameterMultipleLayers param( QStringLiteral( "multi" ), QStringLiteral( "multi" ), QgsProcessing::TypeVector, QVariant(), false );
4003 
4004     QgsProcessingMultipleLayerWidgetWrapper wrapper( &param, type );
4005 
4006     QgsProcessingContext context;
4007 
4008     QWidget *w = wrapper.createWrappedWidget( context );
4009     ( void )w;
4010 
4011     QSignalSpy spy( &wrapper, &QgsProcessingMultipleLayerWidgetWrapper::widgetValueHasChanged );
4012     wrapper.setWidgetValue( QVariantList() << path1 << path2, context );
4013     QCOMPARE( spy.count(), 1 );
4014     QCOMPARE( wrapper.widgetValue().toList(), QVariantList() << path1 << path2 );
4015     QCOMPARE( static_cast< QgsProcessingMultipleLayerPanelWidget * >( wrapper.wrappedWidget() )->value().toList(), QVariantList() << path1 << path2 );
4016 
4017     wrapper.setWidgetValue( path1, context );
4018     QCOMPARE( spy.count(), 2 );
4019     QCOMPARE( wrapper.widgetValue().toStringList(), QStringList() << path1 );
4020     QCOMPARE( static_cast< QgsProcessingMultipleLayerPanelWidget * >( wrapper.wrappedWidget() )->value().toList(), QVariantList() << path1 );
4021     delete w;
4022 
4023     // optional
4024     param = QgsProcessingParameterMultipleLayers( QStringLiteral( "multi" ), QStringLiteral( "multi" ), QgsProcessing::TypeVector, QVariant(), true );
4025 
4026     QgsProcessingMultipleLayerWidgetWrapper wrapper2( &param, type );
4027 
4028     w = wrapper2.createWrappedWidget( context );
4029     QSignalSpy spy2( &wrapper2, &QgsProcessingMultipleLayerWidgetWrapper::widgetValueHasChanged );
4030     wrapper2.setWidgetValue( path2, context );
4031     QCOMPARE( spy2.count(), 1 );
4032     QCOMPARE( wrapper2.widgetValue().toList(), QVariantList() << path2 );
4033 
4034     wrapper2.setWidgetValue( QVariant(), context );
4035     QCOMPARE( spy2.count(), 2 );
4036     QVERIFY( !wrapper2.widgetValue().isValid() );
4037     QVERIFY( static_cast< QgsProcessingMultipleLayerPanelWidget * >( wrapper2.wrappedWidget() )->value().toList().isEmpty() );
4038 
4039     QLabel *l = wrapper.createWrappedLabel();
4040     if ( wrapper.type() != QgsProcessingGui::Batch )
4041     {
4042       QVERIFY( l );
4043       QCOMPARE( l->text(), QStringLiteral( "multi [optional]" ) );
4044       QCOMPARE( l->toolTip(), param.toolTip() );
4045       delete l;
4046     }
4047     else
4048     {
4049       QVERIFY( !l );
4050     }
4051 
4052     // check signal
4053     static_cast< QgsProcessingMultipleLayerPanelWidget * >( wrapper2.wrappedWidget() )->setValue( QVariantList() << path1 );
4054     QCOMPARE( spy2.count(), 3 );
4055 
4056 
4057     if ( wrapper.type() == QgsProcessingGui::Modeler )
4058     {
4059       // different mix of sources
4060 
4061       wrapper2.setWidgetValue( QVariantList()
4062                                << QVariant::fromValue( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg3" ), QStringLiteral( "OUTPUT" ) ) )
4063                                << QVariant::fromValue( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "p1" ) ) )
4064                                << QVariant::fromValue( QgsProcessingModelChildParameterSource::fromStaticValue( QStringLiteral( "something" ) ) ), context ) ;
4065       QCOMPARE( wrapper2.widgetValue().toList().count(), 3 );
4066 
4067       QCOMPARE( wrapper2.widgetValue().toList().at( 0 ).value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::ChildOutput );
4068       QCOMPARE( wrapper2.widgetValue().toList().at( 0 ).value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::ChildOutput );
4069       QCOMPARE( wrapper2.widgetValue().toList().at( 0 ).value< QgsProcessingModelChildParameterSource>().outputChildId(), QStringLiteral( "alg3" ) );
4070       QCOMPARE( wrapper2.widgetValue().toList().at( 0 ).value< QgsProcessingModelChildParameterSource>().outputName(), QStringLiteral( "OUTPUT" ) );
4071       QCOMPARE( wrapper2.widgetValue().toList().at( 1 ).value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::ModelParameter );
4072       QCOMPARE( wrapper2.widgetValue().toList().at( 1 ).value< QgsProcessingModelChildParameterSource>().parameterName(), QStringLiteral( "p1" ) );
4073       QCOMPARE( wrapper2.widgetValue().toList().at( 2 ).value< QgsProcessingModelChildParameterSource>().source(), QgsProcessingModelChildParameterSource::StaticValue );
4074       QCOMPARE( wrapper2.widgetValue().toList().at( 2 ).value< QgsProcessingModelChildParameterSource>().staticValue().toString(), QStringLiteral( "something" ) );
4075       delete w;
4076     }
4077   };
4078 
4079   // standard wrapper
4080   testWrapper( QgsProcessingGui::Standard );
4081 
4082   // batch wrapper
4083   testWrapper( QgsProcessingGui::Batch );
4084 
4085   // modeler wrapper
4086   testWrapper( QgsProcessingGui::Modeler );
4087 
4088   // config widget
4089   QgsProcessingParameterWidgetContext widgetContext;
4090   QgsProcessingContext context;
4091   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "multilayer" ), context, widgetContext );
4092   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
4093   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
4094   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
4095   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
4096 
4097   // using a parameter definition as initial values
4098   QgsProcessingParameterMultipleLayers layersParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ) );
4099   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "multilayer" ), context, widgetContext, &layersParam );
4100   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
4101   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
4102   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
4103   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
4104   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
4105   QCOMPARE( static_cast< QgsProcessingParameterMultipleLayers * >( def.get() )->layerType(), QgsProcessing::TypeVectorAnyGeometry );
4106   layersParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
4107   layersParam.setLayerType( QgsProcessing::TypeRaster );
4108   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "multilayer" ), context, widgetContext, &layersParam );
4109   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
4110   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
4111   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
4112   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
4113   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
4114   QCOMPARE( static_cast< QgsProcessingParameterMultipleLayers * >( def.get() )->layerType(), QgsProcessing::TypeRaster );
4115 }
4116 
testEnumSelectionPanel()4117 void TestProcessingGui::testEnumSelectionPanel()
4118 {
4119   QgsProcessingParameterEnum enumParam( QString(), QString(), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), true );
4120   QgsProcessingEnumPanelWidget w( nullptr, &enumParam );
4121   QSignalSpy spy( &w, &QgsProcessingEnumPanelWidget::changed );
4122 
4123   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "0 options selected" ) );
4124   w.setValue( 1 );
4125   QCOMPARE( spy.count(), 1 );
4126   QCOMPARE( w.value().toList(), QVariantList() << 1 );
4127   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "1 options selected" ) );
4128 
4129   w.setValue( QVariantList() << 2 << 0 );
4130   QCOMPARE( spy.count(), 2 );
4131   QCOMPARE( w.value().toList(), QVariantList() << 2 << 0 );
4132   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "2 options selected" ) );
4133 
4134   w.setValue( QVariant() );
4135   QCOMPARE( spy.count(), 3 );
4136   QCOMPARE( w.value().toList(), QVariantList() );
4137   QCOMPARE( w.mLineEdit->text(), QStringLiteral( "0 options selected" ) );
4138 
4139   // static strings
4140   QgsProcessingParameterEnum enumParam2( QString(), QString(), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), true, QVariant(), false, true );
4141   QgsProcessingEnumPanelWidget w2( nullptr, &enumParam2 );
4142   QSignalSpy spy2( &w2, &QgsProcessingEnumPanelWidget::changed );
4143 
4144   QCOMPARE( w2.mLineEdit->text(), QStringLiteral( "0 options selected" ) );
4145   w2.setValue( QStringLiteral( "a" ) );
4146   QCOMPARE( spy2.count(), 1 );
4147   QCOMPARE( w2.value().toList(), QVariantList() << QStringLiteral( "a" ) );
4148   QCOMPARE( w2.mLineEdit->text(), QStringLiteral( "1 options selected" ) );
4149 
4150   w2.setValue( QVariantList() << QStringLiteral( "c" ) << QStringLiteral( "a" ) );
4151   QCOMPARE( spy2.count(), 2 );
4152   QCOMPARE( w2.value().toList(), QVariantList() << QStringLiteral( "c" ) << QStringLiteral( "a" ) );
4153   QCOMPARE( w2.mLineEdit->text(), QStringLiteral( "2 options selected" ) );
4154 
4155   w2.setValue( QVariant() );
4156   QCOMPARE( spy2.count(), 3 );
4157   QCOMPARE( w2.value().toList(), QVariantList() );
4158   QCOMPARE( w2.mLineEdit->text(), QStringLiteral( "0 options selected" ) );
4159 }
4160 
testEnumCheckboxPanel()4161 void TestProcessingGui::testEnumCheckboxPanel()
4162 {
4163   //single value
4164   QgsProcessingParameterEnum param( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), false );
4165   QgsProcessingEnumCheckboxPanelWidget panel( nullptr, &param );
4166   QSignalSpy spy( &panel, &QgsProcessingEnumCheckboxPanelWidget::changed );
4167 
4168   QCOMPARE( panel.value(), QVariant() );
4169   panel.setValue( 2 );
4170   QCOMPARE( spy.count(), 1 );
4171   QCOMPARE( panel.value().toInt(), 2 );
4172   QVERIFY( !panel.mButtons[ 0 ]->isChecked() );
4173   QVERIFY( !panel.mButtons[ 1 ]->isChecked() );
4174   QVERIFY( panel.mButtons[ 2 ]->isChecked() );
4175   panel.setValue( 0 );
4176   QCOMPARE( spy.count(), 2 );
4177   QCOMPARE( panel.value().toInt(), 0 );
4178   QVERIFY( panel.mButtons[ 0 ]->isChecked() );
4179   QVERIFY( !panel.mButtons[ 1 ]->isChecked() );
4180   QVERIFY( !panel.mButtons[ 2 ]->isChecked() );
4181   panel.mButtons[1]->setChecked( true );
4182   QCOMPARE( spy.count(), 4 );
4183   QCOMPARE( panel.value().toInt(), 1 );
4184   panel.setValue( QVariantList() << 2 );
4185   QCOMPARE( spy.count(), 5 );
4186   QCOMPARE( panel.value().toInt(), 2 );
4187   QVERIFY( !panel.mButtons[ 0 ]->isChecked() );
4188   QVERIFY( !panel.mButtons[ 1 ]->isChecked() );
4189   QVERIFY( panel.mButtons[ 2 ]->isChecked() );
4190 
4191   // multiple value
4192   QgsProcessingParameterEnum param2( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), true );
4193   QgsProcessingEnumCheckboxPanelWidget panel2( nullptr, &param2 );
4194   QSignalSpy spy2( &panel2, &QgsProcessingEnumCheckboxPanelWidget::changed );
4195 
4196   QCOMPARE( panel2.value().toList(), QVariantList() );
4197   panel2.setValue( 2 );
4198   QCOMPARE( spy2.count(), 1 );
4199   QCOMPARE( panel2.value().toList(), QVariantList() << 2 );
4200   QVERIFY( !panel2.mButtons[ 0 ]->isChecked() );
4201   QVERIFY( !panel2.mButtons[ 1 ]->isChecked() );
4202   QVERIFY( panel2.mButtons[ 2 ]->isChecked() );
4203   panel2.setValue( QVariantList() << 0 << 1 );
4204   QCOMPARE( spy2.count(), 2 );
4205   QCOMPARE( panel2.value().toList(), QVariantList() << 0 << 1 );
4206   QVERIFY( panel2.mButtons[ 0 ]->isChecked() );
4207   QVERIFY( panel2.mButtons[ 1 ]->isChecked() );
4208   QVERIFY( !panel2.mButtons[ 2 ]->isChecked() );
4209   panel2.mButtons[0]->setChecked( false );
4210   QCOMPARE( spy2.count(), 3 );
4211   QCOMPARE( panel2.value().toList(), QVariantList()  << 1 );
4212   panel2.mButtons[2]->setChecked( true );
4213   QCOMPARE( spy2.count(), 4 );
4214   QCOMPARE( panel2.value().toList(), QVariantList()  << 1 << 2 );
4215   panel2.deselectAll();
4216   QCOMPARE( spy2.count(), 5 );
4217   QCOMPARE( panel2.value().toList(), QVariantList() );
4218   panel2.selectAll();
4219   QCOMPARE( spy2.count(), 6 );
4220   QCOMPARE( panel2.value().toList(), QVariantList() << 0 << 1 << 2 );
4221 
4222   // multiple value optional
4223   QgsProcessingParameterEnum param3( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), true, QVariant(), true );
4224   QgsProcessingEnumCheckboxPanelWidget panel3( nullptr, &param3 );
4225   QSignalSpy spy3( &panel3, &QgsProcessingEnumCheckboxPanelWidget::changed );
4226 
4227   QCOMPARE( panel3.value().toList(), QVariantList() );
4228   panel3.setValue( 2 );
4229   QCOMPARE( spy3.count(), 1 );
4230   QCOMPARE( panel3.value().toList(), QVariantList() << 2 );
4231   QVERIFY( !panel3.mButtons[ 0 ]->isChecked() );
4232   QVERIFY( !panel3.mButtons[ 1 ]->isChecked() );
4233   QVERIFY( panel3.mButtons[ 2 ]->isChecked() );
4234   panel3.setValue( QVariantList() << 0 << 1 );
4235   QCOMPARE( spy3.count(), 2 );
4236   QCOMPARE( panel3.value().toList(), QVariantList() << 0 << 1 );
4237   QVERIFY( panel3.mButtons[ 0 ]->isChecked() );
4238   QVERIFY( panel3.mButtons[ 1 ]->isChecked() );
4239   QVERIFY( !panel3.mButtons[ 2 ]->isChecked() );
4240   panel3.mButtons[0]->setChecked( false );
4241   QCOMPARE( spy3.count(), 3 );
4242   QCOMPARE( panel3.value().toList(), QVariantList()  << 1 );
4243   panel3.mButtons[2]->setChecked( true );
4244   QCOMPARE( spy3.count(), 4 );
4245   QCOMPARE( panel3.value().toList(), QVariantList()  << 1 << 2 );
4246   panel3.deselectAll();
4247   QCOMPARE( spy3.count(), 5 );
4248   QCOMPARE( panel3.value().toList(), QVariantList() );
4249   panel3.selectAll();
4250   QCOMPARE( spy3.count(), 6 );
4251   QCOMPARE( panel3.value().toList(), QVariantList() << 0 << 1 << 2 );
4252   panel3.setValue( QVariantList() );
4253   QCOMPARE( panel3.value().toList(), QVariantList() );
4254   QVERIFY( !panel3.mButtons[ 0 ]->isChecked() );
4255   QVERIFY( !panel3.mButtons[ 1 ]->isChecked() );
4256   QVERIFY( !panel3.mButtons[ 2 ]->isChecked() );
4257   QCOMPARE( spy3.count(), 7 );
4258   panel3.selectAll();
4259   QCOMPARE( spy3.count(), 8 );
4260   panel3.setValue( QVariant() );
4261   QCOMPARE( panel3.value().toList(), QVariantList() );
4262   QVERIFY( !panel3.mButtons[ 0 ]->isChecked() );
4263   QVERIFY( !panel3.mButtons[ 1 ]->isChecked() );
4264   QVERIFY( !panel3.mButtons[ 2 ]->isChecked() );
4265   QCOMPARE( spy3.count(), 9 );
4266 
4267   //single value using static strings
4268   QgsProcessingParameterEnum param4( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), false, QVariant(), false, true );
4269   QgsProcessingEnumCheckboxPanelWidget panel4( nullptr, &param4 );
4270   QSignalSpy spy4( &panel4, &QgsProcessingEnumCheckboxPanelWidget::changed );
4271 
4272   QCOMPARE( panel4.value(), QVariant() );
4273   panel4.setValue( QStringLiteral( "c" ) );
4274   QCOMPARE( spy4.count(), 1 );
4275   QCOMPARE( panel4.value().toString(), QStringLiteral( "c" ) );
4276   QVERIFY( !panel4.mButtons[ 0 ]->isChecked() );
4277   QVERIFY( !panel4.mButtons[ 1 ]->isChecked() );
4278   QVERIFY( panel4.mButtons[ 2 ]->isChecked() );
4279   panel4.setValue( QStringLiteral( "a" ) );
4280   QCOMPARE( spy4.count(), 2 );
4281   QCOMPARE( panel4.value().toString(), QStringLiteral( "a" ) );
4282   QVERIFY( panel4.mButtons[ 0 ]->isChecked() );
4283   QVERIFY( !panel4.mButtons[ 1 ]->isChecked() );
4284   QVERIFY( !panel4.mButtons[ 2 ]->isChecked() );
4285   panel4.mButtons[1]->setChecked( true );
4286   QCOMPARE( spy4.count(), 4 );
4287   QCOMPARE( panel4.value().toString(), QStringLiteral( "b" ) );
4288   panel4.setValue( QVariantList() << QStringLiteral( "c" ) );
4289   QCOMPARE( spy4.count(), 5 );
4290   QCOMPARE( panel4.value().toString(), QStringLiteral( "c" ) );
4291   QVERIFY( !panel4.mButtons[ 0 ]->isChecked() );
4292   QVERIFY( !panel4.mButtons[ 1 ]->isChecked() );
4293   QVERIFY( panel4.mButtons[ 2 ]->isChecked() );
4294 
4295   // multiple value with static strings
4296   QgsProcessingParameterEnum param5( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), true, QVariant(), false, true );
4297   QgsProcessingEnumCheckboxPanelWidget panel5( nullptr, &param5 );
4298   QSignalSpy spy5( &panel5, &QgsProcessingEnumCheckboxPanelWidget::changed );
4299 
4300   QCOMPARE( panel5.value().toList(), QVariantList() );
4301   panel5.setValue( QStringLiteral( "c" ) );
4302   QCOMPARE( spy5.count(), 1 );
4303   QCOMPARE( panel5.value().toList(), QVariantList() << QStringLiteral( "c" ) );
4304   QVERIFY( !panel5.mButtons[ 0 ]->isChecked() );
4305   QVERIFY( !panel5.mButtons[ 1 ]->isChecked() );
4306   QVERIFY( panel5.mButtons[ 2 ]->isChecked() );
4307   panel5.setValue( QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
4308   QCOMPARE( spy5.count(), 2 );
4309   QCOMPARE( panel5.value().toList(), QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
4310   QVERIFY( panel5.mButtons[ 0 ]->isChecked() );
4311   QVERIFY( panel5.mButtons[ 1 ]->isChecked() );
4312   QVERIFY( !panel5.mButtons[ 2 ]->isChecked() );
4313   panel5.mButtons[0]->setChecked( false );
4314   QCOMPARE( spy5.count(), 3 );
4315   QCOMPARE( panel5.value().toList(), QVariantList()  << QStringLiteral( "b" ) );
4316   panel5.mButtons[2]->setChecked( true );
4317   QCOMPARE( spy5.count(), 4 );
4318   QCOMPARE( panel5.value().toList(), QVariantList() << QStringLiteral( "b" ) << QStringLiteral( "c" ) );
4319   panel5.deselectAll();
4320   QCOMPARE( spy5.count(), 5 );
4321   QCOMPARE( panel5.value().toList(), QVariantList() );
4322   panel5.selectAll();
4323   QCOMPARE( spy5.count(), 6 );
4324   QCOMPARE( panel5.value().toList(), QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ) );
4325 
4326   // multiple value optional with statis strings
4327   QgsProcessingParameterEnum param6( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), true, QVariant(), true, true );
4328   QgsProcessingEnumCheckboxPanelWidget panel6( nullptr, &param6 );
4329   QSignalSpy spy6( &panel6, &QgsProcessingEnumCheckboxPanelWidget::changed );
4330 
4331   QCOMPARE( panel6.value().toList(), QVariantList() );
4332   panel6.setValue( QStringLiteral( "c" ) );
4333   QCOMPARE( spy6.count(), 1 );
4334   QCOMPARE( panel6.value().toList(), QVariantList() << QStringLiteral( "c" ) );
4335   QVERIFY( !panel6.mButtons[ 0 ]->isChecked() );
4336   QVERIFY( !panel6.mButtons[ 1 ]->isChecked() );
4337   QVERIFY( panel6.mButtons[ 2 ]->isChecked() );
4338   panel6.setValue( QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
4339   QCOMPARE( spy6.count(), 2 );
4340   QCOMPARE( panel6.value().toList(), QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
4341   QVERIFY( panel6.mButtons[ 0 ]->isChecked() );
4342   QVERIFY( panel6.mButtons[ 1 ]->isChecked() );
4343   QVERIFY( !panel6.mButtons[ 2 ]->isChecked() );
4344   panel6.mButtons[0]->setChecked( false );
4345   QCOMPARE( spy6.count(), 3 );
4346   QCOMPARE( panel6.value().toList(), QVariantList() << QStringLiteral( "b" ) );
4347   panel6.mButtons[2]->setChecked( true );
4348   QCOMPARE( spy6.count(), 4 );
4349   QCOMPARE( panel6.value().toList(), QVariantList() << QStringLiteral( "b" ) << QStringLiteral( "c" ) );
4350   panel6.deselectAll();
4351   QCOMPARE( spy6.count(), 5 );
4352   QCOMPARE( panel6.value().toList(), QVariantList() );
4353   panel6.selectAll();
4354   QCOMPARE( spy6.count(), 6 );
4355   QCOMPARE( panel6.value().toList(), QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ) );
4356   panel6.setValue( QVariantList() );
4357   QCOMPARE( panel6.value().toList(), QVariantList() );
4358   QVERIFY( !panel6.mButtons[ 0 ]->isChecked() );
4359   QVERIFY( !panel6.mButtons[ 1 ]->isChecked() );
4360   QVERIFY( !panel6.mButtons[ 2 ]->isChecked() );
4361   QCOMPARE( spy6.count(), 7 );
4362   panel6.selectAll();
4363   QCOMPARE( spy6.count(), 8 );
4364   panel6.setValue( QVariant() );
4365   QCOMPARE( panel6.value().toList(), QVariantList() );
4366   QVERIFY( !panel6.mButtons[ 0 ]->isChecked() );
4367   QVERIFY( !panel6.mButtons[ 1 ]->isChecked() );
4368   QVERIFY( !panel6.mButtons[ 2 ]->isChecked() );
4369   QCOMPARE( spy6.count(), 9 );
4370 }
4371 
testEnumWrapper()4372 void TestProcessingGui::testEnumWrapper()
4373 {
4374   auto testWrapper = []( QgsProcessingGui::WidgetType type, bool checkboxStyle = false )
4375   {
4376     // non optional, single value
4377     QgsProcessingParameterEnum param( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), false );
4378     QVariantMap metadata;
4379     QVariantMap wrapperMetadata;
4380     wrapperMetadata.insert( QStringLiteral( "useCheckBoxes" ), true );
4381     metadata.insert( QStringLiteral( "widget_wrapper" ), wrapperMetadata );
4382     if ( checkboxStyle )
4383       param.setMetadata( metadata );
4384 
4385     QgsProcessingEnumWidgetWrapper wrapper( &param, type );
4386 
4387     QgsProcessingContext context;
4388     QWidget *w = wrapper.createWrappedWidget( context );
4389 
4390     QSignalSpy spy( &wrapper, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
4391     wrapper.setWidgetValue( 1, context );
4392     QCOMPARE( spy.count(), 1 );
4393     QCOMPARE( wrapper.widgetValue().toInt(),  1 );
4394     if ( !checkboxStyle )
4395     {
4396       QCOMPARE( static_cast< QComboBox * >( wrapper.wrappedWidget() )->currentIndex(), 1 );
4397       QCOMPARE( static_cast< QComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "b" ) );
4398     }
4399     else
4400     {
4401       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper.wrappedWidget() )->value().toInt(), 1 );
4402     }
4403     wrapper.setWidgetValue( 0, context );
4404     QCOMPARE( spy.count(), 2 );
4405     QCOMPARE( wrapper.widgetValue().toInt(),  0 );
4406     if ( !checkboxStyle )
4407     {
4408       QCOMPARE( static_cast< QComboBox * >( wrapper.wrappedWidget() )->currentIndex(), 0 );
4409       QCOMPARE( static_cast< QComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "a" ) );
4410     }
4411     else
4412     {
4413       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper.wrappedWidget() )->value().toInt(), 0 );
4414     }
4415 
4416     QLabel *l = wrapper.createWrappedLabel();
4417     if ( wrapper.type() != QgsProcessingGui::Batch )
4418     {
4419       QVERIFY( l );
4420       QCOMPARE( l->text(), QStringLiteral( "enum" ) );
4421       QCOMPARE( l->toolTip(), param.toolTip() );
4422       delete l;
4423     }
4424     else
4425     {
4426       QVERIFY( !l );
4427     }
4428 
4429     // check signal
4430     if ( !checkboxStyle )
4431       static_cast< QComboBox * >( wrapper.wrappedWidget() )->setCurrentIndex( 2 );
4432     else
4433       static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper.wrappedWidget() )->setValue( 2 );
4434     QCOMPARE( spy.count(), 3 );
4435 
4436     delete w;
4437 
4438     // optional
4439     QgsProcessingParameterEnum param2( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), false, QVariant(), true );
4440     if ( checkboxStyle )
4441       param2.setMetadata( metadata );
4442 
4443     QgsProcessingEnumWidgetWrapper wrapper2( &param2, type );
4444 
4445     w = wrapper2.createWrappedWidget( context );
4446 
4447     QSignalSpy spy2( &wrapper2, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
4448     wrapper2.setWidgetValue( 1, context );
4449     QCOMPARE( spy2.count(), 1 );
4450     QCOMPARE( wrapper2.widgetValue().toInt(),  1 );
4451     if ( !checkboxStyle )
4452     {
4453       QCOMPARE( static_cast< QComboBox * >( wrapper2.wrappedWidget() )->currentIndex(), 2 );
4454       QCOMPARE( static_cast< QComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "b" ) );
4455     }
4456     else
4457     {
4458       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper2.wrappedWidget() )->value().toInt(), 1 );
4459     }
4460     wrapper2.setWidgetValue( 0, context );
4461     QCOMPARE( spy2.count(), 2 );
4462     QCOMPARE( wrapper2.widgetValue().toInt(),  0 );
4463     if ( !checkboxStyle )
4464     {
4465       QCOMPARE( static_cast< QComboBox * >( wrapper2.wrappedWidget() )->currentIndex(), 1 );
4466       QCOMPARE( static_cast< QComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "a" ) );
4467     }
4468     else
4469     {
4470       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper2.wrappedWidget() )->value().toInt(), 0 );
4471     }
4472     wrapper2.setWidgetValue( QVariant(), context );
4473     QCOMPARE( spy2.count(), 3 );
4474     if ( !checkboxStyle )
4475     {
4476       QVERIFY( !wrapper2.widgetValue().isValid() );
4477       QCOMPARE( static_cast< QComboBox * >( wrapper2.wrappedWidget() )->currentIndex(), 0 );
4478       QCOMPARE( static_cast< QComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "[Not selected]" ) );
4479     }
4480 
4481     // check signal
4482     if ( !checkboxStyle )
4483       static_cast< QComboBox * >( wrapper2.wrappedWidget() )->setCurrentIndex( 2 );
4484     else
4485       static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper2.wrappedWidget() )->setValue( 1 );
4486     QCOMPARE( spy2.count(), 4 );
4487 
4488     delete w;
4489 
4490     // allow multiple, non optional
4491     QgsProcessingParameterEnum param3( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), true, QVariant(), false );
4492     if ( checkboxStyle )
4493       param3.setMetadata( metadata );
4494 
4495     QgsProcessingEnumWidgetWrapper wrapper3( &param3, type );
4496 
4497     w = wrapper3.createWrappedWidget( context );
4498 
4499     QSignalSpy spy3( &wrapper3, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
4500     wrapper3.setWidgetValue( 1, context );
4501     QCOMPARE( spy3.count(), 1 );
4502     QCOMPARE( wrapper3.widgetValue().toList(), QVariantList() << 1 );
4503     if ( !checkboxStyle )
4504       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper3.wrappedWidget() )->value().toList(), QVariantList() << 1 );
4505     else
4506       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper3.wrappedWidget() )->value().toList(), QVariantList() << 1 );
4507     wrapper3.setWidgetValue( 0, context );
4508     QCOMPARE( spy3.count(), 2 );
4509     QCOMPARE( wrapper3.widgetValue().toList(), QVariantList() << 0 );
4510     if ( !checkboxStyle )
4511       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper3.wrappedWidget() )->value().toList(), QVariantList() << 0 );
4512     else
4513       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper3.wrappedWidget() )->value().toList(), QVariantList() << 0 );
4514     wrapper3.setWidgetValue( QVariantList() << 2 << 1, context );
4515     QCOMPARE( spy3.count(), 3 );
4516     if ( !checkboxStyle )
4517     {
4518       QCOMPARE( wrapper3.widgetValue().toList(), QVariantList() << 2 << 1 );
4519       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper3.wrappedWidget() )->value().toList(), QVariantList() << 2 << 1 );
4520     }
4521     else
4522     {
4523       // checkbox style isn't ordered
4524       QCOMPARE( wrapper3.widgetValue().toList(), QVariantList() << 1 << 2 );
4525       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper3.wrappedWidget() )->value().toList(), QVariantList() << 1 << 2 );
4526     }
4527     // check signal
4528     if ( !checkboxStyle )
4529       static_cast< QgsProcessingEnumPanelWidget * >( wrapper3.wrappedWidget() )->setValue( QVariantList() << 0 << 1 );
4530     else
4531       static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper3.wrappedWidget() )->setValue( QVariantList() << 0 << 1 );
4532 
4533     QCOMPARE( spy3.count(), 4 );
4534 
4535     delete w;
4536 
4537     // allow multiple, optional
4538     QgsProcessingParameterEnum param4( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), true, QVariant(), true );
4539     if ( checkboxStyle )
4540       param4.setMetadata( metadata );
4541 
4542     QgsProcessingEnumWidgetWrapper wrapper4( &param4, type );
4543 
4544     w = wrapper4.createWrappedWidget( context );
4545 
4546     QSignalSpy spy4( &wrapper4, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
4547     wrapper4.setWidgetValue( 1, context );
4548     QCOMPARE( spy4.count(), 1 );
4549     QCOMPARE( wrapper4.widgetValue().toList(), QVariantList() << 1 );
4550     if ( !checkboxStyle )
4551       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper4.wrappedWidget() )->value().toList(), QVariantList() << 1 );
4552     else
4553       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper4.wrappedWidget() )->value().toList(), QVariantList() << 1 );
4554     wrapper4.setWidgetValue( 0, context );
4555     QCOMPARE( spy4.count(), 2 );
4556     QCOMPARE( wrapper4.widgetValue().toList(), QVariantList() << 0 );
4557     if ( !checkboxStyle )
4558       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper4.wrappedWidget() )->value().toList(), QVariantList() << 0 );
4559     else
4560       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper4.wrappedWidget() )->value().toList(), QVariantList() << 0 );
4561     wrapper4.setWidgetValue( QVariantList() << 2 << 1, context );
4562     QCOMPARE( spy4.count(), 3 );
4563     if ( !checkboxStyle )
4564     {
4565       QCOMPARE( wrapper4.widgetValue().toList(), QVariantList() << 2 << 1 );
4566       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper4.wrappedWidget() )->value().toList(), QVariantList() << 2 << 1 );
4567     }
4568     else
4569     {
4570       // checkbox style isn't ordered
4571       QCOMPARE( wrapper4.widgetValue().toList(), QVariantList() << 1 << 2 );
4572       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper4.wrappedWidget() )->value().toList(), QVariantList() << 1 << 2 );
4573     }
4574     wrapper4.setWidgetValue( QVariantList(), context );
4575     QCOMPARE( spy4.count(), 4 );
4576     QCOMPARE( wrapper4.widgetValue().toList(), QVariantList() );
4577     if ( !checkboxStyle )
4578       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper4.wrappedWidget() )->value().toList(), QVariantList() );
4579     else
4580       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper4.wrappedWidget() )->value().toList(), QVariantList() );
4581 
4582     wrapper4.setWidgetValue( QVariant(), context );
4583     QCOMPARE( spy4.count(), 5 );
4584     QCOMPARE( wrapper4.widgetValue().toList(), QVariantList() );
4585     if ( !checkboxStyle )
4586       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper4.wrappedWidget() )->value().toList(), QVariantList() );
4587     else
4588       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper4.wrappedWidget() )->value().toList(), QVariantList() );
4589 
4590     // check signal
4591     if ( !checkboxStyle )
4592     {
4593       static_cast< QgsProcessingEnumPanelWidget * >( wrapper4.wrappedWidget() )->setValue( QVariantList() << 0 << 1 );
4594       QCOMPARE( spy4.count(), 6 );
4595       static_cast< QgsProcessingEnumPanelWidget * >( wrapper4.wrappedWidget() )->setValue( QVariant() );
4596       QCOMPARE( spy4.count(), 7 );
4597     }
4598     else
4599     {
4600       static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper4.wrappedWidget() )->setValue( QVariantList() << 0 << 1 );
4601       QCOMPARE( spy4.count(), 6 );
4602       static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper4.wrappedWidget() )->setValue( QVariant() );
4603       QCOMPARE( spy4.count(), 7 );
4604     }
4605 
4606     delete w;
4607 
4608     // non optional, single with static strings
4609     QgsProcessingParameterEnum param5( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), false, QVariant(), false, true );
4610     if ( checkboxStyle )
4611       param5.setMetadata( metadata );
4612 
4613     QgsProcessingEnumWidgetWrapper wrapper5( &param5, type );
4614 
4615     w = wrapper5.createWrappedWidget( context );
4616 
4617     QSignalSpy spy5( &wrapper5, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
4618     wrapper5.setWidgetValue( QStringLiteral( "b" ), context );
4619     QCOMPARE( spy5.count(), 1 );
4620     QCOMPARE( wrapper5.widgetValue().toString(), QStringLiteral( "b" ) );
4621     if ( !checkboxStyle )
4622     {
4623       QCOMPARE( static_cast< QComboBox * >( wrapper5.wrappedWidget() )->currentIndex(), 1 );
4624       QCOMPARE( static_cast< QComboBox * >( wrapper5.wrappedWidget() )->currentText(), QStringLiteral( "b" ) );
4625     }
4626     else
4627     {
4628       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper5.wrappedWidget() )->value().toString(), QStringLiteral( "b" ) );
4629     }
4630     wrapper5.setWidgetValue( QStringLiteral( "a" ), context );
4631     QCOMPARE( spy5.count(), 2 );
4632     QCOMPARE( wrapper5.widgetValue().toString(), QStringLiteral( "a" ) );
4633     if ( !checkboxStyle )
4634     {
4635       QCOMPARE( static_cast< QComboBox * >( wrapper5.wrappedWidget() )->currentIndex(), 0 );
4636       QCOMPARE( static_cast< QComboBox * >( wrapper5.wrappedWidget() )->currentText(), QStringLiteral( "a" ) );
4637     }
4638     else
4639     {
4640       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper5.wrappedWidget() )->value().toString(), QStringLiteral( "a" ) );
4641     }
4642 
4643     // check signal
4644     if ( !checkboxStyle )
4645       static_cast< QComboBox * >( wrapper5.wrappedWidget() )->setCurrentIndex( 2 );
4646     else
4647       static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper5.wrappedWidget() )->setValue( QStringLiteral( "c" ) );
4648     QCOMPARE( spy5.count(), 3 );
4649 
4650     delete w;
4651 
4652     // single, optional with static strings
4653     QgsProcessingParameterEnum param6( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), false, QVariant(), true, true );
4654     if ( checkboxStyle )
4655       param6.setMetadata( metadata );
4656 
4657     QgsProcessingEnumWidgetWrapper wrapper6( &param6, type );
4658 
4659     w = wrapper6.createWrappedWidget( context );
4660 
4661     QSignalSpy spy6( &wrapper6, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
4662     wrapper6.setWidgetValue( QStringLiteral( "b" ), context );
4663     QCOMPARE( spy6.count(), 1 );
4664     QCOMPARE( wrapper6.widgetValue().toString(), QStringLiteral( "b" ) );
4665     if ( !checkboxStyle )
4666     {
4667       QCOMPARE( static_cast< QComboBox * >( wrapper6.wrappedWidget() )->currentIndex(), 2 );
4668       QCOMPARE( static_cast< QComboBox * >( wrapper6.wrappedWidget() )->currentText(), QStringLiteral( "b" ) );
4669     }
4670     else
4671     {
4672       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper6.wrappedWidget() )->value().toString(), QStringLiteral( "b" ) );
4673     }
4674     wrapper6.setWidgetValue( QStringLiteral( "a" ), context );
4675     QCOMPARE( spy6.count(), 2 );
4676     QCOMPARE( wrapper6.widgetValue().toString(), QStringLiteral( "a" ) );
4677     if ( !checkboxStyle )
4678     {
4679       QCOMPARE( static_cast< QComboBox * >( wrapper6.wrappedWidget() )->currentIndex(), 1 );
4680       QCOMPARE( static_cast< QComboBox * >( wrapper6.wrappedWidget() )->currentText(), QStringLiteral( "a" ) );
4681     }
4682     else
4683     {
4684       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper6.wrappedWidget() )->value().toString(), QStringLiteral( "a" ) );
4685     }
4686     wrapper6.setWidgetValue( QVariant(), context );
4687     QCOMPARE( spy6.count(), 3 );
4688     if ( !checkboxStyle )
4689     {
4690       QVERIFY( !wrapper6.widgetValue().isValid() );
4691       QCOMPARE( static_cast< QComboBox * >( wrapper6.wrappedWidget() )->currentIndex(), 0 );
4692       QCOMPARE( static_cast< QComboBox * >( wrapper6.wrappedWidget() )->currentText(), QStringLiteral( "[Not selected]" ) );
4693     }
4694 
4695     // check signal
4696     if ( !checkboxStyle )
4697       static_cast< QComboBox * >( wrapper6.wrappedWidget() )->setCurrentIndex( 2 );
4698     else
4699       static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper6.wrappedWidget() )->setValue( QStringLiteral( "a" ) );
4700     QCOMPARE( spy6.count(), 4 );
4701 
4702     delete w;
4703 
4704     // multiple, non optional with static strings
4705     QgsProcessingParameterEnum param7( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), true, QVariant(), false, true );
4706     if ( checkboxStyle )
4707       param7.setMetadata( metadata );
4708 
4709     QgsProcessingEnumWidgetWrapper wrapper7( &param7, type );
4710 
4711     w = wrapper7.createWrappedWidget( context );
4712 
4713     QSignalSpy spy7( &wrapper7, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
4714     wrapper7.setWidgetValue( QStringLiteral( "b" ), context );
4715     QCOMPARE( spy7.count(), 1 );
4716     QCOMPARE( wrapper7.widgetValue().toList(), QVariantList() << QStringLiteral( "b" ) );
4717     if ( !checkboxStyle )
4718       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper7.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "b" ) );
4719     else
4720       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper7.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "b" ) );
4721     wrapper7.setWidgetValue( QStringLiteral( "a" ), context );
4722     QCOMPARE( spy7.count(), 2 );
4723     QCOMPARE( wrapper7.widgetValue().toList(), QVariantList() << QStringLiteral( "a" ) );
4724     if ( !checkboxStyle )
4725       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper7.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "a" ) );
4726     else
4727       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper7.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "a" ) );
4728     wrapper7.setWidgetValue( QVariantList() << QStringLiteral( "c" ) << QStringLiteral( "b" ), context );
4729     QCOMPARE( spy7.count(), 3 );
4730     if ( !checkboxStyle )
4731     {
4732       QCOMPARE( wrapper7.widgetValue().toList(), QVariantList() << QStringLiteral( "c" ) << QStringLiteral( "b" ) );
4733       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper7.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "c" ) << QStringLiteral( "b" ) );
4734     }
4735     else
4736     {
4737       // checkbox style isn't ordered
4738       QCOMPARE( wrapper7.widgetValue().toList(), QVariantList() << QStringLiteral( "b" ) << QStringLiteral( "c" ) );
4739       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper7.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "b" ) << QStringLiteral( "c" ) );
4740     }
4741     // check signal
4742     if ( !checkboxStyle )
4743       static_cast< QgsProcessingEnumPanelWidget * >( wrapper7.wrappedWidget() )->setValue( QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
4744     else
4745       static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper7.wrappedWidget() )->setValue( QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
4746 
4747     QCOMPARE( spy7.count(), 4 );
4748 
4749     delete w;
4750 
4751     // multiple, optional with static strings
4752     QgsProcessingParameterEnum param8( QStringLiteral( "enum" ), QStringLiteral( "enum" ), QStringList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) << QStringLiteral( "c" ), true, QVariant(), true, true );
4753     if ( checkboxStyle )
4754       param8.setMetadata( metadata );
4755 
4756     QgsProcessingEnumWidgetWrapper wrapper8( &param8, type );
4757 
4758     w = wrapper8.createWrappedWidget( context );
4759 
4760     QSignalSpy spy8( &wrapper8, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
4761     wrapper8.setWidgetValue( QStringLiteral( "b" ), context );
4762     QCOMPARE( spy8.count(), 1 );
4763     QCOMPARE( wrapper8.widgetValue().toList(), QVariantList() << QStringLiteral( "b" ) );
4764     if ( !checkboxStyle )
4765       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper8.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "b" ) );
4766     else
4767       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper8.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "b" ) );
4768     wrapper8.setWidgetValue( QStringLiteral( "a" ), context );
4769     QCOMPARE( spy8.count(), 2 );
4770     QCOMPARE( wrapper8.widgetValue().toList(), QVariantList() << QStringLiteral( "a" ) );
4771     if ( !checkboxStyle )
4772       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper8.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "a" ) );
4773     else
4774       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper8.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "a" ) );
4775     wrapper8.setWidgetValue( QVariantList() << QStringLiteral( "c" ) << QStringLiteral( "b" ), context );
4776     QCOMPARE( spy8.count(), 3 );
4777     if ( !checkboxStyle )
4778     {
4779       QCOMPARE( wrapper8.widgetValue().toList(), QVariantList() << QStringLiteral( "c" ) << QStringLiteral( "b" ) );
4780       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper8.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "c" ) << QStringLiteral( "b" ) );
4781     }
4782     else
4783     {
4784       // checkbox style isn't ordered
4785       QCOMPARE( wrapper8.widgetValue().toList(), QVariantList() << QStringLiteral( "b" ) << QStringLiteral( "c" ) );
4786       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper8.wrappedWidget() )->value().toList(), QVariantList() << QStringLiteral( "b" ) << QStringLiteral( "c" ) );
4787     }
4788     wrapper8.setWidgetValue( QVariantList(), context );
4789     QCOMPARE( spy8.count(), 4 );
4790     QCOMPARE( wrapper8.widgetValue().toList(), QVariantList() );
4791     if ( !checkboxStyle )
4792       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper8.wrappedWidget() )->value().toList(), QVariantList() );
4793     else
4794       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper8.wrappedWidget() )->value().toList(), QVariantList() );
4795 
4796     wrapper8.setWidgetValue( QVariant(), context );
4797     QCOMPARE( spy8.count(), 5 );
4798     QCOMPARE( wrapper8.widgetValue().toList(), QVariantList() );
4799     if ( !checkboxStyle )
4800       QCOMPARE( static_cast< QgsProcessingEnumPanelWidget * >( wrapper8.wrappedWidget() )->value().toList(), QVariantList() );
4801     else
4802       QCOMPARE( static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper8.wrappedWidget() )->value().toList(), QVariantList() );
4803 
4804     // check signal
4805     if ( !checkboxStyle )
4806     {
4807       static_cast< QgsProcessingEnumPanelWidget * >( wrapper8.wrappedWidget() )->setValue( QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
4808       QCOMPARE( spy8.count(), 6 );
4809       static_cast< QgsProcessingEnumPanelWidget * >( wrapper8.wrappedWidget() )->setValue( QVariant() );
4810       QCOMPARE( spy8.count(), 7 );
4811     }
4812     else
4813     {
4814       static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper8.wrappedWidget() )->setValue( QVariantList() << QStringLiteral( "a" ) << QStringLiteral( "b" ) );
4815       QCOMPARE( spy8.count(), 6 );
4816       static_cast< QgsProcessingEnumCheckboxPanelWidget * >( wrapper8.wrappedWidget() )->setValue( QVariant() );
4817       QCOMPARE( spy8.count(), 7 );
4818     }
4819 
4820     delete w;
4821   };
4822 
4823   // standard wrapper
4824   testWrapper( QgsProcessingGui::Standard );
4825 
4826   // batch wrapper
4827   testWrapper( QgsProcessingGui::Batch );
4828 
4829   // modeler wrapper
4830   testWrapper( QgsProcessingGui::Modeler );
4831 
4832   // checkbox style (not for batch or model mode!)
4833   testWrapper( QgsProcessingGui::Standard, true );
4834 
4835   // config widget
4836   QgsProcessingParameterWidgetContext widgetContext;
4837   QgsProcessingContext context;
4838   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "enum" ), context, widgetContext );
4839   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
4840   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
4841   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
4842   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
4843 
4844   // using a parameter definition as initial values
4845   QgsProcessingParameterEnum enumParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringList() << "A" << "B" << "C", false, 2 );
4846   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "enum" ), context, widgetContext, &enumParam );
4847   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
4848   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
4849   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
4850   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
4851   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
4852   QCOMPARE( static_cast< QgsProcessingParameterEnum * >( def.get() )->options(), QStringList() << "A" << "B" << "C" );
4853   QCOMPARE( static_cast< QgsProcessingParameterEnum * >( def.get() )->defaultValue().toStringList(), QStringList() << "2" );
4854   QVERIFY( !static_cast< QgsProcessingParameterEnum * >( def.get() )->allowMultiple() );
4855   enumParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
4856   enumParam.setAllowMultiple( true );
4857   enumParam.setDefaultValue( QVariantList() << 0 << 1 );
4858   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "enum" ), context, widgetContext, &enumParam );
4859   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
4860   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
4861   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
4862   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
4863   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
4864   QCOMPARE( static_cast< QgsProcessingParameterEnum * >( def.get() )->options(), QStringList() << "A" << "B" << "C" );
4865   QCOMPARE( static_cast< QgsProcessingParameterEnum * >( def.get() )->defaultValue().toStringList(), QStringList() << "0" << "1" );
4866   QVERIFY( static_cast< QgsProcessingParameterEnum * >( def.get() )->allowMultiple() );
4867 }
4868 
testLayoutWrapper()4869 void TestProcessingGui::testLayoutWrapper()
4870 {
4871   QgsProject p;
4872   QgsPrintLayout *l1 = new QgsPrintLayout( &p );
4873   l1->setName( "l1" );
4874   p.layoutManager()->addLayout( l1 );
4875   QgsPrintLayout *l2 = new QgsPrintLayout( &p );
4876   l2->setName( "l2" );
4877   p.layoutManager()->addLayout( l2 );
4878 
4879   auto testWrapper = [&p]( QgsProcessingGui::WidgetType type )
4880   {
4881     // non optional
4882     QgsProcessingParameterLayout param( QStringLiteral( "layout" ), QStringLiteral( "layout" ), false );
4883 
4884     QgsProcessingLayoutWidgetWrapper wrapper( &param, type );
4885 
4886     QgsProcessingContext context;
4887     context.setProject( &p );
4888     QgsProcessingParameterWidgetContext widgetContext;
4889     widgetContext.setProject( &p );
4890     wrapper.setWidgetContext( widgetContext );
4891     QWidget *w = wrapper.createWrappedWidget( context );
4892 
4893     QSignalSpy spy( &wrapper, &QgsProcessingLayoutWidgetWrapper::widgetValueHasChanged );
4894     wrapper.setWidgetValue( "l2", context );
4895     QCOMPARE( spy.count(), 1 );
4896     QCOMPARE( wrapper.widgetValue().toString(),  QStringLiteral( "l2" ) );
4897     if ( type != QgsProcessingGui::Modeler )
4898     {
4899       QCOMPARE( static_cast< QgsLayoutComboBox * >( wrapper.wrappedWidget() )->currentIndex(), 1 );
4900       QCOMPARE( static_cast< QgsLayoutComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "l2" ) );
4901     }
4902     else
4903     {
4904       QCOMPARE( static_cast< QComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "l2" ) );
4905     }
4906     wrapper.setWidgetValue( "l1", context );
4907     QCOMPARE( spy.count(), 2 );
4908     QCOMPARE( wrapper.widgetValue().toString(),  QStringLiteral( "l1" ) );
4909     if ( type != QgsProcessingGui::Modeler )
4910     {
4911       QCOMPARE( static_cast< QgsLayoutComboBox * >( wrapper.wrappedWidget() )->currentIndex(), 0 );
4912       QCOMPARE( static_cast< QgsLayoutComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "l1" ) );
4913     }
4914     else
4915     {
4916       QCOMPARE( static_cast< QComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "l1" ) );
4917     }
4918 
4919     QLabel *l = wrapper.createWrappedLabel();
4920     if ( wrapper.type() != QgsProcessingGui::Batch )
4921     {
4922       QVERIFY( l );
4923       QCOMPARE( l->text(), QStringLiteral( "layout" ) );
4924       QCOMPARE( l->toolTip(), param.toolTip() );
4925       delete l;
4926     }
4927     else
4928     {
4929       QVERIFY( !l );
4930     }
4931 
4932     // check signal
4933     if ( type != QgsProcessingGui::Modeler )
4934     {
4935       static_cast< QComboBox * >( wrapper.wrappedWidget() )->setCurrentIndex( 1 );
4936     }
4937     else
4938     {
4939       static_cast< QComboBox * >( wrapper.wrappedWidget() )->setCurrentText( QStringLiteral( "aaaa" ) );
4940     }
4941     QCOMPARE( spy.count(), 3 );
4942 
4943     delete w;
4944 
4945     // optional
4946 
4947     QgsProcessingParameterLayout param2( QStringLiteral( "layout" ), QStringLiteral( "layout" ), QVariant(), true );
4948 
4949     QgsProcessingLayoutWidgetWrapper wrapper2( &param2, type );
4950     wrapper2.setWidgetContext( widgetContext );
4951     w = wrapper2.createWrappedWidget( context );
4952 
4953     QSignalSpy spy2( &wrapper2, &QgsProcessingLayoutWidgetWrapper::widgetValueHasChanged );
4954     wrapper2.setWidgetValue( "l2", context );
4955     QCOMPARE( spy2.count(), 1 );
4956     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "l2" ) );
4957     if ( type != QgsProcessingGui::Modeler )
4958     {
4959       QCOMPARE( static_cast< QgsLayoutComboBox * >( wrapper2.wrappedWidget() )->currentIndex(), 2 );
4960       QCOMPARE( static_cast< QgsLayoutComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "l2" ) );
4961     }
4962     else
4963     {
4964       QCOMPARE( static_cast< QComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "l2" ) );
4965     }
4966     wrapper2.setWidgetValue( "l1", context );
4967     QCOMPARE( spy2.count(), 2 );
4968     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "l1" ) );
4969     if ( type != QgsProcessingGui::Modeler )
4970     {
4971       QCOMPARE( static_cast< QgsLayoutComboBox * >( wrapper2.wrappedWidget() )->currentIndex(), 1 );
4972       QCOMPARE( static_cast< QgsLayoutComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "l1" ) );
4973     }
4974     else
4975     {
4976       QCOMPARE( static_cast< QComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "l1" ) );
4977     }
4978     wrapper2.setWidgetValue( QVariant(), context );
4979     QCOMPARE( spy2.count(), 3 );
4980     QVERIFY( !wrapper2.widgetValue().isValid() );
4981     if ( type != QgsProcessingGui::Modeler )
4982     {
4983       QCOMPARE( static_cast< QgsLayoutComboBox * >( wrapper2.wrappedWidget() )->currentIndex(), 0 );
4984       QVERIFY( static_cast< QgsLayoutComboBox * >( wrapper2.wrappedWidget() )->currentText().isEmpty() );
4985     }
4986     else
4987     {
4988       QVERIFY( static_cast< QComboBox * >( wrapper2.wrappedWidget() )->currentText().isEmpty() );
4989     }
4990 
4991     // check signal
4992     if ( type != QgsProcessingGui::Modeler )
4993       static_cast< QComboBox * >( wrapper2.wrappedWidget() )->setCurrentIndex( 2 );
4994     else
4995       static_cast< QComboBox * >( wrapper2.wrappedWidget() )->setCurrentText( QStringLiteral( "aaa" ) );
4996     QCOMPARE( spy2.count(), 4 );
4997 
4998     delete w;
4999   };
5000 
5001   // standard wrapper
5002   testWrapper( QgsProcessingGui::Standard );
5003 
5004   // batch wrapper
5005   testWrapper( QgsProcessingGui::Batch );
5006 
5007   // modeler wrapper
5008   testWrapper( QgsProcessingGui::Modeler );
5009 
5010 }
5011 
testLayoutItemWrapper()5012 void TestProcessingGui::testLayoutItemWrapper()
5013 {
5014   QgsProject p;
5015   QgsPrintLayout *l1 = new QgsPrintLayout( &p );
5016   l1->setName( "l1" );
5017   p.layoutManager()->addLayout( l1 );
5018   QgsLayoutItemLabel *label1 = new QgsLayoutItemLabel( l1 );
5019   label1->setId( "a" );
5020   l1->addLayoutItem( label1 );
5021   QgsLayoutItemLabel *label2 = new QgsLayoutItemLabel( l1 );
5022   label2->setId( "b" );
5023   l1->addLayoutItem( label2 );
5024 
5025   auto testWrapper = [&p, l1, label1, label2]( QgsProcessingGui::WidgetType type )
5026   {
5027     // non optional
5028     QgsProcessingParameterLayoutItem param( QStringLiteral( "layout" ), QStringLiteral( "layout" ), false );
5029 
5030     QgsProcessingLayoutItemWidgetWrapper wrapper( &param, type );
5031 
5032     QgsProcessingContext context;
5033     context.setProject( &p );
5034     QgsProcessingParameterWidgetContext widgetContext;
5035     widgetContext.setProject( &p );
5036     wrapper.setWidgetContext( widgetContext );
5037     QWidget *w = wrapper.createWrappedWidget( context );
5038 
5039     wrapper.setLayout( l1 );
5040 
5041     QSignalSpy spy( &wrapper, &QgsProcessingLayoutItemWidgetWrapper::widgetValueHasChanged );
5042     wrapper.setWidgetValue( "b", context );
5043     QCOMPARE( spy.count(), 1 );
5044     if ( type != QgsProcessingGui::Modeler )
5045     {
5046       QCOMPARE( wrapper.widgetValue().toString(), label2->uuid() );
5047       QCOMPARE( static_cast< QgsLayoutItemComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "b" ) );
5048     }
5049     else
5050     {
5051       QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "b" ) );
5052       QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text(), QStringLiteral( "b" ) );
5053     }
5054     wrapper.setWidgetValue( "a", context );
5055     QCOMPARE( spy.count(), 2 );
5056     if ( type != QgsProcessingGui::Modeler )
5057     {
5058       QCOMPARE( wrapper.widgetValue().toString(), label1->uuid() );
5059       QCOMPARE( static_cast< QgsLayoutItemComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "a" ) );
5060     }
5061     else
5062     {
5063       QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "a" ) );
5064       QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text(), QStringLiteral( "a" ) );
5065     }
5066 
5067     QLabel *l = wrapper.createWrappedLabel();
5068     if ( wrapper.type() != QgsProcessingGui::Batch )
5069     {
5070       QVERIFY( l );
5071       QCOMPARE( l->text(), QStringLiteral( "layout" ) );
5072       QCOMPARE( l->toolTip(), param.toolTip() );
5073       delete l;
5074     }
5075     else
5076     {
5077       QVERIFY( !l );
5078     }
5079 
5080     // check signal
5081     if ( type != QgsProcessingGui::Modeler )
5082     {
5083       static_cast< QComboBox * >( wrapper.wrappedWidget() )->setCurrentIndex( 1 );
5084     }
5085     else
5086     {
5087       static_cast< QLineEdit * >( wrapper.wrappedWidget() )->setText( QStringLiteral( "aaaa" ) );
5088     }
5089     QCOMPARE( spy.count(), 3 );
5090 
5091     delete w;
5092 
5093     // optional
5094 
5095     QgsProcessingParameterLayoutItem param2( QStringLiteral( "layout" ), QStringLiteral( "layout" ), QVariant(), QString(), -1, true );
5096 
5097     QgsProcessingLayoutItemWidgetWrapper wrapper2( &param2, type );
5098     wrapper2.setWidgetContext( widgetContext );
5099     w = wrapper2.createWrappedWidget( context );
5100     wrapper2.setLayout( l1 );
5101 
5102     QSignalSpy spy2( &wrapper2, &QgsProcessingLayoutItemWidgetWrapper::widgetValueHasChanged );
5103     wrapper2.setWidgetValue( "b", context );
5104     QCOMPARE( spy2.count(), 1 );
5105     if ( type != QgsProcessingGui::Modeler )
5106     {
5107       QCOMPARE( wrapper2.widgetValue().toString(), label2->uuid() );
5108       QCOMPARE( static_cast< QgsLayoutItemComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "b" ) );
5109     }
5110     else
5111     {
5112       QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "b" ) );
5113       QCOMPARE( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text(), QStringLiteral( "b" ) );
5114     }
5115     wrapper2.setWidgetValue( "a", context );
5116     QCOMPARE( spy2.count(), 2 );
5117     if ( type != QgsProcessingGui::Modeler )
5118     {
5119       QCOMPARE( wrapper2.widgetValue().toString(), label1->uuid() );
5120       QCOMPARE( static_cast< QgsLayoutItemComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "a" ) );
5121     }
5122     else
5123     {
5124       QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "a" ) );
5125       QCOMPARE( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text(), QStringLiteral( "a" ) );
5126     }
5127     wrapper2.setWidgetValue( QVariant(), context );
5128     QCOMPARE( spy2.count(), 3 );
5129     QVERIFY( !wrapper2.widgetValue().isValid() );
5130     if ( type != QgsProcessingGui::Modeler )
5131     {
5132       QVERIFY( static_cast< QgsLayoutItemComboBox * >( wrapper2.wrappedWidget() )->currentText().isEmpty() );
5133     }
5134     else
5135     {
5136       QVERIFY( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().isEmpty() );
5137     }
5138 
5139     // check signal
5140     if ( type != QgsProcessingGui::Modeler )
5141       static_cast< QgsLayoutItemComboBox * >( wrapper2.wrappedWidget() )->setCurrentIndex( 1 );
5142     else
5143       static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->setText( QStringLiteral( "aaa" ) );
5144     QCOMPARE( spy2.count(), 4 );
5145 
5146     delete w;
5147   };
5148 
5149   // standard wrapper
5150   testWrapper( QgsProcessingGui::Standard );
5151 
5152   // batch wrapper
5153   testWrapper( QgsProcessingGui::Batch );
5154 
5155   // modeler wrapper
5156   testWrapper( QgsProcessingGui::Modeler );
5157 
5158 
5159   // config widget
5160   QgsProcessingParameterWidgetContext widgetContext;
5161   QgsProcessingContext context;
5162   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "layoutitem" ), context, widgetContext );
5163   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
5164   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5165   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
5166   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5167 
5168   // using a parameter definition as initial values
5169   QgsProcessingParameterLayoutItem itemParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QVariant(), QStringLiteral( "parent" ) );
5170   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "layoutitem" ), context, widgetContext, &itemParam );
5171   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5172   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5173   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5174   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
5175   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5176   QCOMPARE( static_cast< QgsProcessingParameterLayoutItem * >( def.get() )->parentLayoutParameterName(), QStringLiteral( "parent" ) );
5177   itemParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
5178   itemParam.setParentLayoutParameterName( QString() );
5179   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "layoutitem" ), context, widgetContext, &itemParam );
5180   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5181   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5182   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5183   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
5184   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
5185   QVERIFY( static_cast< QgsProcessingParameterLayoutItem * >( def.get() )->parentLayoutParameterName().isEmpty() );
5186 }
5187 
testPointPanel()5188 void TestProcessingGui::testPointPanel()
5189 {
5190   std::unique_ptr< QgsProcessingPointPanel > panel = std::make_unique< QgsProcessingPointPanel >( nullptr );
5191   QSignalSpy spy( panel.get(), &QgsProcessingPointPanel::changed );
5192 
5193   panel->setValue( QgsPointXY( 100, 150 ), QgsCoordinateReferenceSystem() );
5194   QCOMPARE( panel->value().toString(), QStringLiteral( "100.000000,150.000000" ) );
5195   QCOMPARE( spy.count(), 1 );
5196 
5197   panel->setValue( QgsPointXY( 200, 250 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ) );
5198   QCOMPARE( panel->value().toString(), QStringLiteral( "200.000000,250.000000 [EPSG:3111]" ) );
5199   QCOMPARE( spy.count(), 2 );
5200 
5201   panel->setValue( QgsPointXY( 123456.123456789, 654321.987654321 ), QgsCoordinateReferenceSystem() );
5202   QCOMPARE( panel->value().toString(), QStringLiteral( "123456.123457,654321.987654" ) );
5203   QCOMPARE( spy.count(), 3 );
5204 
5205   QVERIFY( !panel->mLineEdit->showClearButton() );
5206   panel->setAllowNull( true );
5207   QVERIFY( panel->mLineEdit->showClearButton() );
5208   panel->clear();
5209   QVERIFY( !panel->value().isValid() );
5210   QCOMPARE( spy.count(), 4 );
5211 
5212   QgsMapCanvas canvas;
5213   canvas.setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ) );
5214   panel->setMapCanvas( &canvas );
5215   panel->updatePoint( QgsPointXY( 1.5, -3.5 ) );
5216   QCOMPARE( panel->value().toString(), QStringLiteral( "1.500000,-3.500000 [EPSG:28356]" ) );
5217   QCOMPARE( spy.count(), 5 );
5218 
5219   panel.reset();
5220 }
5221 
5222 
testPointWrapper()5223 void TestProcessingGui::testPointWrapper()
5224 {
5225   auto testWrapper = []( QgsProcessingGui::WidgetType type )
5226   {
5227     // non optional
5228     QgsProcessingParameterPoint param( QStringLiteral( "point" ), QStringLiteral( "point" ), false );
5229 
5230     QgsProcessingPointWidgetWrapper wrapper( &param, type );
5231 
5232     QgsProcessingContext context;
5233     QWidget *w = wrapper.createWrappedWidget( context );
5234 
5235     QSignalSpy spy( &wrapper, &QgsProcessingLayoutItemWidgetWrapper::widgetValueHasChanged );
5236     wrapper.setWidgetValue( "1,2", context );
5237     QCOMPARE( spy.count(), 1 );
5238     if ( type != QgsProcessingGui::Modeler )
5239     {
5240       QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "1.000000,2.000000" ) );
5241       QCOMPARE( static_cast< QgsProcessingPointPanel * >( wrapper.wrappedWidget() )->mLineEdit->text(), QStringLiteral( "1.000000,2.000000" ) );
5242     }
5243     else
5244     {
5245       QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "1,2" ) );
5246       QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text(), QStringLiteral( "1,2" ) );
5247     }
5248     wrapper.setWidgetValue( "1,2 [EPSG:3111]", context );
5249     QCOMPARE( spy.count(), 2 );
5250     if ( type != QgsProcessingGui::Modeler )
5251     {
5252       QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "1.000000,2.000000 [EPSG:3111]" ) );
5253       QCOMPARE( static_cast< QgsProcessingPointPanel * >( wrapper.wrappedWidget() )->mLineEdit->text(), QStringLiteral( "1.000000,2.000000 [EPSG:3111]" ) );
5254     }
5255     else
5256     {
5257       QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "1,2 [EPSG:3111]" ) );
5258       QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text(), QStringLiteral( "1,2 [EPSG:3111]" ) );
5259     }
5260 
5261     // check signal
5262     if ( type != QgsProcessingGui::Modeler )
5263     {
5264       static_cast< QgsProcessingPointPanel * >( wrapper.wrappedWidget() )->mLineEdit->setText( QStringLiteral( "b" ) );
5265     }
5266     else
5267     {
5268       static_cast< QLineEdit * >( wrapper.wrappedWidget() )->setText( QStringLiteral( "aaaa" ) );
5269     }
5270     QCOMPARE( spy.count(), 3 );
5271 
5272 
5273     QLabel *l = wrapper.createWrappedLabel();
5274     if ( wrapper.type() != QgsProcessingGui::Batch )
5275     {
5276       QVERIFY( l );
5277       QCOMPARE( l->text(), QStringLiteral( "point" ) );
5278       QCOMPARE( l->toolTip(), param.toolTip() );
5279       delete l;
5280     }
5281     else
5282     {
5283       QVERIFY( !l );
5284     }
5285 
5286     delete w;
5287 
5288     // optional
5289 
5290     QgsProcessingParameterPoint param2( QStringLiteral( "point" ), QStringLiteral( "point" ), QVariant(), true );
5291 
5292     QgsProcessingPointWidgetWrapper wrapper2( &param2, type );
5293     w = wrapper2.createWrappedWidget( context );
5294 
5295     QSignalSpy spy2( &wrapper2, &QgsProcessingLayoutItemWidgetWrapper::widgetValueHasChanged );
5296     wrapper2.setWidgetValue( "1,2", context );
5297     QCOMPARE( spy2.count(), 1 );
5298     if ( type != QgsProcessingGui::Modeler )
5299     {
5300       QCOMPARE( static_cast< QgsProcessingPointPanel * >( wrapper2.wrappedWidget() )->mLineEdit->text(), QStringLiteral( "1.000000,2.000000" ) );
5301       QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "1.000000,2.000000" ) );
5302     }
5303     else
5304     {
5305       QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "1,2" ) );
5306       QCOMPARE( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text(), QStringLiteral( "1,2" ) );
5307     }
5308 
5309     wrapper2.setWidgetValue( "1,2 [EPSG:3111]", context );
5310     QCOMPARE( spy2.count(), 2 );
5311     if ( type != QgsProcessingGui::Modeler )
5312     {
5313       QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "1.000000,2.000000 [EPSG:3111]" ) );
5314       QCOMPARE( static_cast< QgsProcessingPointPanel * >( wrapper2.wrappedWidget() )->mLineEdit->text(), QStringLiteral( "1.000000,2.000000 [EPSG:3111]" ) );
5315     }
5316     else
5317     {
5318       QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "1,2 [EPSG:3111]" ) );
5319       QCOMPARE( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text(), QStringLiteral( "1,2 [EPSG:3111]" ) );
5320     }
5321     wrapper2.setWidgetValue( QVariant(), context );
5322     QCOMPARE( spy2.count(), 3 );
5323     QVERIFY( !wrapper2.widgetValue().isValid() );
5324     if ( type == QgsProcessingGui::Modeler )
5325     {
5326       QVERIFY( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().isEmpty() );
5327     }
5328     else
5329     {
5330       QVERIFY( static_cast< QgsProcessingPointPanel * >( wrapper2.wrappedWidget() )->mLineEdit->text().isEmpty() );
5331     }
5332     wrapper2.setWidgetValue( "1,3", context );
5333     QCOMPARE( spy2.count(), 4 );
5334     wrapper2.setWidgetValue( "", context );
5335     QCOMPARE( spy2.count(), 5 );
5336     QVERIFY( !wrapper2.widgetValue().isValid() );
5337     if ( type == QgsProcessingGui::Modeler )
5338     {
5339       QVERIFY( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().isEmpty() );
5340     }
5341     else
5342     {
5343       QVERIFY( static_cast< QgsProcessingPointPanel * >( wrapper2.wrappedWidget() )->mLineEdit->text().isEmpty() );
5344     }
5345 
5346     // check signals
5347     wrapper2.setWidgetValue( "1,3", context );
5348     QCOMPARE( spy2.count(), 6 );
5349     if ( type == QgsProcessingGui::Modeler )
5350     {
5351       static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->clear();
5352     }
5353     else
5354     {
5355       static_cast< QgsProcessingPointPanel * >( wrapper2.wrappedWidget() )->mLineEdit->clear();
5356     }
5357     QCOMPARE( spy2.count(), 7 );
5358 
5359     delete w;
5360   };
5361 
5362   // standard wrapper
5363   testWrapper( QgsProcessingGui::Standard );
5364 
5365   // batch wrapper
5366   testWrapper( QgsProcessingGui::Batch );
5367 
5368   // modeler wrapper
5369   testWrapper( QgsProcessingGui::Modeler );
5370 
5371   // config widget
5372   QgsProcessingContext context;
5373   QgsProcessingParameterWidgetContext widgetContext;
5374   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "point" ), context, widgetContext );
5375   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
5376   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5377   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
5378   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5379 
5380   // using a parameter definition as initial values
5381   QgsProcessingParameterPoint pointParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "1,2" ) );
5382   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "point" ), context, widgetContext, &pointParam );
5383   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5384   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5385   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5386   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
5387   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5388   QCOMPARE( static_cast< QgsProcessingParameterPoint * >( def.get() )->defaultValue().toString(), QStringLiteral( "1.000000,2.000000" ) );
5389   pointParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
5390   pointParam.setDefaultValue( QStringLiteral( "4,7" ) );
5391   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "point" ), context, widgetContext, &pointParam );
5392   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5393   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5394   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5395   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
5396   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
5397   QCOMPARE( static_cast< QgsProcessingParameterPoint * >( def.get() )->defaultValue().toString(), QStringLiteral( "4.000000,7.000000" ) );
5398 
5399 }
5400 
5401 
testGeometryWrapper()5402 void TestProcessingGui::testGeometryWrapper()
5403 {
5404   auto testWrapper = []( QgsProcessingGui::WidgetType type )
5405   {
5406     // non optional
5407     QgsProcessingParameterGeometry param( QStringLiteral( "geometry" ), QStringLiteral( "geometry" ), false );
5408 
5409     QgsProcessingGeometryWidgetWrapper wrapper( &param, type );
5410 
5411     QgsProcessingContext context;
5412     QWidget *w = wrapper.createWrappedWidget( context );
5413 
5414     QSignalSpy spy( &wrapper, &QgsProcessingLayoutItemWidgetWrapper::widgetValueHasChanged );
5415     wrapper.setWidgetValue( QStringLiteral( "POINT (1 2)" ), context );
5416     QCOMPARE( spy.count(), 1 );
5417     QCOMPARE( wrapper.widgetValue().toString().toLower(), QStringLiteral( "point (1 2)" ) );
5418     QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text().toLower(), QStringLiteral( "point (1 2)" ).toLower() );
5419     wrapper.setWidgetValue( QString(), context );
5420     QCOMPARE( spy.count(), 2 );
5421     QVERIFY( wrapper.widgetValue().toString().isEmpty() );
5422     QVERIFY( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text().isEmpty() );
5423 
5424     QLabel *l = wrapper.createWrappedLabel();
5425     if ( wrapper.type() != QgsProcessingGui::Batch )
5426     {
5427       QVERIFY( l );
5428       QCOMPARE( l->text(), QStringLiteral( "geometry" ) );
5429       QCOMPARE( l->toolTip(), param.toolTip() );
5430       delete l;
5431     }
5432     else
5433     {
5434       QVERIFY( !l );
5435     }
5436 
5437     // check signal
5438     static_cast< QLineEdit * >( wrapper.wrappedWidget() )->setText( QStringLiteral( "b" ) );
5439     QCOMPARE( spy.count(), 3 );
5440     static_cast< QLineEdit * >( wrapper.wrappedWidget() )->clear();
5441     QCOMPARE( spy.count(), 4 );
5442 
5443     delete w;
5444 
5445     // optional
5446 
5447     QgsProcessingParameterGeometry param2( QStringLiteral( "geometry" ), QStringLiteral( "geometry" ), QVariant(), true );
5448 
5449     QgsProcessingGeometryWidgetWrapper wrapper2( &param2, type );
5450 
5451     w = wrapper2.createWrappedWidget( context );
5452 
5453     QSignalSpy spy2( &wrapper2, &QgsProcessingLayoutItemWidgetWrapper::widgetValueHasChanged );
5454     wrapper2.setWidgetValue( "POINT (1 2)", context );
5455     QCOMPARE( spy2.count(), 1 );
5456     QCOMPARE( wrapper2.widgetValue().toString().toLower(), QStringLiteral( "point (1 2)" ) );
5457     QCOMPARE( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().toLower(), QStringLiteral( "point (1 2)" ) );
5458 
5459     wrapper2.setWidgetValue( QVariant(), context );
5460     QCOMPARE( spy2.count(), 2 );
5461     QVERIFY( !wrapper2.widgetValue().isValid() );
5462     QVERIFY( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().isEmpty() );
5463 
5464     wrapper2.setWidgetValue( "POINT (1 3)", context );
5465     QCOMPARE( spy2.count(), 3 );
5466     wrapper2.setWidgetValue( "", context );
5467     QCOMPARE( spy2.count(), 4 );
5468     QVERIFY( !wrapper2.widgetValue().isValid() );
5469     QVERIFY( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().isEmpty() );
5470 
5471     delete w;
5472   };
5473 
5474   // standard wrapper
5475   testWrapper( QgsProcessingGui::Standard );
5476 
5477 
5478   // batch wrapper
5479   testWrapper( QgsProcessingGui::Batch );
5480 
5481   // modeler wrapper
5482   testWrapper( QgsProcessingGui::Modeler );
5483 
5484 
5485   // config widget
5486   QgsProcessingContext context;
5487   QgsProcessingParameterWidgetContext widgetContext;
5488   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "geometry" ), context, widgetContext );
5489   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
5490   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5491   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
5492   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5493 
5494   // using a parameter definition as initial values
5495   QgsProcessingParameterGeometry geometryParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "POINT (1 2)" ) );
5496 
5497   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "geometry" ), context, widgetContext, &geometryParam );
5498 
5499   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5500   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5501   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5502   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
5503   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5504   QCOMPARE( static_cast< QgsProcessingParameterGeometry * >( def.get() )->defaultValue().toString().toLower(), QStringLiteral( "point (1 2)" ) );
5505   geometryParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
5506   geometryParam.setDefaultValue( QStringLiteral( "POINT (4 7)" ) );
5507   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "geometry" ), context, widgetContext, &geometryParam );
5508   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5509   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5510   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5511   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
5512   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
5513   QCOMPARE( static_cast< QgsProcessingParameterGeometry * >( def.get() )->defaultValue().toString().toLower(), QStringLiteral( "point (4 7)" ) );
5514 
5515 }
5516 
5517 
5518 
5519 
testExtentWrapper()5520 void TestProcessingGui::testExtentWrapper()
5521 {
5522   auto testWrapper = []( QgsProcessingGui::WidgetType type )
5523   {
5524     // non optional
5525     QgsProcessingParameterExtent param( QStringLiteral( "extent" ), QStringLiteral( "extent" ), false );
5526 
5527     QgsProcessingExtentWidgetWrapper wrapper( &param, type );
5528 
5529     QgsProcessingContext context;
5530     QWidget *w = wrapper.createWrappedWidget( context );
5531 
5532     QSignalSpy spy( &wrapper, &QgsProcessingExtentWidgetWrapper::widgetValueHasChanged );
5533     wrapper.setWidgetValue( "1,2,3,4", context );
5534     QCOMPARE( spy.count(), 1 );
5535     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "1.000000000,2.000000000,3.000000000,4.000000000" ) );
5536     QCOMPARE( static_cast< QgsExtentWidget * >( wrapper.wrappedWidget() )->outputExtent(), QgsRectangle( 1, 3, 2, 4 ) );
5537 
5538     wrapper.setWidgetValue( "1,2,3,4 [EPSG:3111]", context );
5539     QCOMPARE( spy.count(), 2 );
5540     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "1.000000000,2.000000000,3.000000000,4.000000000 [EPSG:3111]" ) );
5541     QCOMPARE( static_cast< QgsExtentWidget * >( wrapper.wrappedWidget() )->outputExtent(), QgsRectangle( 1, 3, 2, 4 ) );
5542     QCOMPARE( static_cast< QgsExtentWidget * >( wrapper.wrappedWidget() )->outputCrs().authid(), QStringLiteral( "EPSG:3111" ) );
5543 
5544     // check signal
5545     static_cast< QgsExtentWidget * >( wrapper.wrappedWidget() )->setOutputExtentFromUser( QgsRectangle( 11, 22, 33, 44 ), QgsCoordinateReferenceSystem() );
5546     QCOMPARE( spy.count(), 3 );
5547 
5548     QLabel *l = wrapper.createWrappedLabel();
5549     if ( wrapper.type() != QgsProcessingGui::Batch )
5550     {
5551       QVERIFY( l );
5552       QCOMPARE( l->text(), QStringLiteral( "extent" ) );
5553       QCOMPARE( l->toolTip(), param.toolTip() );
5554       delete l;
5555     }
5556     else
5557     {
5558       QVERIFY( !l );
5559     }
5560 
5561     delete w;
5562 
5563     // optional
5564 
5565     QgsProcessingParameterExtent param2( QStringLiteral( "extent" ), QStringLiteral( "extent" ), QVariant(), true );
5566 
5567     QgsProcessingExtentWidgetWrapper wrapper2( &param2, type );
5568     w = wrapper2.createWrappedWidget( context );
5569 
5570     QSignalSpy spy2( &wrapper2, &QgsProcessingExtentWidgetWrapper::widgetValueHasChanged );
5571     wrapper2.setWidgetValue( "1,2,3,4", context );
5572     QCOMPARE( spy2.count(), 1 );
5573     QCOMPARE( static_cast< QgsExtentWidget * >( wrapper2.wrappedWidget() )->outputExtent(), QgsRectangle( 1, 3, 2, 4 ) );
5574     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "1.000000000,2.000000000,3.000000000,4.000000000" ) );
5575 
5576     wrapper2.setWidgetValue( "1,2,3,4 [EPSG:3111]", context );
5577     QCOMPARE( spy2.count(), 2 );
5578     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "1.000000000,2.000000000,3.000000000,4.000000000 [EPSG:3111]" ) );
5579     QCOMPARE( static_cast< QgsExtentWidget * >( wrapper2.wrappedWidget() )->outputExtent(), QgsRectangle( 1, 3, 2, 4 ) );
5580     QCOMPARE( static_cast< QgsExtentWidget * >( wrapper2.wrappedWidget() )->outputCrs().authid(), QStringLiteral( "EPSG:3111" ) );
5581     wrapper2.setWidgetValue( QVariant(), context );
5582     QCOMPARE( spy2.count(), 3 );
5583     QVERIFY( !wrapper2.widgetValue().isValid() );
5584     QVERIFY( !static_cast< QgsExtentWidget * >( wrapper2.wrappedWidget() )->isValid() );
5585 
5586     wrapper2.setWidgetValue( "1,3,4,7", context );
5587     QCOMPARE( spy2.count(), 4 );
5588     wrapper2.setWidgetValue( "", context );
5589     QCOMPARE( spy2.count(), 5 );
5590     QVERIFY( !wrapper2.widgetValue().isValid() );
5591     QVERIFY( !static_cast< QgsExtentWidget * >( wrapper2.wrappedWidget() )->isValid() );
5592 
5593     // check signals
5594     wrapper2.setWidgetValue( "1,3,9,8", context );
5595     QCOMPARE( spy2.count(), 6 );
5596     static_cast< QgsExtentWidget * >( wrapper2.wrappedWidget() )->clear();
5597     QCOMPARE( spy2.count(), 7 );
5598 
5599     delete w;
5600 
5601   };
5602 
5603   // standard wrapper
5604   testWrapper( QgsProcessingGui::Standard );
5605 
5606   // batch wrapper
5607   testWrapper( QgsProcessingGui::Batch );
5608 
5609   // modeler wrapper
5610   testWrapper( QgsProcessingGui::Modeler );
5611 
5612   // config widget
5613   QgsProcessingContext context;
5614   QgsProcessingParameterWidgetContext widgetContext;
5615   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "extent" ), context, widgetContext );
5616   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
5617   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5618   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
5619   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5620 
5621   // using a parameter definition as initial values
5622   QgsProcessingParameterExtent extentParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "1,2,3,4" ) );
5623   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "extent" ), context, widgetContext, &extentParam );
5624   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5625   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5626   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5627   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
5628   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5629   QCOMPARE( static_cast< QgsProcessingParameterExtent * >( def.get() )->defaultValue().toString(), QStringLiteral( "1.000000000,2.000000000,3.000000000,4.000000000" ) );
5630   extentParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
5631   extentParam.setDefaultValue( QStringLiteral( "4,7,8,9" ) );
5632   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "extent" ), context, widgetContext, &extentParam );
5633   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5634   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5635   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5636   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
5637   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
5638   QCOMPARE( static_cast< QgsProcessingParameterExtent * >( def.get() )->defaultValue().toString(), QStringLiteral( "4.000000000,7.000000000,8.000000000,9.000000000" ) );
5639 }
5640 
testColorWrapper()5641 void TestProcessingGui::testColorWrapper()
5642 {
5643   auto testWrapper = []( QgsProcessingGui::WidgetType type )
5644   {
5645     QgsProcessingParameterColor param( QStringLiteral( "color" ), QStringLiteral( "color" ) );
5646 
5647     QgsProcessingColorWidgetWrapper wrapper( &param, type );
5648 
5649     QgsProcessingContext context;
5650     QWidget *w = wrapper.createWrappedWidget( context );
5651 
5652     QSignalSpy spy( &wrapper, &QgsProcessingColorWidgetWrapper::widgetValueHasChanged );
5653     wrapper.setWidgetValue( QColor( 255, 0, 0 ), context );
5654     QCOMPARE( spy.count(), 1 );
5655     QCOMPARE( wrapper.widgetValue().value< QColor >().name(),  QStringLiteral( "#ff0000" ) );
5656     QCOMPARE( static_cast< QgsColorButton * >( wrapper.wrappedWidget() )->color(),  QColor( 255, 0, 0 ) );
5657     QVERIFY( !static_cast< QgsColorButton * >( wrapper.wrappedWidget() )->showNull() );
5658     QVERIFY( static_cast< QgsColorButton * >( wrapper.wrappedWidget() )->allowOpacity() );
5659     wrapper.setWidgetValue( QColor(), context );
5660     QCOMPARE( spy.count(), 2 );
5661     QVERIFY( !wrapper.widgetValue().value< QColor >().isValid() );
5662     QVERIFY( !static_cast< QgsColorButton * >( wrapper.wrappedWidget() )->color().isValid() );
5663 
5664     QLabel *l = wrapper.createWrappedLabel();
5665     if ( wrapper.type() != QgsProcessingGui::Batch )
5666     {
5667       QVERIFY( l );
5668       QCOMPARE( l->text(), QStringLiteral( "color" ) );
5669       QCOMPARE( l->toolTip(), param.toolTip() );
5670       delete l;
5671     }
5672     else
5673     {
5674       QVERIFY( !l );
5675     }
5676 
5677     // check signal
5678     static_cast< QgsColorButton * >( wrapper.wrappedWidget() )->setColor( QColor( 0, 255, 0 ) );
5679     QCOMPARE( spy.count(), 3 );
5680 
5681     // with opacity
5682     wrapper.setWidgetValue( QColor( 255, 0, 0, 100 ), context );
5683     QCOMPARE( wrapper.widgetValue().value< QColor >(), QColor( 255, 0, 0, 100 ) );
5684 
5685     delete w;
5686 
5687     // with null
5688     QgsProcessingParameterColor param2( QStringLiteral( "c2" ), QStringLiteral( "c2" ), QColor( 10, 20, 30 ), true, true );
5689 
5690     QgsProcessingColorWidgetWrapper wrapper2( &param2, type );
5691     w = wrapper2.createWrappedWidget( context );
5692     QVERIFY( static_cast< QgsColorButton * >( wrapper2.wrappedWidget() )->showNull() );
5693     QCOMPARE( static_cast< QgsColorButton * >( wrapper2.wrappedWidget() )->color().name(),  QStringLiteral( "#0a141e" ) );
5694     wrapper2.setWidgetValue( QVariant(), context );
5695     QVERIFY( !wrapper2.widgetValue().isValid() );
5696     wrapper2.setWidgetValue( QColor( 255, 0, 255 ), context );
5697     QCOMPARE( wrapper2.widgetValue().value< QColor >().name(), QStringLiteral( "#ff00ff" ) );
5698 
5699     // no opacity
5700     QgsProcessingParameterColor param3( QStringLiteral( "c2" ), QStringLiteral( "c2" ), QColor( 10, 20, 30 ), false, true );
5701 
5702     QgsProcessingColorWidgetWrapper wrapper3( &param3, type );
5703     w = wrapper3.createWrappedWidget( context );
5704     wrapper3.setWidgetValue( QColor( 255, 0, 0, 100 ), context );
5705     QCOMPARE( wrapper3.widgetValue().value< QColor >(), QColor( 255, 0, 0 ) );
5706   };
5707 
5708   // standard wrapper
5709   testWrapper( QgsProcessingGui::Standard );
5710 
5711   // batch wrapper
5712   testWrapper( QgsProcessingGui::Batch );
5713 
5714   // modeler wrapper
5715   testWrapper( QgsProcessingGui::Modeler );
5716 
5717   // config widget
5718   QgsProcessingParameterWidgetContext widgetContext;
5719   QgsProcessingContext context;
5720   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "color" ), context, widgetContext );
5721   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
5722   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5723   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
5724   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5725   QVERIFY( static_cast< QgsProcessingParameterColor * >( def.get() )->opacityEnabled() ); // should default to true
5726 
5727   // using a parameter definition as initial values
5728   QgsProcessingParameterColor colorParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QColor( 255, 0, 0, 100 ), true );
5729   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "color" ), context, widgetContext, &colorParam );
5730   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5731   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5732   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5733   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
5734   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5735   QCOMPARE( static_cast< QgsProcessingParameterColor * >( def.get() )->defaultValue().value< QColor >(), QColor( 255, 0, 0, 100 ) );
5736   QVERIFY( static_cast< QgsProcessingParameterColor * >( def.get() )->opacityEnabled() );
5737   colorParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
5738   colorParam.setOpacityEnabled( false );
5739   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "color" ), context, widgetContext, &colorParam );
5740   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5741   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5742   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5743   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
5744   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
5745   QCOMPARE( static_cast< QgsProcessingParameterColor * >( def.get() )->defaultValue().value< QColor >(), QColor( 255, 0, 0 ) ); // (no opacity!)
5746   QVERIFY( !static_cast< QgsProcessingParameterColor * >( def.get() )->opacityEnabled() );
5747 }
5748 
testCoordinateOperationWrapper()5749 void TestProcessingGui::testCoordinateOperationWrapper()
5750 {
5751   auto testWrapper = []( QgsProcessingGui::WidgetType type )
5752   {
5753     QgsProcessingParameterCoordinateOperation param( QStringLiteral( "op" ), QStringLiteral( "op" ) );
5754 
5755     QgsProcessingCoordinateOperationWidgetWrapper wrapper( &param, type );
5756 
5757     QgsProcessingContext context;
5758     QWidget *w = wrapper.createWrappedWidget( context );
5759     wrapper.setSourceCrsParameterValue( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:26745" ) ) );
5760     wrapper.setDestinationCrsParameterValue( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ) );
5761 
5762     QSignalSpy spy( &wrapper, &QgsProcessingCoordinateOperationWidgetWrapper::widgetValueHasChanged );
5763     wrapper.setWidgetValue( QStringLiteral( "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft +xy_out=m +step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 +lat_1=35.4666666666667 +lat_2=34.0333333333333 +x_0=609601.219202438 +y_0=0 +ellps=clrk66 +step +proj=push +v_3 +step +proj=cart +ellps=clrk66 +step +proj=helmert +x=-8 +y=160 +z=176 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84" ), context );
5764     QCOMPARE( spy.count(), 1 );
5765     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft +xy_out=m +step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 +lat_1=35.4666666666667 +lat_2=34.0333333333333 +x_0=609601.219202438 +y_0=0 +ellps=clrk66 +step +proj=push +v_3 +step +proj=cart +ellps=clrk66 +step +proj=helmert +x=-8 +y=160 +z=176 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84" ) );
5766     switch ( type )
5767     {
5768       case QgsProcessingGui::Standard:
5769       {
5770         QCOMPARE( static_cast< QgsCoordinateOperationWidget * >( wrapper.wrappedWidget() )->selectedOperation().proj, QStringLiteral( "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft +xy_out=m +step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 +lat_1=35.4666666666667 +lat_2=34.0333333333333 +x_0=609601.219202438 +y_0=0 +ellps=clrk66 +step +proj=push +v_3 +step +proj=cart +ellps=clrk66 +step +proj=helmert +x=-8 +y=160 +z=176 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84" ) );
5771         wrapper.setWidgetValue( QStringLiteral( "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft +xy_out=m +step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 +lat_1=35.4666666666667 +lat_2=34.0333333333333 +x_0=609601.219202438 +y_0=0 +ellps=clrk66 +step +proj=push +v_3 +step +proj=cart +ellps=clrk66 +step +proj=helmert +x=-8 +y=159 +z=175 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84" ), context );
5772         QCOMPARE( spy.count(), 2 );
5773         QCOMPARE( static_cast< QgsCoordinateOperationWidget * >( wrapper.wrappedWidget() )->selectedOperation().proj, QStringLiteral( "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft +xy_out=m +step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 +lat_1=35.4666666666667 +lat_2=34.0333333333333 +x_0=609601.219202438 +y_0=0 +ellps=clrk66 +step +proj=push +v_3 +step +proj=cart +ellps=clrk66 +step +proj=helmert +x=-8 +y=159 +z=175 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84" ) );
5774 
5775         // check signal
5776         QgsCoordinateOperationWidget::OperationDetails deets;
5777         deets.proj = QStringLiteral( "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft +xy_out=m +step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 +lat_1=35.4666666666667 +lat_2=34.0333333333333 +x_0=609601.219202438 +y_0=0 +ellps=clrk66 +step +proj=push +v_3 +step +proj=cart +ellps=clrk66 +step +proj=helmert +x=-8 +y=160 +z=176 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84" );
5778         static_cast< QgsCoordinateOperationWidget * >( wrapper.wrappedWidget() )->setSelectedOperation( deets );
5779         QCOMPARE( spy.count(), 3 );
5780         break;
5781       }
5782 
5783       case QgsProcessingGui::Modeler:
5784       case QgsProcessingGui::Batch:
5785       {
5786         QCOMPARE( wrapper.mLineEdit->text(), QStringLiteral( "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft +xy_out=m +step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 +lat_1=35.4666666666667 +lat_2=34.0333333333333 +x_0=609601.219202438 +y_0=0 +ellps=clrk66 +step +proj=push +v_3 +step +proj=cart +ellps=clrk66 +step +proj=helmert +x=-8 +y=160 +z=176 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84" ) );
5787         wrapper.setWidgetValue( QStringLiteral( "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft +xy_out=m +step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 +lat_1=35.4666666666667 +lat_2=34.0333333333333 +x_0=609601.219202438 +y_0=0 +ellps=clrk66 +step +proj=push +v_3 +step +proj=cart +ellps=clrk66 +step +proj=helmert +x=-8 +y=159 +z=175 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84" ), context );
5788         QCOMPARE( spy.count(), 2 );
5789         QCOMPARE( wrapper.mLineEdit->text(), QStringLiteral( "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft +xy_out=m +step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 +lat_1=35.4666666666667 +lat_2=34.0333333333333 +x_0=609601.219202438 +y_0=0 +ellps=clrk66 +step +proj=push +v_3 +step +proj=cart +ellps=clrk66 +step +proj=helmert +x=-8 +y=159 +z=175 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84" ) );
5790 
5791         // check signal
5792         wrapper.mLineEdit->setText( QStringLiteral( "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft +xy_out=m +step +inv +proj=lcc +lat_0=33.5 +lon_0=-118 +lat_1=35.4666666666667 +lat_2=34.0333333333333 +x_0=609601.219202438 +y_0=0 +ellps=clrk66 +step +proj=push +v_3 +step +proj=cart +ellps=clrk66 +step +proj=helmert +x=-8 +y=160 +z=176 +step +inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84" ) );
5793         QCOMPARE( spy.count(), 3 );
5794         break;
5795       }
5796     }
5797 
5798     QLabel *l = wrapper.createWrappedLabel();
5799     if ( wrapper.type() != QgsProcessingGui::Batch )
5800     {
5801       QVERIFY( l );
5802       QCOMPARE( l->text(), QStringLiteral( "op" ) );
5803       QCOMPARE( l->toolTip(), param.toolTip() );
5804       delete l;
5805     }
5806     else
5807     {
5808       QVERIFY( !l );
5809     }
5810 
5811     delete w;
5812   };
5813 
5814   // standard wrapper
5815   testWrapper( QgsProcessingGui::Standard );
5816 
5817   // batch wrapper
5818   testWrapper( QgsProcessingGui::Batch );
5819 
5820   // modeler wrapper
5821   testWrapper( QgsProcessingGui::Modeler );
5822 
5823   // config widget
5824   QgsProcessingParameterWidgetContext widgetContext;
5825   QgsProcessingContext context;
5826   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "coordinateoperation" ), context, widgetContext );
5827   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
5828   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5829   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
5830   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5831 
5832   QVERIFY( !static_cast< QgsProcessingParameterCoordinateOperation * >( def.get() )->sourceCrs().isValid() ); // should default to not set
5833   QVERIFY( !static_cast< QgsProcessingParameterCoordinateOperation * >( def.get() )->destinationCrs().isValid() ); // should default to not set
5834   QVERIFY( static_cast< QgsProcessingParameterCoordinateOperation * >( def.get() )->sourceCrsParameterName().isEmpty() ); // should default to not set
5835   QVERIFY( static_cast< QgsProcessingParameterCoordinateOperation * >( def.get() )->destinationCrsParameterName().isEmpty() ); // should default to not set
5836 
5837   // using a parameter definition as initial values
5838   QgsProcessingParameterCoordinateOperation coordParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "+proj" ), QStringLiteral( "a" ), QStringLiteral( "b" ), QStringLiteral( "EPSG:26745" ), QStringLiteral( "EPSG:4326" ), false );
5839   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "coordinateoperation" ), context, widgetContext, &coordParam );
5840   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5841   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5842   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5843   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
5844   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
5845   QCOMPARE( static_cast< QgsProcessingParameterCoordinateOperation * >( def.get() )->defaultValue().toString(), QStringLiteral( "+proj" ) );
5846   QCOMPARE( static_cast< QgsProcessingParameterCoordinateOperation * >( def.get() )->sourceCrsParameterName(), QStringLiteral( "a" ) );
5847   QCOMPARE( static_cast< QgsProcessingParameterCoordinateOperation * >( def.get() )->destinationCrsParameterName(), QStringLiteral( "b" ) );
5848   QCOMPARE( static_cast< QgsProcessingParameterCoordinateOperation * >( def.get() )->sourceCrs().value< QgsCoordinateReferenceSystem >( ).authid(), QStringLiteral( "EPSG:26745" ) );
5849   QCOMPARE( static_cast< QgsProcessingParameterCoordinateOperation * >( def.get() )->destinationCrs().value< QgsCoordinateReferenceSystem >( ).authid(), QStringLiteral( "EPSG:4326" ) );
5850   coordParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
5851   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "coordinateoperation" ), context, widgetContext, &coordParam );
5852   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
5853   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
5854   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
5855   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
5856   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
5857 }
5858 
mapLayerComboBox()5859 void TestProcessingGui::mapLayerComboBox()
5860 {
5861   QgsProject::instance()->removeAllMapLayers();
5862   QgsProcessingContext context;
5863   context.setProject( QgsProject::instance() );
5864 
5865   // feature source param
5866   std::unique_ptr< QgsProcessingParameterDefinition > param( new QgsProcessingParameterFeatureSource( QStringLiteral( "param" ), QString() ) );
5867   std::unique_ptr< QgsProcessingMapLayerComboBox> combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
5868 
5869   QSignalSpy spy( combo.get(), &QgsProcessingMapLayerComboBox::valueChanged );
5870   QVERIFY( !combo->value().isValid() );
5871   combo->setValue( QStringLiteral( "file path" ), context );
5872   QCOMPARE( spy.count(), 1 );
5873   QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
5874   QVERIFY( !combo->currentLayer() );
5875   QCOMPARE( spy.count(), 1 );
5876   combo->setValue( QVariant(), context ); // not possible, it's not an optional param
5877   QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
5878   QVERIFY( !combo->currentLayer() );
5879   QCOMPARE( spy.count(), 1 );
5880   combo->setValue( QStringLiteral( "file path 2" ), context );
5881   QCOMPARE( combo->value().toString(), QStringLiteral( "file path 2" ) );
5882   QVERIFY( !combo->currentLayer() );
5883   QCOMPARE( spy.count(), 2 );
5884   combo->setValue( QStringLiteral( "file path" ), context );
5885   QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
5886   QVERIFY( !combo->currentLayer() );
5887   QCOMPARE( spy.count(), 3 );
5888   combo->setLayer( nullptr ); // not possible, not optional
5889   QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
5890   QVERIFY( !combo->currentLayer() );
5891   QCOMPARE( spy.count(), 3 );
5892 
5893   // project layers
5894   QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
5895   QgsFeature f;
5896   vl->dataProvider()->addFeature( f );
5897   QgsProject::instance()->addMapLayer( vl );
5898   QVERIFY( vl->isValid() );
5899   QgsVectorLayer *vl2 = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "l2" ), QStringLiteral( "memory" ) );
5900   vl2->dataProvider()->addFeature( f );
5901   QgsProject::instance()->addMapLayer( vl2 );
5902   QVERIFY( vl2->isValid() );
5903 
5904   QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
5905   QVERIFY( !combo->currentLayer() );
5906   QCOMPARE( spy.count(), 3 );
5907 
5908   combo->setLayer( vl );
5909   QCOMPARE( combo->currentLayer(), vl );
5910   QCOMPARE( combo->value().toString(), vl->id() );
5911   QVERIFY( combo->currentText().startsWith( vl->name() ) );
5912   QCOMPARE( spy.count(), 4 );
5913   combo->setLayer( vl );
5914   QCOMPARE( spy.count(), 4 );
5915 
5916   combo->setLayer( vl2 );
5917   QCOMPARE( combo->value().toString(), vl2->id() );
5918   QVERIFY( combo->currentText().startsWith( vl2->name() ) );
5919   QCOMPARE( spy.count(), 5 );
5920 
5921   combo->setValue( QStringLiteral( "file path" ), context );
5922   QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
5923   QVERIFY( !combo->currentLayer() );
5924   QCOMPARE( spy.count(), 6 );
5925 
5926   // setting feature source def, i.e. with selection
5927   QgsProcessingFeatureSourceDefinition sourceDef( vl2->id(), false );
5928   combo->setValue( sourceDef, context );
5929   QCOMPARE( combo->value().toString(), vl2->id() );
5930   QVERIFY( combo->currentText().startsWith( vl2->name() ) );
5931   QCOMPARE( spy.count(), 7 );
5932   // asking for selected features only, but no selection in layer, won't be allowed
5933   sourceDef = QgsProcessingFeatureSourceDefinition( vl2->id(), true );
5934   combo->setValue( sourceDef, context );
5935   QCOMPARE( combo->value().toString(), vl2->id() );
5936   QVERIFY( combo->currentText().startsWith( vl2->name() ) );
5937   QCOMPARE( spy.count(), 7 ); // no change
5938 
5939   // now make a selection in the layer, and repeat
5940   vl2->selectAll();
5941   combo->setValue( sourceDef, context );
5942   QVERIFY( combo->value().canConvert< QgsProcessingFeatureSourceDefinition >() );
5943   QCOMPARE( combo->value().value< QgsProcessingFeatureSourceDefinition >().source.staticValue().toString(), vl2->id() );
5944   QVERIFY( combo->value().value< QgsProcessingFeatureSourceDefinition >().selectedFeaturesOnly );
5945   QVERIFY( combo->currentText().startsWith( vl2->name() ) );
5946   QCOMPARE( spy.count(), 8 );
5947 
5948   // remove layer selection, and check result...
5949   vl2->removeSelection();
5950   QCOMPARE( combo->value().toString(), vl2->id() );
5951   QVERIFY( combo->currentText().startsWith( vl2->name() ) );
5952   QCOMPARE( spy.count(), 9 );
5953 
5954   // phew, nearly there. Let's check another variation
5955   vl2->selectAll();
5956   combo->setValue( sourceDef, context );
5957   QVERIFY( combo->value().canConvert< QgsProcessingFeatureSourceDefinition >() );
5958   QCOMPARE( spy.count(), 10 );
5959   combo->setValue( QVariant::fromValue( vl ), context );
5960   QCOMPARE( combo->value().toString(), vl->id() );
5961   QVERIFY( combo->currentText().startsWith( vl->name() ) );
5962   QCOMPARE( spy.count(), 11 );
5963 
5964   // one last variation - selection to selection
5965   combo->setValue( sourceDef, context );
5966   QCOMPARE( spy.count(), 12 );
5967   QVERIFY( combo->value().value< QgsProcessingFeatureSourceDefinition >().selectedFeaturesOnly );
5968   vl->selectAll();
5969   sourceDef = QgsProcessingFeatureSourceDefinition( vl->id(), true );
5970   combo->setValue( sourceDef, context );
5971   // expect "selected only" state to remain
5972   QVERIFY( combo->value().canConvert< QgsProcessingFeatureSourceDefinition >() );
5973   QCOMPARE( combo->value().value< QgsProcessingFeatureSourceDefinition >().source.staticValue().toString(), vl->id() );
5974   QVERIFY( combo->value().value< QgsProcessingFeatureSourceDefinition >().selectedFeaturesOnly );
5975   QVERIFY( combo->currentText().startsWith( vl->name() ) );
5976   QCOMPARE( spy.count(), 13 );
5977 
5978   // iterate over features
5979   QVERIFY( !( combo->value().value< QgsProcessingFeatureSourceDefinition >().flags & QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature ) );
5980   sourceDef.flags |= QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature;
5981   combo->setValue( sourceDef, context );
5982   QVERIFY( combo->value().value< QgsProcessingFeatureSourceDefinition >().flags & QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature );
5983   sourceDef.flags = QgsProcessingFeatureSourceDefinition::Flags();
5984   combo->setValue( sourceDef, context );
5985   QVERIFY( !( combo->value().value< QgsProcessingFeatureSourceDefinition >().flags & QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature ) );
5986 
5987   // advanced settings
5988   sourceDef.featureLimit = 67;
5989   combo->setValue( sourceDef, context );
5990   QCOMPARE( combo->value().value< QgsProcessingFeatureSourceDefinition >().featureLimit, 67LL );
5991   sourceDef.featureLimit = -1;
5992   combo->setValue( sourceDef, context );
5993   QCOMPARE( combo->value().value< QgsProcessingFeatureSourceDefinition >().featureLimit, -1LL );
5994   sourceDef.flags |= QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck;
5995   sourceDef.geometryCheck = QgsFeatureRequest::GeometrySkipInvalid;
5996   combo->setValue( sourceDef, context );
5997   QVERIFY( combo->value().value< QgsProcessingFeatureSourceDefinition >().flags & QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck );
5998   QCOMPARE( combo->value().value< QgsProcessingFeatureSourceDefinition >().geometryCheck, QgsFeatureRequest::GeometrySkipInvalid );
5999   sourceDef.flags = QgsProcessingFeatureSourceDefinition::Flags();
6000   combo->setValue( sourceDef, context );
6001   QVERIFY( !( combo->value().value< QgsProcessingFeatureSourceDefinition >().flags & QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck ) );
6002 
6003   combo.reset();
6004   param.reset();
6005 
6006   // setup a project with a range of layer types
6007   QgsProject::instance()->removeAllMapLayers();
6008   QgsVectorLayer *point = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6009   QgsProject::instance()->addMapLayer( point );
6010   QgsVectorLayer *line = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6011   QgsProject::instance()->addMapLayer( line );
6012   QgsVectorLayer *polygon = new QgsVectorLayer( QStringLiteral( "Polygon" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6013   QgsProject::instance()->addMapLayer( polygon );
6014   QgsVectorLayer *noGeom = new QgsVectorLayer( QStringLiteral( "None" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6015   QgsProject::instance()->addMapLayer( noGeom );
6016   QgsMeshLayer *mesh = new QgsMeshLayer( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle.2dm", QStringLiteral( "Triangle and Quad Mdal" ), QStringLiteral( "mdal" ) );
6017   mesh->dataProvider()->addDataset( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle_vertex_scalar_with_inactive_face.dat" );
6018   QVERIFY( mesh->isValid() );
6019   QgsProject::instance()->addMapLayer( mesh );
6020   QgsRasterLayer *raster = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/raster/band1_byte_ct_epsg4326.tif", QStringLiteral( "band1_byte" ) );
6021   QgsProject::instance()->addMapLayer( raster );
6022 
6023   // map layer param, all types are acceptable
6024   param = std::make_unique< QgsProcessingParameterMapLayer> ( QStringLiteral( "param" ), QString() );
6025   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6026   combo->setLayer( point );
6027   QCOMPARE( combo->currentLayer(), point );
6028   combo->setLayer( line );
6029   QCOMPARE( combo->currentLayer(), line );
6030   combo->setLayer( polygon );
6031   QCOMPARE( combo->currentLayer(), polygon );
6032   combo->setLayer( noGeom );
6033   QCOMPARE( combo->currentLayer(), noGeom );
6034   combo->setLayer( mesh );
6035   QCOMPARE( combo->currentLayer(), mesh );
6036   combo->setLayer( raster );
6037   QCOMPARE( combo->currentLayer(), raster );
6038   combo.reset();
6039   param.reset();
6040 
6041   // map layer param, only point vector and raster types are acceptable
6042   param = std::make_unique< QgsProcessingParameterMapLayer> ( QStringLiteral( "param" ), QString(), QVariant(), false, QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeRaster );
6043   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6044   combo->setLayer( point );
6045   QCOMPARE( combo->currentLayer(), point );
6046   combo->setLayer( line );
6047   QVERIFY( !combo->currentLayer() );
6048   combo->setLayer( polygon );
6049   QVERIFY( !combo->currentLayer() );
6050   combo->setLayer( noGeom );
6051   QVERIFY( !combo->currentLayer() );
6052   combo->setLayer( mesh );
6053   QVERIFY( !combo->currentLayer() );
6054   combo->setLayer( raster );
6055   QCOMPARE( combo->currentLayer(), raster );
6056   combo.reset();
6057   param.reset();
6058 
6059   // raster layer param, only raster types are acceptable
6060   param = std::make_unique< QgsProcessingParameterRasterLayer> ( QStringLiteral( "param" ), QString() );
6061   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6062   combo->setLayer( point );
6063   QVERIFY( !combo->currentLayer() );
6064   combo->setLayer( line );
6065   QVERIFY( !combo->currentLayer() );
6066   combo->setLayer( polygon );
6067   QVERIFY( !combo->currentLayer() );
6068   combo->setLayer( noGeom );
6069   QVERIFY( !combo->currentLayer() );
6070   combo->setLayer( mesh );
6071   QVERIFY( !combo->currentLayer() );
6072   combo->setLayer( raster );
6073   QCOMPARE( combo->currentLayer(), raster );
6074   combo.reset();
6075   param.reset();
6076 
6077   // mesh layer parm, only mesh types are acceptable
6078   param = std::make_unique< QgsProcessingParameterMeshLayer> ( QStringLiteral( "param" ), QString() );
6079   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6080   combo->setLayer( point );
6081   QVERIFY( !combo->currentLayer() );
6082   combo->setLayer( line );
6083   QVERIFY( !combo->currentLayer() );
6084   combo->setLayer( polygon );
6085   QVERIFY( !combo->currentLayer() );
6086   combo->setLayer( noGeom );
6087   QVERIFY( !combo->currentLayer() );
6088   combo->setLayer( mesh );
6089   QCOMPARE( combo->currentLayer(), mesh );
6090   combo->setLayer( raster );
6091   QVERIFY( !combo->currentLayer() );
6092   combo.reset();
6093   param.reset();
6094 
6095   // feature source and vector layer params
6096   // if not specified, the default is any vector layer with geometry
6097   param = std::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ) );
6098   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6099   auto param2 = std::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ) );
6100   auto combo2 = std::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
6101   combo->setLayer( point );
6102   QCOMPARE( combo->currentLayer(), point );
6103   combo2->setLayer( point );
6104   QCOMPARE( combo2->currentLayer(), point );
6105   combo->setLayer( line );
6106   QCOMPARE( combo->currentLayer(), line );
6107   combo2->setLayer( line );
6108   QCOMPARE( combo2->currentLayer(), line );
6109   combo->setLayer( polygon );
6110   QCOMPARE( combo->currentLayer(), polygon );
6111   combo2->setLayer( polygon );
6112   QCOMPARE( combo2->currentLayer(), polygon );
6113   combo->setLayer( noGeom );
6114   QVERIFY( !combo->currentLayer() );
6115   combo2->setLayer( noGeom );
6116   QVERIFY( !combo2->currentLayer() );
6117   combo->setLayer( mesh );
6118   QVERIFY( !combo->currentLayer() );
6119   combo2->setLayer( mesh );
6120   QVERIFY( !combo2->currentLayer() );
6121   combo->setLayer( raster );
6122   QVERIFY( !combo->currentLayer() );
6123   combo2->setLayer( raster );
6124   QVERIFY( !combo2->currentLayer() );
6125   combo2.reset();
6126   param2.reset();
6127   combo.reset();
6128   param.reset();
6129 
6130   // point layer
6131   param = std::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPoint );
6132   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6133   param2 = std::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPoint );
6134   combo2 = std::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
6135   combo->setLayer( point );
6136   QCOMPARE( combo->currentLayer(), point );
6137   combo2->setLayer( point );
6138   QCOMPARE( combo2->currentLayer(), point );
6139   combo->setLayer( line );
6140   QVERIFY( !combo->currentLayer() );
6141   combo2->setLayer( line );
6142   QVERIFY( !combo2->currentLayer() );
6143   combo->setLayer( polygon );
6144   QVERIFY( !combo->currentLayer() );
6145   combo2->setLayer( polygon );
6146   QVERIFY( !combo2->currentLayer() );
6147   combo->setLayer( noGeom );
6148   QVERIFY( !combo->currentLayer() );
6149   combo2->setLayer( noGeom );
6150   QVERIFY( !combo2->currentLayer() );
6151   combo->setLayer( mesh );
6152   QVERIFY( !combo->currentLayer() );
6153   combo2->setLayer( mesh );
6154   QVERIFY( !combo2->currentLayer() );
6155   combo->setLayer( raster );
6156   QVERIFY( !combo->currentLayer() );
6157   combo2->setLayer( raster );
6158   QVERIFY( !combo2->currentLayer() );
6159   combo2.reset();
6160   param2.reset();
6161   combo.reset();
6162   param.reset();
6163 
6164   // line layer
6165   param = std::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorLine );
6166   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6167   param2 = std::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorLine );
6168   combo2 = std::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
6169   combo->setLayer( point );
6170   QVERIFY( !combo->currentLayer() );
6171   combo2->setLayer( point );
6172   QVERIFY( !combo2->currentLayer() );
6173   combo->setLayer( line );
6174   QCOMPARE( combo->currentLayer(), line );
6175   combo2->setLayer( line );
6176   QCOMPARE( combo2->currentLayer(), line );
6177   combo->setLayer( polygon );
6178   QVERIFY( !combo->currentLayer() );
6179   combo2->setLayer( polygon );
6180   QVERIFY( !combo2->currentLayer() );
6181   combo->setLayer( noGeom );
6182   QVERIFY( !combo->currentLayer() );
6183   combo2->setLayer( noGeom );
6184   QVERIFY( !combo2->currentLayer() );
6185   combo->setLayer( mesh );
6186   QVERIFY( !combo->currentLayer() );
6187   combo2->setLayer( mesh );
6188   QVERIFY( !combo2->currentLayer() );
6189   combo->setLayer( raster );
6190   QVERIFY( !combo->currentLayer() );
6191   combo2->setLayer( raster );
6192   QVERIFY( !combo2->currentLayer() );
6193   combo2.reset();
6194   param2.reset();
6195   combo.reset();
6196   param.reset();
6197 
6198   // polygon
6199   param = std::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPolygon );
6200   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6201   param2 = std::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPolygon );
6202   combo2 = std::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
6203   combo->setLayer( point );
6204   QVERIFY( !combo->currentLayer() );
6205   combo2->setLayer( point );
6206   QVERIFY( !combo2->currentLayer() );
6207   combo->setLayer( line );
6208   QVERIFY( !combo->currentLayer() );
6209   combo2->setLayer( line );
6210   QVERIFY( !combo2->currentLayer() );
6211   combo->setLayer( polygon );
6212   QCOMPARE( combo->currentLayer(), polygon );
6213   combo2->setLayer( polygon );
6214   QCOMPARE( combo2->currentLayer(), polygon );
6215   combo->setLayer( noGeom );
6216   QVERIFY( !combo->currentLayer() );
6217   combo2->setLayer( noGeom );
6218   QVERIFY( !combo2->currentLayer() );
6219   combo->setLayer( mesh );
6220   QVERIFY( !combo->currentLayer() );
6221   combo2->setLayer( mesh );
6222   QVERIFY( !combo2->currentLayer() );
6223   combo->setLayer( raster );
6224   QVERIFY( !combo->currentLayer() );
6225   combo2->setLayer( raster );
6226   QVERIFY( !combo2->currentLayer() );
6227   combo2.reset();
6228   param2.reset();
6229   combo.reset();
6230   param.reset();
6231 
6232   // no geom
6233   param = std::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVector );
6234   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6235   param2 = std::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVector );
6236   combo2 = std::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
6237   combo->setLayer( point );
6238   QCOMPARE( combo->currentLayer(), point );
6239   combo2->setLayer( point );
6240   QCOMPARE( combo2->currentLayer(), point );
6241   combo->setLayer( line );
6242   QCOMPARE( combo->currentLayer(), line );
6243   combo2->setLayer( line );
6244   QCOMPARE( combo2->currentLayer(), line );
6245   combo->setLayer( polygon );
6246   QCOMPARE( combo->currentLayer(), polygon );
6247   combo2->setLayer( polygon );
6248   QCOMPARE( combo2->currentLayer(), polygon );
6249   combo->setLayer( noGeom );
6250   QCOMPARE( combo->currentLayer(), noGeom );
6251   combo2->setLayer( noGeom );
6252   QCOMPARE( combo2->currentLayer(), noGeom );
6253   combo->setLayer( mesh );
6254   QVERIFY( !combo->currentLayer() );
6255   combo2->setLayer( mesh );
6256   QVERIFY( !combo2->currentLayer() );
6257   combo->setLayer( raster );
6258   QVERIFY( !combo->currentLayer() );
6259   combo2->setLayer( raster );
6260   QVERIFY( !combo2->currentLayer() );
6261   combo2.reset();
6262   param2.reset();
6263   combo.reset();
6264   param.reset();
6265 
6266   // any geom
6267   param = std::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorAnyGeometry );
6268   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6269   param2 = std::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorAnyGeometry );
6270   combo2 = std::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
6271   combo->setLayer( point );
6272   QCOMPARE( combo->currentLayer(), point );
6273   combo2->setLayer( point );
6274   QCOMPARE( combo2->currentLayer(), point );
6275   combo->setLayer( line );
6276   QCOMPARE( combo->currentLayer(), line );
6277   combo2->setLayer( line );
6278   QCOMPARE( combo2->currentLayer(), line );
6279   combo->setLayer( polygon );
6280   QCOMPARE( combo->currentLayer(), polygon );
6281   combo2->setLayer( polygon );
6282   QCOMPARE( combo2->currentLayer(), polygon );
6283   combo->setLayer( noGeom );
6284   QVERIFY( !combo->currentLayer() );
6285   combo2->setLayer( noGeom );
6286   QVERIFY( !combo2->currentLayer() );
6287   combo->setLayer( mesh );
6288   QVERIFY( !combo->currentLayer() );
6289   combo2->setLayer( mesh );
6290   QVERIFY( !combo2->currentLayer() );
6291   combo->setLayer( raster );
6292   QVERIFY( !combo->currentLayer() );
6293   combo2->setLayer( raster );
6294   QVERIFY( !combo2->currentLayer() );
6295   combo2.reset();
6296   param2.reset();
6297   combo.reset();
6298   param.reset();
6299 
6300   // combination point and line only
6301   param = std::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine );
6302   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6303   param2 = std::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine );
6304   combo2 = std::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
6305   combo->setLayer( point );
6306   QCOMPARE( combo->currentLayer(), point );
6307   combo2->setLayer( point );
6308   QCOMPARE( combo2->currentLayer(), point );
6309   combo->setLayer( line );
6310   QCOMPARE( combo->currentLayer(), line );
6311   combo2->setLayer( line );
6312   QCOMPARE( combo2->currentLayer(), line );
6313   combo->setLayer( polygon );
6314   QVERIFY( !combo->currentLayer() );
6315   combo2->setLayer( polygon );
6316   QVERIFY( !combo2->currentLayer() );
6317   combo->setLayer( noGeom );
6318   QVERIFY( !combo->currentLayer() );
6319   combo2->setLayer( noGeom );
6320   QVERIFY( !combo2->currentLayer() );
6321   combo->setLayer( mesh );
6322   QVERIFY( !combo->currentLayer() );
6323   combo2->setLayer( mesh );
6324   QVERIFY( !combo2->currentLayer() );
6325   combo->setLayer( raster );
6326   QVERIFY( !combo->currentLayer() );
6327   combo2->setLayer( raster );
6328   QVERIFY( !combo2->currentLayer() );
6329   combo2.reset();
6330   param2.reset();
6331   combo.reset();
6332   param.reset();
6333 
6334   // optional
6335   param = std::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>(), QVariant(), true );
6336   combo = std::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
6337   combo->setLayer( point );
6338   QCOMPARE( combo->currentLayer(), point );
6339   combo->setLayer( nullptr );
6340   QVERIFY( !combo->currentLayer() );
6341   QVERIFY( !combo->value().isValid() );
6342   combo->setLayer( point );
6343   QCOMPARE( combo->currentLayer(), point );
6344   combo->setValue( QVariant(), context );
6345   QVERIFY( !combo->currentLayer() );
6346   QVERIFY( !combo->value().isValid() );
6347 
6348   combo2.reset();
6349   param2.reset();
6350   combo.reset();
6351   param.reset();
6352   QgsProject::instance()->removeAllMapLayers();
6353 }
6354 
testMapLayerWrapper()6355 void TestProcessingGui::testMapLayerWrapper()
6356 {
6357   // setup a project with a range of layer types
6358   QgsProject::instance()->removeAllMapLayers();
6359   QgsVectorLayer *point = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6360   QgsProject::instance()->addMapLayer( point );
6361   QgsVectorLayer *line = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6362   QgsProject::instance()->addMapLayer( line );
6363   QgsVectorLayer *polygon = new QgsVectorLayer( QStringLiteral( "Polygon" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6364   QgsProject::instance()->addMapLayer( polygon );
6365   QgsVectorLayer *noGeom = new QgsVectorLayer( QStringLiteral( "None" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6366   QgsProject::instance()->addMapLayer( noGeom );
6367   QgsMeshLayer *mesh = new QgsMeshLayer( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle.2dm", QStringLiteral( "Triangle and Quad Mdal" ), QStringLiteral( "mdal" ) );
6368   mesh->dataProvider()->addDataset( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle_vertex_scalar_with_inactive_face.dat" );
6369   QVERIFY( mesh->isValid() );
6370   QgsProject::instance()->addMapLayer( mesh );
6371   QgsRasterLayer *raster = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/raster/band1_byte_ct_epsg4326.tif", QStringLiteral( "band1_byte" ) );
6372   QgsProject::instance()->addMapLayer( raster );
6373 
6374   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
6375   {
6376     // non optional
6377     QgsProcessingParameterMapLayer param( QStringLiteral( "layer" ), QStringLiteral( "layer" ), false );
6378 
6379     QgsProcessingMapLayerWidgetWrapper wrapper( &param, type );
6380 
6381     QgsProcessingContext context;
6382     QWidget *w = wrapper.createWrappedWidget( context );
6383 
6384     QSignalSpy spy( &wrapper, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
6385     wrapper.setWidgetValue( QStringLiteral( "bb" ), context );
6386 
6387     switch ( type )
6388     {
6389       case QgsProcessingGui::Standard:
6390       case QgsProcessingGui::Batch:
6391       case QgsProcessingGui::Modeler:
6392         QCOMPARE( spy.count(), 1 );
6393         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "bb" ) );
6394         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6395         wrapper.setWidgetValue( QStringLiteral( "aa" ), context );
6396         QCOMPARE( spy.count(), 2 );
6397         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "aa" ) );
6398         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "aa" ) );
6399         break;
6400     }
6401 
6402     delete w;
6403 
6404     // with project
6405     QgsProcessingParameterWidgetContext widgetContext;
6406     widgetContext.setProject( QgsProject::instance() );
6407     context.setProject( QgsProject::instance() );
6408 
6409     QgsProcessingMapLayerWidgetWrapper wrapper2( &param, type );
6410     wrapper2.setWidgetContext( widgetContext );
6411     w = wrapper2.createWrappedWidget( context );
6412 
6413     QSignalSpy spy2( &wrapper2, &QgsProcessingMapLayerWidgetWrapper::widgetValueHasChanged );
6414     wrapper2.setWidgetValue( QStringLiteral( "bb" ), context );
6415     QCOMPARE( spy2.count(), 1 );
6416     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "bb" ) );
6417     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6418     wrapper2.setWidgetValue( QStringLiteral( "band1_byte" ), context );
6419     QCOMPARE( spy2.count(), 2 );
6420     QCOMPARE( wrapper2.widgetValue().toString(), raster->id() );
6421     switch ( type )
6422     {
6423       case QgsProcessingGui::Standard:
6424       case QgsProcessingGui::Batch:
6425         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "band1_byte [EPSG:4326]" ) );
6426         break;
6427       case QgsProcessingGui::Modeler:
6428         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "band1_byte" ) );
6429         break;
6430     }
6431 
6432     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "band1_byte" ) );
6433 
6434     // check signal
6435     static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->setLayer( polygon );
6436     QCOMPARE( spy2.count(), 3 );
6437     QCOMPARE( wrapper2.widgetValue().toString(), polygon->id() );
6438     switch ( type )
6439     {
6440       case QgsProcessingGui::Standard:
6441       case QgsProcessingGui::Batch:
6442         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "l1 [EPSG:4326]" ) );
6443         break;
6444 
6445       case QgsProcessingGui::Modeler:
6446         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "l1" ) );
6447         break;
6448     }
6449     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "l1" ) );
6450 
6451     delete w;
6452 
6453     // optional
6454     QgsProcessingParameterMapLayer param2( QStringLiteral( "layer" ), QStringLiteral( "layer" ), QVariant(), true );
6455     QgsProcessingMapLayerWidgetWrapper wrapper3( &param2, type );
6456     wrapper3.setWidgetContext( widgetContext );
6457     w = wrapper3.createWrappedWidget( context );
6458 
6459     QSignalSpy spy3( &wrapper3, &QgsProcessingMapLayerWidgetWrapper::widgetValueHasChanged );
6460     wrapper3.setWidgetValue( QStringLiteral( "bb" ), context );
6461     QCOMPARE( spy3.count(), 1 );
6462     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "bb" ) );
6463     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6464     wrapper3.setWidgetValue( QStringLiteral( "band1_byte" ), context );
6465     QCOMPARE( spy3.count(), 2 );
6466     QCOMPARE( wrapper3.widgetValue().toString(), raster->id() );
6467     switch ( type )
6468     {
6469       case QgsProcessingGui::Standard:
6470       case QgsProcessingGui::Batch:
6471         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "band1_byte [EPSG:4326]" ) );
6472         break;
6473       case QgsProcessingGui::Modeler:
6474         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "band1_byte" ) );
6475         break;
6476     }
6477     wrapper3.setWidgetValue( QVariant(), context );
6478     QCOMPARE( spy3.count(), 3 );
6479     QVERIFY( !wrapper3.widgetValue().isValid() );
6480     delete w;
6481 
6482 
6483     QLabel *l = wrapper.createWrappedLabel();
6484     if ( wrapper.type() != QgsProcessingGui::Batch )
6485     {
6486       QVERIFY( l );
6487       QCOMPARE( l->text(), QStringLiteral( "layer" ) );
6488       QCOMPARE( l->toolTip(), param.toolTip() );
6489       delete l;
6490     }
6491     else
6492     {
6493       QVERIFY( !l );
6494     }
6495   };
6496 
6497   // standard wrapper
6498   testWrapper( QgsProcessingGui::Standard );
6499 
6500   // batch wrapper
6501   testWrapper( QgsProcessingGui::Batch );
6502 
6503   // modeler wrapper
6504   testWrapper( QgsProcessingGui::Modeler );
6505 
6506   // config widget
6507   QgsProcessingParameterWidgetContext widgetContext;
6508   QgsProcessingContext context;
6509   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "layer" ), context, widgetContext );
6510   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
6511   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
6512   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
6513   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
6514 
6515   // using a parameter definition as initial values
6516   QgsProcessingParameterMapLayer layerParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QVariant(), false, QList< int >() << QgsProcessing::TypeVectorAnyGeometry );
6517   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "layer" ), context, widgetContext, &layerParam );
6518   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
6519   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
6520   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
6521   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
6522   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
6523   QCOMPARE( static_cast< QgsProcessingParameterMapLayer * >( def.get() )->dataTypes(), QList< int >() << QgsProcessing::TypeVectorAnyGeometry );
6524   layerParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
6525   layerParam.setDataTypes( QList< int >() << QgsProcessing::TypeRaster << QgsProcessing::TypeVectorPoint );
6526   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "layer" ), context, widgetContext, &layerParam );
6527   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
6528   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
6529   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
6530   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
6531   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
6532   QCOMPARE( static_cast< QgsProcessingParameterMapLayer * >( def.get() )->dataTypes(), QList< int >()  << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeRaster );
6533 }
6534 
testRasterLayerWrapper()6535 void TestProcessingGui::testRasterLayerWrapper()
6536 {
6537   // setup a project
6538   QgsProject::instance()->removeAllMapLayers();
6539   QgsRasterLayer *raster = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/raster/band1_byte_ct_epsg4326.tif", QStringLiteral( "band1_byte" ) );
6540   QgsProject::instance()->addMapLayer( raster );
6541   QgsRasterLayer *raster2 = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/raster/band1_byte_ct_epsg4326.tif", QStringLiteral( "band1_byte2" ) );
6542   QgsProject::instance()->addMapLayer( raster2 );
6543 
6544   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
6545   {
6546     // non optional
6547     QgsProcessingParameterRasterLayer param( QStringLiteral( "raster" ), QStringLiteral( "raster" ), false );
6548 
6549     QgsProcessingMapLayerWidgetWrapper wrapper( &param, type );
6550 
6551     QgsProcessingContext context;
6552     QWidget *w = wrapper.createWrappedWidget( context );
6553 
6554     QSignalSpy spy( &wrapper, &QgsProcessingMapLayerWidgetWrapper::widgetValueHasChanged );
6555     wrapper.setWidgetValue( QStringLiteral( "bb" ), context );
6556 
6557     switch ( type )
6558     {
6559       case QgsProcessingGui::Standard:
6560       case QgsProcessingGui::Batch:
6561       case QgsProcessingGui::Modeler:
6562         QCOMPARE( spy.count(), 1 );
6563         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "bb" ) );
6564         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6565         wrapper.setWidgetValue( QStringLiteral( "aa" ), context );
6566         QCOMPARE( spy.count(), 2 );
6567         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "aa" ) );
6568         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "aa" ) );
6569         break;
6570     }
6571 
6572     delete w;
6573 
6574     // with project
6575     QgsProcessingParameterWidgetContext widgetContext;
6576     widgetContext.setProject( QgsProject::instance() );
6577     context.setProject( QgsProject::instance() );
6578 
6579     QgsProcessingMapLayerWidgetWrapper wrapper2( &param, type );
6580     wrapper2.setWidgetContext( widgetContext );
6581     w = wrapper2.createWrappedWidget( context );
6582 
6583     QSignalSpy spy2( &wrapper2, &QgsProcessingRasterLayerWidgetWrapper::widgetValueHasChanged );
6584     wrapper2.setWidgetValue( QStringLiteral( "bb" ), context );
6585     QCOMPARE( spy2.count(), 1 );
6586     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "bb" ) );
6587     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6588     wrapper2.setWidgetValue( QStringLiteral( "band1_byte" ), context );
6589     QCOMPARE( spy2.count(), 2 );
6590     QCOMPARE( wrapper2.widgetValue().toString(), raster->id() );
6591     switch ( type )
6592     {
6593       case QgsProcessingGui::Standard:
6594       case QgsProcessingGui::Batch:
6595         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "band1_byte [EPSG:4326]" ) );
6596         break;
6597       case QgsProcessingGui::Modeler:
6598         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "band1_byte" ) );
6599         break;
6600     }
6601 
6602     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "band1_byte" ) );
6603 
6604     // check signal
6605     static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->setLayer( raster2 );
6606     QCOMPARE( spy2.count(), 3 );
6607     QCOMPARE( wrapper2.widgetValue().toString(), raster2->id() );
6608     switch ( type )
6609     {
6610       case QgsProcessingGui::Standard:
6611       case QgsProcessingGui::Batch:
6612         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "band1_byte2 [EPSG:4326]" ) );
6613         break;
6614 
6615       case QgsProcessingGui::Modeler:
6616         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "band1_byte2" ) );
6617         break;
6618     }
6619     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "band1_byte2" ) );
6620 
6621     delete w;
6622 
6623     // optional
6624     QgsProcessingParameterRasterLayer param2( QStringLiteral( "raster" ), QStringLiteral( "raster" ), QVariant(), true );
6625     QgsProcessingMapLayerWidgetWrapper wrapper3( &param2, type );
6626     wrapper3.setWidgetContext( widgetContext );
6627     w = wrapper3.createWrappedWidget( context );
6628 
6629     QSignalSpy spy3( &wrapper3, &QgsProcessingMapLayerWidgetWrapper::widgetValueHasChanged );
6630     wrapper3.setWidgetValue( QStringLiteral( "bb" ), context );
6631     QCOMPARE( spy3.count(), 1 );
6632     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "bb" ) );
6633     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6634     wrapper3.setWidgetValue( QStringLiteral( "band1_byte" ), context );
6635     QCOMPARE( spy3.count(), 2 );
6636     QCOMPARE( wrapper3.widgetValue().toString(), raster->id() );
6637     switch ( type )
6638     {
6639       case QgsProcessingGui::Standard:
6640       case QgsProcessingGui::Batch:
6641         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "band1_byte [EPSG:4326]" ) );
6642         break;
6643       case QgsProcessingGui::Modeler:
6644         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "band1_byte" ) );
6645         break;
6646     }
6647     wrapper3.setWidgetValue( QVariant(), context );
6648     QCOMPARE( spy3.count(), 3 );
6649     QVERIFY( !wrapper3.widgetValue().isValid() );
6650     delete w;
6651 
6652 
6653     QLabel *l = wrapper.createWrappedLabel();
6654     if ( wrapper.type() != QgsProcessingGui::Batch )
6655     {
6656       QVERIFY( l );
6657       QCOMPARE( l->text(), QStringLiteral( "raster" ) );
6658       QCOMPARE( l->toolTip(), param.toolTip() );
6659       delete l;
6660     }
6661     else
6662     {
6663       QVERIFY( !l );
6664     }
6665   };
6666 
6667   // standard wrapper
6668   testWrapper( QgsProcessingGui::Standard );
6669 
6670   // batch wrapper
6671   testWrapper( QgsProcessingGui::Batch );
6672 
6673   // modeler wrapper
6674   testWrapper( QgsProcessingGui::Modeler );
6675 }
6676 
testVectorLayerWrapper()6677 void TestProcessingGui::testVectorLayerWrapper()
6678 {
6679   // setup a project with a range of vector layers
6680   QgsProject::instance()->removeAllMapLayers();
6681   QgsVectorLayer *point = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "point" ), QStringLiteral( "memory" ) );
6682   QgsProject::instance()->addMapLayer( point );
6683   QgsVectorLayer *line = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6684   QgsProject::instance()->addMapLayer( line );
6685   QgsVectorLayer *polygon = new QgsVectorLayer( QStringLiteral( "Polygon" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6686   QgsProject::instance()->addMapLayer( polygon );
6687   QgsVectorLayer *noGeom = new QgsVectorLayer( QStringLiteral( "None" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6688   QgsProject::instance()->addMapLayer( noGeom );
6689 
6690   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
6691   {
6692     // non optional
6693     QgsProcessingParameterVectorLayer param( QStringLiteral( "vector" ), QStringLiteral( "vector" ), QList<int >() << QgsProcessing::TypeVector, false );
6694 
6695     QgsProcessingVectorLayerWidgetWrapper wrapper( &param, type );
6696 
6697     QgsProcessingContext context;
6698     QWidget *w = wrapper.createWrappedWidget( context );
6699 
6700     QSignalSpy spy( &wrapper, &QgsProcessingVectorLayerWidgetWrapper::widgetValueHasChanged );
6701     wrapper.setWidgetValue( QStringLiteral( "bb" ), context );
6702 
6703     switch ( type )
6704     {
6705       case QgsProcessingGui::Standard:
6706       case QgsProcessingGui::Batch:
6707       case QgsProcessingGui::Modeler:
6708         QCOMPARE( spy.count(), 1 );
6709         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "bb" ) );
6710         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6711         wrapper.setWidgetValue( QStringLiteral( "aa" ), context );
6712         QCOMPARE( spy.count(), 2 );
6713         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "aa" ) );
6714         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "aa" ) );
6715         break;
6716     }
6717 
6718     delete w;
6719 
6720     // with project
6721     QgsProcessingParameterWidgetContext widgetContext;
6722     widgetContext.setProject( QgsProject::instance() );
6723     context.setProject( QgsProject::instance() );
6724 
6725     QgsProcessingMapLayerWidgetWrapper wrapper2( &param, type );
6726     wrapper2.setWidgetContext( widgetContext );
6727     w = wrapper2.createWrappedWidget( context );
6728 
6729     QSignalSpy spy2( &wrapper2, &QgsProcessingMapLayerWidgetWrapper::widgetValueHasChanged );
6730     wrapper2.setWidgetValue( QStringLiteral( "bb" ), context );
6731     QCOMPARE( spy2.count(), 1 );
6732     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "bb" ) );
6733     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6734     wrapper2.setWidgetValue( QStringLiteral( "point" ), context );
6735     QCOMPARE( spy2.count(), 2 );
6736     QCOMPARE( wrapper2.widgetValue().toString(), point->id() );
6737     switch ( type )
6738     {
6739       case QgsProcessingGui::Standard:
6740       case QgsProcessingGui::Batch:
6741         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "point [EPSG:4326]" ) );
6742         break;
6743       case QgsProcessingGui::Modeler:
6744         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "point" ) );
6745         break;
6746     }
6747 
6748     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "point" ) );
6749 
6750     // check signal
6751     static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->setLayer( polygon );
6752     QCOMPARE( spy2.count(), 3 );
6753     QCOMPARE( wrapper2.widgetValue().toString(), polygon->id() );
6754     switch ( type )
6755     {
6756       case QgsProcessingGui::Standard:
6757       case QgsProcessingGui::Batch:
6758         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "l1 [EPSG:4326]" ) );
6759         break;
6760 
6761       case QgsProcessingGui::Modeler:
6762         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "l1" ) );
6763         break;
6764     }
6765     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "l1" ) );
6766 
6767     delete w;
6768 
6769     // optional
6770     QgsProcessingParameterVectorLayer param2( QStringLiteral( "vector" ), QStringLiteral( "vector" ), QList< int >() << QgsProcessing::TypeVector, QVariant(), true );
6771     QgsProcessingVectorLayerWidgetWrapper wrapper3( &param2, type );
6772     wrapper3.setWidgetContext( widgetContext );
6773     w = wrapper3.createWrappedWidget( context );
6774 
6775     QSignalSpy spy3( &wrapper3, &QgsProcessingVectorLayerWidgetWrapper::widgetValueHasChanged );
6776     wrapper3.setWidgetValue( QStringLiteral( "bb" ), context );
6777     QCOMPARE( spy3.count(), 1 );
6778     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "bb" ) );
6779     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6780     wrapper3.setWidgetValue( QStringLiteral( "point" ), context );
6781     QCOMPARE( spy3.count(), 2 );
6782     QCOMPARE( wrapper3.widgetValue().toString(), point->id() );
6783     switch ( type )
6784     {
6785       case QgsProcessingGui::Standard:
6786       case QgsProcessingGui::Batch:
6787         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "point [EPSG:4326]" ) );
6788         break;
6789       case QgsProcessingGui::Modeler:
6790         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "point" ) );
6791         break;
6792     }
6793     wrapper3.setWidgetValue( QVariant(), context );
6794     QCOMPARE( spy3.count(), 3 );
6795     QVERIFY( !wrapper3.widgetValue().isValid() );
6796     delete w;
6797 
6798 
6799     QLabel *l = wrapper.createWrappedLabel();
6800     if ( wrapper.type() != QgsProcessingGui::Batch )
6801     {
6802       QVERIFY( l );
6803       QCOMPARE( l->text(), QStringLiteral( "vector" ) );
6804       QCOMPARE( l->toolTip(), param.toolTip() );
6805       delete l;
6806     }
6807     else
6808     {
6809       QVERIFY( !l );
6810     }
6811   };
6812 
6813   // standard wrapper
6814   testWrapper( QgsProcessingGui::Standard );
6815 
6816   // batch wrapper
6817   testWrapper( QgsProcessingGui::Batch );
6818 
6819   // modeler wrapper
6820   testWrapper( QgsProcessingGui::Modeler );
6821 
6822   // config widget
6823   QgsProcessingParameterWidgetContext widgetContext;
6824   QgsProcessingContext context;
6825   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "vector" ), context, widgetContext );
6826   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
6827   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
6828   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
6829   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
6830 
6831   // using a parameter definition as initial values
6832   QgsProcessingParameterVectorLayer layerParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QList< int >() << QgsProcessing::TypeVectorAnyGeometry );
6833   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "vector" ), context, widgetContext, &layerParam );
6834   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
6835   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
6836   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
6837   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
6838   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
6839   QCOMPARE( static_cast< QgsProcessingParameterVectorLayer * >( def.get() )->dataTypes(), QList< int >() << QgsProcessing::TypeVectorAnyGeometry );
6840   layerParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
6841   layerParam.setDataTypes( QList< int >() << QgsProcessing::TypeVectorLine << QgsProcessing::TypeVectorPoint );
6842   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "vector" ), context, widgetContext, &layerParam );
6843   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
6844   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
6845   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
6846   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
6847   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
6848   QCOMPARE( static_cast< QgsProcessingParameterVectorLayer * >( def.get() )->dataTypes(), QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine );
6849 }
6850 
testFeatureSourceWrapper()6851 void TestProcessingGui::testFeatureSourceWrapper()
6852 {
6853   // setup a project with a range of vector layers
6854   QgsProject::instance()->removeAllMapLayers();
6855   QgsVectorLayer *point = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "point" ), QStringLiteral( "memory" ) );
6856   QgsProject::instance()->addMapLayer( point );
6857   QgsVectorLayer *line = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6858   QgsProject::instance()->addMapLayer( line );
6859   QgsVectorLayer *polygon = new QgsVectorLayer( QStringLiteral( "Polygon" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6860   QgsProject::instance()->addMapLayer( polygon );
6861   QgsVectorLayer *noGeom = new QgsVectorLayer( QStringLiteral( "None" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
6862   QgsProject::instance()->addMapLayer( noGeom );
6863 
6864   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
6865   {
6866     // non optional
6867     QgsProcessingParameterFeatureSource param( QStringLiteral( "source" ), QStringLiteral( "source" ), QList<int >() << QgsProcessing::TypeVector, false );
6868 
6869     QgsProcessingFeatureSourceWidgetWrapper wrapper( &param, type );
6870 
6871     QgsProcessingContext context;
6872     QWidget *w = wrapper.createWrappedWidget( context );
6873 
6874     QSignalSpy spy( &wrapper, &QgsProcessingFeatureSourceWidgetWrapper::widgetValueHasChanged );
6875     wrapper.setWidgetValue( QStringLiteral( "bb" ), context );
6876 
6877     switch ( type )
6878     {
6879       case QgsProcessingGui::Standard:
6880       case QgsProcessingGui::Batch:
6881       case QgsProcessingGui::Modeler:
6882         QCOMPARE( spy.count(), 1 );
6883         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "bb" ) );
6884         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6885         wrapper.setWidgetValue( QStringLiteral( "aa" ), context );
6886         QCOMPARE( spy.count(), 2 );
6887         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "aa" ) );
6888         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "aa" ) );
6889         break;
6890     }
6891 
6892     delete w;
6893 
6894     // with project
6895     QgsProcessingParameterWidgetContext widgetContext;
6896     widgetContext.setProject( QgsProject::instance() );
6897     context.setProject( QgsProject::instance() );
6898 
6899     QgsProcessingMapLayerWidgetWrapper wrapper2( &param, type );
6900     wrapper2.setWidgetContext( widgetContext );
6901     w = wrapper2.createWrappedWidget( context );
6902 
6903     QSignalSpy spy2( &wrapper2, &QgsProcessingMapLayerWidgetWrapper::widgetValueHasChanged );
6904     wrapper2.setWidgetValue( QStringLiteral( "bb" ), context );
6905     QCOMPARE( spy2.count(), 1 );
6906     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "bb" ) );
6907     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6908     wrapper2.setWidgetValue( QStringLiteral( "point" ), context );
6909     QCOMPARE( spy2.count(), 2 );
6910     QCOMPARE( wrapper2.widgetValue().toString(), point->id() );
6911     switch ( type )
6912     {
6913       case QgsProcessingGui::Standard:
6914       case QgsProcessingGui::Batch:
6915         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "point [EPSG:4326]" ) );
6916         break;
6917       case QgsProcessingGui::Modeler:
6918         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "point" ) );
6919         break;
6920     }
6921 
6922     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "point" ) );
6923 
6924     // check signal
6925     static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->setLayer( polygon );
6926     QCOMPARE( spy2.count(), 3 );
6927     QCOMPARE( wrapper2.widgetValue().toString(), polygon->id() );
6928     switch ( type )
6929     {
6930       case QgsProcessingGui::Standard:
6931       case QgsProcessingGui::Batch:
6932         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "l1 [EPSG:4326]" ) );
6933         break;
6934 
6935       case QgsProcessingGui::Modeler:
6936         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "l1" ) );
6937         break;
6938     }
6939     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "l1" ) );
6940 
6941     delete w;
6942 
6943     // optional
6944     QgsProcessingParameterFeatureSource param2( QStringLiteral( "source" ), QStringLiteral( "source" ), QList< int >() << QgsProcessing::TypeVector, QVariant(), true );
6945     QgsProcessingFeatureSourceWidgetWrapper wrapper3( &param2, type );
6946     wrapper3.setWidgetContext( widgetContext );
6947     w = wrapper3.createWrappedWidget( context );
6948 
6949     QSignalSpy spy3( &wrapper3, &QgsProcessingFeatureSourceWidgetWrapper::widgetValueHasChanged );
6950     wrapper3.setWidgetValue( QStringLiteral( "bb" ), context );
6951     QCOMPARE( spy3.count(), 1 );
6952     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "bb" ) );
6953     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
6954     wrapper3.setWidgetValue( QStringLiteral( "point" ), context );
6955     QCOMPARE( spy3.count(), 2 );
6956     QCOMPARE( wrapper3.widgetValue().toString(), point->id() );
6957     switch ( type )
6958     {
6959       case QgsProcessingGui::Standard:
6960       case QgsProcessingGui::Batch:
6961         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "point [EPSG:4326]" ) );
6962         break;
6963       case QgsProcessingGui::Modeler:
6964         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "point" ) );
6965         break;
6966     }
6967     wrapper3.setWidgetValue( QVariant(), context );
6968     QCOMPARE( spy3.count(), 3 );
6969     QVERIFY( !wrapper3.widgetValue().isValid() );
6970     delete w;
6971 
6972 
6973     QLabel *l = wrapper.createWrappedLabel();
6974     if ( wrapper.type() != QgsProcessingGui::Batch )
6975     {
6976       QVERIFY( l );
6977       QCOMPARE( l->text(), QStringLiteral( "source" ) );
6978       QCOMPARE( l->toolTip(), param.toolTip() );
6979       delete l;
6980     }
6981     else
6982     {
6983       QVERIFY( !l );
6984     }
6985   };
6986 
6987   // standard wrapper
6988   testWrapper( QgsProcessingGui::Standard );
6989 
6990   // batch wrapper
6991   testWrapper( QgsProcessingGui::Batch );
6992 
6993   // modeler wrapper
6994   testWrapper( QgsProcessingGui::Modeler );
6995 
6996   // config widget
6997   QgsProcessingParameterWidgetContext widgetContext;
6998   QgsProcessingContext context;
6999   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "source" ), context, widgetContext );
7000   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
7001   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7002   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
7003   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7004 
7005   // using a parameter definition as initial values
7006   QgsProcessingParameterFeatureSource sourceParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QList< int >() << QgsProcessing::TypeVectorAnyGeometry );
7007   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "source" ), context, widgetContext, &sourceParam );
7008   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7009   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7010   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7011   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
7012   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7013   QCOMPARE( static_cast< QgsProcessingParameterFeatureSource * >( def.get() )->dataTypes(), QList< int >() << QgsProcessing::TypeVectorAnyGeometry );
7014   sourceParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
7015   sourceParam.setDataTypes( QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine );
7016   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "source" ), context, widgetContext, &sourceParam );
7017   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7018   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7019   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7020   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
7021   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
7022   QCOMPARE( static_cast< QgsProcessingParameterFeatureSource * >( def.get() )->dataTypes(), QList< int >() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine );
7023 }
7024 
testMeshLayerWrapper()7025 void TestProcessingGui::testMeshLayerWrapper()
7026 {
7027   // setup a project with a range of layer types
7028   QgsProject::instance()->removeAllMapLayers();
7029   QgsMeshLayer *mesh = new QgsMeshLayer( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle.2dm", QStringLiteral( "mesh1" ), QStringLiteral( "mdal" ) );
7030   mesh->dataProvider()->addDataset( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle_vertex_scalar_with_inactive_face.dat" );
7031   QVERIFY( mesh->isValid() );
7032   mesh->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
7033   QgsProject::instance()->addMapLayer( mesh );
7034   QgsMeshLayer *mesh2 = new QgsMeshLayer( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle.2dm", QStringLiteral( "mesh2" ), QStringLiteral( "mdal" ) );
7035   mesh2->dataProvider()->addDataset( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle_vertex_scalar_with_inactive_face.dat" );
7036   QVERIFY( mesh2->isValid() );
7037   mesh2->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
7038   QgsProject::instance()->addMapLayer( mesh2 );
7039 
7040   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
7041   {
7042     // non optional
7043     QgsProcessingParameterMeshLayer param( QStringLiteral( "mesh" ), QStringLiteral( "mesh" ), false );
7044 
7045     QgsProcessingMeshLayerWidgetWrapper wrapper( &param, type );
7046 
7047     QgsProcessingContext context;
7048     QWidget *w = wrapper.createWrappedWidget( context );
7049 
7050     QSignalSpy spy( &wrapper, &QgsProcessingMeshLayerWidgetWrapper::widgetValueHasChanged );
7051     wrapper.setWidgetValue( QStringLiteral( "bb" ), context );
7052 
7053     switch ( type )
7054     {
7055       case QgsProcessingGui::Standard:
7056       case QgsProcessingGui::Batch:
7057       case QgsProcessingGui::Modeler:
7058         QCOMPARE( spy.count(), 1 );
7059         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "bb" ) );
7060         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
7061         wrapper.setWidgetValue( QStringLiteral( "aa" ), context );
7062         QCOMPARE( spy.count(), 2 );
7063         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "aa" ) );
7064         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "aa" ) );
7065         break;
7066     }
7067 
7068     delete w;
7069 
7070     // with project
7071     QgsProcessingParameterWidgetContext widgetContext;
7072     widgetContext.setProject( QgsProject::instance() );
7073     context.setProject( QgsProject::instance() );
7074 
7075     QgsProcessingMapLayerWidgetWrapper wrapper2( &param, type );
7076     wrapper2.setWidgetContext( widgetContext );
7077     w = wrapper2.createWrappedWidget( context );
7078 
7079     QSignalSpy spy2( &wrapper2, &QgsProcessingMeshLayerWidgetWrapper::widgetValueHasChanged );
7080     wrapper2.setWidgetValue( QStringLiteral( "bb" ), context );
7081     QCOMPARE( spy2.count(), 1 );
7082     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "bb" ) );
7083     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
7084     wrapper2.setWidgetValue( QStringLiteral( "mesh2" ), context );
7085     QCOMPARE( spy2.count(), 2 );
7086     QCOMPARE( wrapper2.widgetValue().toString(), mesh2->id() );
7087     switch ( type )
7088     {
7089       case QgsProcessingGui::Standard:
7090       case QgsProcessingGui::Batch:
7091         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "mesh2 [EPSG:4326]" ) );
7092         break;
7093       case QgsProcessingGui::Modeler:
7094         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "mesh2" ) );
7095         break;
7096     }
7097 
7098     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "mesh2" ) );
7099 
7100     // check signal
7101     static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->setLayer( mesh );
7102     QCOMPARE( spy2.count(), 3 );
7103     QCOMPARE( wrapper2.widgetValue().toString(), mesh->id() );
7104     switch ( type )
7105     {
7106       case QgsProcessingGui::Standard:
7107       case QgsProcessingGui::Batch:
7108         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "mesh1 [EPSG:4326]" ) );
7109         break;
7110 
7111       case QgsProcessingGui::Modeler:
7112         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "mesh1" ) );
7113         break;
7114     }
7115     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "mesh1" ) );
7116 
7117     delete w;
7118 
7119     // optional
7120     QgsProcessingParameterMeshLayer param2( QStringLiteral( "mesh" ), QStringLiteral( "mesh" ), QVariant(), true );
7121     QgsProcessingMeshLayerWidgetWrapper wrapper3( &param2, type );
7122     wrapper3.setWidgetContext( widgetContext );
7123     w = wrapper3.createWrappedWidget( context );
7124 
7125     QSignalSpy spy3( &wrapper3, &QgsProcessingMeshLayerWidgetWrapper::widgetValueHasChanged );
7126     wrapper3.setWidgetValue( QStringLiteral( "bb" ), context );
7127     QCOMPARE( spy3.count(), 1 );
7128     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "bb" ) );
7129     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
7130     wrapper3.setWidgetValue( QStringLiteral( "mesh2" ), context );
7131     QCOMPARE( spy3.count(), 2 );
7132     QCOMPARE( wrapper3.widgetValue().toString(), mesh2->id() );
7133     switch ( type )
7134     {
7135       case QgsProcessingGui::Standard:
7136       case QgsProcessingGui::Batch:
7137         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "mesh2 [EPSG:4326]" ) );
7138         break;
7139       case QgsProcessingGui::Modeler:
7140         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "mesh2" ) );
7141         break;
7142     }
7143     wrapper3.setWidgetValue( QVariant(), context );
7144     QCOMPARE( spy3.count(), 3 );
7145     QVERIFY( !wrapper3.widgetValue().isValid() );
7146     delete w;
7147 
7148 
7149     QLabel *l = wrapper.createWrappedLabel();
7150     if ( wrapper.type() != QgsProcessingGui::Batch )
7151     {
7152       QVERIFY( l );
7153       QCOMPARE( l->text(), QStringLiteral( "mesh" ) );
7154       QCOMPARE( l->toolTip(), param.toolTip() );
7155       delete l;
7156     }
7157     else
7158     {
7159       QVERIFY( !l );
7160     }
7161   };
7162 
7163   // standard wrapper
7164   testWrapper( QgsProcessingGui::Standard );
7165 
7166   // batch wrapper
7167   testWrapper( QgsProcessingGui::Batch );
7168 
7169   // modeler wrapper
7170   testWrapper( QgsProcessingGui::Modeler );
7171 }
7172 
paramConfigWidget()7173 void TestProcessingGui::paramConfigWidget()
7174 {
7175   QgsProcessingContext context;
7176   QgsProcessingParameterWidgetContext widgetContext;
7177   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "string" ), context, widgetContext );
7178   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
7179   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7180   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
7181   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7182 
7183   // using a parameter definition as initial values
7184   def->setDescription( QStringLiteral( "test desc" ) );
7185   def->setFlags( QgsProcessingParameterDefinition::FlagOptional );
7186   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "string" ), context, widgetContext, def.get() );
7187   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7188   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7189   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7190   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
7191   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7192   def->setFlags( QgsProcessingParameterDefinition::FlagAdvanced );
7193   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "string" ), context, widgetContext, def.get() );
7194   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7195   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7196   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7197   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
7198   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
7199 }
7200 
testMapThemeWrapper()7201 void TestProcessingGui::testMapThemeWrapper()
7202 {
7203   // add some themes to the project
7204   QgsProject p;
7205   p.mapThemeCollection()->insert( QStringLiteral( "aa" ), QgsMapThemeCollection::MapThemeRecord() );
7206   p.mapThemeCollection()->insert( QStringLiteral( "bb" ), QgsMapThemeCollection::MapThemeRecord() );
7207 
7208   QCOMPARE( p.mapThemeCollection()->mapThemes(), QStringList() << QStringLiteral( "aa" ) << QStringLiteral( "bb" ) );
7209 
7210   auto testWrapper = [&p]( QgsProcessingGui::WidgetType type )
7211   {
7212     // non optional, no existing themes
7213     QgsProcessingParameterMapTheme param( QStringLiteral( "theme" ), QStringLiteral( "theme" ), false );
7214 
7215     QgsProcessingMapThemeWidgetWrapper wrapper( &param, type );
7216 
7217     QgsProcessingContext context;
7218     QWidget *w = wrapper.createWrappedWidget( context );
7219 
7220     QSignalSpy spy( &wrapper, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
7221     wrapper.setWidgetValue( QStringLiteral( "bb" ), context );
7222 
7223     switch ( type )
7224     {
7225       case QgsProcessingGui::Standard:
7226       case QgsProcessingGui::Batch:
7227         // batch or standard mode, only valid themes can be set!
7228         QCOMPARE( spy.count(), 0 );
7229         QVERIFY( !wrapper.widgetValue().isValid() );
7230         QCOMPARE( static_cast< QComboBox * >( wrapper.wrappedWidget() )->currentIndex(), -1 );
7231         wrapper.setWidgetValue( QStringLiteral( "aa" ), context );
7232         QCOMPARE( spy.count(), 0 );
7233         QVERIFY( !wrapper.widgetValue().isValid() );
7234         QCOMPARE( static_cast< QComboBox * >( wrapper.wrappedWidget() )->currentIndex(), -1 );
7235         break;
7236 
7237       case QgsProcessingGui::Modeler:
7238         QCOMPARE( spy.count(), 1 );
7239         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "bb" ) );
7240         QCOMPARE( static_cast< QComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
7241         wrapper.setWidgetValue( QStringLiteral( "aa" ), context );
7242         QCOMPARE( spy.count(), 2 );
7243         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "aa" ) );
7244         QCOMPARE( static_cast< QComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "aa" ) );
7245         break;
7246     }
7247 
7248     delete w;
7249 
7250     // with project
7251     QgsProcessingParameterWidgetContext widgetContext;
7252     widgetContext.setProject( &p );
7253 
7254     QgsProcessingMapThemeWidgetWrapper wrapper2( &param, type );
7255     wrapper2.setWidgetContext( widgetContext );
7256     w = wrapper2.createWrappedWidget( context );
7257 
7258     QSignalSpy spy2( &wrapper2, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
7259     wrapper2.setWidgetValue( QStringLiteral( "bb" ), context );
7260     QCOMPARE( spy2.count(), 1 );
7261     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "bb" ) );
7262     QCOMPARE( static_cast< QComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
7263     wrapper2.setWidgetValue( QStringLiteral( "aa" ), context );
7264     QCOMPARE( spy2.count(), 2 );
7265     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "aa" ) );
7266     QCOMPARE( static_cast< QComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "aa" ) );
7267 
7268     // check signal
7269     static_cast< QComboBox * >( wrapper2.wrappedWidget() )->setCurrentIndex( 2 );
7270     QCOMPARE( spy2.count(), 3 );
7271 
7272     delete w;
7273 
7274     // optional
7275     QgsProcessingParameterMapTheme param2( QStringLiteral( "theme" ), QStringLiteral( "theme" ), true );
7276     QgsProcessingMapThemeWidgetWrapper wrapper3( &param2, type );
7277     wrapper3.setWidgetContext( widgetContext );
7278     w = wrapper3.createWrappedWidget( context );
7279 
7280     QSignalSpy spy3( &wrapper3, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
7281     wrapper3.setWidgetValue( QStringLiteral( "bb" ), context );
7282     QCOMPARE( spy3.count(), 1 );
7283     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "bb" ) );
7284     QCOMPARE( static_cast< QComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
7285     wrapper3.setWidgetValue( QStringLiteral( "aa" ), context );
7286     QCOMPARE( spy3.count(), 2 );
7287     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "aa" ) );
7288     QCOMPARE( static_cast< QComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "aa" ) );
7289     wrapper3.setWidgetValue( QVariant(), context );
7290     QCOMPARE( spy3.count(), 3 );
7291     QVERIFY( !wrapper3.widgetValue().isValid() );
7292     delete w;
7293 
7294 
7295     QLabel *l = wrapper.createWrappedLabel();
7296     if ( wrapper.type() != QgsProcessingGui::Batch )
7297     {
7298       QVERIFY( l );
7299       QCOMPARE( l->text(), QStringLiteral( "theme" ) );
7300       QCOMPARE( l->toolTip(), param.toolTip() );
7301       delete l;
7302     }
7303     else
7304     {
7305       QVERIFY( !l );
7306     }
7307   };
7308 
7309   // standard wrapper
7310   testWrapper( QgsProcessingGui::Standard );
7311 
7312   // batch wrapper
7313   testWrapper( QgsProcessingGui::Batch );
7314 
7315   // modeler wrapper
7316   testWrapper( QgsProcessingGui::Modeler );
7317 
7318 
7319   // config widget
7320   QgsProcessingParameterWidgetContext widgetContext;
7321   widgetContext.setProject( &p );
7322   QgsProcessingContext context;
7323   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "maptheme" ), context, widgetContext );
7324   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
7325   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7326   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
7327   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7328   QVERIFY( !static_cast< QgsProcessingParameterMapTheme * >( def.get() )->defaultValue().isValid() );
7329 
7330   // using a parameter definition as initial values
7331   QgsProcessingParameterMapTheme themeParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "aaa" ), false );
7332   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "maptheme" ), context, widgetContext, &themeParam );
7333   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7334   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7335   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7336   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
7337   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7338   QCOMPARE( static_cast< QgsProcessingParameterMapTheme * >( def.get() )->defaultValue().toString(), QStringLiteral( "aaa" ) );
7339   themeParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
7340   themeParam.setDefaultValue( QStringLiteral( "xxx" ) );
7341   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "maptheme" ), context, widgetContext, &themeParam );
7342   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7343   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7344   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7345   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
7346   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
7347   QCOMPARE( static_cast< QgsProcessingParameterMapTheme * >( def.get() )->defaultValue().toString(), QStringLiteral( "xxx" ) );
7348   themeParam.setDefaultValue( QVariant() );
7349   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "maptheme" ), context, widgetContext, &themeParam );
7350   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7351   QVERIFY( !static_cast< QgsProcessingParameterMapTheme * >( def.get() )->defaultValue().isValid() );
7352 }
7353 
testDateTimeWrapper()7354 void TestProcessingGui::testDateTimeWrapper()
7355 {
7356   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
7357   {
7358     // non optional, no existing themes
7359     QgsProcessingParameterDateTime param( QStringLiteral( "datetime" ), QStringLiteral( "datetime" ), QgsProcessingParameterDateTime::DateTime, QVariant(), false );
7360 
7361     QgsProcessingDateTimeWidgetWrapper wrapper( &param, type );
7362 
7363     QgsProcessingContext context;
7364     QWidget *w = wrapper.createWrappedWidget( context );
7365 
7366     QSignalSpy spy( &wrapper, &QgsProcessingDateTimeWidgetWrapper::widgetValueHasChanged );
7367     // not a date value
7368     wrapper.setWidgetValue( QStringLiteral( "bb" ), context );
7369     QCOMPARE( spy.count(), 0 );
7370     // not optional, so an invalid value gets a date anyway...
7371     QVERIFY( wrapper.widgetValue().isValid() );
7372     wrapper.setWidgetValue( QStringLiteral( "2019-08-07" ), context );
7373     QCOMPARE( spy.count(), 1 );
7374     QVERIFY( wrapper.widgetValue().isValid() );
7375     QCOMPARE( wrapper.widgetValue().toDateTime(), QDateTime( QDate( 2019, 8, 7 ), QTime( 0, 0, 0 ) ) );
7376     QCOMPARE( static_cast< QgsDateTimeEdit * >( wrapper.wrappedWidget() )->dateTime(), QDateTime( QDate( 2019, 8, 7 ), QTime( 0, 0, 0 ) ) );
7377     wrapper.setWidgetValue( QStringLiteral( "2019-08-07" ), context );
7378     QCOMPARE( spy.count(), 1 );
7379 
7380     // check signal
7381     static_cast< QgsDateTimeEdit * >( wrapper.wrappedWidget() )->setDateTime( QDateTime( QDate( 2019, 8, 9 ), QTime( 0, 0, 0 ) ) );
7382     QCOMPARE( spy.count(), 2 );
7383 
7384     delete w;
7385 
7386     // optional
7387     QgsProcessingParameterDateTime param2( QStringLiteral( "datetime" ), QStringLiteral( "datetime" ), QgsProcessingParameterDateTime::DateTime, QVariant(), true );
7388     QgsProcessingDateTimeWidgetWrapper wrapper3( &param2, type );
7389     w = wrapper3.createWrappedWidget( context );
7390     QVERIFY( !wrapper3.widgetValue().isValid() );
7391     QSignalSpy spy3( &wrapper3, &QgsProcessingDateTimeWidgetWrapper::widgetValueHasChanged );
7392     wrapper3.setWidgetValue( QStringLiteral( "bb" ), context );
7393     QCOMPARE( spy3.count(), 0 );
7394     QVERIFY( !wrapper3.widgetValue().isValid() );
7395     QVERIFY( !static_cast< QgsDateTimeEdit * >( wrapper3.wrappedWidget() )->dateTime().isValid() );
7396     wrapper3.setWidgetValue( QStringLiteral( "2019-03-20" ), context );
7397     QCOMPARE( spy3.count(), 1 );
7398     QCOMPARE( wrapper3.widgetValue().toDateTime(), QDateTime( QDate( 2019, 3, 20 ), QTime( 0, 0, 0 ) ) );
7399     QCOMPARE( static_cast< QgsDateTimeEdit * >( wrapper3.wrappedWidget() )->dateTime(), QDateTime( QDate( 2019, 3, 20 ), QTime( 0, 0, 0 ) ) );
7400     wrapper3.setWidgetValue( QVariant(), context );
7401     QCOMPARE( spy3.count(), 2 );
7402     QVERIFY( !wrapper3.widgetValue().isValid() );
7403     delete w;
7404 
7405     // date mode
7406     QgsProcessingParameterDateTime param3( QStringLiteral( "datetime" ), QStringLiteral( "datetime" ), QgsProcessingParameterDateTime::Date, QVariant(), true );
7407     QgsProcessingDateTimeWidgetWrapper wrapper4( &param3, type );
7408     w = wrapper4.createWrappedWidget( context );
7409     QVERIFY( !wrapper4.widgetValue().isValid() );
7410     QSignalSpy spy4( &wrapper4, &QgsProcessingDateTimeWidgetWrapper::widgetValueHasChanged );
7411     wrapper4.setWidgetValue( QStringLiteral( "bb" ), context );
7412     QCOMPARE( spy4.count(), 0 );
7413     QVERIFY( !wrapper4.widgetValue().isValid() );
7414     QVERIFY( !static_cast< QgsDateEdit * >( wrapper4.wrappedWidget() )->date().isValid() );
7415     wrapper4.setWidgetValue( QStringLiteral( "2019-03-20" ), context );
7416     QCOMPARE( spy4.count(), 1 );
7417     QCOMPARE( wrapper4.widgetValue().toDate(), QDate( 2019, 3, 20 ) );
7418     QCOMPARE( static_cast< QgsDateEdit * >( wrapper4.wrappedWidget() )->date(), QDate( 2019, 3, 20 ) );
7419     wrapper4.setWidgetValue( QDate( 2020, 1, 3 ), context );
7420     QCOMPARE( spy4.count(), 2 );
7421     QCOMPARE( wrapper4.widgetValue().toDate(), QDate( 2020, 1, 3 ) );
7422     QCOMPARE( static_cast< QgsDateEdit * >( wrapper4.wrappedWidget() )->date(), QDate( 2020, 1, 3 ) );
7423     wrapper4.setWidgetValue( QVariant(), context );
7424     QCOMPARE( spy4.count(), 3 );
7425     QVERIFY( !wrapper4.widgetValue().isValid() );
7426     delete w;
7427 
7428     // time mode
7429     QgsProcessingParameterDateTime param4( QStringLiteral( "datetime" ), QStringLiteral( "datetime" ), QgsProcessingParameterDateTime::Time, QVariant(), true );
7430     QgsProcessingDateTimeWidgetWrapper wrapper5( &param4, type );
7431     w = wrapper5.createWrappedWidget( context );
7432     QVERIFY( !wrapper5.widgetValue().isValid() );
7433     QSignalSpy spy5( &wrapper5, &QgsProcessingDateTimeWidgetWrapper::widgetValueHasChanged );
7434     wrapper5.setWidgetValue( QStringLiteral( "bb" ), context );
7435     QCOMPARE( spy5.count(), 0 );
7436     QVERIFY( !wrapper5.widgetValue().isValid() );
7437     QVERIFY( !static_cast< QgsTimeEdit * >( wrapper5.wrappedWidget() )->time().isValid() );
7438     wrapper5.setWidgetValue( QStringLiteral( "11:34:56" ), context );
7439     QCOMPARE( spy5.count(), 1 );
7440     QCOMPARE( wrapper5.widgetValue().toTime(), QTime( 11, 34, 56 ) );
7441     QCOMPARE( static_cast< QgsTimeEdit * >( wrapper5.wrappedWidget() )->time(), QTime( 11, 34, 56 ) );
7442     wrapper5.setWidgetValue( QTime( 9, 34, 56 ), context );
7443     QCOMPARE( spy5.count(), 2 );
7444     QCOMPARE( wrapper5.widgetValue().toTime(), QTime( 9, 34, 56 ) );
7445     QCOMPARE( static_cast< QgsTimeEdit * >( wrapper5.wrappedWidget() )->time(), QTime( 9, 34, 56 ) );
7446     wrapper5.setWidgetValue( QVariant(), context );
7447     QCOMPARE( spy5.count(), 3 );
7448     QVERIFY( !wrapper5.widgetValue().isValid() );
7449     delete w;
7450 
7451     QLabel *l = wrapper.createWrappedLabel();
7452     if ( wrapper.type() != QgsProcessingGui::Batch )
7453     {
7454       QVERIFY( l );
7455       QCOMPARE( l->text(), QStringLiteral( "datetime" ) );
7456       QCOMPARE( l->toolTip(), param.toolTip() );
7457       delete l;
7458     }
7459     else
7460     {
7461       QVERIFY( !l );
7462     }
7463   };
7464 
7465   // standard wrapper
7466   testWrapper( QgsProcessingGui::Standard );
7467 
7468   // batch wrapper
7469   testWrapper( QgsProcessingGui::Batch );
7470 
7471   // modeler wrapper
7472   testWrapper( QgsProcessingGui::Modeler );
7473 
7474   // config widget
7475   QgsProcessingParameterWidgetContext widgetContext;
7476   QgsProcessingContext context;
7477   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "datetime" ), context, widgetContext );
7478   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
7479   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7480   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
7481   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7482   QVERIFY( !static_cast< QgsProcessingParameterDateTime * >( def.get() )->defaultValue().isValid() );
7483 
7484   // using a parameter definition as initial values
7485   QgsProcessingParameterDateTime datetimeParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QgsProcessingParameterDateTime::Date, QVariant(), false );
7486   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "datetime" ), context, widgetContext, &datetimeParam );
7487   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7488   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7489   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7490   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
7491   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7492   QCOMPARE( static_cast< QgsProcessingParameterDateTime * >( def.get() )->dataType(), QgsProcessingParameterDateTime::Date );
7493   datetimeParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
7494   datetimeParam.setDefaultValue( QStringLiteral( "xxx" ) );
7495   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "datetime" ), context, widgetContext, &datetimeParam );
7496   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7497   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7498   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7499   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
7500   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
7501   QCOMPARE( static_cast< QgsProcessingParameterDateTime * >( def.get() )->dataType(), QgsProcessingParameterDateTime::Date );
7502 }
7503 
testProviderConnectionWrapper()7504 void TestProcessingGui::testProviderConnectionWrapper()
7505 {
7506   // register some connections
7507   QgsProviderMetadata *md = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) );
7508   QgsAbstractProviderConnection *conn = md->createConnection( QStringLiteral( "test uri" ), QVariantMap() );
7509   md->saveConnection( conn, QStringLiteral( "aa" ) );
7510   md->saveConnection( conn, QStringLiteral( "bb" ) );
7511 
7512   auto testWrapper = []( QgsProcessingGui::WidgetType type )
7513   {
7514     QgsProcessingParameterProviderConnection param( QStringLiteral( "conn" ), QStringLiteral( "connection" ), QStringLiteral( "ogr" ), false );
7515 
7516     QgsProcessingProviderConnectionWidgetWrapper wrapper( &param, type );
7517 
7518     QgsProcessingContext context;
7519     QWidget *w = wrapper.createWrappedWidget( context );
7520 
7521     QSignalSpy spy( &wrapper, &QgsProcessingProviderConnectionWidgetWrapper::widgetValueHasChanged );
7522     wrapper.setWidgetValue( QStringLiteral( "bb" ), context );
7523     QCOMPARE( spy.count(), 1 );
7524     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "bb" ) );
7525     QCOMPARE( static_cast< QgsProviderConnectionComboBox * >( wrapper.wrappedWidget() )->currentConnection(), QStringLiteral( "bb" ) );
7526     wrapper.setWidgetValue( QStringLiteral( "bb" ), context );
7527     QCOMPARE( spy.count(), 1 );
7528     wrapper.setWidgetValue( QStringLiteral( "aa" ), context );
7529     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "aa" ) );
7530     QCOMPARE( spy.count(), 2 );
7531 
7532     switch ( type )
7533     {
7534       case QgsProcessingGui::Standard:
7535       case QgsProcessingGui::Batch:
7536       {
7537         // batch or standard mode, only valid connections can be set!
7538         // not valid
7539         wrapper.setWidgetValue( QStringLiteral( "cc" ), context );
7540         QCOMPARE( spy.count(), 3 );
7541         QVERIFY( !wrapper.widgetValue().isValid() );
7542         QCOMPARE( static_cast< QComboBox * >( wrapper.wrappedWidget() )->currentIndex(), -1 );
7543         break;
7544 
7545       }
7546       case QgsProcessingGui::Modeler:
7547         // invalid connections permitted
7548         wrapper.setWidgetValue( QStringLiteral( "cc" ), context );
7549         QCOMPARE( spy.count(), 3 );
7550         QCOMPARE( static_cast< QgsProviderConnectionComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "cc" ) );
7551         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "cc" ) );
7552         wrapper.setWidgetValue( QStringLiteral( "aa" ), context );
7553         QCOMPARE( spy.count(), 4 );
7554         QCOMPARE( static_cast< QgsProviderConnectionComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "aa" ) );
7555         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "aa" ) );
7556         break;
7557     }
7558 
7559     delete w;
7560     // optional
7561     QgsProcessingParameterProviderConnection param2( QStringLiteral( "conn" ), QStringLiteral( "connection" ), QStringLiteral( "ogr" ), QVariant(), true );
7562     QgsProcessingProviderConnectionWidgetWrapper wrapper3( &param2, type );
7563     w = wrapper3.createWrappedWidget( context );
7564 
7565     QSignalSpy spy3( &wrapper3, &QgsProcessingEnumWidgetWrapper::widgetValueHasChanged );
7566     wrapper3.setWidgetValue( QStringLiteral( "bb" ), context );
7567     QCOMPARE( spy3.count(), 1 );
7568     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "bb" ) );
7569     QCOMPARE( static_cast< QComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
7570     wrapper3.setWidgetValue( QStringLiteral( "aa" ), context );
7571     QCOMPARE( spy3.count(), 2 );
7572     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "aa" ) );
7573     QCOMPARE( static_cast< QComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "aa" ) );
7574     wrapper3.setWidgetValue( QVariant(), context );
7575     QCOMPARE( spy3.count(), 3 );
7576     QVERIFY( !wrapper3.widgetValue().isValid() );
7577     delete w;
7578     QLabel *l = wrapper.createWrappedLabel();
7579     if ( wrapper.type() != QgsProcessingGui::Batch )
7580     {
7581       QVERIFY( l );
7582       QCOMPARE( l->text(), QStringLiteral( "connection" ) );
7583       QCOMPARE( l->toolTip(), param.toolTip() );
7584       delete l;
7585     }
7586     else
7587     {
7588       QVERIFY( !l );
7589     }
7590   };
7591 
7592   // standard wrapper
7593   testWrapper( QgsProcessingGui::Standard );
7594 
7595   // batch wrapper
7596   testWrapper( QgsProcessingGui::Batch );
7597 
7598   // modeler wrapper
7599   testWrapper( QgsProcessingGui::Modeler );
7600 
7601   // config widget
7602   QgsProcessingParameterWidgetContext widgetContext;
7603   QgsProcessingContext context;
7604   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "providerconnection" ), context, widgetContext );
7605   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
7606   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7607   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
7608   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7609   QVERIFY( !static_cast< QgsProcessingParameterProviderConnection * >( def.get() )->defaultValue().isValid() );
7610 
7611   // using a parameter definition as initial values
7612   QgsProcessingParameterProviderConnection connParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "spatialite" ), QStringLiteral( "aaa" ), false );
7613   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "providerconnection" ), context, widgetContext, &connParam );
7614   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7615   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7616   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7617   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
7618   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7619   QCOMPARE( static_cast< QgsProcessingParameterProviderConnection * >( def.get() )->defaultValue().toString(), QStringLiteral( "aaa" ) );
7620   QCOMPARE( static_cast< QgsProcessingParameterProviderConnection * >( def.get() )->providerId(), QStringLiteral( "spatialite" ) );
7621   connParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
7622   connParam.setDefaultValue( QStringLiteral( "xxx" ) );
7623   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "providerconnection" ), context, widgetContext, &connParam );
7624   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7625   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7626   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7627   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
7628   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
7629   QCOMPARE( static_cast< QgsProcessingParameterProviderConnection * >( def.get() )->defaultValue().toString(), QStringLiteral( "xxx" ) );
7630   connParam.setDefaultValue( QVariant() );
7631   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "providerconnection" ), context, widgetContext, &connParam );
7632   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7633   QVERIFY( !static_cast< QgsProcessingParameterProviderConnection * >( def.get() )->defaultValue().isValid() );
7634 }
7635 
testDatabaseSchemaWrapper()7636 void TestProcessingGui::testDatabaseSchemaWrapper()
7637 {
7638 #ifdef ENABLE_PGTEST
7639   // register some connections
7640   QgsProviderMetadata *md = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "postgres" ) );
7641 
7642   QString dbConn = getenv( "QGIS_PGTEST_DB" );
7643   if ( dbConn.isEmpty() )
7644   {
7645     dbConn = "service=\"qgis_test\"";
7646   }
7647   QgsAbstractProviderConnection *conn = md->createConnection( QStringLiteral( "%1 sslmode=disable" ).arg( dbConn ), QVariantMap() );
7648   md->saveConnection( conn, QStringLiteral( "aa" ) );
7649 
7650   const QStringList schemas = dynamic_cast<QgsAbstractDatabaseProviderConnection *>( conn )->schemas();
7651   QVERIFY( !schemas.isEmpty() );
7652 
7653   auto testWrapper = [&schemas]( QgsProcessingGui::WidgetType type )
7654   {
7655     QgsProcessingParameterProviderConnection connParam( QStringLiteral( "conn" ), QStringLiteral( "connection" ), QStringLiteral( "postgres" ), QVariant(), true );
7656     TestLayerWrapper connWrapper( &connParam );
7657 
7658     QgsProcessingParameterDatabaseSchema param( QStringLiteral( "schema" ), QStringLiteral( "schema" ), QStringLiteral( "conn" ), QVariant(), false );
7659 
7660     QgsProcessingDatabaseSchemaWidgetWrapper wrapper( &param, type );
7661 
7662     QgsProcessingContext context;
7663     QWidget *w = wrapper.createWrappedWidget( context );
7664     // no connection associated yet
7665     QCOMPARE( static_cast< QgsDatabaseSchemaComboBox * >( wrapper.wrappedWidget() )->comboBox()->count(), 0 );
7666 
7667     // Set the parent widget connection value
7668     connWrapper.setWidgetValue( QStringLiteral( "aa" ), context );
7669     wrapper.setParentConnectionWrapperValue( &connWrapper );
7670 
7671     // now we should have schemas available
7672     QCOMPARE( static_cast< QgsDatabaseSchemaComboBox * >( wrapper.wrappedWidget() )->comboBox()->count(), schemas.count() );
7673 
7674     QSignalSpy spy( &wrapper, &QgsProcessingDatabaseSchemaWidgetWrapper::widgetValueHasChanged );
7675     wrapper.setWidgetValue( QStringLiteral( "qgis_test" ), context );
7676     QCOMPARE( spy.count(), 1 );
7677     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "qgis_test" ) );
7678     QCOMPARE( static_cast< QgsDatabaseSchemaComboBox * >( wrapper.wrappedWidget() )->currentSchema(), QStringLiteral( "qgis_test" ) );
7679     wrapper.setWidgetValue( QStringLiteral( "public" ), context );
7680     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "public" ) );
7681     QCOMPARE( static_cast< QgsDatabaseSchemaComboBox * >( wrapper.wrappedWidget() )->currentSchema(), QStringLiteral( "public" ) );
7682     QCOMPARE( spy.count(), 2 );
7683     wrapper.setWidgetValue( QStringLiteral( "public" ), context );
7684     QCOMPARE( spy.count(), 2 );
7685 
7686     switch ( type )
7687     {
7688       case QgsProcessingGui::Standard:
7689       case QgsProcessingGui::Batch:
7690       {
7691         // batch or standard mode, only valid schemas can be set!
7692         // not valid
7693         wrapper.setWidgetValue( QStringLiteral( "cc" ), context );
7694         QCOMPARE( spy.count(), 3 );
7695         QVERIFY( !wrapper.widgetValue().isValid() );
7696         QCOMPARE( static_cast< QgsDatabaseSchemaComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentIndex(), -1 );
7697         break;
7698 
7699       }
7700       case QgsProcessingGui::Modeler:
7701         // invalid schemas permitted
7702         wrapper.setWidgetValue( QStringLiteral( "cc" ), context );
7703         QCOMPARE( spy.count(), 3 );
7704         QCOMPARE( static_cast< QgsDatabaseSchemaComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "cc" ) );
7705         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "cc" ) );
7706         wrapper.setWidgetValue( QStringLiteral( "aa" ), context );
7707         QCOMPARE( spy.count(), 4 );
7708         QCOMPARE( static_cast< QgsDatabaseSchemaComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "aa" ) );
7709         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "aa" ) );
7710         break;
7711     }
7712 
7713     // make sure things are ok if connection is changed back to nothing
7714     connWrapper.setWidgetValue( QVariant(), context );
7715     wrapper.setParentConnectionWrapperValue( &connWrapper );
7716     QCOMPARE( static_cast< QgsDatabaseSchemaComboBox * >( wrapper.wrappedWidget() )->comboBox()->count(), 0 );
7717 
7718     switch ( type )
7719     {
7720       case QgsProcessingGui::Standard:
7721       case QgsProcessingGui::Batch:
7722       {
7723         QCOMPARE( spy.count(), 3 );
7724         break;
7725       }
7726 
7727       case QgsProcessingGui::Modeler:
7728         QCOMPARE( spy.count(), 5 );
7729         break;
7730     }
7731     QVERIFY( !wrapper.widgetValue().isValid() );
7732 
7733     wrapper.setWidgetValue( QStringLiteral( "qgis_test" ), context );
7734     switch ( type )
7735     {
7736       case QgsProcessingGui::Standard:
7737       case QgsProcessingGui::Batch:
7738       {
7739         QVERIFY( !wrapper.widgetValue().isValid() );
7740         break;
7741       }
7742 
7743       case QgsProcessingGui::Modeler:
7744         // invalid schemas permitted
7745         QCOMPARE( spy.count(), 6 );
7746         QCOMPARE( static_cast< QgsDatabaseSchemaComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "qgis_test" ) );
7747         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "qgis_test" ) );
7748 
7749         break;
7750     }
7751     delete w;
7752 
7753     connWrapper.setWidgetValue( QStringLiteral( "aa" ), context );
7754 
7755     // optional
7756     QgsProcessingParameterDatabaseSchema param2( QStringLiteral( "schema" ), QStringLiteral( "schema" ), QStringLiteral( "conn" ), QVariant(), true );
7757     QgsProcessingDatabaseSchemaWidgetWrapper wrapper3( &param2, type );
7758     w = wrapper3.createWrappedWidget( context );
7759 
7760     wrapper3.setParentConnectionWrapperValue( &connWrapper );
7761 
7762     QSignalSpy spy3( &wrapper3, &QgsProcessingDatabaseSchemaWidgetWrapper::widgetValueHasChanged );
7763     wrapper3.setWidgetValue( QStringLiteral( "qgis_test" ), context );
7764     QCOMPARE( spy3.count(), 1 );
7765     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "qgis_test" ) );
7766     QCOMPARE( static_cast< QgsDatabaseSchemaComboBox * >( wrapper3.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "qgis_test" ) );
7767     wrapper3.setWidgetValue( QStringLiteral( "public" ), context );
7768     QCOMPARE( spy3.count(), 2 );
7769     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "public" ) );
7770     QCOMPARE( static_cast< QgsDatabaseSchemaComboBox * >( wrapper3.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "public" ) );
7771     wrapper3.setWidgetValue( QVariant(), context );
7772     QCOMPARE( spy3.count(), 3 );
7773     QVERIFY( !wrapper3.widgetValue().isValid() );
7774 
7775     delete w;
7776     QLabel *l = wrapper.createWrappedLabel();
7777     if ( wrapper.type() != QgsProcessingGui::Batch )
7778     {
7779       QVERIFY( l );
7780       QCOMPARE( l->text(), QStringLiteral( "schema" ) );
7781       QCOMPARE( l->toolTip(), param.toolTip() );
7782       delete l;
7783     }
7784     else
7785     {
7786       QVERIFY( !l );
7787     }
7788 
7789   };
7790 
7791   // standard wrapper
7792   testWrapper( QgsProcessingGui::Standard );
7793 
7794   // batch wrapper
7795   testWrapper( QgsProcessingGui::Batch );
7796 
7797   // modeler wrapper
7798   testWrapper( QgsProcessingGui::Modeler );
7799 
7800   // config widget
7801   QgsProcessingParameterWidgetContext widgetContext;
7802   QgsProcessingContext context;
7803   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databaseschema" ), context, widgetContext );
7804   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
7805   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7806   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
7807   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7808   QVERIFY( !static_cast< QgsProcessingParameterDatabaseSchema * >( def.get() )->defaultValue().isValid() );
7809 
7810   // using a parameter definition as initial values
7811   QgsProcessingParameterDatabaseSchema schemaParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "connparam" ), QStringLiteral( "aaa" ), false );
7812   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databaseschema" ), context, widgetContext, &schemaParam );
7813   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7814   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7815   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7816   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
7817   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
7818   QCOMPARE( static_cast< QgsProcessingParameterDatabaseSchema * >( def.get() )->defaultValue().toString(), QStringLiteral( "aaa" ) );
7819   QCOMPARE( static_cast< QgsProcessingParameterDatabaseSchema * >( def.get() )->parentConnectionParameterName(), QStringLiteral( "connparam" ) );
7820   schemaParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
7821   schemaParam.setDefaultValue( QStringLiteral( "xxx" ) );
7822   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databaseschema" ), context, widgetContext, &schemaParam );
7823   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7824   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
7825   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
7826   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
7827   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
7828   QCOMPARE( static_cast< QgsProcessingParameterDatabaseSchema * >( def.get() )->defaultValue().toString(), QStringLiteral( "xxx" ) );
7829   schemaParam.setDefaultValue( QVariant() );
7830   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databaseschema" ), context, widgetContext, &schemaParam );
7831   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
7832   QVERIFY( !static_cast< QgsProcessingParameterDatabaseSchema * >( def.get() )->defaultValue().isValid() );
7833 #endif
7834 }
7835 
testDatabaseTableWrapper()7836 void TestProcessingGui::testDatabaseTableWrapper()
7837 {
7838 #ifdef ENABLE_PGTEST
7839   // register some connections
7840   QgsProviderMetadata *md = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "postgres" ) );
7841 
7842   QString dbConn = getenv( "QGIS_PGTEST_DB" );
7843   if ( dbConn.isEmpty() )
7844   {
7845     dbConn = "service=\"qgis_test\"";
7846   }
7847   QgsAbstractProviderConnection *conn = md->createConnection( QStringLiteral( "%1 sslmode=disable" ).arg( dbConn ), QVariantMap() );
7848   md->saveConnection( conn, QStringLiteral( "aa" ) );
7849 
7850   const QList<QgsAbstractDatabaseProviderConnection::TableProperty> tables = dynamic_cast<QgsAbstractDatabaseProviderConnection *>( conn )->tables( QStringLiteral( "qgis_test" ) );
7851   QStringList tableNames;
7852   for ( const QgsAbstractDatabaseProviderConnection::TableProperty &prop : tables )
7853     tableNames << prop.tableName();
7854 
7855   QVERIFY( !tableNames.isEmpty() );
7856 
7857   auto testWrapper = [&tableNames]( QgsProcessingGui::WidgetType type )
7858   {
7859     QgsProcessingParameterProviderConnection connParam( QStringLiteral( "conn" ), QStringLiteral( "connection" ), QStringLiteral( "postgres" ), QVariant(), true );
7860     TestLayerWrapper connWrapper( &connParam );
7861     QgsProcessingParameterDatabaseSchema schemaParam( QStringLiteral( "schema" ), QStringLiteral( "schema" ), QStringLiteral( "connection" ), QVariant(), true );
7862     TestLayerWrapper schemaWrapper( &schemaParam );
7863 
7864     QgsProcessingParameterDatabaseTable param( QStringLiteral( "table" ), QStringLiteral( "table" ), QStringLiteral( "conn" ), QStringLiteral( "schema" ), QVariant(), false );
7865 
7866     QgsProcessingDatabaseTableWidgetWrapper wrapper( &param, type );
7867 
7868     QgsProcessingContext context;
7869     QWidget *w = wrapper.createWrappedWidget( context );
7870     // no connection associated yet
7871     QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->count(), 0 );
7872 
7873     // Set the parent widget connection value
7874     connWrapper.setWidgetValue( QStringLiteral( "aa" ), context );
7875     wrapper.setParentConnectionWrapperValue( &connWrapper );
7876     schemaWrapper.setWidgetValue( QStringLiteral( "qgis_test" ), context );
7877     wrapper.setParentSchemaWrapperValue( &schemaWrapper );
7878 
7879     // now we should have tables available
7880     QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->count(), tableNames.count() );
7881 
7882     QSignalSpy spy( &wrapper, &QgsProcessingDatabaseTableWidgetWrapper::widgetValueHasChanged );
7883     wrapper.setWidgetValue( QStringLiteral( "someData" ), context );
7884     QCOMPARE( spy.count(), 1 );
7885     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "someData" ) );
7886     QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->currentTable(), QStringLiteral( "someData" ) );
7887     wrapper.setWidgetValue( QStringLiteral( "some_poly_data" ), context );
7888     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "some_poly_data" ) );
7889     QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->currentTable(), QStringLiteral( "some_poly_data" ) );
7890     QCOMPARE( spy.count(), 2 );
7891     wrapper.setWidgetValue( QStringLiteral( "some_poly_data" ), context );
7892     QCOMPARE( spy.count(), 2 );
7893 
7894     switch ( type )
7895     {
7896       case QgsProcessingGui::Standard:
7897       case QgsProcessingGui::Batch:
7898       {
7899         // batch or standard mode, only valid tables can be set!
7900         // not valid
7901         wrapper.setWidgetValue( QStringLiteral( "cc" ), context );
7902         QCOMPARE( spy.count(), 3 );
7903         QVERIFY( !wrapper.widgetValue().isValid() );
7904         QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentIndex(), -1 );
7905         break;
7906 
7907       }
7908       case QgsProcessingGui::Modeler:
7909         // invalid tables permitted
7910         wrapper.setWidgetValue( QStringLiteral( "cc" ), context );
7911         QCOMPARE( spy.count(), 3 );
7912         QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "cc" ) );
7913         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "cc" ) );
7914         wrapper.setWidgetValue( QStringLiteral( "someData" ), context );
7915         QCOMPARE( spy.count(), 4 );
7916         QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "someData" ) );
7917         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "someData" ) );
7918         break;
7919     }
7920 
7921     // make sure things are ok if connection is changed back to nothing
7922     connWrapper.setWidgetValue( QVariant(), context );
7923     wrapper.setParentConnectionWrapperValue( &connWrapper );
7924     QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->count(), 0 );
7925 
7926     switch ( type )
7927     {
7928       case QgsProcessingGui::Standard:
7929       case QgsProcessingGui::Batch:
7930       {
7931         QCOMPARE( spy.count(), 3 );
7932         break;
7933       }
7934 
7935       case QgsProcessingGui::Modeler:
7936         QCOMPARE( spy.count(), 5 );
7937         break;
7938     }
7939     QVERIFY( !wrapper.widgetValue().isValid() );
7940 
7941     wrapper.setWidgetValue( QStringLiteral( "some_poly_data" ), context );
7942     switch ( type )
7943     {
7944       case QgsProcessingGui::Standard:
7945       case QgsProcessingGui::Batch:
7946       {
7947         QVERIFY( !wrapper.widgetValue().isValid() );
7948         break;
7949       }
7950 
7951       case QgsProcessingGui::Modeler:
7952         // invalid tables permitted
7953         QCOMPARE( spy.count(), 6 );
7954         QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "some_poly_data" ) );
7955         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "some_poly_data" ) );
7956 
7957         break;
7958     }
7959     delete w;
7960 
7961     connWrapper.setWidgetValue( QStringLiteral( "aa" ), context );
7962 
7963     // optional
7964     QgsProcessingParameterDatabaseTable param2( QStringLiteral( "table" ), QStringLiteral( "table" ), QStringLiteral( "conn" ), QStringLiteral( "schema" ), QVariant(), true );
7965     QgsProcessingDatabaseTableWidgetWrapper wrapper3( &param2, type );
7966     w = wrapper3.createWrappedWidget( context );
7967 
7968     wrapper3.setParentConnectionWrapperValue( &connWrapper );
7969     wrapper3.setParentSchemaWrapperValue( &schemaWrapper );
7970 
7971     QSignalSpy spy3( &wrapper3, &QgsProcessingDatabaseTableWidgetWrapper::widgetValueHasChanged );
7972     wrapper3.setWidgetValue( QStringLiteral( "someData" ), context );
7973     QCOMPARE( spy3.count(), 1 );
7974     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "someData" ) );
7975     QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper3.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "someData" ) );
7976     wrapper3.setWidgetValue( QStringLiteral( "some_poly_data" ), context );
7977     QCOMPARE( spy3.count(), 2 );
7978     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "some_poly_data" ) );
7979     QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper3.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "some_poly_data" ) );
7980     wrapper3.setWidgetValue( QVariant(), context );
7981     QCOMPARE( spy3.count(), 3 );
7982     QVERIFY( !wrapper3.widgetValue().isValid() );
7983 
7984     delete w;
7985 
7986     // allowing new table names
7987     QgsProcessingParameterDatabaseTable param3( QStringLiteral( "table" ), QStringLiteral( "table" ), QStringLiteral( "conn" ), QStringLiteral( "schema" ), QVariant(), false, true );
7988     QgsProcessingDatabaseTableWidgetWrapper wrapper4( &param3, type );
7989     w = wrapper4.createWrappedWidget( context );
7990 
7991     wrapper4.setParentConnectionWrapperValue( &connWrapper );
7992     wrapper4.setParentSchemaWrapperValue( &schemaWrapper );
7993 
7994     QSignalSpy spy4( &wrapper4, &QgsProcessingDatabaseTableWidgetWrapper::widgetValueHasChanged );
7995     wrapper4.setWidgetValue( QStringLiteral( "someData" ), context );
7996     QCOMPARE( spy4.count(), 1 );
7997     QCOMPARE( wrapper4.widgetValue().toString(), QStringLiteral( "someData" ) );
7998     QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper4.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "someData" ) );
7999     wrapper4.setWidgetValue( QStringLiteral( "some_poly_data" ), context );
8000     QCOMPARE( spy4.count(), 2 );
8001     QCOMPARE( wrapper4.widgetValue().toString(), QStringLiteral( "some_poly_data" ) );
8002     QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper4.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "some_poly_data" ) );
8003     wrapper4.setWidgetValue( QVariant(), context );
8004     QCOMPARE( spy4.count(), 3 );
8005     QVERIFY( !wrapper4.widgetValue().isValid() );
8006     // should always allow non existing table names
8007     wrapper4.setWidgetValue( QStringLiteral( "someDataxxxxxxxxxxxxxxxxxxxx" ), context );
8008     QCOMPARE( spy4.count(), 4 );
8009     QCOMPARE( wrapper4.widgetValue().toString(), QStringLiteral( "someDataxxxxxxxxxxxxxxxxxxxx" ) );
8010     QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper4.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "someDataxxxxxxxxxxxxxxxxxxxx" ) );
8011 
8012 
8013     delete w;
8014 
8015 
8016     QLabel *l = wrapper.createWrappedLabel();
8017     if ( wrapper.type() != QgsProcessingGui::Batch )
8018     {
8019       QVERIFY( l );
8020       QCOMPARE( l->text(), QStringLiteral( "table" ) );
8021       QCOMPARE( l->toolTip(), param.toolTip() );
8022       delete l;
8023     }
8024     else
8025     {
8026       QVERIFY( !l );
8027     }
8028   };
8029 
8030   // standard wrapper
8031   testWrapper( QgsProcessingGui::Standard );
8032 
8033   // batch wrapper
8034   testWrapper( QgsProcessingGui::Batch );
8035 
8036   // modeler wrapper
8037   testWrapper( QgsProcessingGui::Modeler );
8038 
8039   // config widget
8040   QgsProcessingParameterWidgetContext widgetContext;
8041   QgsProcessingContext context;
8042   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databasetable" ), context, widgetContext );
8043   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
8044   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
8045   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
8046   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
8047   QVERIFY( !static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->defaultValue().isValid() );
8048 
8049   // using a parameter definition as initial values
8050   QgsProcessingParameterDatabaseTable tableParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "connparam" ), QStringLiteral( "schemaparam" ), QStringLiteral( "aaa" ), false );
8051   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databasetable" ), context, widgetContext, &tableParam );
8052   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
8053   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
8054   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
8055   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
8056   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
8057   QCOMPARE( static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->defaultValue().toString(), QStringLiteral( "aaa" ) );
8058   QCOMPARE( static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->parentConnectionParameterName(), QStringLiteral( "connparam" ) );
8059   QCOMPARE( static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->parentSchemaParameterName(), QStringLiteral( "schemaparam" ) );
8060   tableParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
8061   tableParam.setDefaultValue( QStringLiteral( "xxx" ) );
8062   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databasetable" ), context, widgetContext, &tableParam );
8063   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
8064   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
8065   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
8066   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
8067   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
8068   QCOMPARE( static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->defaultValue().toString(), QStringLiteral( "xxx" ) );
8069   tableParam.setDefaultValue( QVariant() );
8070   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databasetable" ), context, widgetContext, &tableParam );
8071   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
8072   QVERIFY( !static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->defaultValue().isValid() );
8073 #endif
8074 }
8075 
testFieldMapWidget()8076 void TestProcessingGui::testFieldMapWidget()
8077 {
8078   QgsProcessingFieldMapPanelWidget widget;
8079 
8080   QVariantMap map;
8081   map.insert( QStringLiteral( "name" ), QStringLiteral( "n" ) );
8082   map.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::Double ) );
8083   map.insert( QStringLiteral( "length" ), 8 );
8084   map.insert( QStringLiteral( "precision" ), 5 );
8085   QVariantMap map2;
8086   map2.insert( QStringLiteral( "name" ), QStringLiteral( "n2" ) );
8087   map2.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::String ) );
8088   map2.insert( QStringLiteral( "expression" ), QStringLiteral( "'abc' || \"def\"" ) );
8089 
8090   QSignalSpy spy( &widget, &QgsProcessingFieldMapPanelWidget::changed );
8091   widget.setValue( QVariantList() << map << map2 );
8092   QCOMPARE( spy.size(), 1 );
8093 
8094   QCOMPARE( widget.value().toList().size(), 2 );
8095   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
8096   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::Double ) );
8097   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "length" ) ).toInt(), 8 );
8098   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "precision" ) ).toInt(), 5 );
8099   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "expression" ) ).toString(), QString() );
8100   QCOMPARE( widget.value().toList().at( 1 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
8101   QCOMPARE( widget.value().toList().at( 1 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::String ) );
8102   QCOMPARE( widget.value().toList().at( 1 ).toMap().value( QStringLiteral( "expression" ) ).toString(), QStringLiteral( "'abc' || \"def\"" ) );
8103 }
8104 
testFieldMapWrapper()8105 void TestProcessingGui::testFieldMapWrapper()
8106 {
8107   const QgsProcessingAlgorithm *centroidAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:centroids" ) );
8108   const QgsProcessingParameterDefinition *layerDef = centroidAlg->parameterDefinition( QStringLiteral( "INPUT" ) );
8109 
8110   auto testWrapper = [layerDef]( QgsProcessingGui::WidgetType type )
8111   {
8112     QgsProcessingParameterFieldMapping param( QStringLiteral( "mapping" ), QStringLiteral( "mapping" ) );
8113 
8114     QgsProcessingFieldMapWidgetWrapper wrapper( &param, type );
8115 
8116     QgsProcessingContext context;
8117     QWidget *w = wrapper.createWrappedWidget( context );
8118 
8119     QVariantMap map;
8120     map.insert( QStringLiteral( "name" ), QStringLiteral( "n" ) );
8121     map.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::Double ) );
8122     map.insert( QStringLiteral( "length" ), 8 );
8123     map.insert( QStringLiteral( "precision" ), 5 );
8124     QVariantMap map2;
8125     map2.insert( QStringLiteral( "name" ), QStringLiteral( "n2" ) );
8126     map2.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::String ) );
8127     map2.insert( QStringLiteral( "expression" ), QStringLiteral( "'abc' || \"def\"" ) );
8128 
8129     QSignalSpy spy( &wrapper, &QgsProcessingFieldMapWidgetWrapper::widgetValueHasChanged );
8130     wrapper.setWidgetValue( QVariantList() << map << map2, context );
8131     QCOMPARE( spy.count(), 1 );
8132     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
8133     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::Double ) );
8134     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "length" ) ).toInt(), 8 );
8135     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "precision" ) ).toInt(), 5 );
8136     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "expression" ) ).toString(), QString() );
8137     QCOMPARE( wrapper.widgetValue().toList().at( 1 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
8138     QCOMPARE( wrapper.widgetValue().toList().at( 1 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::String ) );
8139     QCOMPARE( wrapper.widgetValue().toList().at( 1 ).toMap().value( QStringLiteral( "expression" ) ).toString(), QStringLiteral( "'abc' || \"def\"" ) );
8140 
8141     QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper.wrappedWidget() )->value().toList().count(), 2 );
8142     QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper.wrappedWidget() )->value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
8143     QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper.wrappedWidget() )->value().toList().at( 1 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
8144     wrapper.setWidgetValue( QVariantList() << map, context );
8145     QCOMPARE( spy.count(), 2 );
8146     QCOMPARE( wrapper.widgetValue().toList().size(), 1 );
8147     QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper.wrappedWidget() )->value().toList().size(), 1 );
8148 
8149     QLabel *l = wrapper.createWrappedLabel();
8150     if ( wrapper.type() != QgsProcessingGui::Batch )
8151     {
8152       QVERIFY( l );
8153       QCOMPARE( l->text(), QStringLiteral( "mapping" ) );
8154       QCOMPARE( l->toolTip(), param.toolTip() );
8155       delete l;
8156     }
8157     else
8158     {
8159       QVERIFY( !l );
8160     }
8161 
8162     // check signal
8163     static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper.wrappedWidget() )->setValue( QVariantList() << map << map2 );
8164     QCOMPARE( spy.count(), 3 );
8165 
8166     delete w;
8167 
8168     // with layer
8169     param.setParentLayerParameterName( QStringLiteral( "other" ) );
8170     QgsProcessingFieldMapWidgetWrapper wrapper2( &param, type );
8171     w = wrapper2.createWrappedWidget( context );
8172 
8173     QSignalSpy spy2( &wrapper2, &QgsProcessingFieldMapWidgetWrapper::widgetValueHasChanged );
8174     wrapper2.setWidgetValue( QVariantList() << map, context );
8175     QCOMPARE( spy2.count(), 1 );
8176     QCOMPARE( wrapper2.widgetValue().toList().size(),  1 );
8177     QCOMPARE( wrapper2.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
8178     QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper2.wrappedWidget() )->value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
8179 
8180     wrapper2.setWidgetValue( QVariantList() << map2, context );
8181     QCOMPARE( spy2.count(), 2 );
8182     QCOMPARE( wrapper2.widgetValue().toList().size(),  1 );
8183     QCOMPARE( wrapper2.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
8184     QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper2.wrappedWidget() )->value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
8185 
8186     static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper2.wrappedWidget() )->setValue( QVariantList() << map );
8187     QCOMPARE( spy2.count(), 3 );
8188 
8189     TestLayerWrapper layerWrapper( layerDef );
8190     QgsProject p;
8191     QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
8192     p.addMapLayer( vl );
8193 
8194     QVERIFY( !wrapper2.mPanel->layer() );
8195     layerWrapper.setWidgetValue( QVariant::fromValue( vl ), context );
8196     wrapper2.setParentLayerWrapperValue( &layerWrapper );
8197     QCOMPARE( wrapper2.mPanel->layer(), vl );
8198 
8199     // should not be owned by wrapper
8200     QVERIFY( !wrapper2.mParentLayer.get() );
8201     layerWrapper.setWidgetValue( QVariant(), context );
8202     wrapper2.setParentLayerWrapperValue( &layerWrapper );
8203     QVERIFY( !wrapper2.mPanel->layer() );
8204 
8205     layerWrapper.setWidgetValue( vl->id(), context );
8206     wrapper2.setParentLayerWrapperValue( &layerWrapper );
8207     QVERIFY( !wrapper2.mPanel->layer() );
8208     QVERIFY( !wrapper2.mParentLayer.get() );
8209 
8210     // with project layer
8211     context.setProject( &p );
8212     TestProcessingContextGenerator generator( context );
8213     wrapper2.registerProcessingContextGenerator( &generator );
8214 
8215     layerWrapper.setWidgetValue( vl->id(), context );
8216     wrapper2.setParentLayerWrapperValue( &layerWrapper );
8217     QCOMPARE( wrapper2.mPanel->layer(), vl );
8218     QVERIFY( !wrapper2.mParentLayer.get() );
8219 
8220     // non-project layer
8221     QString pointFileName = TEST_DATA_DIR + QStringLiteral( "/points.shp" );
8222     layerWrapper.setWidgetValue( pointFileName, context );
8223     wrapper2.setParentLayerWrapperValue( &layerWrapper );
8224     QCOMPARE( wrapper2.mPanel->layer()->publicSource(), pointFileName );
8225     // must be owned by wrapper, or layer may be deleted while still required by wrapper
8226     QCOMPARE( wrapper2.mParentLayer->publicSource(), pointFileName );
8227 
8228   };
8229 
8230   // standard wrapper
8231   testWrapper( QgsProcessingGui::Standard );
8232 
8233   // batch wrapper
8234   testWrapper( QgsProcessingGui::Batch );
8235 
8236   // modeler wrapper
8237   testWrapper( QgsProcessingGui::Modeler );
8238 
8239   // config widget
8240   QgsProcessingParameterWidgetContext widgetContext;
8241   QgsProcessingContext context;
8242   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "fields_mapping" ), context, widgetContext );
8243   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
8244   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
8245   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
8246   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
8247 
8248   // using a parameter definition as initial values
8249   QgsProcessingParameterFieldMapping mapParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "parent" ) );
8250   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "fields_mapping" ), context, widgetContext, &mapParam );
8251   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
8252   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
8253   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
8254   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
8255   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
8256   QCOMPARE( static_cast< QgsProcessingParameterFieldMapping * >( def.get() )->parentLayerParameterName(), QStringLiteral( "parent" ) );
8257   mapParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
8258   mapParam.setParentLayerParameterName( QString() );
8259   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "fields_mapping" ), context, widgetContext, &mapParam );
8260   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
8261   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
8262   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
8263   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
8264   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
8265   QVERIFY( static_cast< QgsProcessingParameterFieldMapping * >( def.get() )->parentLayerParameterName().isEmpty() );
8266 }
8267 
testAggregateWidget()8268 void TestProcessingGui::testAggregateWidget()
8269 {
8270   QgsProcessingAggregatePanelWidget widget;
8271 
8272   QVariantMap map;
8273   map.insert( QStringLiteral( "name" ), QStringLiteral( "n" ) );
8274   map.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::Double ) );
8275   map.insert( QStringLiteral( "length" ), 8 );
8276   map.insert( QStringLiteral( "precision" ), 5 );
8277   QVariantMap map2;
8278   map2.insert( QStringLiteral( "name" ), QStringLiteral( "n2" ) );
8279   map2.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::String ) );
8280   map2.insert( QStringLiteral( "input" ), QStringLiteral( "'abc' || \"def\"" ) );
8281   map2.insert( QStringLiteral( "aggregate" ), QStringLiteral( "concatenate" ) );
8282   map2.insert( QStringLiteral( "delimiter" ), QStringLiteral( "|" ) );
8283 
8284   QSignalSpy spy( &widget, &QgsProcessingAggregatePanelWidget::changed );
8285   widget.setValue( QVariantList() << map << map2 );
8286   QCOMPARE( spy.size(), 1 );
8287 
8288   QCOMPARE( widget.value().toList().size(), 2 );
8289   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
8290   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::Double ) );
8291   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "length" ) ).toInt(), 8 );
8292   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "precision" ) ).toInt(), 5 );
8293   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "input" ) ).toString(), QString() );
8294   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "aggregate" ) ).toString(), QString() );
8295   QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "delimiter" ) ).toString(), QString() );
8296   QCOMPARE( widget.value().toList().at( 1 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
8297   QCOMPARE( widget.value().toList().at( 1 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::String ) );
8298   QCOMPARE( widget.value().toList().at( 1 ).toMap().value( QStringLiteral( "input" ) ).toString(), QStringLiteral( "'abc' || \"def\"" ) );
8299   QCOMPARE( widget.value().toList().at( 1 ).toMap().value( QStringLiteral( "aggregate" ) ).toString(), QStringLiteral( "concatenate" ) );
8300   QCOMPARE( widget.value().toList().at( 1 ).toMap().value( QStringLiteral( "delimiter" ) ).toString(), QStringLiteral( "|" ) );
8301 }
8302 
testAggregateWrapper()8303 void TestProcessingGui::testAggregateWrapper()
8304 {
8305   const QgsProcessingAlgorithm *centroidAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:centroids" ) );
8306   const QgsProcessingParameterDefinition *layerDef = centroidAlg->parameterDefinition( QStringLiteral( "INPUT" ) );
8307 
8308   auto testWrapper = [layerDef]( QgsProcessingGui::WidgetType type )
8309   {
8310     QgsProcessingParameterAggregate param( QStringLiteral( "mapping" ), QStringLiteral( "mapping" ) );
8311 
8312     QgsProcessingAggregateWidgetWrapper wrapper( &param, type );
8313 
8314     QgsProcessingContext context;
8315     QWidget *w = wrapper.createWrappedWidget( context );
8316 
8317     QVariantMap map;
8318     map.insert( QStringLiteral( "name" ), QStringLiteral( "n" ) );
8319     map.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::Double ) );
8320     map.insert( QStringLiteral( "length" ), 8 );
8321     map.insert( QStringLiteral( "precision" ), 5 );
8322     QVariantMap map2;
8323     map2.insert( QStringLiteral( "name" ), QStringLiteral( "n2" ) );
8324     map2.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::String ) );
8325     map2.insert( QStringLiteral( "input" ), QStringLiteral( "'abc' || \"def\"" ) );
8326     map2.insert( QStringLiteral( "aggregate" ), QStringLiteral( "concatenate" ) );
8327     map2.insert( QStringLiteral( "delimiter" ), QStringLiteral( "|" ) );
8328 
8329     QSignalSpy spy( &wrapper, &QgsProcessingFieldMapWidgetWrapper::widgetValueHasChanged );
8330     wrapper.setWidgetValue( QVariantList() << map << map2, context );
8331     QCOMPARE( spy.count(), 1 );
8332     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
8333     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::Double ) );
8334     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "length" ) ).toInt(), 8 );
8335     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "precision" ) ).toInt(), 5 );
8336     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "input" ) ).toString(), QString() );
8337     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "aggregate" ) ).toString(), QString() );
8338     QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "delimiter" ) ).toString(), QString() );
8339     QCOMPARE( wrapper.widgetValue().toList().at( 1 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
8340     QCOMPARE( wrapper.widgetValue().toList().at( 1 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::String ) );
8341     QCOMPARE( wrapper.widgetValue().toList().at( 1 ).toMap().value( QStringLiteral( "input" ) ).toString(), QStringLiteral( "'abc' || \"def\"" ) );
8342     QCOMPARE( wrapper.widgetValue().toList().at( 1 ).toMap().value( QStringLiteral( "aggregate" ) ).toString(), QStringLiteral( "concatenate" ) );
8343     QCOMPARE( wrapper.widgetValue().toList().at( 1 ).toMap().value( QStringLiteral( "delimiter" ) ).toString(), QStringLiteral( "|" ) );
8344 
8345     QCOMPARE( static_cast< QgsProcessingAggregatePanelWidget * >( wrapper.wrappedWidget() )->value().toList().count(), 2 );
8346     QCOMPARE( static_cast< QgsProcessingAggregatePanelWidget * >( wrapper.wrappedWidget() )->value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
8347     QCOMPARE( static_cast< QgsProcessingAggregatePanelWidget * >( wrapper.wrappedWidget() )->value().toList().at( 1 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
8348     wrapper.setWidgetValue( QVariantList() << map, context );
8349     QCOMPARE( spy.count(), 2 );
8350     QCOMPARE( wrapper.widgetValue().toList().size(), 1 );
8351     QCOMPARE( static_cast< QgsProcessingAggregatePanelWidget * >( wrapper.wrappedWidget() )->value().toList().size(), 1 );
8352 
8353     QLabel *l = wrapper.createWrappedLabel();
8354     if ( wrapper.type() != QgsProcessingGui::Batch )
8355     {
8356       QVERIFY( l );
8357       QCOMPARE( l->text(), QStringLiteral( "mapping" ) );
8358       QCOMPARE( l->toolTip(), param.toolTip() );
8359       delete l;
8360     }
8361     else
8362     {
8363       QVERIFY( !l );
8364     }
8365 
8366     // check signal
8367     static_cast< QgsProcessingAggregatePanelWidget * >( wrapper.wrappedWidget() )->setValue( QVariantList() << map << map2 );
8368     QCOMPARE( spy.count(), 3 );
8369 
8370     delete w;
8371 
8372     // with layer
8373     param.setParentLayerParameterName( QStringLiteral( "other" ) );
8374     QgsProcessingAggregateWidgetWrapper wrapper2( &param, type );
8375     w = wrapper2.createWrappedWidget( context );
8376 
8377     QSignalSpy spy2( &wrapper2, &QgsProcessingAggregateWidgetWrapper::widgetValueHasChanged );
8378     wrapper2.setWidgetValue( QVariantList() << map, context );
8379     QCOMPARE( spy2.count(), 1 );
8380     QCOMPARE( wrapper2.widgetValue().toList().size(),  1 );
8381     QCOMPARE( wrapper2.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
8382     QCOMPARE( static_cast< QgsProcessingAggregatePanelWidget * >( wrapper2.wrappedWidget() )->value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
8383 
8384     wrapper2.setWidgetValue( QVariantList() << map2, context );
8385     QCOMPARE( spy2.count(), 2 );
8386     QCOMPARE( wrapper2.widgetValue().toList().size(),  1 );
8387     QCOMPARE( wrapper2.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
8388     QCOMPARE( static_cast< QgsProcessingAggregatePanelWidget * >( wrapper2.wrappedWidget() )->value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
8389 
8390     static_cast< QgsProcessingAggregatePanelWidget * >( wrapper2.wrappedWidget() )->setValue( QVariantList() << map );
8391     QCOMPARE( spy2.count(), 3 );
8392 
8393 
8394     TestLayerWrapper layerWrapper( layerDef );
8395     QgsProject p;
8396     QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
8397     p.addMapLayer( vl );
8398 
8399     QVERIFY( !wrapper2.mPanel->layer() );
8400     layerWrapper.setWidgetValue( QVariant::fromValue( vl ), context );
8401     wrapper2.setParentLayerWrapperValue( &layerWrapper );
8402     QCOMPARE( wrapper2.mPanel->layer(), vl );
8403 
8404     // should not be owned by wrapper
8405     QVERIFY( !wrapper2.mParentLayer.get() );
8406     layerWrapper.setWidgetValue( QVariant(), context );
8407     wrapper2.setParentLayerWrapperValue( &layerWrapper );
8408     QVERIFY( !wrapper2.mPanel->layer() );
8409 
8410     layerWrapper.setWidgetValue( vl->id(), context );
8411     wrapper2.setParentLayerWrapperValue( &layerWrapper );
8412     QVERIFY( !wrapper2.mPanel->layer() );
8413     QVERIFY( !wrapper2.mParentLayer.get() );
8414 
8415     // with project layer
8416     context.setProject( &p );
8417     TestProcessingContextGenerator generator( context );
8418     wrapper2.registerProcessingContextGenerator( &generator );
8419 
8420     layerWrapper.setWidgetValue( vl->id(), context );
8421     wrapper2.setParentLayerWrapperValue( &layerWrapper );
8422     QCOMPARE( wrapper2.mPanel->layer(), vl );
8423     QVERIFY( !wrapper2.mParentLayer.get() );
8424 
8425     // non-project layer
8426     QString pointFileName = TEST_DATA_DIR + QStringLiteral( "/points.shp" );
8427     layerWrapper.setWidgetValue( pointFileName, context );
8428     wrapper2.setParentLayerWrapperValue( &layerWrapper );
8429     QCOMPARE( wrapper2.mPanel->layer()->publicSource(), pointFileName );
8430     // must be owned by wrapper, or layer may be deleted while still required by wrapper
8431     QCOMPARE( wrapper2.mParentLayer->publicSource(), pointFileName );
8432 
8433   };
8434 
8435   // standard wrapper
8436   testWrapper( QgsProcessingGui::Standard );
8437 
8438   // batch wrapper
8439   testWrapper( QgsProcessingGui::Batch );
8440 
8441   // modeler wrapper
8442   testWrapper( QgsProcessingGui::Modeler );
8443 
8444   // config widget
8445   QgsProcessingParameterWidgetContext widgetContext;
8446   QgsProcessingContext context;
8447   std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "aggregates" ), context, widgetContext );
8448   std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
8449   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
8450   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
8451   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
8452 
8453   // using a parameter definition as initial values
8454   QgsProcessingParameterAggregate mapParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "parent" ) );
8455   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "aggregates" ), context, widgetContext, &mapParam );
8456   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
8457   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
8458   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
8459   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
8460   QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
8461   QCOMPARE( static_cast< QgsProcessingParameterAggregate * >( def.get() )->parentLayerParameterName(), QStringLiteral( "parent" ) );
8462   mapParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
8463   mapParam.setParentLayerParameterName( QString() );
8464   widget = std::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "aggregates" ), context, widgetContext, &mapParam );
8465   def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
8466   QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
8467   QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
8468   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
8469   QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
8470   QVERIFY( static_cast< QgsProcessingParameterAggregate * >( def.get() )->parentLayerParameterName().isEmpty() );
8471 }
8472 
testOutputDefinitionWidget()8473 void TestProcessingGui::testOutputDefinitionWidget()
8474 {
8475   QgsProcessingParameterFeatureSink sink( QStringLiteral( "test" ) );
8476   QgsProcessingLayerOutputDestinationWidget panel( &sink, false );
8477 
8478   QSignalSpy skipSpy( &panel, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
8479   QSignalSpy changedSpy( &panel, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
8480 
8481   QVariant v = panel.value();
8482   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8483   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8484   QVERIFY( !panel.outputIsSkipped() );
8485 
8486   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8487   v = panel.value();
8488   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8489   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8490   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8491   QVERIFY( !panel.outputIsSkipped() );
8492   QCOMPARE( skipSpy.count(), 0 );
8493   QCOMPARE( changedSpy.count(), 0 );
8494   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8495   QCOMPARE( skipSpy.count(), 0 );
8496   QCOMPARE( changedSpy.count(), 0 );
8497 
8498   QgsProcessingOutputLayerDefinition def;
8499   def.sink.setStaticValue( QgsProcessing::TEMPORARY_OUTPUT );
8500   def.createOptions.insert( QStringLiteral( "fileEncoding" ), QStringLiteral( "utf8" ) );
8501   panel.setValue( def );
8502   QCOMPARE( skipSpy.count(), 0 );
8503   QCOMPARE( changedSpy.count(), 0 );
8504   v = panel.value();
8505   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8506   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "utf8" ) );
8507   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8508 
8509   panel.setValue( QStringLiteral( "memory:" ) );
8510   v = panel.value();
8511   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8512   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "utf8" ) );
8513   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8514   QVERIFY( !panel.outputIsSkipped() );
8515   QCOMPARE( skipSpy.count(), 0 );
8516   QCOMPARE( changedSpy.count(), 0 );
8517   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8518   QCOMPARE( skipSpy.count(), 0 );
8519   QCOMPARE( changedSpy.count(), 0 );
8520 
8521   def.sink.setStaticValue( QStringLiteral( "memory:" ) );
8522   panel.setValue( def );
8523   QCOMPARE( skipSpy.count(), 0 );
8524   QCOMPARE( changedSpy.count(), 0 );
8525   v = panel.value();
8526   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8527   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "utf8" ) );
8528   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8529 
8530   panel.setValue( QStringLiteral( "ogr:dbname='/me/a.gpkg' table=\"d\" (geom) sql=''" ) );
8531   QCOMPARE( skipSpy.count(), 0 );
8532   QCOMPARE( changedSpy.count(), 1 );
8533   v = panel.value();
8534   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8535   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "utf8" ) );
8536   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QStringLiteral( "ogr:dbname='/me/a.gpkg' table=\"d\" (geom) sql=''" ) );
8537   QVERIFY( !panel.outputIsSkipped() );
8538   panel.setValue( QStringLiteral( "ogr:dbname='/me/a.gpkg' table=\"d\" (geom) sql=''" ) );
8539   QCOMPARE( skipSpy.count(), 0 );
8540   QCOMPARE( changedSpy.count(), 1 );
8541 
8542   panel.setValue( QStringLiteral( "postgis:dbname='oraclesux' host=10.1.1.221 port=5432 user='qgis' password='qgis' table=\"stufff\".\"output\" (the_geom) sql=" ) );
8543   v = panel.value();
8544   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8545   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "utf8" ) );
8546   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QStringLiteral( "postgis:dbname='oraclesux' host=10.1.1.221 port=5432 user='qgis' password='qgis' table=\"stufff\".\"output\" (the_geom) sql=" ) );
8547   QVERIFY( !panel.outputIsSkipped() );
8548   QCOMPARE( skipSpy.count(), 0 );
8549   QCOMPARE( changedSpy.count(), 2 );
8550   panel.setValue( QStringLiteral( "postgis:dbname='oraclesux' host=10.1.1.221 port=5432 user='qgis' password='qgis' table=\"stufff\".\"output\" (the_geom) sql=" ) );
8551   QCOMPARE( skipSpy.count(), 0 );
8552   QCOMPARE( changedSpy.count(), 2 );
8553 
8554   panel.setValue( QStringLiteral( "/home/me/test.shp" ) );
8555   v = panel.value();
8556   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8557   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "utf8" ) );
8558   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QStringLiteral( "/home/me/test.shp" ) );
8559   QVERIFY( !panel.outputIsSkipped() );
8560   QCOMPARE( skipSpy.count(), 0 );
8561   QCOMPARE( changedSpy.count(), 3 );
8562   panel.setValue( QStringLiteral( "/home/me/test.shp" ) );
8563   QCOMPARE( skipSpy.count(), 0 );
8564   QCOMPARE( changedSpy.count(), 3 );
8565   panel.setValue( QStringLiteral( "/home/me/test2.shp" ) );
8566   QCOMPARE( skipSpy.count(), 0 );
8567   QCOMPARE( changedSpy.count(), 4 );
8568 
8569   QgsSettings settings;
8570   settings.setValue( QStringLiteral( "/Processing/Configuration/OUTPUTS_FOLDER" ), TEST_DATA_DIR );
8571   panel.setValue( QStringLiteral( "test.shp" ) );
8572   v = panel.value();
8573   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8574   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "utf8" ) );
8575   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QString( TEST_DATA_DIR + QStringLiteral( "/test.shp" ) ) );
8576 
8577   // optional, test skipping
8578   sink.setFlags( sink.flags() | QgsProcessingParameterDefinition::FlagOptional );
8579   sink.setCreateByDefault( true );
8580   QgsProcessingLayerOutputDestinationWidget panel2( &sink, false );
8581 
8582   QSignalSpy skipSpy2( &panel2, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
8583   QSignalSpy changedSpy2( &panel2, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
8584 
8585   v = panel2.value();
8586   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8587   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8588   QVERIFY( !panel2.outputIsSkipped() );
8589 
8590   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8591   v = panel2.value();
8592   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8593   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8594   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8595   QVERIFY( !panel2.outputIsSkipped() );
8596   QCOMPARE( skipSpy2.count(), 0 );
8597   QCOMPARE( changedSpy2.count(), 0 );
8598   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8599   QCOMPARE( skipSpy2.count(), 0 );
8600   QCOMPARE( changedSpy2.count(), 0 );
8601 
8602   panel2.setValue( QVariant() );
8603   v = panel2.value();
8604   QVERIFY( !v.isValid() );
8605   QVERIFY( panel2.outputIsSkipped() );
8606   QCOMPARE( skipSpy2.count(), 1 );
8607   QCOMPARE( changedSpy2.count(), 1 );
8608   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8609   QCOMPARE( skipSpy2.count(), 2 );
8610   QCOMPARE( changedSpy2.count(), 2 );
8611 
8612   sink.setCreateByDefault( false );
8613   QgsProcessingLayerOutputDestinationWidget panel3( &sink, false );
8614 
8615   QSignalSpy skipSpy3( &panel3, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
8616   QSignalSpy changedSpy3( &panel3, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
8617 
8618   v = panel3.value();
8619   QVERIFY( !v.isValid() );
8620   QVERIFY( panel3.outputIsSkipped() );
8621 
8622   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8623   v = panel3.value();
8624   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8625   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8626   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8627   QVERIFY( !panel3.outputIsSkipped() );
8628   QCOMPARE( skipSpy3.count(), 1 );
8629   QCOMPARE( changedSpy3.count(), 1 );
8630   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8631   QCOMPARE( skipSpy3.count(), 1 );
8632   QCOMPARE( changedSpy3.count(), 1 );
8633 
8634   panel3.setValue( QVariant() );
8635   v = panel3.value();
8636   QVERIFY( !v.isValid() );
8637   QVERIFY( panel3.outputIsSkipped() );
8638   QCOMPARE( skipSpy3.count(), 2 );
8639   QCOMPARE( changedSpy3.count(), 2 );
8640   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8641   QCOMPARE( skipSpy3.count(), 3 );
8642   QCOMPARE( changedSpy3.count(), 3 );
8643 
8644   // with remapping
8645   def = QgsProcessingOutputLayerDefinition( QStringLiteral( "test.shp" ) );
8646   QgsRemappingSinkDefinition remap;
8647   QMap< QString, QgsProperty > fieldMap;
8648   fieldMap.insert( QStringLiteral( "field1" ), QgsProperty::fromField( QStringLiteral( "source1" ) ) );
8649   fieldMap.insert( QStringLiteral( "field2" ), QgsProperty::fromExpression( QStringLiteral( "source || source2" ) ) );
8650   remap.setFieldMap( fieldMap );
8651   def.setRemappingDefinition( remap );
8652 
8653   panel3.setValue( def );
8654   v = panel3.value();
8655   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8656   QVERIFY( v.value< QgsProcessingOutputLayerDefinition>().useRemapping() );
8657   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().remappingDefinition().fieldMap().size(), 2 );
8658   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().remappingDefinition().fieldMap().value( QStringLiteral( "field1" ) ), QgsProperty::fromField( QStringLiteral( "source1" ) ) );
8659   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().remappingDefinition().fieldMap().value( QStringLiteral( "field2" ) ), QgsProperty::fromExpression( QStringLiteral( "source || source2" ) ) );
8660 
8661   panel3.setValue( QStringLiteral( "other.shp" ) );
8662   v = panel3.value();
8663   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8664   QVERIFY( !v.value< QgsProcessingOutputLayerDefinition>().useRemapping() );
8665 }
8666 
testOutputDefinitionWidgetVectorOut()8667 void TestProcessingGui::testOutputDefinitionWidgetVectorOut()
8668 {
8669   QgsProcessingParameterVectorDestination vector( QStringLiteral( "test" ) );
8670   QgsProcessingLayerOutputDestinationWidget panel( &vector, false );
8671 
8672   QSignalSpy skipSpy( &panel, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
8673   QSignalSpy changedSpy( &panel, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
8674 
8675   QVariant v = panel.value();
8676   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8677   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8678   QVERIFY( !panel.outputIsSkipped() );
8679 
8680   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8681   v = panel.value();
8682   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8683   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8684   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8685   QVERIFY( !panel.outputIsSkipped() );
8686   QCOMPARE( skipSpy.count(), 0 );
8687   QCOMPARE( changedSpy.count(), 0 );
8688   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8689   QCOMPARE( skipSpy.count(), 0 );
8690   QCOMPARE( changedSpy.count(), 0 );
8691 
8692   panel.setValue( QStringLiteral( "memory:" ) );
8693   v = panel.value();
8694   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8695   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8696   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8697   QVERIFY( !panel.outputIsSkipped() );
8698   QCOMPARE( skipSpy.count(), 0 );
8699   QCOMPARE( changedSpy.count(), 0 );
8700   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8701   QCOMPARE( skipSpy.count(), 0 );
8702   QCOMPARE( changedSpy.count(), 0 );
8703 
8704 
8705   panel.setValue( QStringLiteral( "ogr:dbname='/me/a.gpkg' table=\"d\" (geom) sql=''" ) );
8706   QCOMPARE( skipSpy.count(), 0 );
8707   QCOMPARE( changedSpy.count(), 1 );
8708   v = panel.value();
8709   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8710   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8711   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QStringLiteral( "ogr:dbname='/me/a.gpkg' table=\"d\" (geom) sql=''" ) );
8712   QVERIFY( !panel.outputIsSkipped() );
8713   panel.setValue( QStringLiteral( "ogr:dbname='/me/a.gpkg' table=\"d\" (geom) sql=''" ) );
8714   QCOMPARE( skipSpy.count(), 0 );
8715   QCOMPARE( changedSpy.count(), 1 );
8716 
8717   panel.setValue( QStringLiteral( "postgis:dbname='oraclesux' host=10.1.1.221 port=5432 user='qgis' password='qgis' table=\"stufff\".\"output\" (the_geom) sql=" ) );
8718   v = panel.value();
8719   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8720   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8721   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QStringLiteral( "postgis:dbname='oraclesux' host=10.1.1.221 port=5432 user='qgis' password='qgis' table=\"stufff\".\"output\" (the_geom) sql=" ) );
8722   QVERIFY( !panel.outputIsSkipped() );
8723   QCOMPARE( skipSpy.count(), 0 );
8724   QCOMPARE( changedSpy.count(), 2 );
8725   panel.setValue( QStringLiteral( "postgis:dbname='oraclesux' host=10.1.1.221 port=5432 user='qgis' password='qgis' table=\"stufff\".\"output\" (the_geom) sql=" ) );
8726   QCOMPARE( skipSpy.count(), 0 );
8727   QCOMPARE( changedSpy.count(), 2 );
8728 
8729   panel.setValue( QStringLiteral( "/home/me/test.shp" ) );
8730   v = panel.value();
8731   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8732   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8733   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QStringLiteral( "/home/me/test.shp" ) );
8734   QVERIFY( !panel.outputIsSkipped() );
8735   QCOMPARE( skipSpy.count(), 0 );
8736   QCOMPARE( changedSpy.count(), 3 );
8737   panel.setValue( QStringLiteral( "/home/me/test.shp" ) );
8738   QCOMPARE( skipSpy.count(), 0 );
8739   QCOMPARE( changedSpy.count(), 3 );
8740   panel.setValue( QStringLiteral( "/home/me/test2.shp" ) );
8741   QCOMPARE( skipSpy.count(), 0 );
8742   QCOMPARE( changedSpy.count(), 4 );
8743 
8744   QgsSettings settings;
8745   settings.setValue( QStringLiteral( "/Processing/Configuration/OUTPUTS_FOLDER" ), TEST_DATA_DIR );
8746   panel.setValue( QStringLiteral( "test.shp" ) );
8747   v = panel.value();
8748   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8749   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8750   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QString( TEST_DATA_DIR + QStringLiteral( "/test.shp" ) ) );
8751 
8752   // optional, test skipping
8753   vector.setFlags( vector.flags() | QgsProcessingParameterDefinition::FlagOptional );
8754   vector.setCreateByDefault( true );
8755   QgsProcessingLayerOutputDestinationWidget panel2( &vector, false );
8756 
8757   QSignalSpy skipSpy2( &panel2, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
8758   QSignalSpy changedSpy2( &panel2, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
8759 
8760   v = panel2.value();
8761   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8762   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8763   QVERIFY( !panel2.outputIsSkipped() );
8764 
8765   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8766   v = panel2.value();
8767   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8768   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8769   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8770   QVERIFY( !panel2.outputIsSkipped() );
8771   QCOMPARE( skipSpy2.count(), 0 );
8772   QCOMPARE( changedSpy2.count(), 0 );
8773   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8774   QCOMPARE( skipSpy2.count(), 0 );
8775   QCOMPARE( changedSpy2.count(), 0 );
8776 
8777   panel2.setValue( QVariant() );
8778   v = panel2.value();
8779   QVERIFY( !v.isValid() );
8780   QVERIFY( panel2.outputIsSkipped() );
8781   QCOMPARE( skipSpy2.count(), 1 );
8782   QCOMPARE( changedSpy2.count(), 1 );
8783   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8784   QCOMPARE( skipSpy2.count(), 2 );
8785   QCOMPARE( changedSpy2.count(), 2 );
8786 
8787   vector.setCreateByDefault( false );
8788   QgsProcessingLayerOutputDestinationWidget panel3( &vector, false );
8789 
8790   QSignalSpy skipSpy3( &panel3, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
8791   QSignalSpy changedSpy3( &panel3, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
8792 
8793   v = panel3.value();
8794   QVERIFY( !v.isValid() );
8795   QVERIFY( panel3.outputIsSkipped() );
8796 
8797   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8798   v = panel3.value();
8799   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8800   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8801   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8802   QVERIFY( !panel3.outputIsSkipped() );
8803   QCOMPARE( skipSpy3.count(), 1 );
8804   QCOMPARE( changedSpy3.count(), 1 );
8805   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8806   QCOMPARE( skipSpy3.count(), 1 );
8807   QCOMPARE( changedSpy3.count(), 1 );
8808 
8809   panel3.setValue( QVariant() );
8810   v = panel3.value();
8811   QVERIFY( !v.isValid() );
8812   QVERIFY( panel3.outputIsSkipped() );
8813   QCOMPARE( skipSpy3.count(), 2 );
8814   QCOMPARE( changedSpy3.count(), 2 );
8815   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8816   QCOMPARE( skipSpy3.count(), 3 );
8817   QCOMPARE( changedSpy3.count(), 3 );
8818 }
8819 
testOutputDefinitionWidgetRasterOut()8820 void TestProcessingGui::testOutputDefinitionWidgetRasterOut()
8821 {
8822   QgsProcessingParameterRasterDestination raster( QStringLiteral( "test" ) );
8823   QgsProcessingLayerOutputDestinationWidget panel( &raster, false );
8824 
8825   QSignalSpy skipSpy( &panel, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
8826   QSignalSpy changedSpy( &panel, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
8827 
8828   QVariant v = panel.value();
8829   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8830   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8831   QVERIFY( !panel.outputIsSkipped() );
8832 
8833   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8834   v = panel.value();
8835   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8836   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8837   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8838   QVERIFY( !panel.outputIsSkipped() );
8839   QCOMPARE( skipSpy.count(), 0 );
8840   QCOMPARE( changedSpy.count(), 0 );
8841   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8842   QCOMPARE( skipSpy.count(), 0 );
8843   QCOMPARE( changedSpy.count(), 0 );
8844 
8845   panel.setValue( QStringLiteral( "/home/me/test.tif" ) );
8846   QCOMPARE( skipSpy.count(), 0 );
8847   QCOMPARE( changedSpy.count(), 1 );
8848   v = panel.value();
8849   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8850   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8851   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QStringLiteral( "/home/me/test.tif" ) );
8852   QVERIFY( !panel.outputIsSkipped() );
8853   panel.setValue( QStringLiteral( "/home/me/test.tif" ) );
8854   QCOMPARE( skipSpy.count(), 0 );
8855   QCOMPARE( changedSpy.count(), 1 );
8856 
8857   QgsSettings settings;
8858   settings.setValue( QStringLiteral( "/Processing/Configuration/OUTPUTS_FOLDER" ), TEST_DATA_DIR );
8859   panel.setValue( QStringLiteral( "test.tif" ) );
8860   v = panel.value();
8861   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8862   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8863   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QString( TEST_DATA_DIR + QStringLiteral( "/test.tif" ) ) );
8864 
8865   // optional, test skipping
8866   raster.setFlags( raster.flags() | QgsProcessingParameterDefinition::FlagOptional );
8867   raster.setCreateByDefault( true );
8868   QgsProcessingLayerOutputDestinationWidget panel2( &raster, false );
8869 
8870   QSignalSpy skipSpy2( &panel2, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
8871   QSignalSpy changedSpy2( &panel2, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
8872 
8873   v = panel2.value();
8874   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8875   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8876   QVERIFY( !panel2.outputIsSkipped() );
8877 
8878   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8879   v = panel2.value();
8880   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8881   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8882   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8883   QVERIFY( !panel2.outputIsSkipped() );
8884   QCOMPARE( skipSpy2.count(), 0 );
8885   QCOMPARE( changedSpy2.count(), 0 );
8886   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8887   QCOMPARE( skipSpy2.count(), 0 );
8888   QCOMPARE( changedSpy2.count(), 0 );
8889 
8890   panel2.setValue( QVariant() );
8891   v = panel2.value();
8892   QVERIFY( !v.isValid() );
8893   QVERIFY( panel2.outputIsSkipped() );
8894   QCOMPARE( skipSpy2.count(), 1 );
8895   QCOMPARE( changedSpy2.count(), 1 );
8896   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8897   QCOMPARE( skipSpy2.count(), 2 );
8898   QCOMPARE( changedSpy2.count(), 2 );
8899 
8900   raster.setCreateByDefault( false );
8901   QgsProcessingLayerOutputDestinationWidget panel3( &raster, false );
8902 
8903   QSignalSpy skipSpy3( &panel3, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
8904   QSignalSpy changedSpy3( &panel3, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
8905 
8906   v = panel3.value();
8907   QVERIFY( !v.isValid() );
8908   QVERIFY( panel3.outputIsSkipped() );
8909 
8910   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8911   v = panel3.value();
8912   QVERIFY( v.canConvert< QgsProcessingOutputLayerDefinition>() );
8913   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().createOptions.value( QStringLiteral( "fileEncoding" ) ).toString(), QStringLiteral( "System" ) );
8914   QCOMPARE( v.value< QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), QgsProcessing::TEMPORARY_OUTPUT );
8915   QVERIFY( !panel3.outputIsSkipped() );
8916   QCOMPARE( skipSpy3.count(), 1 );
8917   QCOMPARE( changedSpy3.count(), 1 );
8918   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8919   QCOMPARE( skipSpy3.count(), 1 );
8920   QCOMPARE( changedSpy3.count(), 1 );
8921 
8922   panel3.setValue( QVariant() );
8923   v = panel3.value();
8924   QVERIFY( !v.isValid() );
8925   QVERIFY( panel3.outputIsSkipped() );
8926   QCOMPARE( skipSpy3.count(), 2 );
8927   QCOMPARE( changedSpy3.count(), 2 );
8928   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8929   QCOMPARE( skipSpy3.count(), 3 );
8930   QCOMPARE( changedSpy3.count(), 3 );
8931 }
8932 
testOutputDefinitionWidgetFolder()8933 void TestProcessingGui::testOutputDefinitionWidgetFolder()
8934 {
8935   QgsProcessingParameterFolderDestination folder( QStringLiteral( "test" ) );
8936   QgsProcessingLayerOutputDestinationWidget panel( &folder, false );
8937 
8938   QSignalSpy skipSpy( &panel, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
8939   QSignalSpy changedSpy( &panel, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
8940 
8941   QVariant v = panel.value();
8942   QCOMPARE( v.toString(), QgsProcessing::TEMPORARY_OUTPUT );
8943   QVERIFY( !panel.outputIsSkipped() );
8944 
8945   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8946   v = panel.value();
8947   QCOMPARE( v.toString(), QgsProcessing::TEMPORARY_OUTPUT );
8948   QVERIFY( !panel.outputIsSkipped() );
8949   QCOMPARE( skipSpy.count(), 0 );
8950   QCOMPARE( changedSpy.count(), 0 );
8951   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8952   QCOMPARE( skipSpy.count(), 0 );
8953   QCOMPARE( changedSpy.count(), 0 );
8954 
8955   panel.setValue( QStringLiteral( "/home/me/" ) );
8956   QCOMPARE( skipSpy.count(), 0 );
8957   QCOMPARE( changedSpy.count(), 1 );
8958   v = panel.value();
8959   QCOMPARE( v.toString(), QStringLiteral( "/home/me/" ) );
8960   QVERIFY( !panel.outputIsSkipped() );
8961   panel.setValue( QStringLiteral( "/home/me/" ) );
8962   QCOMPARE( skipSpy.count(), 0 );
8963   QCOMPARE( changedSpy.count(), 1 );
8964 
8965   QgsSettings settings;
8966   settings.setValue( QStringLiteral( "/Processing/Configuration/OUTPUTS_FOLDER" ), TEST_DATA_DIR );
8967   panel.setValue( QStringLiteral( "mystuff" ) );
8968   v = panel.value();
8969   QCOMPARE( v.toString(), QString( TEST_DATA_DIR + QStringLiteral( "/mystuff" ) ) );
8970 
8971   // optional, test skipping
8972   folder.setFlags( folder.flags() | QgsProcessingParameterDefinition::FlagOptional );
8973   folder.setCreateByDefault( true );
8974   QgsProcessingLayerOutputDestinationWidget panel2( &folder, false );
8975 
8976   QSignalSpy skipSpy2( &panel2, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
8977   QSignalSpy changedSpy2( &panel2, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
8978 
8979   v = panel2.value();
8980   QCOMPARE( v.toString(), QgsProcessing::TEMPORARY_OUTPUT );
8981   QVERIFY( !panel2.outputIsSkipped() );
8982 
8983   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8984   v = panel2.value();
8985   QCOMPARE( v.toString(), QgsProcessing::TEMPORARY_OUTPUT );
8986   QVERIFY( !panel2.outputIsSkipped() );
8987   QCOMPARE( skipSpy2.count(), 0 );
8988   QCOMPARE( changedSpy2.count(), 0 );
8989   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
8990   QCOMPARE( skipSpy2.count(), 0 );
8991   QCOMPARE( changedSpy2.count(), 0 );
8992 
8993   panel2.setValue( QVariant() );
8994   v = panel2.value();
8995   QVERIFY( !v.isValid() );
8996   QVERIFY( panel2.outputIsSkipped() );
8997   QCOMPARE( skipSpy2.count(), 1 );
8998   QCOMPARE( changedSpy2.count(), 1 );
8999   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9000   QCOMPARE( skipSpy2.count(), 2 );
9001   QCOMPARE( changedSpy2.count(), 2 );
9002 
9003   folder.setCreateByDefault( false );
9004   QgsProcessingLayerOutputDestinationWidget panel3( &folder, false );
9005 
9006   QSignalSpy skipSpy3( &panel3, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
9007   QSignalSpy changedSpy3( &panel3, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
9008 
9009   v = panel3.value();
9010   QVERIFY( !v.isValid() );
9011   QVERIFY( panel3.outputIsSkipped() );
9012 
9013   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9014   v = panel3.value();
9015   QCOMPARE( v.toString(), QgsProcessing::TEMPORARY_OUTPUT );
9016   QVERIFY( !panel3.outputIsSkipped() );
9017   QCOMPARE( skipSpy3.count(), 1 );
9018   QCOMPARE( changedSpy3.count(), 1 );
9019   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9020   QCOMPARE( skipSpy3.count(), 1 );
9021   QCOMPARE( changedSpy3.count(), 1 );
9022 
9023   panel3.setValue( QVariant() );
9024   v = panel3.value();
9025   QVERIFY( !v.isValid() );
9026   QVERIFY( panel3.outputIsSkipped() );
9027   QCOMPARE( skipSpy3.count(), 2 );
9028   QCOMPARE( changedSpy3.count(), 2 );
9029   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9030   QCOMPARE( skipSpy3.count(), 3 );
9031   QCOMPARE( changedSpy3.count(), 3 );
9032 }
9033 
testOutputDefinitionWidgetFileOut()9034 void TestProcessingGui::testOutputDefinitionWidgetFileOut()
9035 {
9036   QgsProcessingParameterFileDestination file( QStringLiteral( "test" ) );
9037   QgsProcessingLayerOutputDestinationWidget panel( &file, false );
9038 
9039   QSignalSpy skipSpy( &panel, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
9040   QSignalSpy changedSpy( &panel, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
9041 
9042   QVariant v = panel.value();
9043   QCOMPARE( v.toString(), QgsProcessing::TEMPORARY_OUTPUT );
9044   QVERIFY( !panel.outputIsSkipped() );
9045 
9046   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9047   v = panel.value();
9048   QCOMPARE( v.toString(), QgsProcessing::TEMPORARY_OUTPUT );
9049   QVERIFY( !panel.outputIsSkipped() );
9050   QCOMPARE( skipSpy.count(), 0 );
9051   QCOMPARE( changedSpy.count(), 0 );
9052   panel.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9053   QCOMPARE( skipSpy.count(), 0 );
9054   QCOMPARE( changedSpy.count(), 0 );
9055 
9056   panel.setValue( QStringLiteral( "/home/me/test.tif" ) );
9057   QCOMPARE( skipSpy.count(), 0 );
9058   QCOMPARE( changedSpy.count(), 1 );
9059   v = panel.value();
9060   QCOMPARE( v.toString(), QStringLiteral( "/home/me/test.tif" ) );
9061   QVERIFY( !panel.outputIsSkipped() );
9062   panel.setValue( QStringLiteral( "/home/me/test.tif" ) );
9063   QCOMPARE( skipSpy.count(), 0 );
9064   QCOMPARE( changedSpy.count(), 1 );
9065 
9066   QgsSettings settings;
9067   settings.setValue( QStringLiteral( "/Processing/Configuration/OUTPUTS_FOLDER" ), TEST_DATA_DIR );
9068   panel.setValue( QStringLiteral( "test.tif" ) );
9069   v = panel.value();
9070   QCOMPARE( v.toString(), QString( TEST_DATA_DIR + QStringLiteral( "/test.tif" ) ) );
9071 
9072   // optional, test skipping
9073   file.setFlags( file.flags() | QgsProcessingParameterDefinition::FlagOptional );
9074   file.setCreateByDefault( true );
9075   QgsProcessingLayerOutputDestinationWidget panel2( &file, false );
9076 
9077   QSignalSpy skipSpy2( &panel2, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
9078   QSignalSpy changedSpy2( &panel2, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
9079 
9080   v = panel2.value();
9081   QCOMPARE( v.toString(), QgsProcessing::TEMPORARY_OUTPUT );
9082   QVERIFY( !panel2.outputIsSkipped() );
9083 
9084   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9085   v = panel2.value();
9086   QCOMPARE( v.toString(), QgsProcessing::TEMPORARY_OUTPUT );
9087   QVERIFY( !panel2.outputIsSkipped() );
9088   QCOMPARE( skipSpy2.count(), 0 );
9089   QCOMPARE( changedSpy2.count(), 0 );
9090   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9091   QCOMPARE( skipSpy2.count(), 0 );
9092   QCOMPARE( changedSpy2.count(), 0 );
9093 
9094   panel2.setValue( QVariant() );
9095   v = panel2.value();
9096   QVERIFY( !v.isValid() );
9097   QVERIFY( panel2.outputIsSkipped() );
9098   QCOMPARE( skipSpy2.count(), 1 );
9099   QCOMPARE( changedSpy2.count(), 1 );
9100   panel2.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9101   QCOMPARE( skipSpy2.count(), 2 );
9102   QCOMPARE( changedSpy2.count(), 2 );
9103 
9104   file.setCreateByDefault( false );
9105   QgsProcessingLayerOutputDestinationWidget panel3( &file, false );
9106 
9107   QSignalSpy skipSpy3( &panel3, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged );
9108   QSignalSpy changedSpy3( &panel3, &QgsProcessingLayerOutputDestinationWidget::destinationChanged );
9109 
9110   v = panel3.value();
9111   QVERIFY( !v.isValid() );
9112   QVERIFY( panel3.outputIsSkipped() );
9113 
9114   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9115   v = panel3.value();
9116   QCOMPARE( v.toString(), QgsProcessing::TEMPORARY_OUTPUT );
9117   QVERIFY( !panel3.outputIsSkipped() );
9118   QCOMPARE( skipSpy3.count(), 1 );
9119   QCOMPARE( changedSpy3.count(), 1 );
9120   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9121   QCOMPARE( skipSpy3.count(), 1 );
9122   QCOMPARE( changedSpy3.count(), 1 );
9123 
9124   panel3.setValue( QVariant() );
9125   v = panel3.value();
9126   QVERIFY( !v.isValid() );
9127   QVERIFY( panel3.outputIsSkipped() );
9128   QCOMPARE( skipSpy3.count(), 2 );
9129   QCOMPARE( changedSpy3.count(), 2 );
9130   panel3.setValue( QgsProcessing::TEMPORARY_OUTPUT );
9131   QCOMPARE( skipSpy3.count(), 3 );
9132   QCOMPARE( changedSpy3.count(), 3 );
9133 }
9134 
testFeatureSourceOptionsWidget()9135 void TestProcessingGui::testFeatureSourceOptionsWidget()
9136 {
9137   QgsProcessingFeatureSourceOptionsWidget w;
9138   QSignalSpy spy( &w, &QgsProcessingFeatureSourceOptionsWidget::widgetChanged );
9139 
9140   w.setFeatureLimit( 66 );
9141   QCOMPARE( spy.count(), 1 );
9142   QCOMPARE( w.featureLimit(), 66 );
9143   w.setFeatureLimit( 66 );
9144   QCOMPARE( spy.count(), 1 );
9145   w.setFeatureLimit( -1 );
9146   QCOMPARE( spy.count(), 2 );
9147   QCOMPARE( w.featureLimit(), -1 );
9148 
9149   w.setGeometryCheckMethod( false, QgsFeatureRequest::GeometrySkipInvalid );
9150   QCOMPARE( spy.count(), 2 );
9151   QVERIFY( !w.isOverridingInvalidGeometryCheck() );
9152   w.setGeometryCheckMethod( true, QgsFeatureRequest::GeometrySkipInvalid );
9153   QCOMPARE( spy.count(), 3 );
9154   QVERIFY( w.isOverridingInvalidGeometryCheck() );
9155   QCOMPARE( w.geometryCheckMethod(), QgsFeatureRequest::GeometrySkipInvalid );
9156   w.setGeometryCheckMethod( true, QgsFeatureRequest::GeometrySkipInvalid );
9157   QCOMPARE( spy.count(), 3 );
9158   w.setGeometryCheckMethod( true, QgsFeatureRequest::GeometryAbortOnInvalid );
9159   QCOMPARE( spy.count(), 4 );
9160   QVERIFY( w.isOverridingInvalidGeometryCheck() );
9161   QCOMPARE( w.geometryCheckMethod(), QgsFeatureRequest::GeometryAbortOnInvalid );
9162   w.setGeometryCheckMethod( false, QgsFeatureRequest::GeometryAbortOnInvalid );
9163   QVERIFY( !w.isOverridingInvalidGeometryCheck() );
9164   QCOMPARE( spy.count(), 5 );
9165 }
9166 
testVectorOutWrapper()9167 void TestProcessingGui::testVectorOutWrapper()
9168 {
9169   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
9170   {
9171     // non optional
9172     QgsProcessingParameterVectorDestination param( QStringLiteral( "vector" ), QStringLiteral( "vector" ) );
9173 
9174     QgsProcessingVectorDestinationWidgetWrapper wrapper( &param, type );
9175 
9176     QgsProcessingContext context;
9177     QWidget *w = wrapper.createWrappedWidget( context );
9178 
9179     QSignalSpy spy( &wrapper, &QgsProcessingVectorDestinationWidgetWrapper::widgetValueHasChanged );
9180     wrapper.setWidgetValue( QStringLiteral( "/bb.shp" ), context );
9181 
9182     switch ( type )
9183     {
9184       case QgsProcessingGui::Standard:
9185       case QgsProcessingGui::Batch:
9186       case QgsProcessingGui::Modeler:
9187         QCOMPARE( spy.count(), 1 );
9188         QCOMPARE( wrapper.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.shp" ) );
9189         QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->value().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.shp" ) );
9190         wrapper.setWidgetValue( QStringLiteral( "/aa.shp" ), context );
9191         QCOMPARE( spy.count(), 2 );
9192         QCOMPARE( wrapper.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/aa.shp" ) );
9193         QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->value().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/aa.shp" ) );
9194         break;
9195     }
9196 
9197     // check signal
9198     static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->setValue( QStringLiteral( "/cc.shp" ) );
9199     QCOMPARE( spy.count(), 3 );
9200     QCOMPARE( wrapper.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/cc.shp" ) );
9201     delete w;
9202 
9203     // optional
9204     QgsProcessingParameterVectorDestination param2( QStringLiteral( "vector" ), QStringLiteral( "vector" ), QgsProcessing::TypeVector, QVariant(), true );
9205     QgsProcessingVectorDestinationWidgetWrapper wrapper3( &param2, type );
9206     w = wrapper3.createWrappedWidget( context );
9207 
9208     QSignalSpy spy3( &wrapper3, &QgsProcessingVectorDestinationWidgetWrapper::widgetValueHasChanged );
9209     wrapper3.setWidgetValue( QStringLiteral( "/bb.shp" ), context );
9210     QCOMPARE( spy3.count(), 1 );
9211     QCOMPARE( wrapper3.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.shp" ) );
9212     QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper3.wrappedWidget() )->value().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.shp" ) );
9213     wrapper3.setWidgetValue( QVariant(), context );
9214     QCOMPARE( spy3.count(), 2 );
9215     QVERIFY( !wrapper3.widgetValue().isValid() );
9216     delete w;
9217 
9218     QLabel *l = wrapper.createWrappedLabel();
9219     if ( wrapper.type() != QgsProcessingGui::Batch )
9220     {
9221       QVERIFY( l );
9222       QCOMPARE( l->text(), QStringLiteral( "vector" ) );
9223       QCOMPARE( l->toolTip(), param.toolTip() );
9224       delete l;
9225     }
9226     else
9227     {
9228       QVERIFY( !l );
9229     }
9230   };
9231 
9232   // standard wrapper
9233   testWrapper( QgsProcessingGui::Standard );
9234 
9235   // batch wrapper
9236   // testWrapper( QgsProcessingGui::Batch );
9237 
9238   // modeler wrapper
9239   testWrapper( QgsProcessingGui::Modeler );
9240 }
9241 
testSinkWrapper()9242 void TestProcessingGui::testSinkWrapper()
9243 {
9244   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
9245   {
9246     // non optional
9247     QgsProcessingParameterFeatureSink param( QStringLiteral( "sink" ), QStringLiteral( "sink" ) );
9248 
9249     QgsProcessingFeatureSinkWidgetWrapper wrapper( &param, type );
9250 
9251     QgsProcessingContext context;
9252     QWidget *w = wrapper.createWrappedWidget( context );
9253 
9254     QSignalSpy spy( &wrapper, &QgsProcessingFeatureSinkWidgetWrapper::widgetValueHasChanged );
9255     wrapper.setWidgetValue( QStringLiteral( "/bb.shp" ), context );
9256 
9257     switch ( type )
9258     {
9259       case QgsProcessingGui::Standard:
9260       case QgsProcessingGui::Batch:
9261       case QgsProcessingGui::Modeler:
9262         QCOMPARE( spy.count(), 1 );
9263         QCOMPARE( wrapper.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.shp" ) );
9264         QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->value().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.shp" ) );
9265         wrapper.setWidgetValue( QStringLiteral( "/aa.shp" ), context );
9266         QCOMPARE( spy.count(), 2 );
9267         QCOMPARE( wrapper.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/aa.shp" ) );
9268         QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->value().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/aa.shp" ) );
9269         break;
9270     }
9271 
9272     // check signal
9273     static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->setValue( QStringLiteral( "/cc.shp" ) );
9274     QCOMPARE( spy.count(), 3 );
9275     QCOMPARE( wrapper.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/cc.shp" ) );
9276     delete w;
9277 
9278     // optional
9279     QgsProcessingParameterFeatureSink param2( QStringLiteral( "sink" ), QStringLiteral( "sink" ), QgsProcessing::TypeVector, QVariant(), true );
9280     QgsProcessingFeatureSinkWidgetWrapper wrapper3( &param2, type );
9281     w = wrapper3.createWrappedWidget( context );
9282 
9283     QSignalSpy spy3( &wrapper3, &QgsProcessingFeatureSinkWidgetWrapper::widgetValueHasChanged );
9284     wrapper3.setWidgetValue( QStringLiteral( "/bb.shp" ), context );
9285     QCOMPARE( spy3.count(), 1 );
9286     QCOMPARE( wrapper3.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.shp" ) );
9287     QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper3.wrappedWidget() )->value().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.shp" ) );
9288     wrapper3.setWidgetValue( QVariant(), context );
9289     QCOMPARE( spy3.count(), 2 );
9290     QVERIFY( !wrapper3.widgetValue().isValid() );
9291     delete w;
9292 
9293     QLabel *l = wrapper.createWrappedLabel();
9294     if ( wrapper.type() != QgsProcessingGui::Batch )
9295     {
9296       QVERIFY( l );
9297       QCOMPARE( l->text(), QStringLiteral( "sink" ) );
9298       QCOMPARE( l->toolTip(), param.toolTip() );
9299       delete l;
9300     }
9301     else
9302     {
9303       QVERIFY( !l );
9304     }
9305   };
9306 
9307   // standard wrapper
9308   testWrapper( QgsProcessingGui::Standard );
9309 
9310   // batch wrapper
9311   // testWrapper( QgsProcessingGui::Batch );
9312 
9313   // modeler wrapper
9314   testWrapper( QgsProcessingGui::Modeler );
9315 }
9316 
testRasterOutWrapper()9317 void TestProcessingGui::testRasterOutWrapper()
9318 {
9319   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
9320   {
9321     // non optional
9322     QgsProcessingParameterRasterDestination param( QStringLiteral( "raster" ), QStringLiteral( "raster" ) );
9323 
9324     QgsProcessingRasterDestinationWidgetWrapper wrapper( &param, type );
9325 
9326     QgsProcessingContext context;
9327     QWidget *w = wrapper.createWrappedWidget( context );
9328 
9329     QSignalSpy spy( &wrapper, &QgsProcessingRasterDestinationWidgetWrapper::widgetValueHasChanged );
9330     wrapper.setWidgetValue( QStringLiteral( "/bb.tif" ), context );
9331 
9332     switch ( type )
9333     {
9334       case QgsProcessingGui::Standard:
9335       case QgsProcessingGui::Batch:
9336       case QgsProcessingGui::Modeler:
9337         QCOMPARE( spy.count(), 1 );
9338         QCOMPARE( wrapper.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.tif" ) );
9339         QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->value().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.tif" ) );
9340         wrapper.setWidgetValue( QStringLiteral( "/aa.tif" ), context );
9341         QCOMPARE( spy.count(), 2 );
9342         QCOMPARE( wrapper.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/aa.tif" ) );
9343         QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->value().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/aa.tif" ) );
9344         break;
9345     }
9346 
9347     // check signal
9348     static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->setValue( QStringLiteral( "/cc.tif" ) );
9349     QCOMPARE( spy.count(), 3 );
9350     QCOMPARE( wrapper.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/cc.tif" ) );
9351     delete w;
9352 
9353     // optional
9354     QgsProcessingParameterRasterDestination param2( QStringLiteral( "raster" ), QStringLiteral( "raster" ), QVariant(), true );
9355     QgsProcessingRasterDestinationWidgetWrapper wrapper3( &param2, type );
9356     w = wrapper3.createWrappedWidget( context );
9357 
9358     QSignalSpy spy3( &wrapper3, &QgsProcessingRasterDestinationWidgetWrapper::widgetValueHasChanged );
9359     wrapper3.setWidgetValue( QStringLiteral( "/bb.tif" ), context );
9360     QCOMPARE( spy3.count(), 1 );
9361     QCOMPARE( wrapper3.widgetValue().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.tif" ) );
9362     QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper3.wrappedWidget() )->value().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), QStringLiteral( "/bb.tif" ) );
9363     wrapper3.setWidgetValue( QVariant(), context );
9364     QCOMPARE( spy3.count(), 2 );
9365     QVERIFY( !wrapper3.widgetValue().isValid() );
9366     delete w;
9367 
9368     QLabel *l = wrapper.createWrappedLabel();
9369     if ( wrapper.type() != QgsProcessingGui::Batch )
9370     {
9371       QVERIFY( l );
9372       QCOMPARE( l->text(), QStringLiteral( "raster" ) );
9373       QCOMPARE( l->toolTip(), param.toolTip() );
9374       delete l;
9375     }
9376     else
9377     {
9378       QVERIFY( !l );
9379     }
9380   };
9381 
9382   // standard wrapper
9383   testWrapper( QgsProcessingGui::Standard );
9384 
9385   // batch wrapper
9386   // testWrapper( QgsProcessingGui::Batch );
9387 
9388   // modeler wrapper
9389   testWrapper( QgsProcessingGui::Modeler );
9390 }
9391 
testFileOutWrapper()9392 void TestProcessingGui::testFileOutWrapper()
9393 {
9394   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
9395   {
9396     // non optional
9397     QgsProcessingParameterFileDestination param( QStringLiteral( "file" ), QStringLiteral( "file" ) );
9398 
9399     QgsProcessingFileDestinationWidgetWrapper wrapper( &param, type );
9400 
9401     QgsProcessingContext context;
9402     QWidget *w = wrapper.createWrappedWidget( context );
9403 
9404     QSignalSpy spy( &wrapper, &QgsProcessingFileDestinationWidgetWrapper::widgetValueHasChanged );
9405     wrapper.setWidgetValue( QStringLiteral( "/bb.tif" ), context );
9406 
9407     switch ( type )
9408     {
9409       case QgsProcessingGui::Standard:
9410       case QgsProcessingGui::Batch:
9411       case QgsProcessingGui::Modeler:
9412         QCOMPARE( spy.count(), 1 );
9413         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "/bb.tif" ) );
9414         QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->value().toString(), QStringLiteral( "/bb.tif" ) );
9415         wrapper.setWidgetValue( QStringLiteral( "/aa.tif" ), context );
9416         QCOMPARE( spy.count(), 2 );
9417         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "/aa.tif" ) );
9418         QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->value().toString(), QStringLiteral( "/aa.tif" ) );
9419         break;
9420     }
9421 
9422     // check signal
9423     static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->setValue( QStringLiteral( "/cc.tif" ) );
9424     QCOMPARE( spy.count(), 3 );
9425     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "/cc.tif" ) );
9426     delete w;
9427 
9428     // optional
9429     QgsProcessingParameterFileDestination param2( QStringLiteral( "file" ), QStringLiteral( "file" ), QString(), QVariant(), true );
9430     QgsProcessingFileDestinationWidgetWrapper wrapper3( &param2, type );
9431     w = wrapper3.createWrappedWidget( context );
9432 
9433     QSignalSpy spy3( &wrapper3, &QgsProcessingFileDestinationWidgetWrapper::widgetValueHasChanged );
9434     wrapper3.setWidgetValue( QStringLiteral( "/bb.tif" ), context );
9435     QCOMPARE( spy3.count(), 1 );
9436     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "/bb.tif" ) );
9437     QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper3.wrappedWidget() )->value().toString(), QStringLiteral( "/bb.tif" ) );
9438     wrapper3.setWidgetValue( QVariant(), context );
9439     QCOMPARE( spy3.count(), 2 );
9440     QVERIFY( !wrapper3.widgetValue().isValid() );
9441     delete w;
9442 
9443     QLabel *l = wrapper.createWrappedLabel();
9444     if ( wrapper.type() != QgsProcessingGui::Batch )
9445     {
9446       QVERIFY( l );
9447       QCOMPARE( l->text(), QStringLiteral( "file" ) );
9448       QCOMPARE( l->toolTip(), param.toolTip() );
9449       delete l;
9450     }
9451     else
9452     {
9453       QVERIFY( !l );
9454     }
9455   };
9456 
9457   // standard wrapper
9458   testWrapper( QgsProcessingGui::Standard );
9459 
9460   // batch wrapper
9461   // testWrapper( QgsProcessingGui::Batch );
9462 
9463   // modeler wrapper
9464   testWrapper( QgsProcessingGui::Modeler );
9465 }
9466 
testFolderOutWrapper()9467 void TestProcessingGui::testFolderOutWrapper()
9468 {
9469   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
9470   {
9471     // non optional
9472     QgsProcessingParameterFolderDestination param( QStringLiteral( "folder" ), QStringLiteral( "folder" ) );
9473 
9474     QgsProcessingFolderDestinationWidgetWrapper wrapper( &param, type );
9475 
9476     QgsProcessingContext context;
9477     QWidget *w = wrapper.createWrappedWidget( context );
9478 
9479     QSignalSpy spy( &wrapper, &QgsProcessingFolderDestinationWidgetWrapper::widgetValueHasChanged );
9480     wrapper.setWidgetValue( QStringLiteral( "/bb" ), context );
9481 
9482     switch ( type )
9483     {
9484       case QgsProcessingGui::Standard:
9485       case QgsProcessingGui::Batch:
9486       case QgsProcessingGui::Modeler:
9487         QCOMPARE( spy.count(), 1 );
9488         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "/bb" ) );
9489         QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->value().toString(), QStringLiteral( "/bb" ) );
9490         wrapper.setWidgetValue( QStringLiteral( "/aa" ), context );
9491         QCOMPARE( spy.count(), 2 );
9492         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "/aa" ) );
9493         QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->value().toString(), QStringLiteral( "/aa" ) );
9494         break;
9495     }
9496 
9497     // check signal
9498     static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper.wrappedWidget() )->setValue( QStringLiteral( "/cc" ) );
9499     QCOMPARE( spy.count(), 3 );
9500     QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "/cc" ) );
9501     delete w;
9502 
9503     // optional
9504     QgsProcessingParameterFolderDestination param2( QStringLiteral( "folder" ), QStringLiteral( "folder" ), QVariant(), true );
9505     QgsProcessingFolderDestinationWidgetWrapper wrapper3( &param2, type );
9506     w = wrapper3.createWrappedWidget( context );
9507 
9508     QSignalSpy spy3( &wrapper3, &QgsProcessingFolderDestinationWidgetWrapper::widgetValueHasChanged );
9509     wrapper3.setWidgetValue( QStringLiteral( "/bb" ), context );
9510     QCOMPARE( spy3.count(), 1 );
9511     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "/bb" ) );
9512     QCOMPARE( static_cast< QgsProcessingLayerOutputDestinationWidget * >( wrapper3.wrappedWidget() )->value().toString(), QStringLiteral( "/bb" ) );
9513     wrapper3.setWidgetValue( QVariant(), context );
9514     QCOMPARE( spy3.count(), 2 );
9515     QVERIFY( !wrapper3.widgetValue().isValid() );
9516     delete w;
9517 
9518     QLabel *l = wrapper.createWrappedLabel();
9519     if ( wrapper.type() != QgsProcessingGui::Batch )
9520     {
9521       QVERIFY( l );
9522       QCOMPARE( l->text(), QStringLiteral( "folder" ) );
9523       QCOMPARE( l->toolTip(), param.toolTip() );
9524       delete l;
9525     }
9526     else
9527     {
9528       QVERIFY( !l );
9529     }
9530   };
9531 
9532   // standard wrapper
9533   testWrapper( QgsProcessingGui::Standard );
9534 
9535   // batch wrapper
9536   // testWrapper( QgsProcessingGui::Batch );
9537 
9538   // modeler wrapper
9539   testWrapper( QgsProcessingGui::Modeler );
9540 }
9541 
testTinInputLayerWrapper()9542 void TestProcessingGui::testTinInputLayerWrapper()
9543 {
9544   QgsProcessingParameterTinInputLayers definition( QStringLiteral( "TIN input layers" ) ) ;
9545   QgsProcessingTinInputLayersWidgetWrapper wrapper;
9546 
9547   std::unique_ptr<QWidget> w( wrapper.createWidget() );
9548   QVERIFY( w );
9549 
9550   QSignalSpy spy( &wrapper, &QgsProcessingTinInputLayersWidgetWrapper::widgetValueHasChanged );
9551 
9552   QgsProcessingContext context;
9553   QgsProject project;
9554   context.setProject( &project );
9555   QgsVectorLayer *vectorLayer = new QgsVectorLayer( QStringLiteral( "Point" ),
9556       QStringLiteral( "PointLayerForTin" ),
9557       QStringLiteral( "memory" ) );
9558   project.addMapLayer( vectorLayer );
9559 
9560   QVariantList layerList;
9561   QVariantMap layerMap;
9562   layerMap["source"] = "PointLayerForTin";
9563   layerMap["type"] = 0;
9564   layerMap["attributeIndex"] = -1;
9565   layerList.append( layerMap );
9566 
9567   QVERIFY( definition.checkValueIsAcceptable( layerList, &context ) );
9568   wrapper.setWidgetValue( layerList, context );
9569   QCOMPARE( spy.count(), 1 );
9570 
9571   QVariant value = wrapper.widgetValue();
9572 
9573   QVERIFY( definition.checkValueIsAcceptable( value, &context ) );
9574   QString valueAsPythonString = definition.valueAsPythonString( value, context );
9575   QCOMPARE( valueAsPythonString, QStringLiteral( "[{'source': 'PointLayerForTin','type': 0,'attributeIndex': -1}]" ) );
9576 }
9577 
testDxfLayersWrapper()9578 void TestProcessingGui::testDxfLayersWrapper()
9579 {
9580   QgsProcessingParameterDxfLayers definition( QStringLiteral( "DXF layers" ) ) ;
9581   QgsProcessingDxfLayersWidgetWrapper wrapper;
9582 
9583   std::unique_ptr<QWidget> w( wrapper.createWidget() );
9584   QVERIFY( w );
9585 
9586   QSignalSpy spy( &wrapper, &QgsProcessingTinInputLayersWidgetWrapper::widgetValueHasChanged );
9587 
9588   QgsProcessingContext context;
9589   QgsProject project;
9590   context.setProject( &project );
9591   QgsVectorLayer *vectorLayer = new QgsVectorLayer( QStringLiteral( "Point" ),
9592       QStringLiteral( "PointLayer" ),
9593       QStringLiteral( "memory" ) );
9594   project.addMapLayer( vectorLayer );
9595 
9596   QVariantList layerList;
9597   QVariantMap layerMap;
9598   layerMap["layer"] = "PointLayer";
9599   layerMap["attributeIndex"] = -1;
9600   layerList.append( layerMap );
9601 
9602   QVERIFY( definition.checkValueIsAcceptable( layerList, &context ) );
9603   wrapper.setWidgetValue( layerList, context );
9604   QCOMPARE( spy.count(), 1 );
9605 
9606   QVariant value = wrapper.widgetValue();
9607 
9608   QVERIFY( definition.checkValueIsAcceptable( value, &context ) );
9609   QString valueAsPythonString = definition.valueAsPythonString( value, context );
9610   QCOMPARE( valueAsPythonString, QStringLiteral( "[{'layer': '%1','attributeIndex': -1}]" ).arg( vectorLayer->source() ) );
9611 }
9612 
testMeshDatasetWrapperLayerInProject()9613 void TestProcessingGui::testMeshDatasetWrapperLayerInProject()
9614 {
9615   QgsProcessingParameterMeshLayer layerDefinition( QStringLiteral( "layer" ), QStringLiteral( "layer" ) );
9616   QgsProcessingMeshLayerWidgetWrapper layerWrapper( &layerDefinition );
9617 
9618   QSet<int> supportedDataType( {QgsMeshDatasetGroupMetadata::DataOnVertices} );
9619   QgsProcessingParameterMeshDatasetGroups groupsDefinition( QStringLiteral( "groups" ),
9620       QStringLiteral( "groups" ),
9621       QStringLiteral( "layer" ),
9622       supportedDataType );
9623   QgsProcessingMeshDatasetGroupsWidgetWrapper groupsWrapper( &groupsDefinition );
9624 
9625   QgsProcessingParameterMeshDatasetTime timeDefinition( QStringLiteral( "time" ), QStringLiteral( "time" ), QStringLiteral( "layer" ), QStringLiteral( "groups" ) );
9626   QgsProcessingMeshDatasetTimeWidgetWrapper timeWrapper( &timeDefinition );
9627 
9628   QList<QgsAbstractProcessingParameterWidgetWrapper *> wrappers;
9629   wrappers << &layerWrapper << &groupsWrapper << &timeWrapper;
9630 
9631   QgsProject project;
9632   QgsProcessingContext context;
9633   context.setProject( &project );
9634   QgsProcessingParameterWidgetContext widgetContext;
9635   std::unique_ptr<QgsMapCanvas> mapCanvas = std::make_unique<QgsMapCanvas>();
9636   widgetContext.setMapCanvas( mapCanvas.get() );
9637 
9638   widgetContext.setProject( &project );
9639   layerWrapper.setWidgetContext( widgetContext );
9640   groupsWrapper.setWidgetContext( widgetContext );
9641   timeWrapper.setWidgetContext( widgetContext );
9642 
9643   TestProcessingContextGenerator generator( context );
9644   layerWrapper.registerProcessingContextGenerator( &generator );
9645   groupsWrapper.registerProcessingContextGenerator( &generator );
9646   timeWrapper.registerProcessingContextGenerator( &generator );
9647 
9648   QSignalSpy layerSpy( &layerWrapper, &QgsProcessingMeshLayerWidgetWrapper::widgetValueHasChanged );
9649   QSignalSpy groupsSpy( &groupsWrapper, &QgsProcessingMeshDatasetGroupsWidgetWrapper::widgetValueHasChanged );
9650   QSignalSpy timeSpy( &timeWrapper, &QgsProcessingMeshDatasetTimeWidgetWrapper::widgetValueHasChanged );
9651 
9652   std::unique_ptr<QWidget> layerWidget( layerWrapper.createWrappedWidget( context ) );
9653   std::unique_ptr<QWidget> groupWidget( groupsWrapper.createWrappedWidget( context ) );
9654   std::unique_ptr<QWidget> timeWidget( timeWrapper.createWrappedWidget( context ) );
9655   QgsProcessingMeshDatasetGroupsWidget *datasetGroupWidget = qobject_cast<QgsProcessingMeshDatasetGroupsWidget *>( groupWidget.get() );
9656   QgsProcessingMeshDatasetTimeWidget *datasetTimeWidget = qobject_cast<QgsProcessingMeshDatasetTimeWidget *>( timeWidget.get() );
9657 
9658   QVERIFY( layerWidget );
9659   QVERIFY( groupWidget );
9660   QVERIFY( datasetGroupWidget );
9661   QVERIFY( timeWidget );
9662 
9663   groupsWrapper.postInitialize( wrappers );
9664   timeWrapper.postInitialize( wrappers );
9665 
9666   QString dataDir = QString( TEST_DATA_DIR ); //defined in CmakeLists.txt
9667   dataDir += "/mesh";
9668   QString uri( dataDir + "/quad_and_triangle.2dm" );
9669   QString meshLayerName = QStringLiteral( "mesh layer" );
9670   QgsMeshLayer *layer = new QgsMeshLayer( uri, meshLayerName, QStringLiteral( "mdal" ) );
9671   QVERIFY( layer->isValid() );
9672   layer->addDatasets( dataDir + "/quad_and_triangle_vertex_scalar.dat" );
9673   layer->addDatasets( dataDir + "/quad_and_triangle_vertex_vector.dat" );
9674   layer->addDatasets( dataDir + "/quad_and_triangle_els_face_scalar.dat" );
9675   layer->addDatasets( dataDir + "/quad_and_triangle_els_face_vector.dat" );
9676   QgsMeshRendererSettings settings = layer->rendererSettings();
9677   // 1 dataset on vertices and 1 dataset on faces
9678   settings.setActiveScalarDatasetGroup( 1 );
9679   settings.setActiveVectorDatasetGroup( 4 );
9680   layer->setRendererSettings( settings );
9681   QCOMPARE( layer->datasetGroupCount(), 5 );
9682 
9683   layerSpy.clear();
9684   groupsSpy.clear();
9685   timeSpy.clear();
9686 
9687   // without layer in the project
9688   QString meshOutOfProject( dataDir + "/trap_steady_05_3D.nc" );
9689   layerWrapper.setWidgetValue( meshOutOfProject, context );
9690 
9691   QCOMPARE( layerSpy.count(), 1 );
9692   QCOMPARE( groupsSpy.count(), 1 );
9693   QCOMPARE( timeSpy.count(), 1 );
9694 
9695   QVERIFY( datasetTimeWidget->radioButtonDatasetGroupTimeStep->isChecked() );
9696 
9697   QVariantList groups;
9698   groups << 0;
9699   groupsWrapper.setWidgetValue( groups, context );
9700   QVERIFY( groupsDefinition.checkValueIsAcceptable( groupsWrapper.widgetValue() ) );
9701   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsWrapper.widgetValue() ), QList<int>() << 0 );
9702   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "static" ) );
9703 
9704   QCOMPARE( layerSpy.count(), 1 );
9705   QCOMPARE( groupsSpy.count(), 2 );
9706   QCOMPARE( timeSpy.count(), 3 );
9707 
9708   // with layer in the project
9709   layerSpy.clear();
9710   groupsSpy.clear();
9711   timeSpy.clear();
9712 
9713   project.addMapLayer( layer );
9714   static_cast<QgsMeshLayerTemporalProperties *>( layer->temporalProperties() )->setReferenceTime(
9715     QDateTime( QDate( 2020, 01, 01 ), QTime( 0, 0, 0 ), Qt::UTC ), layer->dataProvider()->temporalCapabilities() );
9716   layerWrapper.setWidgetValue( meshLayerName, context );
9717 
9718   QCOMPARE( layerSpy.count(), 1 );
9719   QCOMPARE( groupsSpy.count(), 1 );
9720   QCOMPARE( timeSpy.count(), 2 );
9721 
9722   datasetGroupWidget->selectCurrentActiveDatasetGroup();
9723 
9724   QCOMPARE( layerSpy.count(), 1 );
9725   QCOMPARE( groupsSpy.count(), 2 );
9726   QCOMPARE( timeSpy.count(), 3 );
9727 
9728   QVariant groupsValue = groupsWrapper.widgetValue();
9729   QVERIFY( groupsValue.type() == QVariant::List );
9730   QVariantList groupsList = groupsValue.toList();
9731   QCOMPARE( groupsList.count(), 1 );
9732   QCOMPARE( groupsList.at( 0 ).toInt(), 1 );
9733   QString pythonString = groupsDefinition.valueAsPythonString( groupsValue, context );
9734   QCOMPARE( pythonString, QStringLiteral( "[1]" ) );
9735   QVERIFY( groupsDefinition.checkValueIsAcceptable( groupsValue ) );
9736   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsValue ), QList<int>( {1} ) );
9737 
9738   // 2 datasets on vertices
9739   settings = layer->rendererSettings();
9740   settings.setActiveVectorDatasetGroup( 2 );
9741   layer->setRendererSettings( settings );
9742   datasetGroupWidget->selectCurrentActiveDatasetGroup();
9743 
9744   QCOMPARE( layerSpy.count(), 1 );
9745   QCOMPARE( groupsSpy.count(), 3 );
9746   QCOMPARE( timeSpy.count(), 4 );
9747 
9748   pythonString = groupsDefinition.valueAsPythonString( groupsWrapper.widgetValue(), context );
9749   QCOMPARE( pythonString, QStringLiteral( "[1,2]" ) );
9750   QVERIFY( groupsDefinition.checkValueIsAcceptable( groupsWrapper.widgetValue() ) );
9751   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsWrapper.widgetValue() ), QList<int>() << 1 << 2 );
9752 
9753   datasetTimeWidget->radioButtonDatasetGroupTimeStep->setChecked( true );
9754   QCOMPARE( layerSpy.count(), 1 );
9755   QCOMPARE( groupsSpy.count(), 3 );
9756   QCOMPARE( timeSpy.count(), 4 ); //radioButtonDatasetGroupTimeStep already checked
9757 
9758   QVariant timeValue = timeWrapper.widgetValue();
9759   QVERIFY( timeValue.type() == QVariant::Map );
9760   QVariantMap timeValueMap = timeValue.toMap();
9761   QCOMPARE( timeValueMap[QStringLiteral( "type" )].toString(), QStringLiteral( "dataset-time-step" ) );
9762   pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context );
9763   QCOMPARE( pythonString, QStringLiteral( "{'type': 'dataset-time-step','value': [1,0]}" ) );
9764   QVERIFY( timeDefinition.checkValueIsAcceptable( timeValue ) );
9765   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeValue ), QStringLiteral( "dataset-time-step" ) );
9766   QVERIFY( QgsProcessingParameterMeshDatasetTime::timeValueAsDatasetIndex( timeValue ) == QgsMeshDatasetIndex( 1, 0 ) );
9767 
9768   datasetTimeWidget->radioButtonDefinedDateTime->setChecked( true );
9769   QDateTime dateTime = QDateTime( QDate( 2020, 1, 1 ), QTime( 0, 1, 0 ), Qt::UTC );
9770   datasetTimeWidget->dateTimeEdit->setDateTime( dateTime );
9771   QCOMPARE( layerSpy.count(), 1 );
9772   QCOMPARE( groupsSpy.count(), 3 );
9773   QCOMPARE( timeSpy.count(), 6 );
9774   pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context );
9775   QCOMPARE( pythonString, QStringLiteral( "{'type': 'defined-date-time','value': QDateTime(QDate(2020, 1, 1), QTime(0, 1, 0))}" ) );
9776   QVERIFY( timeDefinition.checkValueIsAcceptable( timeWrapper.widgetValue() ) );
9777   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "defined-date-time" ) );
9778   QCOMPARE( QgsProcessingParameterMeshDatasetTime::timeValueAsDefinedDateTime( timeWrapper.widgetValue() ), dateTime );
9779 
9780   QVERIFY( !datasetTimeWidget->radioButtonCurrentCanvasTime->isEnabled() );
9781   mapCanvas->setTemporalRange( QgsDateTimeRange( QDateTime( QDate( 2021, 1, 1 ), QTime( 0, 3, 0 ), Qt::UTC ), QDateTime( QDate( 2020, 1, 1 ), QTime( 0, 5, 0 ), Qt::UTC ) ) );
9782   QVERIFY( datasetTimeWidget->radioButtonCurrentCanvasTime->isEnabled() );
9783 
9784   datasetTimeWidget->radioButtonCurrentCanvasTime->setChecked( true );
9785   QCOMPARE( layerSpy.count(), 1 );
9786   QCOMPARE( groupsSpy.count(), 3 );
9787   QCOMPARE( timeSpy.count(), 8 );
9788   pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context );
9789   QCOMPARE( pythonString, QStringLiteral( "{'type': 'current-context-time'}" ) );
9790   QVERIFY( timeDefinition.checkValueIsAcceptable( timeWrapper.widgetValue() ) );
9791   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "current-context-time" ) );
9792 
9793   // 0 dataset on vertices
9794   settings = layer->rendererSettings();
9795   settings.setActiveScalarDatasetGroup( -1 );
9796   settings.setActiveVectorDatasetGroup( -1 );
9797   layer->setRendererSettings( settings );
9798   datasetGroupWidget->selectCurrentActiveDatasetGroup();
9799   QVERIFY( !datasetTimeWidget->isEnabled() );
9800   pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context );
9801   QCOMPARE( pythonString, QStringLiteral( "{'type': 'static'}" ) );
9802   QVERIFY( timeDefinition.checkValueIsAcceptable( timeWrapper.widgetValue() ) );
9803   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "static" ) );
9804 
9805   // 1 static dataset on vertices
9806   settings = layer->rendererSettings();
9807   settings.setActiveScalarDatasetGroup( 0 );
9808   settings.setActiveVectorDatasetGroup( -1 );
9809   layer->setRendererSettings( settings );
9810   datasetGroupWidget->selectCurrentActiveDatasetGroup();
9811   QVERIFY( !datasetTimeWidget->isEnabled() );
9812   pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context );
9813   QCOMPARE( pythonString, QStringLiteral( "{'type': 'static'}" ) );
9814   QVERIFY( timeDefinition.checkValueIsAcceptable( timeWrapper.widgetValue() ) );
9815   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "static" ) );
9816 
9817   groupsWrapper.setWidgetValue( 3, context );
9818   QCOMPARE( datasetGroupWidget->value(), QVariantList( {3} ) );
9819   groupsWrapper.setWidgetValue( QVariantList( {1, 2, 3} ), context );
9820   QCOMPARE( datasetGroupWidget->value().toList(), QVariantList( {1, 2, 3} ) );
9821   groupsWrapper.setWidgetValue( QVariantList( {"1", "2", "3"} ), context );
9822   QCOMPARE( datasetGroupWidget->value().toList(), QVariantList( {1, 2, 3} ) );
9823   groupsWrapper.setWidgetValue( QgsProperty::fromExpression( QStringLiteral( "1+3" ) ), context );
9824   QCOMPARE( datasetGroupWidget->value().toList(), QVariantList( {4} ) );
9825 
9826   timeWrapper.setWidgetValue( QDateTime( QDate( 2020, 01, 02 ), QTime( 1, 2, 3 ) ), context );
9827   pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context );
9828   QCOMPARE( pythonString, QStringLiteral( "{'type': 'defined-date-time','value': QDateTime(QDate(2020, 1, 2), QTime(1, 2, 3))}" ) );
9829   timeWrapper.setWidgetValue( QVariant::fromValue( QDateTime( QDate( 2020, 02, 01 ), QTime( 3, 2, 1 ) ) ).toString(), context );
9830   pythonString = timeDefinition.valueAsPythonString( timeWrapper.widgetValue(), context );
9831   QCOMPARE( pythonString, QStringLiteral( "{'type': 'defined-date-time','value': QDateTime(QDate(2020, 2, 1), QTime(3, 2, 1))}" ) );
9832 }
9833 
testMeshDatasetWrapperLayerOutsideProject()9834 void TestProcessingGui::testMeshDatasetWrapperLayerOutsideProject()
9835 {
9836   QgsProcessingParameterMeshLayer layerDefinition( QStringLiteral( "layer" ), QStringLiteral( "layer" ) );
9837   QgsProcessingMeshLayerWidgetWrapper layerWrapper( &layerDefinition );
9838 
9839   QSet<int> supportedDataType( {QgsMeshDatasetGroupMetadata::DataOnFaces} );
9840   QgsProcessingParameterMeshDatasetGroups groupsDefinition( QStringLiteral( "groups" ),
9841       QStringLiteral( "groups" ),
9842       QStringLiteral( "layer" ),
9843       supportedDataType );
9844   QgsProcessingMeshDatasetGroupsWidgetWrapper groupsWrapper( &groupsDefinition );
9845 
9846   QgsProcessingParameterMeshDatasetTime timeDefinition( QStringLiteral( "time" ), QStringLiteral( "time" ), QStringLiteral( "layer" ), QStringLiteral( "groups" ) );
9847   QgsProcessingMeshDatasetTimeWidgetWrapper timeWrapper( &timeDefinition );
9848 
9849   QList<QgsAbstractProcessingParameterWidgetWrapper *> wrappers;
9850   wrappers << &layerWrapper << &groupsWrapper << &timeWrapper;
9851 
9852   QgsProject project;
9853   QgsProcessingContext context;
9854   context.setProject( &project );
9855   QgsProcessingParameterWidgetContext widgetContext;
9856   std::unique_ptr<QgsMapCanvas> mapCanvas = std::make_unique<QgsMapCanvas>();
9857   widgetContext.setMapCanvas( mapCanvas.get() );
9858 
9859   widgetContext.setProject( &project );
9860   layerWrapper.setWidgetContext( widgetContext );
9861   groupsWrapper.setWidgetContext( widgetContext );
9862   timeWrapper.setWidgetContext( widgetContext );
9863 
9864   TestProcessingContextGenerator generator( context );
9865   layerWrapper.registerProcessingContextGenerator( &generator );
9866   groupsWrapper.registerProcessingContextGenerator( &generator );
9867   timeWrapper.registerProcessingContextGenerator( &generator );
9868 
9869   QSignalSpy layerSpy( &layerWrapper, &QgsProcessingMeshLayerWidgetWrapper::widgetValueHasChanged );
9870   QSignalSpy groupsSpy( &groupsWrapper, &QgsProcessingMeshDatasetGroupsWidgetWrapper::widgetValueHasChanged );
9871   QSignalSpy timeSpy( &timeWrapper, &QgsProcessingMeshDatasetTimeWidgetWrapper::widgetValueHasChanged );
9872 
9873   std::unique_ptr<QWidget> layerWidget( layerWrapper.createWrappedWidget( context ) );
9874   std::unique_ptr<QWidget> groupWidget( groupsWrapper.createWrappedWidget( context ) );
9875   std::unique_ptr<QWidget> timeWidget( timeWrapper.createWrappedWidget( context ) );
9876   QgsProcessingMeshDatasetGroupsWidget *datasetGroupWidget = qobject_cast<QgsProcessingMeshDatasetGroupsWidget *>( groupWidget.get() );
9877   QgsProcessingMeshDatasetTimeWidget *datasetTimeWidget = qobject_cast<QgsProcessingMeshDatasetTimeWidget *>( timeWidget.get() );
9878 
9879   QVERIFY( layerWidget );
9880   QVERIFY( groupWidget );
9881   QVERIFY( datasetGroupWidget );
9882   QVERIFY( timeWidget );
9883 
9884   groupsWrapper.postInitialize( wrappers );
9885   timeWrapper.postInitialize( wrappers );
9886 
9887   layerSpy.clear();
9888   groupsSpy.clear();
9889   timeSpy.clear();
9890 
9891   QString dataDir = QString( TEST_DATA_DIR ); //defined in CmakeLists.txt
9892   QString meshOutOfProject( dataDir + "/mesh/trap_steady_05_3D.nc" );
9893   layerWrapper.setWidgetValue( meshOutOfProject, context );
9894 
9895   QCOMPARE( layerSpy.count(), 1 );
9896   QCOMPARE( groupsSpy.count(), 1 );
9897   QCOMPARE( timeSpy.count(), 1 );
9898 
9899   QVariantList groups;
9900   groups << 0;
9901   groupsWrapper.setWidgetValue( groups, context );
9902   QCOMPARE( layerSpy.count(), 1 );
9903   QCOMPARE( groupsSpy.count(), 2 );
9904   QCOMPARE( timeSpy.count(), 3 );
9905   QVERIFY( groupsDefinition.checkValueIsAcceptable( groupsWrapper.widgetValue() ) );
9906   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsWrapper.widgetValue() ), QList<int>() << 0 );
9907   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "static" ) );
9908   QVERIFY( !datasetTimeWidget->isEnabled() );
9909 
9910   groups << 11;
9911   groupsWrapper.setWidgetValue( groups, context );
9912   QCOMPARE( layerSpy.count(), 1 );
9913   QCOMPARE( groupsSpy.count(), 3 );
9914   QCOMPARE( timeSpy.count(), 5 );
9915   QVERIFY( datasetTimeWidget->isEnabled() );
9916   QVERIFY( groupsDefinition.checkValueIsAcceptable( groupsWrapper.widgetValue() ) );
9917   QCOMPARE( QgsProcessingParameterMeshDatasetGroups::valueAsDatasetGroup( groupsWrapper.widgetValue() ), QList<int>() << 0 << 11 );
9918   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "dataset-time-step" ) );
9919   QVERIFY( QgsProcessingParameterMeshDatasetTime::timeValueAsDatasetIndex( timeWrapper.widgetValue() ) == QgsMeshDatasetIndex( 11, 0 ) );
9920 
9921   QVERIFY( datasetTimeWidget->radioButtonDefinedDateTime->isEnabled() );
9922   QVERIFY( !datasetTimeWidget->radioButtonCurrentCanvasTime->isEnabled() );
9923 
9924   datasetTimeWidget->radioButtonDefinedDateTime->setChecked( true );
9925   QCOMPARE( QgsProcessingParameterMeshDatasetTime::valueAsTimeType( timeWrapper.widgetValue() ), QStringLiteral( "defined-date-time" ) );
9926   QCOMPARE( QgsProcessingParameterMeshDatasetTime::timeValueAsDefinedDateTime( timeWrapper.widgetValue() ),
9927             QDateTime( QDate( 1990, 1, 1 ), QTime( 0, 0, 0 ), Qt::UTC ) );
9928 
9929 
9930   mapCanvas->setTemporalRange( QgsDateTimeRange( QDateTime( QDate( 2021, 1, 1 ), QTime( 0, 3, 0 ), Qt::UTC ), QDateTime( QDate( 2020, 1, 1 ), QTime( 0, 5, 0 ), Qt::UTC ) ) );
9931   QVERIFY( datasetTimeWidget->radioButtonCurrentCanvasTime->isEnabled() );
9932 
9933 }
9934 
testPointCloudLayerWrapper()9935 void TestProcessingGui::testPointCloudLayerWrapper()
9936 {
9937   // setup a project with a range of layer types
9938   QgsProject::instance()->removeAllMapLayers();
9939   QgsPointCloudLayer *cloud1 = new QgsPointCloudLayer( QStringLiteral( TEST_DATA_DIR ) + "/point_clouds/ept/sunshine-coast/ept.json", QStringLiteral( "cloud1" ), QStringLiteral( "ept" ) );
9940   QVERIFY( cloud1->isValid() );
9941   QgsProject::instance()->addMapLayer( cloud1 );
9942   QgsPointCloudLayer *cloud2 = new QgsPointCloudLayer( QStringLiteral( TEST_DATA_DIR ) + "/point_clouds/ept/sunshine-coast/ept.json", QStringLiteral( "cloud2" ), QStringLiteral( "ept" ) );
9943   QVERIFY( cloud2->isValid() );
9944   QgsProject::instance()->addMapLayer( cloud2 );
9945 
9946   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
9947   {
9948     // non optional
9949     QgsProcessingParameterPointCloudLayer param( QStringLiteral( "cloud" ), QStringLiteral( "cloud" ), false );
9950 
9951     QgsProcessingPointCloudLayerWidgetWrapper wrapper( &param, type );
9952 
9953     QgsProcessingContext context;
9954     QWidget *w = wrapper.createWrappedWidget( context );
9955 
9956     QSignalSpy spy( &wrapper, &QgsProcessingPointCloudLayerWidgetWrapper::widgetValueHasChanged );
9957     wrapper.setWidgetValue( QStringLiteral( "bb" ), context );
9958 
9959     switch ( type )
9960     {
9961       case QgsProcessingGui::Standard:
9962       case QgsProcessingGui::Batch:
9963       case QgsProcessingGui::Modeler:
9964         QCOMPARE( spy.count(), 1 );
9965         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "bb" ) );
9966         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
9967         wrapper.setWidgetValue( QStringLiteral( "aa" ), context );
9968         QCOMPARE( spy.count(), 2 );
9969         QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "aa" ) );
9970         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper.wrappedWidget() )->currentText(), QStringLiteral( "aa" ) );
9971         break;
9972     }
9973 
9974     delete w;
9975 
9976     // with project
9977     QgsProcessingParameterWidgetContext widgetContext;
9978     widgetContext.setProject( QgsProject::instance() );
9979     context.setProject( QgsProject::instance() );
9980 
9981     QgsProcessingMapLayerWidgetWrapper wrapper2( &param, type );
9982     wrapper2.setWidgetContext( widgetContext );
9983     w = wrapper2.createWrappedWidget( context );
9984 
9985     QSignalSpy spy2( &wrapper2, &QgsProcessingPointCloudLayerWidgetWrapper::widgetValueHasChanged );
9986     wrapper2.setWidgetValue( QStringLiteral( "bb" ), context );
9987     QCOMPARE( spy2.count(), 1 );
9988     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "bb" ) );
9989     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
9990     wrapper2.setWidgetValue( QStringLiteral( "cloud2" ), context );
9991     QCOMPARE( spy2.count(), 2 );
9992     QCOMPARE( wrapper2.widgetValue().toString(), cloud2->id() );
9993     switch ( type )
9994     {
9995       case QgsProcessingGui::Standard:
9996       case QgsProcessingGui::Batch:
9997         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "cloud2 [EPSG:28356]" ) );
9998         break;
9999       case QgsProcessingGui::Modeler:
10000         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "cloud2" ) );
10001         break;
10002     }
10003 
10004     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "cloud2" ) );
10005 
10006     // check signal
10007     static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->setLayer( cloud1 );
10008     QCOMPARE( spy2.count(), 3 );
10009     QCOMPARE( wrapper2.widgetValue().toString(), cloud1->id() );
10010     switch ( type )
10011     {
10012       case QgsProcessingGui::Standard:
10013       case QgsProcessingGui::Batch:
10014         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "cloud1 [EPSG:28356]" ) );
10015         break;
10016 
10017       case QgsProcessingGui::Modeler:
10018         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "cloud1" ) );
10019         break;
10020     }
10021     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "cloud1" ) );
10022 
10023     delete w;
10024 
10025     // optional
10026     QgsProcessingParameterPoint param2( QStringLiteral( "cloud" ), QStringLiteral( "cloud" ), QVariant(), true );
10027     QgsProcessingPointCloudLayerWidgetWrapper wrapper3( &param2, type );
10028     wrapper3.setWidgetContext( widgetContext );
10029     w = wrapper3.createWrappedWidget( context );
10030 
10031     QSignalSpy spy3( &wrapper3, &QgsProcessingPointCloudLayerWidgetWrapper::widgetValueHasChanged );
10032     wrapper3.setWidgetValue( QStringLiteral( "bb" ), context );
10033     QCOMPARE( spy3.count(), 1 );
10034     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "bb" ) );
10035     QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "bb" ) );
10036     wrapper3.setWidgetValue( QStringLiteral( "cloud2" ), context );
10037     QCOMPARE( spy3.count(), 2 );
10038     QCOMPARE( wrapper3.widgetValue().toString(), cloud2->id() );
10039     switch ( type )
10040     {
10041       case QgsProcessingGui::Standard:
10042       case QgsProcessingGui::Batch:
10043         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "cloud2 [EPSG:28356]" ) );
10044         break;
10045       case QgsProcessingGui::Modeler:
10046         QCOMPARE( static_cast< QgsProcessingMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "cloud2" ) );
10047         break;
10048     }
10049     wrapper3.setWidgetValue( QVariant(), context );
10050     QCOMPARE( spy3.count(), 3 );
10051     QVERIFY( !wrapper3.widgetValue().isValid() );
10052     delete w;
10053 
10054     QLabel *l = wrapper.createWrappedLabel();
10055     if ( wrapper.type() != QgsProcessingGui::Batch )
10056     {
10057       QVERIFY( l );
10058       QCOMPARE( l->text(), QStringLiteral( "cloud" ) );
10059       QCOMPARE( l->toolTip(), param.toolTip() );
10060       delete l;
10061     }
10062     else
10063     {
10064       QVERIFY( !l );
10065     }
10066   };
10067 
10068   // standard wrapper
10069   testWrapper( QgsProcessingGui::Standard );
10070 
10071   // batch wrapper
10072   testWrapper( QgsProcessingGui::Batch );
10073 
10074   // modeler wrapper
10075   testWrapper( QgsProcessingGui::Modeler );
10076 }
10077 
testAnnotationLayerWrapper()10078 void TestProcessingGui::testAnnotationLayerWrapper()
10079 {
10080   // setup a project with a range of layer types
10081   QgsProject::instance()->removeAllMapLayers();
10082   QgsAnnotationLayer *layer1 = new QgsAnnotationLayer( QStringLiteral( "secondary annotations" ), QgsAnnotationLayer::LayerOptions( QgsProject::instance()->transformContext() ) );
10083   QVERIFY( layer1->isValid() );
10084   QgsProject::instance()->addMapLayer( layer1 );
10085 
10086   auto testWrapper = [ = ]( QgsProcessingGui::WidgetType type )
10087   {
10088     // non optional
10089     QgsProcessingParameterAnnotationLayer param( QStringLiteral( "annotation" ), QStringLiteral( "annotation" ), false );
10090 
10091     QgsProcessingAnnotationLayerWidgetWrapper wrapper( &param, type );
10092 
10093     QgsProcessingContext context;
10094 
10095     // with project
10096     QgsProcessingParameterWidgetContext widgetContext;
10097     widgetContext.setProject( QgsProject::instance() );
10098     context.setProject( QgsProject::instance() );
10099 
10100     QgsProcessingAnnotationLayerWidgetWrapper wrapper2( &param, type );
10101     wrapper2.setWidgetContext( widgetContext );
10102     QWidget *w = wrapper2.createWrappedWidget( context );
10103 
10104     QSignalSpy spy2( &wrapper2, &QgsProcessingAnnotationLayerWidgetWrapper::widgetValueHasChanged );
10105     wrapper2.setWidgetValue( QStringLiteral( "main" ), context );
10106     QCOMPARE( spy2.count(), 1 );
10107     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "main" ) );
10108     QCOMPARE( qgis::down_cast< QgsMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "Annotations" ) );
10109     wrapper2.setWidgetValue( QStringLiteral( "secondary annotations" ), context );
10110     QCOMPARE( spy2.count(), 2 );
10111     QCOMPARE( wrapper2.widgetValue().toString(), layer1->id() );
10112     switch ( type )
10113     {
10114       case QgsProcessingGui::Standard:
10115       case QgsProcessingGui::Batch:
10116         QCOMPARE( qgis::down_cast< QgsMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "secondary annotations" ) );
10117         break;
10118       case QgsProcessingGui::Modeler:
10119         QCOMPARE( qgis::down_cast< QgsMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "secondary annotations" ) );
10120         break;
10121     }
10122 
10123     QCOMPARE( static_cast< QgsMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "secondary annotations" ) );
10124 
10125     // check signal
10126     static_cast< QgsMapLayerComboBox * >( wrapper2.wrappedWidget() )->setLayer( QgsProject::instance()->mainAnnotationLayer() );
10127     QCOMPARE( spy2.count(), 3 );
10128     QCOMPARE( wrapper2.widgetValue().toString(), QStringLiteral( "main" ) );
10129     switch ( type )
10130     {
10131       case QgsProcessingGui::Standard:
10132       case QgsProcessingGui::Batch:
10133         QCOMPARE( qgis::down_cast< QgsMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "Annotations" ) );
10134         break;
10135 
10136       case QgsProcessingGui::Modeler:
10137         QCOMPARE( qgis::down_cast< QgsMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentText(), QStringLiteral( "Annotations" ) );
10138         break;
10139     }
10140     QCOMPARE( qgis::down_cast< QgsMapLayerComboBox * >( wrapper2.wrappedWidget() )->currentLayer()->name(), QStringLiteral( "Annotations" ) );
10141 
10142     delete w;
10143 
10144     // optional
10145     QgsProcessingParameterAnnotationLayer param2( QStringLiteral( "annotation" ), QStringLiteral( "annotation" ), QVariant(), true );
10146     QgsProcessingAnnotationLayerWidgetWrapper wrapper3( &param2, type );
10147     wrapper3.setWidgetContext( widgetContext );
10148     w = wrapper3.createWrappedWidget( context );
10149 
10150     QSignalSpy spy3( &wrapper3, &QgsProcessingAnnotationLayerWidgetWrapper::widgetValueHasChanged );
10151     wrapper3.setWidgetValue( QStringLiteral( "main" ), context );
10152     QCOMPARE( spy3.count(), 1 );
10153     QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "main" ) );
10154     QCOMPARE( qgis::down_cast< QgsMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "Annotations" ) );
10155     wrapper3.setWidgetValue( QStringLiteral( "secondary annotations" ), context );
10156     QCOMPARE( spy3.count(), 2 );
10157     QCOMPARE( wrapper3.widgetValue().toString(), layer1->id() );
10158     switch ( type )
10159     {
10160       case QgsProcessingGui::Standard:
10161       case QgsProcessingGui::Batch:
10162         QCOMPARE( qgis::down_cast< QgsMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "secondary annotations" ) );
10163         break;
10164       case QgsProcessingGui::Modeler:
10165         QCOMPARE( qgis::down_cast< QgsMapLayerComboBox * >( wrapper3.wrappedWidget() )->currentText(), QStringLiteral( "secondary annotations" ) );
10166         break;
10167     }
10168     wrapper3.setWidgetValue( QVariant(), context );
10169     QCOMPARE( spy3.count(), 3 );
10170     QVERIFY( !wrapper3.widgetValue().isValid() );
10171     delete w;
10172 
10173     QLabel *l = wrapper.createWrappedLabel();
10174     if ( wrapper.type() != QgsProcessingGui::Batch )
10175     {
10176       QVERIFY( l );
10177       QCOMPARE( l->text(), QStringLiteral( "annotation" ) );
10178       QCOMPARE( l->toolTip(), param.toolTip() );
10179       delete l;
10180     }
10181     else
10182     {
10183       QVERIFY( !l );
10184     }
10185   };
10186 
10187   // standard wrapper
10188   testWrapper( QgsProcessingGui::Standard );
10189 
10190   // batch wrapper
10191   testWrapper( QgsProcessingGui::Batch );
10192 
10193   // modeler wrapper
10194   testWrapper( QgsProcessingGui::Modeler );
10195 }
10196 
testModelGraphicsView()10197 void TestProcessingGui::testModelGraphicsView()
10198 {
10199   // test model
10200   QgsProcessingModelAlgorithm model1;
10201 
10202   QgsProcessingModelChildAlgorithm algc1;
10203   algc1.setChildId( "buffer" );
10204   algc1.setAlgorithmId( "native:buffer" );
10205   QgsProcessingModelParameter param;
10206   param.setParameterName( QStringLiteral( "LAYER" ) );
10207   param.setSize( QSizeF( 500, 400 ) );
10208   param.setPosition( QPointF( 101, 102 ) );
10209   param.comment()->setDescription( QStringLiteral( "input comment" ) );
10210   model1.addModelParameter( new QgsProcessingParameterMapLayer( QStringLiteral( "LAYER" ) ), param );
10211   algc1.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "LAYER" ) ) );
10212   algc1.comment()->setDescription( QStringLiteral( "alg comment" ) );
10213   algc1.comment()->setSize( QSizeF( 300, 200 ) );
10214   algc1.comment()->setPosition( QPointF( 201, 202 ) );
10215 
10216   QgsProcessingModelOutput modelOut;
10217   modelOut.setChildId( algc1.childId() );
10218   modelOut.setChildOutputName( QStringLiteral( "my_output" ) );
10219   modelOut.comment()->setDescription( QStringLiteral( "output comm" ) );
10220   QMap< QString, QgsProcessingModelOutput > outs;
10221   outs.insert( QStringLiteral( "OUTPUT" ), modelOut );
10222   algc1.setModelOutputs( outs );
10223   model1.addChildAlgorithm( algc1 );
10224 
10225   QgsProcessingModelGroupBox groupBox;
10226   groupBox.setDescription( QStringLiteral( "group" ) );
10227   model1.addGroupBox( groupBox );
10228 
10229   // hiding comments
10230   QgsProcessingContext context;
10231   QgsModelGraphicsScene scene2;
10232   scene2.setModel( &model1 );
10233   scene2.setFlags( QgsModelGraphicsScene::FlagHideComments );
10234   scene2.createItems( &model1, context );
10235   QList< QGraphicsItem * > items = scene2.items();
10236   QgsModelParameterGraphicItem *layerItem = nullptr;
10237   for ( QGraphicsItem *item : items )
10238   {
10239     if ( QgsModelParameterGraphicItem *param = dynamic_cast< QgsModelParameterGraphicItem * >( item ) )
10240     {
10241       layerItem = param;
10242       break;
10243     }
10244   }
10245   QVERIFY( layerItem );
10246   QgsModelCommentGraphicItem *layerCommentItem = nullptr;
10247   for ( QGraphicsItem *item : items )
10248   {
10249     if ( QgsModelCommentGraphicItem *comment = dynamic_cast< QgsModelCommentGraphicItem * >( item ) )
10250     {
10251       layerCommentItem = comment;
10252       break;
10253     }
10254   }
10255   // should not exist
10256   QVERIFY( !layerCommentItem );
10257 
10258 
10259   QgsModelGraphicsScene scene;
10260   QVERIFY( !scene.model() );
10261   scene.setModel( &model1 );
10262   QCOMPARE( scene.model(), &model1 );
10263 
10264   QVERIFY( scene.items().empty() );
10265   scene.createItems( &model1, context );
10266   items = scene.items();
10267   layerItem = nullptr;
10268   for ( QGraphicsItem *item : items )
10269   {
10270     if ( QgsModelParameterGraphicItem *param = dynamic_cast< QgsModelParameterGraphicItem * >( item ) )
10271     {
10272       layerItem = param;
10273 
10274     }
10275   }
10276   QVERIFY( layerItem );
10277   QCOMPARE( dynamic_cast<  QgsProcessingModelParameter * >( layerItem->component() )->parameterName(), QStringLiteral( "LAYER" ) );
10278   QCOMPARE( layerItem->itemRect().size(), QSizeF( 500, 400 ) );
10279   QCOMPARE( layerItem->scenePos(), QPointF( 101, 102 ) );
10280 
10281   QgsModelChildAlgorithmGraphicItem *algItem = nullptr;
10282   for ( QGraphicsItem *item : items )
10283   {
10284     if ( QgsModelChildAlgorithmGraphicItem *param = dynamic_cast< QgsModelChildAlgorithmGraphicItem * >( item ) )
10285     {
10286       algItem = param;
10287       break;
10288     }
10289   }
10290   QVERIFY( algItem );
10291   QCOMPARE( dynamic_cast<  QgsProcessingModelChildAlgorithm * >( algItem->component() )->algorithmId(), QStringLiteral( "native:buffer" ) );
10292 
10293   QgsModelOutputGraphicItem *outputItem = nullptr;
10294   for ( QGraphicsItem *item : items )
10295   {
10296     if ( QgsModelOutputGraphicItem *comment = dynamic_cast< QgsModelOutputGraphicItem * >( item ) )
10297     {
10298       outputItem = comment;
10299       break;
10300     }
10301   }
10302   QVERIFY( outputItem );
10303   QCOMPARE( dynamic_cast< QgsProcessingModelOutput * >( outputItem->component() )->childOutputName(), QStringLiteral( "my_output" ) );
10304 
10305 
10306   layerCommentItem = nullptr;
10307   QgsModelCommentGraphicItem *algCommentItem = nullptr;
10308   QgsModelCommentGraphicItem *outputCommentItem = nullptr;
10309   for ( QGraphicsItem *item : items )
10310   {
10311     if ( QgsModelCommentGraphicItem *comment = dynamic_cast< QgsModelCommentGraphicItem * >( item ) )
10312     {
10313       if ( comment->parentComponentItem() == layerItem )
10314       {
10315         layerCommentItem = comment;
10316       }
10317       else if ( comment->parentComponentItem() == algItem )
10318       {
10319         algCommentItem = comment;
10320       }
10321       else if ( comment->parentComponentItem() == outputItem )
10322       {
10323         outputCommentItem = comment;
10324       }
10325     }
10326   }
10327 
10328   QVERIFY( algCommentItem );
10329   QCOMPARE( algCommentItem->component()->description(), QStringLiteral( "alg comment" ) );
10330   QCOMPARE( algCommentItem->itemRect().size(), QSizeF( 300, 200 ) );
10331   QCOMPARE( algCommentItem->scenePos(), QPointF( 201, 202 ) );
10332 
10333   QVERIFY( layerCommentItem );
10334   QCOMPARE( layerCommentItem->component()->description(), QStringLiteral( "input comment" ) );
10335 
10336   QVERIFY( outputCommentItem );
10337   QCOMPARE( outputCommentItem->component()->description(), QStringLiteral( "output comm" ) );
10338 
10339   QgsModelGroupBoxGraphicItem *groupItem = nullptr;
10340   for ( QGraphicsItem *item : items )
10341   {
10342     if ( QgsModelGroupBoxGraphicItem *comment = dynamic_cast< QgsModelGroupBoxGraphicItem * >( item ) )
10343     {
10344       groupItem = comment;
10345       break;
10346     }
10347   }
10348   QVERIFY( groupItem );
10349   QCOMPARE( dynamic_cast< QgsProcessingModelGroupBox * >( groupItem->component() )->description(), QStringLiteral( "group" ) );
10350 
10351 
10352   QgsModelGraphicsView view;
10353   view.setModelScene( &scene );
10354 
10355   // copy some items
10356   view.copyItems( QList< QgsModelComponentGraphicItem * >() << layerItem << algItem << groupItem, QgsModelGraphicsView::ClipboardCopy );
10357 
10358 
10359   // second view to paste into
10360   QgsProcessingModelAlgorithm algDest;
10361   QVERIFY( algDest.childAlgorithms().empty() );
10362   QVERIFY( algDest.parameterComponents().empty() );
10363   QVERIFY( algDest.groupBoxes().empty() );
10364   QgsModelGraphicsScene sceneDest;
10365   sceneDest.setModel( &algDest );
10366   QgsModelGraphicsView viewDest;
10367   viewDest.setModelScene( &sceneDest );
10368   viewDest.pasteItems( QgsModelGraphicsView::PasteModeInPlace );
10369 
10370   QCOMPARE( algDest.parameterComponents().size(), 1 );
10371   QCOMPARE( algDest.parameterComponents().value( QStringLiteral( "LAYER" ) ).parameterName(), QStringLiteral( "LAYER" ) );
10372   // comment should not be copied, was not selected
10373   QCOMPARE( algDest.parameterComponents().value( QStringLiteral( "LAYER" ) ).comment()->description(), QString() );
10374   QCOMPARE( algDest.childAlgorithms().size(), 1 );
10375   QCOMPARE( algDest.childAlgorithms().keys().at( 0 ), QStringLiteral( "native:buffer_1" ) );
10376   QCOMPARE( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_1" ) ).algorithmId(), QStringLiteral( "native:buffer" ) );
10377   QCOMPARE( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_1" ) ).comment()->description(), QString() );
10378   // output was not selected
10379   QVERIFY( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_1" ) ).modelOutputs().empty() );
10380   QCOMPARE( algDest.groupBoxes().size(), 1 );
10381   QCOMPARE( algDest.groupBoxes().at( 0 ).description(), QStringLiteral( "group" ) );
10382 
10383   // copy comments and output (not output comment though!)
10384   view.copyItems( QList< QgsModelComponentGraphicItem * >() << layerItem << layerCommentItem << algItem << algCommentItem << outputItem << groupItem, QgsModelGraphicsView::ClipboardCopy );
10385   viewDest.pasteItems( QgsModelGraphicsView::PasteModeInPlace );
10386 
10387   QCOMPARE( algDest.parameterComponents().size(), 2 );
10388   QCOMPARE( algDest.parameterComponents().value( QStringLiteral( "LAYER" ) ).parameterName(), QStringLiteral( "LAYER" ) );
10389   QCOMPARE( algDest.parameterComponents().value( QStringLiteral( "LAYER (2)" ) ).parameterName(), QStringLiteral( "LAYER (2)" ) );
10390   QCOMPARE( algDest.parameterComponents().value( QStringLiteral( "LAYER" ) ).comment()->description(), QString() );
10391   QCOMPARE( algDest.parameterComponents().value( QStringLiteral( "LAYER (2)" ) ).comment()->description(), QStringLiteral( "input comment" ) );
10392   QCOMPARE( algDest.childAlgorithms().size(), 2 );
10393   QCOMPARE( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_1" ) ).algorithmId(), QStringLiteral( "native:buffer" ) );
10394   QCOMPARE( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_1" ) ).comment()->description(), QString() );
10395   QCOMPARE( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_2" ) ).algorithmId(), QStringLiteral( "native:buffer" ) );
10396   QCOMPARE( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_2" ) ).comment()->description(), QStringLiteral( "alg comment" ) );
10397   QVERIFY( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_1" ) ).modelOutputs().empty() );
10398   QCOMPARE( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_2" ) ).modelOutputs().size(), 1 );
10399   // output comment wasn't selected
10400   QCOMPARE( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_2" ) ).modelOutputs().value( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_2" ) ).modelOutputs().keys().at( 0 ) ).comment()->description(), QString() );
10401   QCOMPARE( algDest.groupBoxes().size(), 2 );
10402   QCOMPARE( algDest.groupBoxes().at( 0 ).description(), QStringLiteral( "group" ) );
10403   QCOMPARE( algDest.groupBoxes().at( 1 ).description(), QStringLiteral( "group" ) );
10404 
10405   // output and output comment
10406   view.copyItems( QList< QgsModelComponentGraphicItem * >() << algItem << outputItem << outputCommentItem, QgsModelGraphicsView::ClipboardCopy );
10407   viewDest.pasteItems( QgsModelGraphicsView::PasteModeInPlace );
10408   QCOMPARE( algDest.childAlgorithms().size(), 3 );
10409   QCOMPARE( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_3" ) ).modelOutputs().size(), 1 );
10410   QCOMPARE( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_3" ) ).modelOutputs().value( algDest.childAlgorithms().value( QStringLiteral( "native:buffer_3" ) ).modelOutputs().keys().at( 0 ) ).comment()->description(), QStringLiteral( "output comm" ) );
10411 }
10412 
cleanupTempDir()10413 void TestProcessingGui::cleanupTempDir()
10414 {
10415   QDir tmpDir = QDir( mTempDir );
10416   if ( tmpDir.exists() )
10417   {
10418     for ( const QString &tf : tmpDir.entryList( QDir::NoDotAndDotDot | QDir::Files ) )
10419     {
10420       QVERIFY2( tmpDir.remove( mTempDir + '/' + tf ), qPrintable( "Could not remove " + mTempDir + '/' + tf ) );
10421     }
10422     QVERIFY2( tmpDir.rmdir( mTempDir ), qPrintable( "Could not remove directory " + mTempDir ) );
10423   }
10424 }
10425 
10426 QGSTEST_MAIN( TestProcessingGui )
10427 #include "testprocessinggui.moc"
10428