1
0
mirror of https://github.com/qgis/QGIS.git synced 2025-04-26 00:02:43 -04:00

Port parameter checking to c++

This commit is contained in:
Nyall Dawson 2017-05-16 15:21:41 +10:00
parent a23a6ac631
commit ef59d0c454
22 changed files with 1197 additions and 553 deletions

@ -139,6 +139,17 @@ class QgsProcessingAlgorithm
:rtype: bool
%End
virtual bool checkParameterValues( const QVariantMap &parameters,
QgsProcessingContext &context, QString *message /Out/ = 0 ) const;
%Docstring
Checks the supplied ``parameter`` values to verify that they satisfy the requirements
of this algorithm in the supplied ``context``. The ``message`` parameter will be
filled with explanatory text if validation fails.
Overridden implementations should also check this base class implementation.
:return: true if parameters are acceptable for the algorithm.
:rtype: bool
%End
QgsProcessingProvider *provider() const;
%Docstring
Returns the provider to which this algorithm belongs.

@ -168,6 +168,13 @@ class QgsProcessingParameterDefinition
@see flags()
%End
virtual bool checkValueIsAcceptable( const QVariant &input ) const;
%Docstring
Checks whether the specified ``input`` value is acceptable for the
parameter. Returns true if the value can be accepted.
:rtype: bool
%End
protected:
@ -356,7 +363,6 @@ class QgsProcessingParameterBoolean : QgsProcessingParameterDefinition
%End
virtual QString type() const;
};
class QgsProcessingParameterCrs : QgsProcessingParameterDefinition
@ -421,7 +427,6 @@ class QgsProcessingParameterExtent : QgsProcessingParameterDefinition
virtual QString type() const;
};
@ -444,6 +449,8 @@ class QgsProcessingParameterPoint : QgsProcessingParameterDefinition
%End
virtual QString type() const;
virtual bool checkValueIsAcceptable( const QVariant &input ) const;
};
@ -472,6 +479,8 @@ class QgsProcessingParameterFile : QgsProcessingParameterDefinition
%End
virtual QString type() const;
virtual bool checkValueIsAcceptable( const QVariant &input ) const;
Behavior behavior() const;
%Docstring
@ -522,6 +531,8 @@ class QgsProcessingParameterMatrix : QgsProcessingParameterDefinition
%End
virtual QString type() const;
virtual bool checkValueIsAcceptable( const QVariant &input ) const;
QStringList headers() const;
%Docstring
@ -590,6 +601,8 @@ class QgsProcessingParameterMultipleLayers : QgsProcessingParameterDefinition
%End
virtual QString type() const;
virtual bool checkValueIsAcceptable( const QVariant &input ) const;
QgsProcessingParameterDefinition::LayerType layerType() const;
%Docstring
@ -602,6 +615,21 @@ class QgsProcessingParameterMultipleLayers : QgsProcessingParameterDefinition
%Docstring
Sets the layer ``type`` for layers acceptable by the parameter.
.. seealso:: layerType()
%End
int minimumNumberInputs() const;
%Docstring
Returns the minimum number of layers required for the parameter. If the return value is < 1
then the parameter accepts any number of layers.
.. seealso:: setMinimumNumberInputs()
:rtype: int
%End
void setMinimumNumberInputs( int minimum );
%Docstring
Sets the ``minimum`` number of layers required for the parameter. The minimum must be >= 1
if the parameter is not optional.
.. seealso:: minimumNumberInputs()
%End
};
@ -636,6 +664,8 @@ class QgsProcessingParameterNumber : QgsProcessingParameterDefinition
%End
virtual QString type() const;
virtual bool checkValueIsAcceptable( const QVariant &input ) const;
double minimum() const;
%Docstring
@ -699,6 +729,8 @@ class QgsProcessingParameterRange : QgsProcessingParameterDefinition
%End
virtual QString type() const;
virtual bool checkValueIsAcceptable( const QVariant &input ) const;
QgsProcessingParameterNumber::Type dataType() const;
%Docstring
@ -758,6 +790,8 @@ class QgsProcessingParameterEnum : QgsProcessingParameterDefinition
%End
virtual QString type() const;
virtual bool checkValueIsAcceptable( const QVariant &input ) const;
QStringList options() const;
%Docstring
@ -911,6 +945,8 @@ class QgsProcessingParameterTableField : QgsProcessingParameterDefinition
%End
virtual QString type() const;
virtual bool checkValueIsAcceptable( const QVariant &input ) const;
QString parentLayerParameter() const;
%Docstring

@ -115,6 +115,6 @@ class GdalParametersPanel(ParametersPanel):
commands = [c for c in commands if c not in ['cmd.exe', '/C ']]
self.text.setPlainText(" ".join(commands))
except AlgorithmDialogBase.InvalidParameterValue as e:
self.text.setPlainText(self.tr("Invalid value for parameter '{0}'").format(e.parameter.description))
self.text.setPlainText(self.tr("Invalid value for parameter '{0}'").format(e.parameter.description()))
except:
self.text.setPlainText("")

@ -36,7 +36,8 @@ from qgis.PyQt.QtCore import QCoreApplication, QUrl
from qgis.core import (QgsRasterLayer,
QgsApplication,
QgsProcessingUtils,
QgsMessageLog)
QgsMessageLog,
QgsProcessingAlgorithm)
from qgis.utils import iface
from processing.core.GeoAlgorithm import GeoAlgorithm
@ -578,9 +579,9 @@ class Grass7Algorithm(GeoAlgorithm):
message = Grass7Utils.checkGrass7IsInstalled()
return not message, message
def checkParameterValuesBeforeExecuting(self):
def checkParameterValues(self, parameters, context):
if self.module:
if hasattr(self.module, 'checkParameterValuesBeforeExecuting'):
func = getattr(self.module, 'checkParameterValuesBeforeExecuting')
return func(self)
return
return func(self), None
return super(Grass7Algorithm, self).checkParameterValues(parameters, context)

@ -184,6 +184,6 @@ class FieldsPyculator(QgisAlgorithm):
del writer
def checkParameterValuesBeforeExecuting(self):
def checkParameterValues(self, parameters, context):
# TODO check that formula is correct and fields exist
pass
return super(FieldsPyculator, self).checkParameterValues(parameters, context)

@ -164,11 +164,12 @@ class FieldsCalculator(QgisAlgorithm):
self.tr('An error occurred while evaluating the calculation '
'string:\n{0}').format(error))
def checkParameterValuesBeforeExecuting(self):
def checkParameterValues(self, parameters, context):
newField = self.getParameterValue(self.NEW_FIELD)
fieldName = self.getParameterValue(self.FIELD_NAME).strip()
if newField and len(fieldName) == 0:
return self.tr('Field name is not set. Please enter a field name')
return super(FieldsCalculator, self).checkParameterValues(parameters, context)
def createCustomParametersWidget(self, parent):
return FieldsCalculatorDialog(self)

@ -209,8 +209,10 @@ class FieldsCalculatorDialog(BASE, WIDGET):
parameters['FORMULA'] = self.builder.expressionText()
parameters['OUTPUT_LAYER'] = self.leOutputFile.text().strip() or None
msg = self.alg.checkParameterValuesBeforeExecuting()
if msg:
context = dataobjects.createContext()
ok, msg = self.alg.checkParameterValues(parameters, context)
if not ok:
QMessageBox.warning(
self, self.tr('Unable to execute algorithm'), msg)
return {}

