1# -*- coding: utf-8 -*-
2
3"""
4***************************************************************************
5    ParametersTest
6    ---------------------
7    Date                 : August 2017
8    Copyright            : (C) 2017 by Nyall Dawson
9    Email                : nyall dot dawson at gmail dot com
10***************************************************************************
11*                                                                         *
12*   This program is free software; you can redistribute it and/or modify  *
13*   it under the terms of the GNU General Public License as published by  *
14*   the Free Software Foundation; either version 2 of the License, or     *
15*   (at your option) any later version.                                   *
16*                                                                         *
17***************************************************************************
18"""
19
20__author__ = 'Nyall Dawson'
21__date__ = 'August 2017'
22__copyright__ = '(C) 2017, Nyall Dawson'
23
24import os
25from qgis.testing import start_app, unittest
26from qgis.core import (QgsApplication,
27                       QgsCoordinateReferenceSystem,
28                       QgsProcessingParameterMatrix,
29                       QgsProcessingOutputLayerDefinition,
30                       QgsProcessingParameterFeatureSink,
31                       QgsProcessingParameterFileDestination,
32                       QgsProcessingParameterFolderDestination,
33                       QgsProcessingParameterVectorDestination,
34                       QgsProcessingParameterRasterDestination,
35                       QgsProcessingParameterRange,
36                       QgsFeature,
37                       QgsProcessingModelAlgorithm,
38                       QgsUnitTypes,
39                       QgsProject)
40from qgis.analysis import QgsNativeAlgorithms
41
42from processing.gui.AlgorithmDialog import AlgorithmDialog
43from processing.gui.BatchAlgorithmDialog import BatchAlgorithmDialog
44from processing.modeler.ModelerParametersDialog import ModelerParametersDialog
45from processing.gui.wrappers import (
46    BandWidgetWrapper,
47    BooleanWidgetWrapper,
48    CrsWidgetWrapper,
49    DistanceWidgetWrapper,
50    EnumWidgetWrapper,
51    ExpressionWidgetWrapper,
52    ExtentWidgetWrapper,
53    FeatureSourceWidgetWrapper,
54    FileWidgetWrapper,
55    FixedTableWidgetWrapper,
56    MapLayerWidgetWrapper,
57    MeshWidgetWrapper,
58    MultipleLayerWidgetWrapper,
59    NumberWidgetWrapper,
60    PointWidgetWrapper,
61    ProcessingConfig,
62    QgsProcessingFeatureSourceDefinition,
63    QgsProcessingParameterBand,
64    QgsProcessingParameterBoolean,
65    QgsProcessingParameterCrs,
66    QgsProcessingParameterDistance,
67    QgsProcessingParameterDuration,
68    QgsProcessingParameterEnum,
69    QgsProcessingParameterExpression,
70    QgsProcessingParameterExtent,
71    QgsProcessingParameterFeatureSource,
72    QgsProcessingParameterField,
73    QgsProcessingParameterFile,
74    QgsProcessingParameterMapLayer,
75    QgsProcessingParameterMeshLayer,
76    QgsProcessingParameterMultipleLayers,
77    QgsProcessingParameterNumber,
78    QgsProcessingParameterPoint,
79    QgsProcessingParameterRasterLayer,
80    QgsProcessingParameterString,
81    QgsProcessingParameterVectorLayer,
82    QgsVectorLayer,
83    RangeWidgetWrapper,
84    RasterWidgetWrapper,
85    StringWidgetWrapper,
86    TableFieldWidgetWrapper,
87    VectorLayerWidgetWrapper,
88    WidgetWrapperFactory,
89)
90
91start_app()
92QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())
93
94testDataPath = os.path.join(os.path.dirname(__file__), 'testdata')
95
96
97class AlgorithmDialogTest(unittest.TestCase):
98
99    def testCreation(self):
100        alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
101        a = AlgorithmDialog(alg)
102        self.assertEqual(a.mainWidget().algorithm(), alg)
103
104
105class WrappersTest(unittest.TestCase):
106
107    @classmethod
108    def setUpClass(cls):
109        ProcessingConfig.initialize()
110
111    def checkConstructWrapper(self, param, expected_wrapper_class):
112        alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
113
114        # algorithm dialog
115        dlg = AlgorithmDialog(alg)
116        wrapper = WidgetWrapperFactory.create_wrapper_from_class(param, dlg)
117        self.assertIsNotNone(wrapper)
118        self.assertIsInstance(wrapper, expected_wrapper_class)
119        self.assertEqual(wrapper.dialog, dlg)
120        self.assertIsNotNone(wrapper.widget)
121        wrapper.widget.deleteLater()
122        del wrapper.widget
123        del wrapper
124
125        alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
126        # batch dialog
127        dlg = BatchAlgorithmDialog(alg)
128        wrapper = WidgetWrapperFactory.create_wrapper_from_class(param, dlg)
129        self.assertIsNotNone(wrapper)
130        self.assertIsInstance(wrapper, expected_wrapper_class)
131        self.assertEqual(wrapper.dialog, dlg)
132        self.assertIsNotNone(wrapper.widget)
133
134        alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
135
136        # modeler dialog
137        model = QgsProcessingModelAlgorithm()
138        dlg = ModelerParametersDialog(alg, model)
139        wrapper = WidgetWrapperFactory.create_wrapper_from_class(param, dlg)
140        self.assertIsNotNone(wrapper)
141        self.assertIsInstance(wrapper, expected_wrapper_class)
142        self.assertEqual(wrapper.dialog, dlg)
143        self.assertIsNotNone(wrapper.widget)
144
145        wrapper.widget.deleteLater()
146        del wrapper.widget
147
148    def testBoolean(self):
149        self.checkConstructWrapper(QgsProcessingParameterBoolean('test'), BooleanWidgetWrapper)
150
151    def testCrs(self):
152        self.checkConstructWrapper(QgsProcessingParameterCrs('test'), CrsWidgetWrapper)
153
154    def testExtent(self):
155        self.checkConstructWrapper(QgsProcessingParameterExtent('test'), ExtentWidgetWrapper)
156
157    def testPoint(self):
158        self.checkConstructWrapper(QgsProcessingParameterPoint('test'), PointWidgetWrapper)
159
160    def testFile(self):
161        self.checkConstructWrapper(QgsProcessingParameterFile('test'), FileWidgetWrapper)
162
163    def testMultiInput(self):
164        self.checkConstructWrapper(QgsProcessingParameterMultipleLayers('test'), MultipleLayerWidgetWrapper)
165
166    def testRasterInput(self):
167        self.checkConstructWrapper(QgsProcessingParameterRasterLayer('test'), RasterWidgetWrapper)
168
169    def testEnum(self):
170        self.checkConstructWrapper(QgsProcessingParameterEnum('test'), EnumWidgetWrapper)
171
172    def testString(self):
173        self.checkConstructWrapper(QgsProcessingParameterString('test'), StringWidgetWrapper)
174
175    def testExpression(self):
176        self.checkConstructWrapper(QgsProcessingParameterExpression('test'), ExpressionWidgetWrapper)
177
178    def testVector(self):
179        self.checkConstructWrapper(QgsProcessingParameterVectorLayer('test'), VectorLayerWidgetWrapper)
180
181    def testField(self):
182        self.checkConstructWrapper(QgsProcessingParameterField('test'), TableFieldWidgetWrapper)
183
184    def testSource(self):
185        self.checkConstructWrapper(QgsProcessingParameterFeatureSource('test'), FeatureSourceWidgetWrapper)
186
187        # dummy layer
188        layer = QgsVectorLayer('Point', 'test', 'memory')
189        # need at least one feature in order to have a selection
190        layer.dataProvider().addFeature(QgsFeature())
191        layer.selectAll()
192
193        self.assertTrue(layer.isValid())
194        QgsProject.instance().addMapLayer(layer)
195
196        alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
197        dlg = AlgorithmDialog(alg)
198        param = QgsProcessingParameterFeatureSource('test')
199        wrapper = FeatureSourceWidgetWrapper(param, dlg)
200        widget = wrapper.createWidget()
201
202        # check layer value
203        widget.show()
204        wrapper.setValue(layer.id())
205        self.assertEqual(wrapper.value(), layer.id())
206
207        # check selected only - expect a QgsProcessingFeatureSourceDefinition
208        wrapper.setValue(QgsProcessingFeatureSourceDefinition(layer.id(), True))
209        value = wrapper.value()
210        self.assertIsInstance(value, QgsProcessingFeatureSourceDefinition)
211        self.assertTrue(value.selectedFeaturesOnly)
212        self.assertEqual(value.source.staticValue(), layer.id())
213
214        # NOT selected only, expect a direct layer id or source value
215        wrapper.setValue(QgsProcessingFeatureSourceDefinition(layer.id(), False))
216        value = wrapper.value()
217        self.assertEqual(value, layer.id())
218
219        # with non-project layer
220        wrapper.setValue('/home/my_layer.shp')
221        value = wrapper.value()
222        self.assertEqual(value, '/home/my_layer.shp')
223
224        widget.deleteLater()
225        del widget
226
227    def testRange(self):
228        # minimal test to check if wrapper generate GUI for each processign context
229        self.checkConstructWrapper(QgsProcessingParameterRange('test'), RangeWidgetWrapper)
230
231        alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
232        dlg = AlgorithmDialog(alg)
233        param = QgsProcessingParameterRange(
234            name='test',
235            description='test',
236            type=QgsProcessingParameterNumber.Double,
237            defaultValue="0.0,100.0")
238
239        wrapper = RangeWidgetWrapper(param, dlg)
240        widget = wrapper.createWidget()
241
242        # range values check
243
244        # check initial value
245        self.assertEqual(widget.getValue(), '0.0,100.0')
246        # check set/get
247        widget.setValue("100.0,200.0")
248        self.assertEqual(widget.getValue(), '100.0,200.0')
249        # check that min/max are mutually adapted
250        widget.setValue("200.0,100.0")
251        self.assertEqual(widget.getValue(), '100.0,100.0')
252        widget.spnMax.setValue(50)
253        self.assertEqual(widget.getValue(), '50.0,50.0')
254        widget.spnMin.setValue(100)
255        self.assertEqual(widget.getValue(), '100.0,100.0')
256
257        # check for integers
258        param = QgsProcessingParameterRange(
259            name='test',
260            description='test',
261            type=QgsProcessingParameterNumber.Integer,
262            defaultValue="0.1,100.1")
263
264        wrapper = RangeWidgetWrapper(param, dlg)
265        widget = wrapper.createWidget()
266
267        # range values check
268
269        # check initial value
270        self.assertEqual(widget.getValue(), '0.0,100.0')
271        # check rounding
272        widget.setValue("100.1,200.1")
273        self.assertEqual(widget.getValue(), '100.0,200.0')
274        widget.setValue("100.6,200.6")
275        self.assertEqual(widget.getValue(), '101.0,201.0')
276        # check set/get
277        widget.setValue("100.1,200.1")
278        self.assertEqual(widget.getValue(), '100.0,200.0')
279        # check that min/max are mutually adapted
280        widget.setValue("200.1,100.1")
281        self.assertEqual(widget.getValue(), '100.0,100.0')
282        widget.spnMax.setValue(50.1)
283        self.assertEqual(widget.getValue(), '50.0,50.0')
284        widget.spnMin.setValue(100.1)
285        self.assertEqual(widget.getValue(), '100.0,100.0')
286
287    def testMapLayer(self):
288        self.checkConstructWrapper(QgsProcessingParameterMapLayer('test'), MapLayerWidgetWrapper)
289
290    def testMeshLayer(self):
291        self.checkConstructWrapper(QgsProcessingParameterMeshLayer('test'), MeshWidgetWrapper)
292
293    def testDistance(self):
294        self.checkConstructWrapper(QgsProcessingParameterDistance('test'), DistanceWidgetWrapper)
295
296        alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
297        dlg = AlgorithmDialog(alg)
298        param = QgsProcessingParameterDistance('test')
299        wrapper = DistanceWidgetWrapper(param, dlg)
300        widget = wrapper.createWidget()
301
302        # test units
303        widget.show()
304
305        # crs values
306        widget.setUnitParameterValue('EPSG:3111')
307        self.assertEqual(widget.label.text(), 'meters')
308        self.assertFalse(widget.warning_label.isVisible())
309        self.assertTrue(widget.units_combo.isVisible())
310        self.assertFalse(widget.label.isVisible())
311        self.assertEqual(widget.units_combo.currentData(), QgsUnitTypes.DistanceMeters)
312
313        widget.setUnitParameterValue('EPSG:4326')
314        self.assertEqual(widget.label.text(), 'degrees')
315        self.assertTrue(widget.warning_label.isVisible())
316        self.assertFalse(widget.units_combo.isVisible())
317        self.assertTrue(widget.label.isVisible())
318
319        widget.setUnitParameterValue(QgsCoordinateReferenceSystem('EPSG:3111'))
320        self.assertEqual(widget.label.text(), 'meters')
321        self.assertFalse(widget.warning_label.isVisible())
322        self.assertTrue(widget.units_combo.isVisible())
323        self.assertFalse(widget.label.isVisible())
324        self.assertEqual(widget.units_combo.currentData(), QgsUnitTypes.DistanceMeters)
325
326        widget.setUnitParameterValue(QgsCoordinateReferenceSystem('EPSG:4326'))
327        self.assertEqual(widget.label.text(), 'degrees')
328        self.assertTrue(widget.warning_label.isVisible())
329        self.assertFalse(widget.units_combo.isVisible())
330        self.assertTrue(widget.label.isVisible())
331
332        # layer values
333        vl = QgsVectorLayer("Polygon?crs=epsg:3111&field=pk:int", "vl", "memory")
334        widget.setUnitParameterValue(vl)
335        self.assertEqual(widget.label.text(), 'meters')
336        self.assertFalse(widget.warning_label.isVisible())
337        self.assertTrue(widget.units_combo.isVisible())
338        self.assertFalse(widget.label.isVisible())
339        self.assertEqual(widget.units_combo.currentData(), QgsUnitTypes.DistanceMeters)
340
341        vl2 = QgsVectorLayer("Polygon?crs=epsg:4326&field=pk:int", "vl", "memory")
342        widget.setUnitParameterValue(vl2)
343        self.assertEqual(widget.label.text(), 'degrees')
344        self.assertTrue(widget.warning_label.isVisible())
345        self.assertFalse(widget.units_combo.isVisible())
346        self.assertTrue(widget.label.isVisible())
347
348        # unresolvable values
349        widget.setUnitParameterValue(vl.id())
350        self.assertEqual(widget.label.text(), '<unknown>')
351        self.assertFalse(widget.warning_label.isVisible())
352        self.assertFalse(widget.units_combo.isVisible())
353        self.assertTrue(widget.label.isVisible())
354
355        # resolvable text value
356        QgsProject.instance().addMapLayer(vl)
357        widget.setUnitParameterValue(vl.id())
358        self.assertEqual(widget.label.text(), 'meters')
359        self.assertFalse(widget.warning_label.isVisible())
360        self.assertTrue(widget.units_combo.isVisible())
361        self.assertFalse(widget.label.isVisible())
362        self.assertEqual(widget.units_combo.currentData(), QgsUnitTypes.DistanceMeters)
363
364        widget.setValue(5)
365        self.assertEqual(widget.getValue(), 5)
366        widget.units_combo.setCurrentIndex(widget.units_combo.findData(QgsUnitTypes.DistanceKilometers))
367        self.assertEqual(widget.getValue(), 5000)
368        widget.setValue(2)
369        self.assertEqual(widget.getValue(), 2000)
370
371        widget.setUnitParameterValue(vl.id())
372        self.assertEqual(widget.getValue(), 2)
373        widget.setValue(5)
374        self.assertEqual(widget.getValue(), 5)
375
376        widget.deleteLater()
377
378    def testMatrix(self):
379        self.checkConstructWrapper(QgsProcessingParameterMatrix('test'), FixedTableWidgetWrapper)
380
381        alg = QgsApplication.processingRegistry().createAlgorithmById('native:centroids')
382        dlg = AlgorithmDialog(alg)
383        param = QgsProcessingParameterMatrix('test', 'test', 2, True, ['x', 'y'], [['a', 'b'], ['c', 'd']])
384        wrapper = FixedTableWidgetWrapper(param, dlg)
385        widget = wrapper.createWidget()
386
387        # check that default value is initially set
388        self.assertEqual(wrapper.value(), [['a', 'b'], ['c', 'd']])
389
390        # test widget
391        widget.show()
392        wrapper.setValue([[1, 2], [3, 4]])
393        self.assertEqual(wrapper.value(), [[1, 2], [3, 4]])
394
395        widget.deleteLater()
396
397    def testNumber(self):
398        self.checkConstructWrapper(QgsProcessingParameterNumber('test'), NumberWidgetWrapper)
399
400    def testBand(self):
401        self.checkConstructWrapper(QgsProcessingParameterBand('test'), BandWidgetWrapper)
402
403
404if __name__ == '__main__':
405    unittest.main()
406