@ -342,17 +342,16 @@ class SagaAlgorithm(GeoAlgorithm):
sessionExportedLayers[source] = destFilename
return 'io_gdal 0 -TRANSFORM 1 -RESAMPLING 0 -GRIDS "' + destFilename + '" -FILES "' + source + '"'
def checkParameterValuesBeforeExecuting(self):
def checkParameterValues(self, parameters, context):
"""
We check that there are no multiband layers, which are not
supported by SAGA, and that raster layers have the same grid extent
"""
extent = None
context = dataobjects.createContext()
for param in self.parameters:
for param in self.parameterDefinitions():
files = []
if isinstance(param, ParameterRaster):
files = [param.value]
files = [parameters[param.name()]]
elif (isinstance(param, ParameterMultipleInput) and
param.datatype == dataobjects.TYPE_RASTER):
if param.value is not None:
@ -362,12 +361,13 @@ class SagaAlgorithm(GeoAlgorithm):
if layer is None:
continue
if layer.bandCount() > 1:
return self.tr('Input layer {0} has more than one band.\n'
'Multiband layers are not supported by SAGA').format(layer.name())
return False, self.tr('Input layer {0} has more than one band.\n'
'Multiband layers are not supported by SAGA').format(layer.name())
if not self.allowUnmatchingGridExtents:
if extent is None:
extent = (layer.extent(), layer.height(), layer.width())
else:
extent2 = (layer.extent(), layer.height(), layer.width())
if extent != extent2:
return self.tr("Input layers do not have the same grid extent.")
return False, self.tr("Input layers do not have the same grid extent.")
return super(SagaAlgorithm, self).checkParameterValues(parameters, context)

@ -90,18 +90,6 @@ class GeoAlgorithm(QgsProcessingAlgorithm):
"""
return None
def checkParameterValuesBeforeExecuting(self):
"""If there is any check to do before launching the execution
of the algorithm, it should be done here.
If values are not correct, a message should be returned
explaining the problem.
This check is called from the parameters dialog, and also when
calling from the console.
"""
return None
def processBeforeAddingToModeler(self, alg, model):
"""Add here any task that has to be performed before adding an algorithm
to a model, such as changing the value of a parameter depending on value
@ -150,23 +138,6 @@ class GeoAlgorithm(QgsProcessingAlgorithm):
QgsMessageLog.logMessage('\n'.join(lines), self.tr('Processing'), QgsMessageLog.CRITICAL)
raise GeoAlgorithmExecutionException(str(e) + self.tr('\nSee log for more details'), lines, e)
def _checkParameterValuesBeforeExecuting(self, context=None):
if context is None:
context = dataobjects.createContext()
for param in self.parameters:
if isinstance(param, (ParameterRaster, ParameterVector,
ParameterMultipleInput)):
if param.value:
if isinstance(param, ParameterMultipleInput):
inputlayers = param.value.split(';')
else:
inputlayers = [param.value]
for inputlayer in inputlayers:
obj = QgsProcessingUtils.mapLayerFromString(inputlayer, context)
if obj is None:
return "Wrong parameter value: " + param.value
return self.checkParameterValuesBeforeExecuting()
def runPostExecutionScript(self, feedback):
scriptFile = ProcessingConfig.getSetting(
ProcessingConfig.POST_EXECUTION_SCRIPT)

@ -204,8 +204,8 @@ class Processing(object):
else:
context = dataobjects.createContext()
msg = alg._checkParameterValuesBeforeExecuting(context)
if msg:
ok, msg = alg.checkParameterValues(parameters, context)
if not ok:
# fix_print_with_import
print('Unable to execute algorithm\n' + str(msg))
QgsMessageLog.logMessage(Processing.tr('Unable to execute algorithm\n{0}').format(msg),

@ -101,18 +101,15 @@ class AlgorithmDialog(AlgorithmDialogBase):
continue
if not param.isDestination():
wrapper = self.mainWidget.wrappers[param.name()]
value = None
if wrapper.widget:
value = wrapper.value()
parameters[param.name()] = value
#TODO
#if not self.setParamValue(param, wrapper):
# raise AlgorithmDialogBase.InvalidParameterValue(param, wrapper.widget)
if not param.checkValueIsAcceptable(value):
raise AlgorithmDialogBase.InvalidParameterValue(param, wrapper.widget)
else:
parameters[param.name()] = self.mainWidget.outputWidgets[param.name()].getValue()
# TODO
#if isinstance(output, (OutputRaster, OutputVector, OutputTable)):
# output.open = self.mainWidget.checkBoxes[param.name()].isChecked()
return parameters
@ -127,12 +124,6 @@ class AlgorithmDialog(AlgorithmDialogBase):
return layer_outputs
def setParamValue(self, param, wrapper):
if wrapper.widget:
return param.setValue(wrapper.value())
else:
return True
def checkExtentCRS(self):
unmatchingCRS = False
hasExtent = False
@ -199,9 +190,7 @@ class AlgorithmDialog(AlgorithmDialogBase):
QMessageBox.No)
if reply == QMessageBox.No:
return
# TODO
#msg = self.alg._checkParameterValuesBeforeExecuting(context)
msg = None
ok, msg = self.alg.checkParameterValues(parameters, context)
if msg:
QMessageBox.warning(
self, self.tr('Unable to execute algorithm'), msg)
@ -258,7 +247,7 @@ class AlgorithmDialog(AlgorithmDialogBase):
except:
pass
self.bar.clearWidgets()
self.bar.pushMessage("", self.tr("Wrong or missing parameter value: {0}").format(e.parameter.description),
self.bar.pushMessage("", self.tr("Wrong or missing parameter value: {0}").format(e.parameter.description()),
level=QgsMessageBar.WARNING, duration=5)
def finish(self, result, context):

@ -222,9 +222,6 @@ class AlgorithmDialogBase(BASE, WIDGET):
def getParamValues(self):
return {}
def setParamValue(self, param, widget, alg=None):
pass
def accept(self):
pass

@ -73,6 +73,8 @@ class BatchAlgorithmDialog(AlgorithmDialogBase):
self.load = []
self.canceled = False
context = dataobjects.createContext()
for row in range(self.mainWidget.tblParameters.rowCount()):
col = 0
parameters = {}
@ -81,9 +83,9 @@ class BatchAlgorithmDialog(AlgorithmDialogBase):
continue
wrapper = self.mainWidget.wrappers[row][col]
parameters[param.name()] = wrapper.value()
if not self.mainWidget.setParamValue(param, wrapper, alg):
if not param.checkValueIsAcceptable(wrapper.value(), context):
self.bar.pushMessage("", self.tr('Wrong or missing parameter value: {0} (row {1})').format(
param.description, row + 1),
param.description(), row + 1),
level=QgsMessageBar.WARNING, duration=5)
self.algs = None
return
@ -122,8 +124,6 @@ class BatchAlgorithmDialog(AlgorithmDialogBase):
except:
pass
context = dataobjects.createContext()
for count, parameters in enumerate(self.alg_parameters):
self.setText(self.tr('\nProcessing algorithm {0}/{1}...').format(count + 1, len(self.alg_parameters)))
self.setInfo(self.tr('<b>Algorithm {0} starting...</b>').format(self.alg.displayName()))

@ -32,7 +32,8 @@ import json
from qgis.PyQt import uic
from qgis.PyQt.QtWidgets import QTableWidgetItem, QComboBox, QHeaderView, QFileDialog, QMessageBox
from qgis.core import QgsApplication
from qgis.core import (QgsApplication,
QgsProcessingParameterDefinition)
from qgis.gui import QgsMessageBar
from processing.gui.BatchOutputSelectionPanel import BatchOutputSelectionPanel
@ -47,6 +48,7 @@ from processing.core.parameters import ParameterPoint # NOQA
from processing.core.parameters import ParameterSelection # NOQA
from processing.core.parameters import ParameterFixedTable # NOQA
from processing.core.parameters import ParameterMultipleInput # NOQA
from processing.tools import dataobjects
pluginPath = os.path.split(os.path.dirname(__file__))[0]
WIDGET, BASE = uic.loadUiType(
@ -180,21 +182,22 @@ class BatchPanel(BASE, WIDGET):
def save(self):
toSave = []
context = dataobjects.createContext()
for row in range(self.tblParameters.rowCount()):
algParams = {}
algOutputs = {}
col = 0
alg = self.alg
for param in alg.parameters:
if param.hidden:
for param in alg.parameterDefinitions():
if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
wrapper = self.wrappers[row][col]
if not self.setParamValue(param, wrapper, alg):
if not param.checkValueIsAcceptable(wrapper.value, context):
self.parent.bar.pushMessage("", self.tr('Wrong or missing parameter value: {0} (row {1})').format(
param.description, row + 1),
param.description(), row + 1),
level=QgsMessageBar.WARNING, duration=5)
return
algParams[param.name] = param.getValueAsCommandLineParameter()
algParams[param.name()] = param.getValueAsCommandLineParameter()
col += 1
for out in alg.outputs:
if out.hidden:
@ -221,9 +224,6 @@ class BatchPanel(BASE, WIDGET):
with open(filename, 'w') as f:
json.dump(toSave, f)
def setParamValue(self, param, wrapper, alg=None):
return param.setValue(wrapper.value())
def setCellWrapper(self, row, column, wrapper):
self.wrappers[row][column] = wrapper
self.tblParameters.setCellWidget(row, column, wrapper.widget)

@ -34,7 +34,8 @@ from qgis.PyQt.QtWidgets import (QDialog, QDialogButtonBox, QLabel, QLineEdit,
QTextBrowser)
from qgis.PyQt.QtNetwork import QNetworkRequest, QNetworkReply
from qgis.core import QgsNetworkAccessManager
from qgis.core import (QgsNetworkAccessManager,
QgsProcessingParameterDefinition)
from qgis.gui import (QgsMessageBar,
QgsScrollArea)
@ -320,13 +321,13 @@ class ModelerParametersDialog(QDialog):
alg = Algorithm(self._alg.id())
alg.setName(self.model)
alg.description = self.descriptionBox.text()
params = self._alg.parameters
params = self._alg.parameterDefinitions()
outputs = self._alg.outputs
for param in params:
if param.hidden:
if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
if not self.setParamValue(alg, param, self.wrappers[param.name]):
self.bar.pushMessage("Error", "Wrong or missing value for parameter '%s'" % param.description,
if not param.checkValueIsAcceptable(self.wrappers[param.name()].value):
self.bar.pushMessage("Error", "Wrong or missing value for parameter '%s'" % param.description(),
level=QgsMessageBar.WARNING)
return None
for output in outputs:
@ -343,15 +344,6 @@ class ModelerParametersDialog(QDialog):
self._alg.processBeforeAddingToModeler(alg, self.model)
return alg
def setParamValue(self, alg, param, wrapper):
try:
if wrapper.widget:
value = wrapper.value()
alg.params[param.name] = value
return True
except InvalidParameterValue:
return False
def okPressed(self):
self.alg = self.createAlgorithm()
if self.alg is not None:

@ -33,7 +33,7 @@ from processing.preconfigured.PreconfiguredUtils import algAsDict
from processing.preconfigured.PreconfiguredUtils import preconfiguredAlgorithmsFolder
from processing.gui.AlgorithmDialogBase import AlgorithmDialogBase
from processing.gui.AlgorithmDialog import AlgorithmDialog
from processing.tools import dataobjects
from qgis.PyQt.QtWidgets import QMessageBox, QVBoxLayout, QLabel, QLineEdit, QWidget
from qgis.PyQt.QtGui import QPalette, QColor
@ -53,11 +53,12 @@ class PreconfiguredAlgorithmDialog(AlgorithmDialog):
self.tabWidget.addTab(self.settingsPanel, "Description")
def accept(self):
context = dataobjects.createContext()
try:
parameters = self.getParamValues()
self.setOutputValues()
msg = self.alg._checkParameterValuesBeforeExecuting()
if msg:
ok, msg = self.alg.checkParameterValues(parameters, context)
if not ok:
QMessageBox.warning(
self, self.tr('Unable to execute algorithm'), msg)
return
@ -80,7 +81,7 @@ class PreconfiguredAlgorithmDialog(AlgorithmDialog):
palette.setColor(QPalette.Base, QColor(255, 255, 0))
e.widget.setPalette(palette)
self.parent.bar.pushMessage("", self.tr('Missing parameter value: {0}').format(
e.parameter.description),
e.parameter.description()),
level=QgsMessageBar.WARNING, duration=5)
return
except:

@ -84,35 +84,6 @@ class ParameterTest(unittest.TestCase):
class ParameterBooleanTest(unittest.TestCase):
def testSetValue(self):
parameter = ParameterBoolean('myName', 'myDescription')
self.assertEqual(parameter.value, None)
parameter.setValue(False)
self.assertEqual(parameter.value, False)
parameter.setValue(True)
self.assertEqual(parameter.value, True)
def testDefault(self):
parameter = ParameterBoolean('myName', 'myDescription', default=False, optional=True)
self.assertEqual(parameter.value, False)
parameter.setValue(None)
self.assertEqual(parameter.value, None)
def testOptional(self):
optionalParameter = ParameterBoolean('myName', 'myDescription', default=False, optional=True)
self.assertEqual(optionalParameter.value, False)
optionalParameter.setValue(True)
self.assertEqual(optionalParameter.value, True)
self.assertTrue(optionalParameter.setValue(None))
self.assertEqual(optionalParameter.value, None)
requiredParameter = ParameterBoolean('myName', 'myDescription', default=False, optional=False)
self.assertEqual(requiredParameter.value, False)
requiredParameter.setValue(True)
self.assertEqual(requiredParameter.value, True)
self.assertFalse(requiredParameter.setValue(None))
self.assertEqual(requiredParameter.value, True)
def testScriptCode(self):
parameter = ParameterBoolean('myName', 'myDescription')
code = parameter.getAsScriptCode()
@ -129,26 +100,6 @@ class ParameterBooleanTest(unittest.TestCase):
class ParameterCRSTest(unittest.TestCase):
def testSetValue(self):
parameter = ParameterCrs('myName', 'myDesc')
self.assertTrue(parameter.setValue('EPSG:12003'))
self.assertEqual(parameter.value, 'EPSG:12003')
def testOptional(self):
optionalParameter = ParameterCrs('myName', 'myDesc', default='EPSG:4326', optional=True)
self.assertEqual(optionalParameter.value, 'EPSG:4326')
optionalParameter.setValue('EPSG:12003')
self.assertEqual(optionalParameter.value, 'EPSG:12003')
self.assertTrue(optionalParameter.setValue(None))
self.assertEqual(optionalParameter.value, None)
requiredParameter = ParameterCrs('myName', 'myDesc', default='EPSG:4326', optional=False)
self.assertEqual(requiredParameter.value, 'EPSG:4326')
requiredParameter.setValue('EPSG:12003')
self.assertEqual(requiredParameter.value, 'EPSG:12003')
self.assertFalse(requiredParameter.setValue(None))
self.assertEqual(requiredParameter.value, 'EPSG:12003')
def testScriptCode(self):
parameter = ParameterCrs('myName', 'myDescription')
code = parameter.getAsScriptCode()
@ -176,33 +127,6 @@ class ParameterDataObjectTest(unittest.TestCase):
class ParameterExtentTest(unittest.TestCase):
def testSetValue(self):
parameter = ParameterExtent('myName', 'myDesc')
self.assertTrue(parameter.setValue('0,2,2,4'))
self.assertEqual(parameter.value, '0,2,2,4')
def testSetInvalidValue(self):
parameter = ParameterExtent('myName', 'myDesc')
self.assertFalse(parameter.setValue('0,2,0'))
self.assertFalse(parameter.setValue('0,2,0,a'))
def testOptional(self):
optionalParameter = ParameterExtent('myName', 'myDesc', default='0,1,0,1', optional=True)
self.assertEqual(optionalParameter.value, '0,1,0,1')
optionalParameter.setValue('1,2,3,4')
self.assertEqual(optionalParameter.value, '1,2,3,4')
self.assertTrue(optionalParameter.setValue(None))
# Extent is unique in that it will let you set `None`, whereas other
# optional parameters become "default" when assigning None.
self.assertEqual(optionalParameter.value, None)
requiredParameter = ParameterExtent('myName', 'myDesc', default='0,1,0,1', optional=False)
self.assertEqual(requiredParameter.value, '0,1,0,1')
requiredParameter.setValue('1,2,3,4')
self.assertEqual(requiredParameter.value, '1,2,3,4')
self.assertFalse(requiredParameter.setValue(None))
self.assertEqual(requiredParameter.value, '1,2,3,4')
def testScriptCode(self):
parameter = ParameterExtent('myName', 'myDescription')
code = parameter.getAsScriptCode()
@ -218,33 +142,6 @@ class ParameterExtentTest(unittest.TestCase):
class ParameterPointTest(unittest.TestCase):
def testSetValue(self):
parameter = ParameterPoint('myName', 'myDesc')
self.assertTrue(parameter.setValue('0,2'))
self.assertEqual(parameter.value, '0,2')
def testSetInvalidValue(self):
parameter = ParameterPoint('myName', 'myDesc')
self.assertFalse(parameter.setValue('0'))
self.assertFalse(parameter.setValue('0,a'))
def testOptional(self):
optionalParameter = ParameterPoint('myName', 'myDesc', default='0,1', optional=True)
self.assertEqual(optionalParameter.value, '0,1')
optionalParameter.setValue('1,2')
self.assertEqual(optionalParameter.value, '1,2')
self.assertTrue(optionalParameter.setValue(None))
# Point like Extent is unique in that it will let you set `None`, whereas other
# optional parameters become "default" when assigning None.
self.assertEqual(optionalParameter.value, None)
requiredParameter = ParameterPoint('myName', 'myDesc', default='0,1', optional=False)
self.assertEqual(requiredParameter.value, '0,1')
requiredParameter.setValue('1,2')
self.assertEqual(requiredParameter.value, '1,2')
self.assertFalse(requiredParameter.setValue(None))
self.assertEqual(requiredParameter.value, '1,2')
def testScriptCode(self):
parameter = ParameterPoint('myName', 'myDescription')
code = parameter.getAsScriptCode()
@ -260,50 +157,6 @@ class ParameterPointTest(unittest.TestCase):
class ParameterSelectionTest(unittest.TestCase):
def testSetValue(self):
parameter = ParameterSelection('myName', 'myDesc', ['option1', 'option2', 'option3'])
self.assertIsNone(parameter.value)
self.assertEqual(parameter.setValue(1), True)
self.assertEqual(parameter.value, 1)
self.assertEqual(parameter.setValue('1'), True)
self.assertEqual(parameter.value, 1)
self.assertEqual(parameter.setValue(1.0), True)
self.assertEqual(parameter.value, 1)
self.assertEqual(parameter.setValue('1a'), False)
self.assertEqual(parameter.setValue([1]), False)
def testMultiple(self):
parameter = ParameterSelection('myName', 'myDesc', ['option1', 'option2', 'option3'], multiple=True)
self.assertEqual(parameter.setValue(1), True)
self.assertEqual(parameter.value, 1)
self.assertEqual(parameter.setValue([0, 1]), True)
self.assertEqual(parameter.value, [0, 1])
self.assertEqual(parameter.setValue(['0', '1']), True)
self.assertEqual(parameter.value, [0, 1])
def testDefault(self):
parameter = ParameterSelection('myName', 'myDesc', ['option1', 'option2', 'option3'], default=0)
self.assertEqual(parameter.value, 0)
parameter = ParameterSelection('myName', 'myDesc', ['option1', 'option2', 'option3'], default=0.0)
self.assertEqual(parameter.value, 0)
parameter = ParameterSelection('myName', 'myDesc', ['option1', 'option2', 'option3'], default='a')
self.assertEqual(parameter.value, None)
def testOptional(self):
optionalParameter = ParameterSelection('myName', 'myDesc', ['option1', 'option2', 'option3'], default=0, optional=True)
self.assertEqual(optionalParameter.value, 0)
optionalParameter.setValue(1)
self.assertEqual(optionalParameter.value, 1)
self.assertTrue(optionalParameter.setValue(None))
self.assertEqual(optionalParameter.value, None)
requiredParameter = ParameterSelection('myName', 'myDesc', ['option1', 'option2', 'option3'], default=0, optional=False)
self.assertEqual(requiredParameter.value, 0)
requiredParameter.setValue(1)
self.assertEqual(requiredParameter.value, 1)
self.assertFalse(requiredParameter.setValue(None))
self.assertEqual(requiredParameter.value, 1)
def testTupleOptions(self):
options = (
('o1', 'option1'),
@ -323,43 +176,6 @@ class ParameterSelectionTest(unittest.TestCase):
class ParameterFileTest(unittest.TestCase):
def testSetValue(self):
parameter = ParameterFile('myName', 'myDesc')
self.assertTrue(parameter.setValue('myFile.png'))
self.assertEqual(parameter.value, 'myFile.png')
def testOptional(self):
optionalParameter = ParameterFile('myName', 'myDesc', optional=True)
self.assertTrue(optionalParameter.setValue('myFile.png'))
self.assertEqual(optionalParameter.value, 'myFile.png')
self.assertTrue(optionalParameter.setValue(""))
self.assertEqual(optionalParameter.value, '')
self.assertTrue(optionalParameter.setValue(None))
self.assertEqual(optionalParameter.value, None)
requiredParameter = ParameterFile('myName', 'myDesc', optional=False)
self.assertTrue(requiredParameter.setValue('myFile.png'))
self.assertEqual(requiredParameter.value, 'myFile.png')
self.assertFalse(requiredParameter.setValue(''))
self.assertEqual(requiredParameter.value, 'myFile.png')
self.assertFalse(requiredParameter.setValue(' '))
self.assertEqual(requiredParameter.value, 'myFile.png')
self.assertFalse(requiredParameter.setValue(None))
self.assertEqual(requiredParameter.value, 'myFile.png')
def testSetValueWithExtension(self):
parameter = ParameterFile('myName', 'myDesc', ext="png")
self.assertTrue(parameter.setValue('myFile.png'))
self.assertEqual(parameter.value, 'myFile.png')
self.assertFalse(parameter.setValue('myFile.bmp'))
self.assertEqual(parameter.value, 'myFile.png')
def testGetValueAsCommandLineParameter(self):
parameter = ParameterFile('myName', 'myDesc')
parameter.setValue('myFile.png')
@ -393,72 +209,9 @@ class TestParameterFixedTable(unittest.TestCase):
table = [[]]
self.assertEqual(ParameterFixedTable.tableToString(table), '')
def testSetStringValue(self):
parameter = ParameterFixedTable('myName', 'myDesc')
self.assertTrue(parameter.setValue('1,2,3'))
self.assertEqual(parameter.value, '1,2,3')
def testSet2DListValue(self):
table = [
['a0', 'a1', 'a2'],
['b0', 'b1', 'b2']
]
parameter = ParameterFixedTable('myName', 'myDesc')
self.assertTrue(parameter.setValue(table))
self.assertEqual(parameter.value, 'a0,a1,a2,b0,b1,b2')
def testOptional(self):
parameter = ParameterFixedTable('myName', 'myDesc', optional=True)
self.assertTrue(parameter.setValue('1,2,3'))
self.assertEqual(parameter.value, '1,2,3')
self.assertTrue(parameter.setValue(None))
self.assertEqual(parameter.value, None)
parameter = ParameterFixedTable('myName', 'myDesc', optional=False)
self.assertFalse(parameter.setValue(None))
self.assertEqual(parameter.value, None)
self.assertTrue(parameter.setValue('1,2,3'))
self.assertEqual(parameter.value, '1,2,3')
self.assertFalse(parameter.setValue(None))
self.assertEqual(parameter.value, '1,2,3')
class ParameterMultipleInputTest(unittest.TestCase):
def testOptional(self):
parameter = ParameterMultipleInput('myName', 'myDesc', optional=True)
self.assertTrue(parameter.setValue('myLayerFile.shp'))
self.assertEqual(parameter.value, 'myLayerFile.shp')
self.assertTrue(parameter.setValue(None))
self.assertEqual(parameter.value, None)
parameter = ParameterMultipleInput('myName', 'myDesc', optional=False)
self.assertFalse(parameter.setValue(None))
self.assertEqual(parameter.value, None)
self.assertTrue(parameter.setValue("myLayerFile.shp"))
self.assertEqual(parameter.value, "myLayerFile.shp")
self.assertFalse(parameter.setValue(None))
self.assertEqual(parameter.value, "myLayerFile.shp")
def testMultipleInput(self):
parameter = ParameterMultipleInput('myName', 'myDesc', optional=True)
self.assertTrue(parameter.setMinNumInputs(1))
parameter = ParameterMultipleInput('myName', 'myDesc', optional=False)
self.assertFalse(parameter.setMinNumInputs(0))
parameter.setMinNumInputs(2)
self.assertTrue(parameter.setValue(['myLayerFile.shp', 'myLayerFile2.shp']))
parameter.setMinNumInputs(3)
self.assertFalse(parameter.setValue(['myLayerFile.shp', 'myLayerFile2.shp']))
def testGetAsStringWhenRaster(self):
parameter = ParameterMultipleInput('myName', 'myDesc', datatype=dataobjects.TYPE_RASTER)
@ -502,57 +255,6 @@ class ParameterMultipleInputTest(unittest.TestCase):
class ParameterNumberTest(unittest.TestCase):
def testSetValue(self):
parameter = ParameterNumber('myName', 'myDescription')
self.assertTrue(parameter.setValue(5))
self.assertEqual(parameter.value, 5)
def testSetValueOnlyValidNumbers(self):
parameter = ParameterNumber('myName', 'myDescription')
self.assertFalse(parameter.setValue('not a number'))
self.assertEqual(parameter.value, None)
def testIsInteger(self):
floatParameter = ParameterNumber('myname', 'myDescription', default=1.0)
self.assertFalse(floatParameter.isInteger)
intParameter = ParameterNumber('myname', 'myDescription', default=10)
self.assertTrue(intParameter.isInteger)
strFloatParameter = ParameterNumber('myname', 'myDescription', default="1.0")
self.assertFalse(strFloatParameter.isInteger)
strIntParameter = ParameterNumber('myname', 'myDescription', default="10")
self.assertTrue(strIntParameter.isInteger)
def testMaxValue(self):
parameter = ParameterNumber('myName', 'myDescription', maxValue=10)
self.assertFalse(parameter.setValue(11))
self.assertEqual(parameter.value, None)
self.assertTrue(parameter.setValue(10))
self.assertEqual(parameter.value, 10)
def testMinValue(self):
parameter = ParameterNumber('myName', 'myDescription', minValue=3)
self.assertFalse(parameter.setValue(1))
self.assertEqual(parameter.value, None)
self.assertFalse(parameter.setValue(-2))
self.assertEqual(parameter.value, None)
self.assertTrue(parameter.setValue(3))
self.assertEqual(parameter.value, 3)
def testOptional(self):
optionalParameter = ParameterNumber('myName', 'myDescription', default=1.0, optional=True)
self.assertEqual(optionalParameter.value, 1.0)
optionalParameter.setValue(5)
self.assertEqual(optionalParameter.value, 5)
self.assertTrue(optionalParameter.setValue(None))
self.assertEqual(optionalParameter.value, None)
requiredParameter = ParameterNumber('myName', 'myDescription', default=1.0, optional=False)
self.assertEqual(requiredParameter.value, 1.0)
requiredParameter.setValue(5)
self.assertEqual(requiredParameter.value, 5)
self.assertFalse(requiredParameter.setValue(None))
self.assertEqual(requiredParameter.value, 5)
def testScriptCode(self):
parameter = ParameterNumber('myName', 'myDescription')
code = parameter.getAsScriptCode()
@ -568,26 +270,6 @@ class ParameterNumberTest(unittest.TestCase):
class ParameterStringTest(unittest.TestCase):
def testSetValue(self):
parameter = ParameterString('myName', 'myDescription')
self.assertTrue(parameter.setValue('test'))
self.assertEqual(parameter.value, 'test')
def testOptional(self):
optionalParameter = ParameterString('myName', 'myDesc', default='test', optional=True)
self.assertEqual(optionalParameter.value, 'test')
optionalParameter.setValue('check')
self.assertEqual(optionalParameter.value, 'check')
self.assertTrue(optionalParameter.setValue(None))
self.assertEqual(optionalParameter.value, None)
requiredParameter = ParameterString('myName', 'myDesc', default='test', optional=False)
self.assertEqual(requiredParameter.value, 'test')
requiredParameter.setValue('check')
self.assertEqual(requiredParameter.value, 'check')
self.assertFalse(requiredParameter.setValue(None))
self.assertEqual(requiredParameter.value, 'check')
def testScriptCode(self):
parameter = ParameterString('myName', 'myDescription', default='test')
code = parameter.getAsScriptCode()
@ -620,26 +302,6 @@ class ParameterStringTest(unittest.TestCase):
class ParameterExpressionTest(unittest.TestCase):
def testSetValue(self):
parameter = ParameterExpression('myName', 'myDescription')
self.assertTrue(parameter.setValue('\'a\' || "field"'))
self.assertEqual(parameter.value, '\'a\' || "field"')
def testOptional(self):
optionalParameter = ParameterExpression('myName', 'myDesc', default='test', optional=True)
self.assertEqual(optionalParameter.value, 'test')
optionalParameter.setValue('check')
self.assertEqual(optionalParameter.value, 'check')
self.assertTrue(optionalParameter.setValue(None))
self.assertEqual(optionalParameter.value, None)
requiredParameter = ParameterExpression('myName', 'myDesc', default='test', optional=False)
self.assertEqual(requiredParameter.value, 'test')
requiredParameter.setValue('check')
self.assertEqual(requiredParameter.value, 'check')
self.assertFalse(requiredParameter.setValue(None))
self.assertEqual(requiredParameter.value, 'check')
def testScriptCode(self):
parameter = ParameterExpression('myName', 'myDescription', default='test')
code = parameter.getAsScriptCode()

@ -71,6 +71,20 @@ bool QgsProcessingAlgorithm::canExecute( QString * ) const
return true;
}
bool QgsProcessingAlgorithm::checkParameterValues( const QVariantMap &parameters, QgsProcessingContext &context, QString *message ) const
{
Q_FOREACH ( const QgsProcessingParameterDefinition *def, mParameters )
{
if ( !def->checkValueIsAcceptable( parameters.value( def->name() ), &context ) )
{
if ( message )
*message = QObject::tr( "Incorrect parameter value for %1" ).arg( def->name() );
return false;
}
}
return true;
}
QgsProcessingProvider *QgsProcessingAlgorithm::provider() const
{
return mProvider;

@ -150,6 +150,16 @@ class CORE_EXPORT QgsProcessingAlgorithm
*/
virtual bool canExecute( QString *errorMessage SIP_OUT = nullptr ) const;
/**
* Checks the supplied \a parameter values to verify that they satisfy the requirements
* of this algorithm in the supplied \a context. The \a message parameter will be
* filled with explanatory text if validation fails.
* Overridden implementations should also check this base class implementation.
* \returns true if parameters are acceptable for the algorithm.
*/
virtual bool checkParameterValues( const QVariantMap &parameters,
QgsProcessingContext &context, QString *message SIP_OUT = nullptr ) const;
/**
* Returns the provider to which this algorithm belongs.
*/

@ -241,9 +241,12 @@ QgsCoordinateReferenceSystem QgsProcessingParameters::parameterAsCrs( const QgsP
QgsRectangle QgsProcessingParameters::parameterAsExtent( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, const QString &name, QgsProcessingContext &context )
{
QString rectText = parameterAsString( definition, parameters, name, context );
if ( rectText.isEmpty() )
rectText = definition->defaultValue().toString();
QVariant val = parameters.value( name );
QString rectText;
if ( val.canConvert<QgsProperty>() )
rectText = val.value< QgsProperty >().valueAsString( context.expressionContext(), definition ? definition->defaultValue().toString() : QString() );
else
rectText = val.toString();
if ( rectText.isEmpty() )
return QgsRectangle();
@ -445,6 +448,17 @@ QgsProcessingParameterDefinition::QgsProcessingParameterDefinition( const QStrin
, mFlags( optional ? FlagOptional : 0 )
{}
bool QgsProcessingParameterDefinition::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.type() == QVariant::String && input.toString().isEmpty() )
return mFlags & FlagOptional;
return true;
}
QgsProcessingParameterBoolean::QgsProcessingParameterBoolean( const QString &name, const QString &description, const QVariant &defaultValue, bool optional )
: QgsProcessingParameterDefinition( name, description, defaultValue, optional )
{}
@ -455,24 +469,136 @@ QgsProcessingParameterCrs::QgsProcessingParameterCrs( const QString &name, const
}
bool QgsProcessingParameterCrs::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
if ( input.type() != QVariant::String || input.toString().isEmpty() )
return mFlags & FlagOptional;
return true;
}
QgsProcessingParameterMapLayer::QgsProcessingParameterMapLayer( const QString &name, const QString &description, const QVariant &defaultValue, bool optional )
: QgsProcessingParameterDefinition( name, description, defaultValue, optional )
{
}
bool QgsProcessingParameterMapLayer::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
if ( input.type() != QVariant::String || input.toString().isEmpty() )
return mFlags & FlagOptional;
if ( !context )
{
// that's as far as we can get without a context
return true;
}
// try to load as layer
if ( QgsProcessingUtils::mapLayerFromString( input.toString(), *context ) )
return true;
return false;
}
QgsProcessingParameterExtent::QgsProcessingParameterExtent( const QString &name, const QString &description, const QVariant &defaultValue, bool optional )
: QgsProcessingParameterDefinition( name, description, defaultValue, optional )
{
}
bool QgsProcessingParameterExtent::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
if ( input.type() != QVariant::String || input.toString().isEmpty() )
return mFlags & FlagOptional;
if ( !context )
{
// that's as far as we can get without a context
return true;
}
QStringList parts = input.toString().split( ',' );
if ( parts.count() == 4 )
{
bool xMinOk = false;
( void )parts.at( 0 ).toDouble( &xMinOk );
bool xMaxOk = false;
( void )parts.at( 1 ).toDouble( &xMaxOk );
bool yMinOk = false;
( void )parts.at( 2 ).toDouble( &yMinOk );
bool yMaxOk = false;
( void )parts.at( 3 ).toDouble( &yMaxOk );
if ( xMinOk && xMaxOk && yMinOk && yMaxOk )
return true;
}
// try as layer extent
if ( QgsProcessingUtils::mapLayerFromString( input.toString(), *context ) )
return true;
return false;
}
QgsProcessingParameterPoint::QgsProcessingParameterPoint( const QString &name, const QString &description, const QVariant &defaultValue, bool optional )
: QgsProcessingParameterDefinition( name, description, defaultValue, optional )
{
}
bool QgsProcessingParameterPoint::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
if ( input.type() == QVariant::String )
{
if ( input.toString().isEmpty() )
return mFlags & FlagOptional;
}
QStringList parts = input.toString().split( ',' );
if ( parts.count() == 2 )
{
bool xOk = false;
( void )parts.at( 0 ).toDouble( &xOk );
bool yOk = false;
( void )parts.at( 1 ).toDouble( &yOk );
return xOk && yOk;
}
else
return false;
}
QgsProcessingParameterFile::QgsProcessingParameterFile( const QString &name, const QString &description, Behavior behavior, const QString &extension, const QVariant &defaultValue, bool optional )
: QgsProcessingParameterDefinition( name, description, defaultValue, optional )
, mBehavior( behavior )
@ -481,6 +607,36 @@ QgsProcessingParameterFile::QgsProcessingParameterFile( const QString &name, con
}
bool QgsProcessingParameterFile::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
QString string = input.toString().trimmed();
if ( input.type() != QVariant::String || string.isEmpty() )
return mFlags & FlagOptional;
switch ( mBehavior )
{
case File:
{
if ( !mExtension.isEmpty() )
return string.endsWith( mExtension, Qt::CaseInsensitive );
return true;
}
case Folder:
return true;
}
return true;
}
QgsProcessingParameterMatrix::QgsProcessingParameterMatrix( const QString &name, const QString &description, int numberRows, bool fixedNumberRows, const QStringList &headers, const QVariant &defaultValue, bool optional )
: QgsProcessingParameterDefinition( name, description, defaultValue, optional )
, mHeaders( headers )
@ -490,6 +646,31 @@ QgsProcessingParameterMatrix::QgsProcessingParameterMatrix( const QString &name,
}
bool QgsProcessingParameterMatrix::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.type() == QVariant::String )
{
if ( input.toString().isEmpty() )
return mFlags & FlagOptional;
return true;
}
else if ( input.type() == QVariant::List )
{
if ( input.toList().isEmpty() )
return mFlags & FlagOptional;
return true;
}
else if ( input.type() == QVariant::Double || input.type() == QVariant::Int )
{
return true;
}
return false;
}
QStringList QgsProcessingParameterMatrix::headers() const
{
return mHeaders;
@ -527,6 +708,63 @@ QgsProcessingParameterMultipleLayers::QgsProcessingParameterMultipleLayers( cons
}
bool QgsProcessingParameterMultipleLayers::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.type() == QVariant::String )
{
if ( input.toString().isEmpty() )
return mFlags & FlagOptional;
if ( mMinimumNumberInputs > 1 )
return false;
if ( !context )
return true;
return QgsProcessingUtils::mapLayerFromString( input.toString(), *context );
}
else if ( input.type() == QVariant::List )
{
if ( input.toList().count() < mMinimumNumberInputs )
return mFlags & FlagOptional;
if ( mMinimumNumberInputs > input.toList().count() )
return false;
if ( !context )
return true;
Q_FOREACH ( const QVariant &v, input.toList() )
{
if ( !QgsProcessingUtils::mapLayerFromString( v.toString(), *context ) )
return false;
}
return true;
}
else if ( input.type() == QVariant::StringList )
{
if ( input.toStringList().count() < mMinimumNumberInputs )
return mFlags & FlagOptional;
if ( mMinimumNumberInputs > input.toStringList().count() )
return false;
if ( !context )
return true;
Q_FOREACH ( const QString &v, input.toStringList() )
{
if ( !QgsProcessingUtils::mapLayerFromString( v, *context ) )
return false;
}
return true;
}
return false;
}
QgsProcessingParameterDefinition::LayerType QgsProcessingParameterMultipleLayers::layerType() const
{
return mLayerType;
@ -537,6 +775,17 @@ void QgsProcessingParameterMultipleLayers::setLayerType( LayerType type )
mLayerType = type;
}
int QgsProcessingParameterMultipleLayers::minimumNumberInputs() const
{
return mMinimumNumberInputs;
}
void QgsProcessingParameterMultipleLayers::setMinimumNumberInputs( int minimumNumberInputs )
{
if ( mMinimumNumberInputs >= 1 || !( flags() & QgsProcessingParameterDefinition::FlagOptional ) )
mMinimumNumberInputs = minimumNumberInputs;
}
QgsProcessingParameterNumber::QgsProcessingParameterNumber( const QString &name, const QString &description, Type type, const QVariant &defaultValue, bool optional, double minValue, double maxValue )
: QgsProcessingParameterDefinition( name, description, defaultValue, optional )
, mMin( minValue )
@ -546,6 +795,27 @@ QgsProcessingParameterNumber::QgsProcessingParameterNumber( const QString &name,
}
bool QgsProcessingParameterNumber::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
bool ok = false;
double res = input.toDouble( &ok );
if ( !ok )
return mFlags & FlagOptional;
if ( res < mMin || res > mMax )
return false;
return true;
}
double QgsProcessingParameterNumber::minimum() const
{
return mMin;
@ -583,6 +853,46 @@ QgsProcessingParameterRange::QgsProcessingParameterRange( const QString &name, c
}
bool QgsProcessingParameterRange::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
if ( input.type() == QVariant::String )
{
QStringList list = input.toString().split( ',' );
if ( list.count() != 2 )
return mFlags & FlagOptional;
bool ok = false;
list.at( 0 ).toDouble( &ok );
bool ok2 = false;
list.at( 1 ).toDouble( &ok2 );
if ( !ok || !ok2 )
return mFlags & FlagOptional;
return true;
}
else if ( input.type() == QVariant::List )
{
if ( input.toList().count() != 2 )
return mFlags & FlagOptional;
bool ok = false;
input.toList().at( 0 ).toDouble( &ok );
bool ok2 = false;
input.toList().at( 1 ).toDouble( &ok2 );
if ( !ok || !ok2 )
return mFlags & FlagOptional;
return true;
}
return false;
}
QgsProcessingParameterNumber::Type QgsProcessingParameterRange::dataType() const
{
return mDataType;
@ -599,6 +909,32 @@ QgsProcessingParameterRasterLayer::QgsProcessingParameterRasterLayer( const QStr
}
bool QgsProcessingParameterRasterLayer::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
if ( input.type() != QVariant::String || input.toString().isEmpty() )
return mFlags & FlagOptional;
if ( !context )
{
// that's as far as we can get without a context
return true;
}
// try to load as layer
if ( QgsProcessingUtils::mapLayerFromString( input.toString(), *context ) )
return true;
return false;
}
QgsProcessingParameterEnum::QgsProcessingParameterEnum( const QString &name, const QString &description, const QStringList &options, bool allowMultiple, const QVariant &defaultValue, bool optional )
: QgsProcessingParameterDefinition( name, description, defaultValue, optional )
, mOptions( options )
@ -607,6 +943,62 @@ QgsProcessingParameterEnum::QgsProcessingParameterEnum( const QString &name, con
}
bool QgsProcessingParameterEnum::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
if ( input.type() == QVariant::List )
{
if ( !mAllowMultiple )
return false;
Q_FOREACH ( const QVariant &val, input.toList() )
{
bool ok = false;
int res = val.toInt( &ok );
if ( !ok )
return false;
else if ( res < 0 || res >= mOptions.count() )
return false;
}
return true;
}
else if ( input.type() == QVariant::String )
{
QStringList parts = input.toString().split( ',' );
if ( parts.count() > 1 && !mAllowMultiple )
return false;
Q_FOREACH ( const QString &part, parts )
{
bool ok = false;
int res = part.toInt( &ok );
if ( !ok )
return false;
else if ( res < 0 || res >= mOptions.count() )
return false;
}
return true;
}
else if ( input.type() == QVariant::Int || input.type() == QVariant::Double )
{
bool ok = false;
int res = input.toInt( &ok );
if ( !ok )
return false;
else if ( res >= 0 && res < mOptions.count() )
return true;
}
return false;
}
QStringList QgsProcessingParameterEnum::options() const
{
return mOptions;
@ -676,6 +1068,38 @@ QgsProcessingParameterTableField::QgsProcessingParameterTableField( const QStrin
}
bool QgsProcessingParameterTableField::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
if ( input.type() == QVariant::List || input.type() == QVariant::StringList )
{
if ( !mAllowMultiple )
return false;
}
else if ( input.type() == QVariant::String )
{
if ( input.toString().isEmpty() )
return mFlags & FlagOptional;
QStringList parts = input.toString().split( ';' );
if ( parts.count() > 1 && !mAllowMultiple )
return false;
}
else
{
if ( input.toString().isEmpty() )
return mFlags & FlagOptional;
}
return true;
}
QString QgsProcessingParameterTableField::parentLayerParameter() const
{
return mParentLayerParameter;
@ -713,6 +1137,32 @@ QgsProcessingParameterVectorLayer::QgsProcessingParameterVectorLayer( const QStr
}
bool QgsProcessingParameterVectorLayer::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
if ( input.type() != QVariant::String || input.toString().isEmpty() )
return mFlags & FlagOptional;
if ( !context )
{
// that's as far as we can get without a context
return true;
}
// try to load as layer
if ( QgsProcessingUtils::mapLayerFromString( input.toString(), *context ) )
return true;
return false;
}
QgsProcessingParameterDefinition::LayerType QgsProcessingParameterVectorLayer::dataType() const
{
return mDataType;
@ -732,6 +1182,25 @@ QgsProcessingParameterOutputVectorLayer::QgsProcessingParameterOutputVectorLayer
}
bool QgsProcessingParameterOutputVectorLayer::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const
{
if ( !input.isValid() )
return mFlags & FlagOptional;
if ( input.canConvert<QgsProperty>() )
{
return true;
}
if ( input.type() != QVariant::String )
return false;
if ( input.toString().isEmpty() )
return mFlags & FlagOptional;
return true;
}
QgsProcessingParameterDefinition::LayerType QgsProcessingParameterOutputVectorLayer::dataType() const
{
return mDataType;

@ -186,6 +186,15 @@ class CORE_EXPORT QgsProcessingParameterDefinition
*/
void setFlags( const Flags &flags ) { mFlags = flags; }
/**
* Checks whether the specified \a input value is acceptable for the
* parameter. Returns true if the value can be accepted.
* The optional \a context parameter can be specified to allow a more stringent
* check to be performed, capable of checking for the presence of required
* layers and other factors within the context.
*/
virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const;
protected:
//! Parameter name
@ -360,7 +369,6 @@ class CORE_EXPORT QgsProcessingParameterBoolean : public QgsProcessingParameterD
bool optional = false );
QString type() const override { return QStringLiteral( "boolean" ); }
};
/**
@ -380,6 +388,7 @@ class CORE_EXPORT QgsProcessingParameterCrs : public QgsProcessingParameterDefin
bool optional = false );
QString type() const override { return QStringLiteral( "crs" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
};
/**
@ -399,6 +408,7 @@ class CORE_EXPORT QgsProcessingParameterMapLayer : public QgsProcessingParameter
bool optional = false );
QString type() const override { return QStringLiteral( "layer" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
};
/**
@ -418,7 +428,7 @@ class CORE_EXPORT QgsProcessingParameterExtent : public QgsProcessingParameterDe
bool optional = false );
QString type() const override { return QStringLiteral( "extent" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
};
@ -440,6 +450,7 @@ class CORE_EXPORT QgsProcessingParameterPoint : public QgsProcessingParameterDef
bool optional = false );
QString type() const override { return QStringLiteral( "point" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
};
@ -467,6 +478,7 @@ class CORE_EXPORT QgsProcessingParameterFile : public QgsProcessingParameterDefi
bool optional = false );
QString type() const override { return QStringLiteral( "file" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
/**
* Returns the parameter behavior (e.g. File or Folder).
@ -517,6 +529,7 @@ class CORE_EXPORT QgsProcessingParameterMatrix : public QgsProcessingParameterDe
bool optional = false );
QString type() const override { return QStringLiteral( "matrix" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
/**
* Returns a list of column headers (if set).
@ -586,6 +599,7 @@ class CORE_EXPORT QgsProcessingParameterMultipleLayers : public QgsProcessingPar
bool optional = false );
QString type() const override { return QStringLiteral( "multilayer" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
/**
* Returns the layer type for layers acceptable by the parameter.
@ -599,9 +613,24 @@ class CORE_EXPORT QgsProcessingParameterMultipleLayers : public QgsProcessingPar
*/
void setLayerType( QgsProcessingParameterDefinition::LayerType type );
/**
* Returns the minimum number of layers required for the parameter. If the return value is < 1
* then the parameter accepts any number of layers.
* \see setMinimumNumberInputs()
*/
int minimumNumberInputs() const;
/**
* Sets the \a minimum number of layers required for the parameter. The minimum must be >= 1
* if the parameter is not optional.
* \see minimumNumberInputs()
*/
void setMinimumNumberInputs( int minimum );
private:
LayerType mLayerType = TypeVectorAny;
int mMinimumNumberInputs = 0;
};
@ -634,6 +663,7 @@ class CORE_EXPORT QgsProcessingParameterNumber : public QgsProcessingParameterDe
);
QString type() const override { return QStringLiteral( "number" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
/**
* Returns the minimum value acceptable by the parameter.
@ -697,6 +727,7 @@ class CORE_EXPORT QgsProcessingParameterRange : public QgsProcessingParameterDef
bool optional = false );
QString type() const override { return QStringLiteral( "range" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
/**
* Returns the acceptable data type for the range.
@ -732,6 +763,7 @@ class CORE_EXPORT QgsProcessingParameterRasterLayer : public QgsProcessingParame
bool optional = false );
QString type() const override { return QStringLiteral( "raster" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
};
@ -754,6 +786,7 @@ class CORE_EXPORT QgsProcessingParameterEnum : public QgsProcessingParameterDefi
bool optional = false );
QString type() const override { return QStringLiteral( "enum" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
/**
* Returns the list of acceptable options for the parameter.
@ -909,6 +942,7 @@ class CORE_EXPORT QgsProcessingParameterTableField : public QgsProcessingParamet
bool optional = false );
QString type() const override { return QStringLiteral( "field" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
/**
* Returns the name of the parent layer parameter, or an empty string if this is not set.
@ -971,7 +1005,7 @@ class CORE_EXPORT QgsProcessingParameterVectorLayer : public QgsProcessingParame
bool optional = false );
QString type() const override { return QStringLiteral( "vector" ); }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
/**
* Returns the layer type for layers acceptable by the parameter.
@ -1012,6 +1046,7 @@ class CORE_EXPORT QgsProcessingParameterOutputVectorLayer : public QgsProcessing
QString type() const override { return QStringLiteral( "vectorOut" ); }
bool isDestination() const override { return true; }
bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override;
/**
* Returns the layer type for the output layer associated with the parameter.

File diff suppressed because it is too large Load Diff