1876 lines
79 KiB
Python
Executable File

# -*- coding: utf-8 -*-
"""
***************************************************************************
wrappers.py - Standard parameters widget wrappers
---------------------
Date : May 2016
Copyright : (C) 2016 by Arnaud Morvan, Victor Olaya
Email : arnaud dot morvan at camptocamp dot com
volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = 'Arnaud Morvan'
__date__ = 'May 2016'
__copyright__ = '(C) 2016, Arnaud Morvan'
import locale
import os
import re
from functools import cmp_to_key
from inspect import isclass
from copy import deepcopy
from qgis.core import (
QgsApplication,
QgsUnitTypes,
QgsCoordinateReferenceSystem,
QgsExpression,
QgsExpressionContextGenerator,
QgsFieldProxyModel,
QgsMapLayerProxyModel,
QgsWkbTypes,
QgsSettings,
QgsProject,
QgsMapLayer,
QgsMapLayerType,
QgsVectorLayer,
QgsProcessing,
QgsProcessingUtils,
QgsProcessingParameterDefinition,
QgsProcessingParameterBoolean,
QgsProcessingParameterCrs,
QgsProcessingParameterExtent,
QgsProcessingParameterPoint,
QgsProcessingParameterFile,
QgsProcessingParameterMultipleLayers,
QgsProcessingParameterNumber,
QgsProcessingParameterDistance,
QgsProcessingParameterRasterLayer,
QgsProcessingParameterEnum,
QgsProcessingParameterString,
QgsProcessingParameterExpression,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMeshLayer,
QgsProcessingParameterField,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterMapLayer,
QgsProcessingParameterBand,
QgsProcessingParameterMatrix,
QgsProcessingParameterDistance,
QgsProcessingFeatureSourceDefinition,
QgsProcessingOutputRasterLayer,
QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers,
QgsProcessingOutputFile,
QgsProcessingOutputString,
QgsProcessingOutputNumber,
QgsProcessingModelChildParameterSource,
QgsProcessingModelAlgorithm,
QgsRasterDataProvider,
NULL,
Qgis)
from qgis.PyQt.QtWidgets import (
QCheckBox,
QComboBox,
QLabel,
QDialog,
QFileDialog,
QHBoxLayout,
QVBoxLayout,
QLineEdit,
QPlainTextEdit,
QToolButton,
QWidget,
)
from qgis.gui import (
QgsGui,
QgsExpressionLineEdit,
QgsExpressionBuilderDialog,
QgsFieldComboBox,
QgsFieldExpressionWidget,
QgsProjectionSelectionDialog,
QgsMapLayerComboBox,
QgsProjectionSelectionWidget,
QgsRasterBandComboBox,
QgsProcessingGui,
QgsAbstractProcessingParameterWidgetWrapper,
QgsProcessingMapLayerComboBox
)
from qgis.PyQt.QtCore import pyqtSignal, QObject, QVariant, Qt
from qgis.utils import iface
from processing.core.ProcessingConfig import ProcessingConfig
from processing.modeler.MultilineTextPanel import MultilineTextPanel
from processing.gui.NumberInputPanel import NumberInputPanel, ModelerNumberInputPanel, DistanceInputPanel
from processing.gui.RangePanel import RangePanel
from processing.gui.PointSelectionPanel import PointSelectionPanel
from processing.gui.FileSelectionPanel import FileSelectionPanel
from processing.gui.CheckboxesPanel import CheckboxesPanel
from processing.gui.MultipleInputPanel import MultipleInputPanel
from processing.gui.BatchInputSelectionPanel import BatchInputSelectionPanel
from processing.gui.FixedTablePanel import FixedTablePanel
from processing.gui.ExtentSelectionPanel import ExtentSelectionPanel
from processing.gui.ParameterGuiUtils import getFileFilter
from processing.tools import dataobjects
DIALOG_STANDARD = QgsProcessingGui.Standard
DIALOG_BATCH = QgsProcessingGui.Batch
DIALOG_MODELER = QgsProcessingGui.Modeler
class InvalidParameterValue(Exception):
pass
dialogTypes = {"AlgorithmDialog": DIALOG_STANDARD,
"ModelerParametersDialog": DIALOG_MODELER,
"BatchAlgorithmDialog": DIALOG_BATCH}
def getExtendedLayerName(layer):
authid = layer.crs().authid()
if ProcessingConfig.getSetting(ProcessingConfig.SHOW_CRS_DEF) and authid is not None:
return u'{} [{}]'.format(layer.name(), authid)
else:
return layer.name()
class WidgetWrapper(QgsAbstractProcessingParameterWidgetWrapper):
NOT_SET_OPTION = '~~~~!!!!NOT SET!!!!~~~~~~~'
def __init__(self, param, dialog, row=0, col=0, **kwargs):
self.dialogType = dialogTypes.get(dialog.__class__.__name__, QgsProcessingGui.Standard)
super().__init__(param, self.dialogType)
self.dialog = dialog
self.row = row
self.col = col
self.widget = self.createWidget(**kwargs)
self.label = self.createLabel()
if param.defaultValue() is not None:
self.setValue(param.defaultValue())
def comboValue(self, validator=None, combobox=None):
if combobox is None:
combobox = self.widget
idx = combobox.findText(combobox.currentText())
if idx < 0:
v = combobox.currentText().strip()
if validator is not None and not validator(v):
raise InvalidParameterValue()
return v
if combobox.currentData() == self.NOT_SET_OPTION:
return None
elif combobox.currentData() is not None:
return combobox.currentData()
else:
return combobox.currentText()
def createWidget(self, **kwargs):
pass
def createLabel(self):
if self.dialogType == DIALOG_BATCH:
return None
desc = self.parameterDefinition().description()
if isinstance(self.parameterDefinition(), QgsProcessingParameterExtent):
desc += self.tr(' (xmin, xmax, ymin, ymax)')
if isinstance(self.parameterDefinition(), QgsProcessingParameterPoint):
desc += self.tr(' (x, y)')
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
desc += self.tr(' [optional]')
label = QLabel(desc)
label.setToolTip(self.parameterDefinition().name())
return label
def setValue(self, value):
pass
def value(self):
return None
def widgetValue(self):
return self.value()
def setWidgetValue(self, value, context):
self.setValue(value)
def setComboValue(self, value, combobox=None):
if combobox is None:
combobox = self.widget
if isinstance(value, list):
if value:
value = value[0]
else:
value = None
values = [combobox.itemData(i) for i in range(combobox.count())]
try:
idx = values.index(value)
combobox.setCurrentIndex(idx)
return
except ValueError:
pass
if combobox.isEditable():
if value is not None:
combobox.setEditText(str(value))
else:
combobox.setCurrentIndex(0)
def refresh(self):
pass
def getFileName(self, initial_value=''):
"""Shows a file open dialog"""
settings = QgsSettings()
if os.path.isdir(initial_value):
path = initial_value
elif os.path.isdir(os.path.dirname(initial_value)):
path = os.path.dirname(initial_value)
elif settings.contains('/Processing/LastInputPath'):
path = str(settings.value('/Processing/LastInputPath'))
else:
path = ''
# TODO: should use selectedFilter argument for default file format
filename, selected_filter = QFileDialog.getOpenFileName(self.widget, self.tr('Select File'),
path, getFileFilter(self.parameterDefinition()))
if filename:
settings.setValue('/Processing/LastInputPath',
os.path.dirname(str(filename)))
return filename, selected_filter
class BasicWidgetWrapper(WidgetWrapper):
def createWidget(self):
return QLineEdit()
def setValue(self, value):
self.widget.setText(value)
def value(self):
return self.widget.text()
class BooleanWidgetWrapper(WidgetWrapper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
.. deprecated:: 3.4
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("BooleanWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
def createLabel(self):
if self.dialogType == DIALOG_STANDARD:
return None
else:
return super().createLabel()
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
return QCheckBox()
elif self.dialogType == DIALOG_BATCH:
widget = QComboBox()
widget.addItem(self.tr('Yes'), True)
widget.addItem(self.tr('No'), False)
return widget
else:
widget = QComboBox()
widget.addItem(self.tr('Yes'), True)
widget.addItem(self.tr('No'), False)
bools = self.dialog.getAvailableValuesOfType(QgsProcessingParameterBoolean, None)
for b in bools:
widget.addItem(self.dialog.resolveValueDescription(b), b)
return widget
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType == DIALOG_STANDARD:
self.widget.setChecked(value)
else:
self.setComboValue(value)
def value(self):
if self.dialogType == DIALOG_STANDARD:
return self.widget.isChecked()
else:
return self.comboValue()
class CrsWidgetWrapper(WidgetWrapper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
.. deprecated:: 3.4
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("CrsWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
def createWidget(self):
if self.dialogType == DIALOG_MODELER:
self.combo = QComboBox()
widget = QWidget()
layout = QHBoxLayout()
layout.setMargin(0)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(1)
layout.addWidget(self.combo)
btn = QToolButton()
btn.setIcon(QgsApplication.getThemeIcon("mActionSetProjection.svg"))
btn.setToolTip(self.tr("Select CRS"))
btn.clicked.connect(self.selectProjection)
layout.addWidget(btn)
widget.setLayout(layout)
self.combo.setEditable(True)
crss = self.dialog.getAvailableValuesOfType((QgsProcessingParameterCrs, QgsProcessingParameterString), QgsProcessingOutputString)
for crs in crss:
self.combo.addItem(self.dialog.resolveValueDescription(crs), crs)
layers = self.dialog.getAvailableValuesOfType([QgsProcessingParameterRasterLayer,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMeshLayer,
QgsProcessingParameterFeatureSource],
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputRasterLayer,
QgsProcessingOutputMapLayer])
for l in layers:
self.combo.addItem("Crs of layer " + self.dialog.resolveValueDescription(l), l)
if self.parameterDefinition().defaultValue():
self.combo.setEditText(self.parameterDefinition().defaultValue())
return widget
else:
widget = QgsProjectionSelectionWidget()
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
widget.setOptionVisible(QgsProjectionSelectionWidget.CrsNotSet, True)
if self.parameterDefinition().defaultValue():
if self.parameterDefinition().defaultValue() == 'ProjectCrs':
crs = QgsProject.instance().crs()
else:
crs = QgsCoordinateReferenceSystem(self.parameterDefinition().defaultValue())
widget.setCrs(crs)
else:
widget.setOptionVisible(QgsProjectionSelectionWidget.CrsNotSet, True)
widget.crsChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
def selectProjection(self):
dialog = QgsProjectionSelectionDialog(self.widget)
current_crs = QgsCoordinateReferenceSystem(self.combo.currentText())
if current_crs.isValid():
dialog.setCrs(current_crs)
if dialog.exec_():
self.setValue(dialog.crs().authid())
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType == DIALOG_MODELER:
self.setComboValue(value, self.combo)
elif value == 'ProjectCrs':
self.widget.setCrs(QgsProject.instance().crs())
else:
self.widget.setCrs(QgsCoordinateReferenceSystem(value))
def value(self):
if self.dialogType == DIALOG_MODELER:
return self.comboValue(combobox=self.combo)
else:
crs = self.widget.crs()
if crs.isValid():
return self.widget.crs().authid()
else:
return None
class ExtentWidgetWrapper(WidgetWrapper):
USE_MIN_COVERING_EXTENT = "[Use min covering extent]"
def createWidget(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
widget = ExtentSelectionPanel(self.dialog, self.parameterDefinition())
widget.hasChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
else:
widget = QComboBox()
widget.setEditable(True)
extents = self.dialog.getAvailableValuesOfType(QgsProcessingParameterExtent, (QgsProcessingOutputString))
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
widget.addItem(self.USE_MIN_COVERING_EXTENT, None)
layers = self.dialog.getAvailableValuesOfType([QgsProcessingParameterFeatureSource,
QgsProcessingParameterRasterLayer,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMeshLayer],
[QgsProcessingOutputRasterLayer,
QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer])
for ex in extents:
widget.addItem(self.dialog.resolveValueDescription(ex), ex)
for l in layers:
widget.addItem("Extent of " + self.dialog.resolveValueDescription(l), l)
if not self.parameterDefinition().defaultValue():
widget.setEditText(self.parameterDefinition().defaultValue())
return widget
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
self.widget.setExtentFromString(value)
else:
self.setComboValue(value)
def value(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
return self.widget.getValue()
else:
idx = self.widget.findText(self.widget.currentText())
if idx < 0:
s = str(self.widget.currentText()).strip()
if s:
try:
tokens = s.split(',')
if len(tokens) != 4:
raise InvalidParameterValue()
for token in tokens:
float(token)
except:
raise InvalidParameterValue()
elif self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
s = None
else:
raise InvalidParameterValue()
return s
else:
return self.widget.currentData()
class PointWidgetWrapper(WidgetWrapper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
.. deprecated:: 3.4
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("PointWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
def createWidget(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
return PointSelectionPanel(self.dialog, self.parameterDefinition().defaultValue())
else:
item = QComboBox()
item.setEditable(True)
points = self.dialog.getAvailableValuesOfType((QgsProcessingParameterPoint, QgsProcessingParameterString), (QgsProcessingOutputString))
for p in points:
item.addItem(self.dialog.resolveValueDescription(p), p)
item.setEditText(str(self.parameterDefinition().defaultValue()))
return item
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
self.widget.setPointFromString(value)
else:
self.setComboValue(value)
def value(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
return self.widget.getValue()
else:
idx = self.widget.findText(self.widget.currentText())
if idx < 0:
s = str(self.widget.currentText()).strip()
if s:
try:
tokens = s.split(',')
if len(tokens) != 2:
raise InvalidParameterValue()
for token in tokens:
float(token)
except:
raise InvalidParameterValue()
elif self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
s = None
else:
raise InvalidParameterValue()
return s
else:
return self.widget.currentData()
class FileWidgetWrapper(WidgetWrapper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
.. deprecated:: 3.4
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("FileWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
def createWidget(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
return FileSelectionPanel(self.parameterDefinition().behavior() == QgsProcessingParameterFile.Folder,
self.parameterDefinition().extension())
else:
self.combo = QComboBox()
self.combo.setEditable(True)
files = self.dialog.getAvailableValuesOfType(QgsProcessingParameterFile, (QgsProcessingOutputRasterLayer, QgsProcessingOutputVectorLayer, QgsProcessingOutputMapLayer, QgsProcessingOutputFile, QgsProcessingOutputString))
for f in files:
self.combo.addItem(self.dialog.resolveValueDescription(f), f)
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
self.combo.setEditText("")
widget = QWidget()
layout = QHBoxLayout()
layout.setMargin(0)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(6)
layout.addWidget(self.combo)
btn = QToolButton()
btn.setText('')
btn.setToolTip(self.tr("Select file"))
btn.clicked.connect(self.selectFile)
layout.addWidget(btn)
widget.setLayout(layout)
return widget
def selectFile(self):
settings = QgsSettings()
if os.path.isdir(os.path.dirname(self.combo.currentText())):
path = os.path.dirname(self.combo.currentText())
if settings.contains('/Processing/LastInputPath'):
path = settings.value('/Processing/LastInputPath')
else:
path = ''
if self.parameterDefinition().extension():
filter = self.tr('{} files').format(
self.parameterDefinition().extension().upper()) + ' (*.' + self.parameterDefinition().extension() + self.tr(
');;All files (*.*)')
else:
filter = self.tr('All files (*.*)')
filename, selected_filter = QFileDialog.getOpenFileName(self.widget,
self.tr('Select File'), path,
filter)
if filename:
self.combo.setEditText(filename)
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
self.widget.setText(value)
else:
self.setComboValue(value, combobox=self.combo)
def value(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
return self.widget.getValue()
else:
return self.comboValue(combobox=self.combo)
class FixedTableWidgetWrapper(WidgetWrapper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
.. deprecated:: 3.4
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("FixedTableWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
def createWidget(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
return FixedTablePanel(self.parameterDefinition())
else:
self.combobox = QComboBox()
values = self.dialog.getAvailableValuesOfType(QgsProcessingParameterMatrix)
for v in values:
self.combobox.addItem(self.dialog.resolveValueDescription(v), v)
return self.combobox
def setValue(self, value):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
self.widget.setValue(value)
else:
self.setComboValue(value, combobox=self.combobox)
def value(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
return self.widget.table
else:
return self.comboValue(combobox=self.combobox)
class MultipleLayerWidgetWrapper(WidgetWrapper):
def _getOptions(self):
if self.parameterDefinition().layerType() == QgsProcessing.TypeVectorAnyGeometry:
options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers])
elif self.parameterDefinition().layerType() == QgsProcessing.TypeVector:
options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers],
[QgsProcessing.TypeVector])
elif self.parameterDefinition().layerType() == QgsProcessing.TypeVectorPoint:
options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers],
[QgsProcessing.TypeVectorPoint,
QgsProcessing.TypeVectorAnyGeometry])
elif self.parameterDefinition().layerType() == QgsProcessing.TypeVectorLine:
options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers],
[QgsProcessing.TypeVectorLine,
QgsProcessing.TypeVectorAnyGeometry])
elif self.parameterDefinition().layerType() == QgsProcessing.TypeVectorPolygon:
options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers],
[QgsProcessing.TypeVectorPolygon,
QgsProcessing.TypeVectorAnyGeometry])
elif self.parameterDefinition().layerType() == QgsProcessing.TypeRaster:
options = self.dialog.getAvailableValuesOfType(
(QgsProcessingParameterRasterLayer, QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputRasterLayer,
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers])
elif self.parameterDefinition().layerType() == QgsProcessing.TypeMesh:
options = self.dialog.getAvailableValuesOfType(
(QgsProcessingParameterMeshLayer, QgsProcessingParameterMultipleLayers),
[])
elif self.parameterDefinition().layerType() == QgsProcessing.TypeMapLayer:
options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterRasterLayer,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMeshLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputRasterLayer,
QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers])
else:
options = self.dialog.getAvailableValuesOfType(QgsProcessingParameterFile, QgsProcessingOutputFile)
options = sorted(options, key=lambda opt: self.dialog.resolveValueDescription(opt))
return options
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
if self.parameterDefinition().layerType() == QgsProcessing.TypeFile:
return MultipleInputPanel(datatype=QgsProcessing.TypeFile)
else:
if self.parameterDefinition().layerType() == QgsProcessing.TypeRaster:
options = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)
elif self.parameterDefinition().layerType() == QgsProcessing.TypeMesh:
options = QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False)
elif self.parameterDefinition().layerType() in (QgsProcessing.TypeVectorAnyGeometry, QgsProcessing.TypeVector):
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
elif self.parameterDefinition().layerType() == QgsProcessing.TypeMapLayer:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
options.extend(QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False))
options.extend(QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False))
else:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.parameterDefinition().layerType()],
False)
opts = [getExtendedLayerName(opt) for opt in options]
return MultipleInputPanel(opts, datatype=self.parameterDefinition().layerType())
elif self.dialogType == DIALOG_BATCH:
widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
widget.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
else:
options = [self.dialog.resolveValueDescription(opt) for opt in self._getOptions()]
return MultipleInputPanel(options, datatype=self.parameterDefinition().layerType())
def refresh(self):
if self.parameterDefinition().layerType() != QgsProcessing.TypeFile:
if self.parameterDefinition().layerType() == QgsProcessing.TypeRaster:
options = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)
elif self.parameterDefinition().layerType() == QgsProcessing.TypeMesh:
options = QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False)
elif self.parameterDefinition().layerType() in (QgsProcessing.TypeVectorAnyGeometry, QgsProcessing.TypeVector):
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
elif self.parameterDefinition().layerType() == QgsProcessing.TypeMapLayer:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
options.extend(QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False))
options.extend(QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False))
else:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.parameterDefinition().layerType()],
False)
opts = [getExtendedLayerName(opt) for opt in options]
self.widget.updateForOptions(opts)
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType == DIALOG_STANDARD:
pass # TODO
elif self.dialogType == DIALOG_BATCH:
return self.widget.setValue(value)
else:
options = self._getOptions()
if not isinstance(value, (tuple, list)):
value = [value]
selected_options = []
for sel in value:
if sel in options:
selected_options.append(options.index(sel))
elif isinstance(sel, QgsProcessingModelChildParameterSource):
selected_options.append(sel.staticValue())
else:
selected_options.append(sel)
self.widget.setSelectedItems(selected_options)
def value(self):
if self.dialogType == DIALOG_STANDARD:
if self.parameterDefinition().layerType() == QgsProcessing.TypeFile:
return self.widget.selectedoptions
else:
if self.parameterDefinition().layerType() == QgsProcessing.TypeRaster:
options = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)
elif self.parameterDefinition().layerType() == QgsProcessing.TypeMesh:
options = QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False)
elif self.parameterDefinition().layerType() in (QgsProcessing.TypeVectorAnyGeometry, QgsProcessing.TypeVector):
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
elif self.parameterDefinition().layerType() == QgsProcessing.TypeMapLayer:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
options.extend(QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False))
options.extend(QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False))
else:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.parameterDefinition().layerType()],
False)
return [options[i] if isinstance(i, int) else i for i in self.widget.selectedoptions]
elif self.dialogType == DIALOG_BATCH:
return self.widget.value()
else:
options = self._getOptions()
values = [options[i] if isinstance(i, int) else QgsProcessingModelChildParameterSource.fromStaticValue(i)
for i in self.widget.selectedoptions]
if len(values) == 0 and not self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
raise InvalidParameterValue()
return values
class NumberWidgetWrapper(WidgetWrapper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
.. deprecated:: 3.4
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("NumberWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
def createWidget(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
widget = NumberInputPanel(self.parameterDefinition())
widget.hasChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
else:
return ModelerNumberInputPanel(self.parameterDefinition(), self.dialog)
def setValue(self, value):
if value is None or value == NULL:
return
self.widget.setValue(value)
def value(self):
return self.widget.getValue()
def postInitialize(self, wrappers):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH) and self.parameterDefinition().isDynamic():
for wrapper in wrappers:
if wrapper.parameterDefinition().name() == self.parameterDefinition().dynamicLayerParameterName():
self.widget.setDynamicLayer(wrapper.parameterValue())
wrapper.widgetValueHasChanged.connect(self.parentLayerChanged)
break
def parentLayerChanged(self, wrapper):
self.widget.setDynamicLayer(wrapper.parameterValue())
class DistanceWidgetWrapper(WidgetWrapper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
.. deprecated:: 3.4
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("DistanceWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
def createWidget(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
widget = DistanceInputPanel(self.parameterDefinition())
widget.hasChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
else:
return ModelerNumberInputPanel(self.parameterDefinition(), self.dialog)
def setValue(self, value):
if value is None or value == NULL:
return
self.widget.setValue(value)
def value(self):
return self.widget.getValue()
def postInitialize(self, wrappers):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
for wrapper in wrappers:
if wrapper.parameterDefinition().name() == self.parameterDefinition().dynamicLayerParameterName():
self.widget.setDynamicLayer(wrapper.parameterValue())
wrapper.widgetValueHasChanged.connect(self.dynamicLayerChanged)
if wrapper.parameterDefinition().name() == self.parameterDefinition().parentParameterName():
self.widget.setUnitParameterValue(wrapper.parameterValue())
wrapper.widgetValueHasChanged.connect(self.parentParameterChanged)
def dynamicLayerChanged(self, wrapper):
self.widget.setDynamicLayer(wrapper.parameterValue())
def parentParameterChanged(self, wrapper):
self.widget.setUnitParameterValue(wrapper.parameterValue())
class RangeWidgetWrapper(WidgetWrapper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
.. deprecated:: 3.4
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("RangeWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
def createWidget(self):
widget = RangePanel(self.parameterDefinition())
widget.hasChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
def setValue(self, value):
if value is None or value == NULL:
return
self.widget.setValue(value)
def value(self):
return self.widget.getValue()
class MapLayerWidgetWrapper(WidgetWrapper):
NOT_SELECTED = '[Not selected]'
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
self.combo = QgsProcessingMapLayerComboBox(self.parameterDefinition())
self.context = dataobjects.createContext()
try:
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
self.combo.setValue(self.parameterDefinition().defaultValue(), self.context)
else:
if self.parameterDefinition().defaultValue():
self.combo.setvalue(self.parameterDefinition().defaultValue(), self.context)
else:
self.combo.setLayer(iface.activeLayer())
except:
pass
self.combo.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
self.combo.triggerFileSelection.connect(self.selectFile)
return self.combo
elif self.dialogType == DIALOG_BATCH:
widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
widget.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
else:
self.combo = QComboBox()
layers = self.getAvailableLayers()
self.combo.setEditable(True)
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
self.combo.addItem(self.NOT_SELECTED, self.NOT_SET_OPTION)
for layer in layers:
self.combo.addItem(self.dialog.resolveValueDescription(layer), layer)
widget = QWidget()
layout = QHBoxLayout()
layout.setMargin(0)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(6)
layout.addWidget(self.combo)
btn = QToolButton()
btn.setText('')
btn.setToolTip(self.tr("Select file"))
btn.clicked.connect(self.selectFile)
layout.addWidget(btn)
widget.setLayout(layout)
return widget
def getAvailableLayers(self):
return self.dialog.getAvailableValuesOfType(
[QgsProcessingParameterRasterLayer, QgsProcessingParameterMeshLayer, QgsProcessingParameterVectorLayer, QgsProcessingParameterMapLayer, QgsProcessingParameterString],
[QgsProcessingOutputRasterLayer, QgsProcessingOutputVectorLayer, QgsProcessingOutputMapLayer, QgsProcessingOutputString, QgsProcessingOutputFile])
def selectFile(self):
filename, selected_filter = self.getFileName(self.combo.currentText())
if filename:
if isinstance(self.combo, QgsProcessingMapLayerComboBox):
self.combo.setValue(filename, self.context)
elif isinstance(self.combo, QgsMapLayerComboBox):
items = self.combo.additionalItems()
items.append(filename)
self.combo.setAdditionalItems(items)
self.combo.setCurrentIndex(self.combo.findText(filename))
else:
self.combo.setEditText(filename)
self.widgetValueHasChanged.emit(self)
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType == DIALOG_STANDARD:
if isinstance(value, str):
layer = QgsProject.instance().mapLayer(value)
if layer is not None:
value = layer
self.combo.setValue(value, self.context)
elif self.dialogType == DIALOG_BATCH:
self.widget.setValue(value)
else:
self.setComboValue(value, combobox=self.combo)
self.widgetValueHasChanged.emit(self)
def value(self):
if self.dialogType == DIALOG_STANDARD:
return self.combo.value()
elif self.dialogType == DIALOG_BATCH:
return self.widget.getValue()
else:
def validator(v):
if not bool(v):
return self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional
else:
return os.path.exists(v)
return self.comboValue(validator, combobox=self.combo)
class RasterWidgetWrapper(MapLayerWidgetWrapper):
def getAvailableLayers(self):
return self.dialog.getAvailableValuesOfType((QgsProcessingParameterRasterLayer, QgsProcessingParameterString),
(QgsProcessingOutputRasterLayer, QgsProcessingOutputFile, QgsProcessingOutputString))
def selectFile(self):
filename, selected_filter = self.getFileName(self.combo.currentText())
if filename:
filename = dataobjects.getRasterSublayer(filename, self.parameterDefinition())
if isinstance(self.combo, QgsProcessingMapLayerComboBox):
self.combo.setValue(filename, self.context)
elif isinstance(self.combo, QgsMapLayerComboBox):
items = self.combo.additionalItems()
items.append(filename)
self.combo.setAdditionalItems(items)
self.combo.setCurrentIndex(self.combo.findText(filename))
else:
self.combo.setEditText(filename)
self.widgetValueHasChanged.emit(self)
class MeshWidgetWrapper(MapLayerWidgetWrapper):
def getAvailableLayers(self):
return self.dialog.getAvailableValuesOfType((QgsProcessingParameterMeshLayer, QgsProcessingParameterString),
())
def selectFile(self):
filename, selected_filter = self.getFileName(self.combo.currentText())
if filename:
if isinstance(self.combo, QgsProcessingMapLayerComboBox):
self.combo.setValue(filename, self.context)
elif isinstance(self.combo, QgsMapLayerComboBox):
items = self.combo.additionalItems()
items.append(filename)
self.combo.setAdditionalItems(items)
self.combo.setCurrentIndex(self.combo.findText(filename))
else:
self.combo.setEditText(filename)
self.widgetValueHasChanged.emit(self)
class EnumWidgetWrapper(WidgetWrapper):
NOT_SELECTED = '[Not selected]'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
.. deprecated:: 3.4
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("EnumWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
def createWidget(self, useCheckBoxes=False, columns=1):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
self._useCheckBoxes = useCheckBoxes
if self._useCheckBoxes and not self.dialogType == DIALOG_BATCH:
return CheckboxesPanel(options=self.parameterDefinition().options(),
multiple=self.parameterDefinition().allowMultiple(),
columns=columns)
if self.parameterDefinition().allowMultiple():
return MultipleInputPanel(options=self.parameterDefinition().options())
else:
widget = QComboBox()
for i, option in enumerate(self.parameterDefinition().options()):
widget.addItem(option, i)
if self.parameterDefinition().defaultValue():
widget.setCurrentIndex(widget.findData(self.parameterDefinition().defaultValue()))
return widget
else:
self.combobox = QComboBox()
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
self.combobox.addItem(self.NOT_SELECTED, self.NOT_SET_OPTION)
for i, option in enumerate(self.parameterDefinition().options()):
self.combobox.addItem(option, i)
values = self.dialog.getAvailableValuesOfType(QgsProcessingParameterEnum)
for v in values:
self.combobox.addItem(self.dialog.resolveValueDescription(v), v)
return self.combobox
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
if self._useCheckBoxes and not self.dialogType == DIALOG_BATCH:
self.widget.setValue(value)
return
if self.parameterDefinition().allowMultiple():
self.widget.setSelectedItems(value)
else:
self.widget.setCurrentIndex(self.widget.findData(value))
else:
self.setComboValue(value, combobox=self.combobox)
def value(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
if self._useCheckBoxes and not self.dialogType == DIALOG_BATCH:
return self.widget.value()
if self.parameterDefinition().allowMultiple():
return self.widget.selectedoptions
else:
return self.widget.currentData()
else:
return self.comboValue(combobox=self.combobox)
class FeatureSourceWidgetWrapper(WidgetWrapper):
NOT_SELECTED = '[Not selected]'
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
self.combo = QgsProcessingMapLayerComboBox(self.parameterDefinition())
self.context = dataobjects.createContext()
try:
if iface.activeLayer().type() == QgsMapLayerType.VectorLayer:
self.combo.setLayer(iface.activeLayer())
except:
pass
self.combo.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
self.combo.triggerFileSelection.connect(self.selectFile)
return self.combo
elif self.dialogType == DIALOG_BATCH:
widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
widget.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
else:
self.combo = QComboBox()
layers = self.dialog.getAvailableValuesOfType(
(QgsProcessingParameterFeatureSource, QgsProcessingParameterVectorLayer),
(QgsProcessingOutputVectorLayer, QgsProcessingOutputMapLayer, QgsProcessingOutputString, QgsProcessingOutputFile), self.parameterDefinition().dataTypes())
self.combo.setEditable(True)
for layer in layers:
self.combo.addItem(self.dialog.resolveValueDescription(layer), layer)
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
self.combo.setEditText("")
widget = QWidget()
layout = QHBoxLayout()
layout.setMargin(0)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(2)
layout.addWidget(self.combo)
btn = QToolButton()
btn.setText('')
btn.setToolTip(self.tr("Select file"))
btn.clicked.connect(self.selectFile)
layout.addWidget(btn)
widget.setLayout(layout)
return widget
def selectFile(self):
filename, selected_filter = self.getFileName(self.combo.currentText())
if filename:
filename = dataobjects.getRasterSublayer(filename, self.parameterDefinition())
if isinstance(self.combo, QgsProcessingMapLayerComboBox):
self.combo.setValue(filename, self.context)
elif isinstance(self.combo, QgsMapLayerComboBox):
items = self.combo.additionalItems()
items.append(filename)
self.combo.setAdditionalItems(items)
self.combo.setCurrentIndex(self.combo.findText(filename))
else:
self.combo.setEditText(filename)
self.widgetValueHasChanged.emit(self)
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType == DIALOG_STANDARD:
if isinstance(value, str):
layer = QgsProject.instance().mapLayer(value)
if layer is not None:
value = layer
self.combo.setValue(value, self.context)
elif self.dialogType == DIALOG_BATCH:
self.widget.setValue(value)
else:
self.setComboValue(value, combobox=self.combo)
self.widgetValueHasChanged.emit(self)
def value(self):
if self.dialogType == DIALOG_STANDARD:
return self.combo.value()
elif self.dialogType == DIALOG_BATCH:
return self.widget.getValue()
else:
def validator(v):
if not bool(v):
return self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional
else:
return os.path.exists(v)
if self.combo.currentText():
return self.comboValue(validator, combobox=self.combo)
else:
return None
class StringWidgetWrapper(WidgetWrapper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
.. deprecated:: 3.4
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("StringWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
if self.parameterDefinition().multiLine():
widget = QPlainTextEdit()
else:
self._lineedit = QLineEdit()
widget = self._lineedit
elif self.dialogType == DIALOG_BATCH:
widget = QLineEdit()
else:
# strings, numbers, files and table fields are all allowed input types
strings = self.dialog.getAvailableValuesOfType(
[QgsProcessingParameterString, QgsProcessingParameterNumber, QgsProcessingParameterDistance, QgsProcessingParameterFile,
QgsProcessingParameterField, QgsProcessingParameterExpression],
[QgsProcessingOutputString, QgsProcessingOutputFile])
options = [(self.dialog.resolveValueDescription(s), s) for s in strings]
if self.parameterDefinition().multiLine():
widget = MultilineTextPanel(options)
else:
widget = QComboBox()
widget.setEditable(True)
for desc, val in options:
widget.addItem(desc, val)
return widget
def showExpressionsBuilder(self):
context = dataobjects.createExpressionContext()
value = self.value()
if not isinstance(value, str):
value = ''
dlg = QgsExpressionBuilderDialog(None, value, self.widget, 'generic', context)
dlg.setWindowTitle(self.tr('Expression based input'))
if dlg.exec_() == QDialog.Accepted:
exp = QgsExpression(dlg.expressionText())
if not exp.hasParserError():
if self.dialogType == DIALOG_STANDARD:
self.setValue(str(exp.evaluate(context)))
else:
self.setValue(dlg.expressionText())
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType == DIALOG_STANDARD:
if self.parameterDefinition().multiLine():
self.widget.setPlainText(value)
else:
self._lineedit.setText(value)
elif self.dialogType == DIALOG_BATCH:
self.widget.setText(value)
else:
if self.parameterDefinition().multiLine():
self.widget.setValue(value)
else:
self.setComboValue(value)
def value(self):
if self.dialogType == DIALOG_STANDARD:
if self.parameterDefinition().multiLine():
text = self.widget.toPlainText()
else:
text = self._lineedit.text()
return text
elif self.dialogType == DIALOG_BATCH:
return self.widget.text()
else:
if self.parameterDefinition().multiLine():
value = self.widget.getValue()
option = self.widget.getOption()
if option == MultilineTextPanel.USE_TEXT:
if value == '':
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
return None
else:
raise InvalidParameterValue()
else:
return value
else:
return value
else:
def validator(v):
return bool(v) or self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional
return self.comboValue(validator)
class ExpressionWidgetWrapper(WidgetWrapper):
def __init__(self, param, dialog, row=0, col=0, **kwargs):
"""
.. deprecated:: 3.4
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("StringWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
super().__init__(param, dialog, row, col, **kwargs)
self.context = dataobjects.createContext()
def createWidget(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
if self.parameterDefinition().parentLayerParameterName():
widget = QgsFieldExpressionWidget()
else:
widget = QgsExpressionLineEdit()
if self.parameterDefinition().defaultValue():
widget.setExpression(self.parameterDefinition().defaultValue())
else:
strings = self.dialog.getAvailableValuesOfType(
[QgsProcessingParameterExpression, QgsProcessingParameterString, QgsProcessingParameterNumber, QgsProcessingParameterDistance],
(QgsProcessingOutputString, QgsProcessingOutputNumber))
options = [(self.dialog.resolveValueDescription(s), s) for s in strings]
widget = QComboBox()
widget.setEditable(True)
for desc, val in options:
widget.addItem(desc, val)
widget.setEditText(self.parameterDefinition().defaultValue() or "")
return widget
def postInitialize(self, wrappers):
for wrapper in wrappers:
if wrapper.parameterDefinition().name() == self.parameterDefinition().parentLayerParameterName():
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
self.setLayer(wrapper.parameterValue())
wrapper.widgetValueHasChanged.connect(self.parentLayerChanged)
break
def parentLayerChanged(self, wrapper):
self.setLayer(wrapper.parameterValue())
def setLayer(self, layer):
if isinstance(layer, QgsProcessingFeatureSourceDefinition):
layer, ok = layer.source.valueAsString(self.context.expressionContext())
if isinstance(layer, str):
layer = QgsProcessingUtils.mapLayerFromString(layer, self.context)
self.widget.setLayer(layer)
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
self.widget.setExpression(value)
else:
self.setComboValue(value)
def value(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
try:
return self.widget.asExpression()
except:
return self.widget.expression()
else:
def validator(v):
return bool(v) or self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional
return self.comboValue(validator)
class VectorLayerWidgetWrapper(WidgetWrapper):
NOT_SELECTED = '[Not selected]'
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
self.combo = QgsProcessingMapLayerComboBox(self.parameterDefinition())
self.context = dataobjects.createContext()
try:
if iface.activeLayer().type() == QgsMapLayerType.VectorLayer:
self.combo.setLayer(iface.activeLayer())
except:
pass
self.combo.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
self.combo.triggerFileSelection.connect(self.selectFile)
return self.combo
elif self.dialogType == DIALOG_BATCH:
widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
widget.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
else:
self.combo = QComboBox()
self.combo.setEditable(True)
tables = self.dialog.getAvailableValuesOfType((QgsProcessingParameterVectorLayer, QgsProcessingParameterString),
(QgsProcessingOutputVectorLayer, QgsProcessingOutputMapLayer, QgsProcessingOutputFile, QgsProcessingOutputString))
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
self.combo.addItem(self.NOT_SELECTED, self.NOT_SET_OPTION)
for table in tables:
self.combo.addItem(self.dialog.resolveValueDescription(table), table)
widget = QWidget()
layout = QHBoxLayout()
layout.setMargin(0)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(6)
layout.addWidget(self.combo)
btn = QToolButton()
btn.setText('')
btn.setToolTip(self.tr("Select file"))
btn.clicked.connect(self.selectFile)
layout.addWidget(btn)
widget.setLayout(layout)
return widget
def selectFile(self):
filename, selected_filter = self.getFileName(self.combo.currentText())
if filename:
filename = dataobjects.getRasterSublayer(filename, self.parameterDefinition())
if isinstance(self.combo, QgsProcessingMapLayerComboBox):
self.combo.setValue(filename, self.context)
elif isinstance(self.combo, QgsMapLayerComboBox):
items = self.combo.additionalItems()
items.append(filename)
self.combo.setAdditionalItems(items)
self.combo.setCurrentIndex(self.combo.findText(filename))
else:
self.combo.setEditText(filename)
self.widgetValueHasChanged.emit(self)
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType == DIALOG_STANDARD:
if isinstance(value, str):
layer = QgsProject.instance().mapLayer(value)
if layer is not None:
value = layer
self.combo.setValue(value, self.context)
elif self.dialogType == DIALOG_BATCH:
return self.widget.setValue(value)
else:
self.setComboValue(value, combobox=self.combo)
self.widgetValueHasChanged.emit(self)
def value(self):
if self.dialogType == DIALOG_STANDARD:
return self.combo.value()
elif self.dialogType == DIALOG_BATCH:
return self.widget.getValue()
else:
def validator(v):
return bool(v) or self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional
return self.comboValue(validator, combobox=self.combo)
class TableFieldWidgetWrapper(WidgetWrapper):
NOT_SET = '[Not set]'
def __init__(self, param, dialog, row=0, col=0, **kwargs):
super().__init__(param, dialog, row, col, **kwargs)
"""
.. deprecated:: 3.12
Do not use, will be removed in QGIS 4.0
"""
from warnings import warn
warn("TableFieldWidgetWrapper is deprecated and will be removed in QGIS 4.0", DeprecationWarning)
self.context = dataobjects.createContext()
def createWidget(self):
self._layer = None
self.parent_file_based_layers = {}
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
if self.parameterDefinition().allowMultiple():
return MultipleInputPanel(options=[])
else:
widget = QgsFieldComboBox()
widget.setAllowEmptyFieldName(self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional)
widget.fieldChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
if self.parameterDefinition().dataType() == QgsProcessingParameterField.Numeric:
widget.setFilters(QgsFieldProxyModel.Numeric)
elif self.parameterDefinition().dataType() == QgsProcessingParameterField.String:
widget.setFilters(QgsFieldProxyModel.String)
elif self.parameterDefinition().dataType() == QgsProcessingParameterField.DateTime:
widget.setFilters(QgsFieldProxyModel.Date | QgsFieldProxyModel.Time)
return widget
else:
widget = QComboBox()
widget.setEditable(True)
fields = self.dialog.getAvailableValuesOfType([QgsProcessingParameterField, QgsProcessingParameterString],
[QgsProcessingOutputString])
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
widget.addItem(self.NOT_SET, self.NOT_SET_OPTION)
for f in fields:
widget.addItem(self.dialog.resolveValueDescription(f), f)
widget.setToolTip(
self.tr(
'Input parameter, or name of field (separate field names with ; for multiple field parameters)'))
return widget
def postInitialize(self, wrappers):
for wrapper in wrappers:
if wrapper.parameterDefinition().name() == self.parameterDefinition().parentLayerParameterName():
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
self.setLayer(wrapper.parameterValue())
wrapper.widgetValueHasChanged.connect(self.parentValueChanged)
break
def parentValueChanged(self, wrapper):
value = wrapper.parameterValue()
if isinstance(value, str) and value in self.parent_file_based_layers:
self.setLayer(self.parent_file_based_layers[value])
else:
self.setLayer(value)
if isinstance(value, str):
self.parent_file_based_layers[value] = self._layer
def setLayer(self, layer):
if isinstance(layer, QgsProcessingFeatureSourceDefinition):
layer, ok = layer.source.valueAsString(self.context.expressionContext())
if isinstance(layer, str):
if not layer: # empty string
layer = None
else:
layer = QgsProcessingUtils.mapLayerFromString(layer, self.context)
if not isinstance(layer, QgsVectorLayer) or not layer.isValid():
self.dialog.messageBar().clearWidgets()
self.dialog.messageBar().pushMessage("", self.tr("Could not load selected layer/table. Dependent field could not be populated"),
level=Qgis.Warning, duration=5)
return
self._layer = layer
self.refreshItems()
if self.parameterDefinition().allowMultiple() and self.parameterDefinition().defaultToAllFields():
self.setValue(self.getFields())
def refreshItems(self):
if self.parameterDefinition().allowMultiple():
self.widget.updateForOptions(self.getFields())
else:
self.widget.setLayer(self._layer)
self.widget.setCurrentIndex(0)
if self.parameterDefinition().defaultValue() is not None:
self.setValue(self.parameterDefinition().defaultValue())
def getFields(self):
if self._layer is None:
return []
fieldTypes = []
if self.parameterDefinition().dataType() == QgsProcessingParameterField.String:
fieldTypes = [QVariant.String]
elif self.parameterDefinition().dataType() == QgsProcessingParameterField.Numeric:
fieldTypes = [QVariant.Int, QVariant.Double, QVariant.LongLong,
QVariant.UInt, QVariant.ULongLong]
elif self.parameterDefinition().dataType() == QgsProcessingParameterField.DateTime:
fieldTypes = [QVariant.Date, QVariant.Time, QVariant.DateTime]
fieldNames = []
for field in self._layer.fields():
if not fieldTypes or field.type() in fieldTypes:
fieldNames.append(str(field.name()))
return fieldNames
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
if self.parameterDefinition().allowMultiple():
options = self.widget.options
selected = []
if isinstance(value, str):
value = value.split(';')
for v in value:
for i, opt in enumerate(options):
if opt == v:
selected.append(i)
# case insensitive check - only do if matching case value is not present
elif v not in options and opt.lower() == v.lower():
selected.append(i)
self.widget.setSelectedItems(selected)
else:
self.widget.setField(value)
else:
self.setComboValue(value)
def value(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
if self.parameterDefinition().allowMultiple():
return [self.widget.options[i] for i in self.widget.selectedoptions]
else:
f = self.widget.currentField()
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional and not f:
return None
return f
else:
def validator(v):
return bool(v) or self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional
return self.comboValue(validator)
class BandWidgetWrapper(WidgetWrapper):
NOT_SET = '[Not set]'
def __init__(self, param, dialog, row=0, col=0, **kwargs):
super().__init__(param, dialog, row, col, **kwargs)
self.context = dataobjects.createContext()
def createWidget(self):
self._layer = None
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
if self.parameterDefinition().allowMultiple():
return MultipleInputPanel(options=[])
widget = QgsRasterBandComboBox()
widget.setShowNotSetOption(self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional)
widget.bandChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
else:
widget = QComboBox()
widget.setEditable(True)
fields = self.dialog.getAvailableValuesOfType([QgsProcessingParameterBand, QgsProcessingParameterDistance, QgsProcessingParameterNumber],
[QgsProcessingOutputNumber])
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
widget.addItem(self.NOT_SET, self.NOT_SET_OPTION)
for f in fields:
widget.addItem(self.dialog.resolveValueDescription(f), f)
return widget
def postInitialize(self, wrappers):
for wrapper in wrappers:
if wrapper.parameterDefinition().name() == self.parameterDefinition().parentLayerParameterName():
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
self.setLayer(wrapper.parameterValue())
wrapper.widgetValueHasChanged.connect(self.parentValueChanged)
break
def parentValueChanged(self, wrapper):
self.setLayer(wrapper.parameterValue())
def setLayer(self, layer):
if isinstance(layer, QgsProcessingParameterRasterLayer):
layer, ok = layer.source.valueAsString(self.context.expressionContext())
if isinstance(layer, str):
layer = QgsProcessingUtils.mapLayerFromString(layer, self.context)
self._layer = layer
self.refreshItems()
def getBands(self):
bands = []
if self._layer is not None:
provider = self._layer.dataProvider()
for band in range(1, provider.bandCount() + 1):
name = provider.generateBandName(band)
interpretation = provider.colorInterpretationName(band)
if interpretation != "Undefined":
name = name + ' ({})'.format(interpretation)
bands.append(name)
return bands
def refreshItems(self):
if self.param.allowMultiple():
self.widget.setSelectedItems([])
self.widget.updateForOptions(self.getBands())
else:
self.widget.setLayer(self._layer)
self.widget.setCurrentIndex(0)
def setValue(self, value):
if value is None or value == NULL:
return
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
if self.parameterDefinition().allowMultiple():
options = self.widget.options
selected = []
if isinstance(value, str):
value = value.split(';')
for v in value:
for i, opt in enumerate(options):
match = re.search('(?:\\A|[^0-9]){}(?:\\Z|[^0-9]|)'.format(v), opt)
if match:
selected.append(i)
self.widget.setSelectedItems(selected)
else:
self.widget.setBand(value)
else:
self.setComboValue(value)
def value(self):
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
if self.parameterDefinition().allowMultiple():
bands = []
for i in self.widget.selectedoptions:
match = re.search('(?:\\A|[^0-9])([0-9]+)(?:\\Z|[^0-9]|)', self.widget.options[i])
if match:
bands.append(match.group(1))
return bands
else:
f = self.widget.currentBand()
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional and not f:
return None
return f
else:
def validator(v):
return bool(v) or self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional
return self.comboValue(validator)
class WidgetWrapperFactory:
"""
Factory for parameter widget wrappers
"""
@staticmethod
def create_wrapper(param, dialog, row=0, col=0):
wrapper_metadata = param.metadata().get('widget_wrapper', None)
# VERY messy logic here to avoid breaking 3.0 API which allowed metadata "widget_wrapper" value to be either
# a string name of a class OR a dict.
# TODO QGIS 4.0 -- require widget_wrapper to be a dict.
if wrapper_metadata and (not isinstance(wrapper_metadata, dict) or wrapper_metadata.get('class', None) is not None):
return WidgetWrapperFactory.create_wrapper_from_metadata(param, dialog, row, col)
else:
# try from c++ registry first
class_type = dialog.__class__.__name__
if class_type == 'ModelerParametersDialog':
wrapper = QgsGui.processingGuiRegistry().createModelerParameterWidget(dialog.model,
dialog.childId,
param,
dialog.context)
else:
dialog_type = dialogTypes.get(class_type,
QgsProcessingGui.Standard)
wrapper = QgsGui.processingGuiRegistry().createParameterWidgetWrapper(param, dialog_type)
if wrapper is not None:
wrapper.setDialog(dialog)
return wrapper
# fallback to Python registry
return WidgetWrapperFactory.create_wrapper_from_class(param, dialog, row, col)
@staticmethod
def create_wrapper_from_metadata(param, dialog, row=0, col=0):
wrapper = param.metadata().get('widget_wrapper', None)
params = {}
# wrapper metadata should be a dict with class key
if isinstance(wrapper, dict):
params = deepcopy(wrapper)
wrapper = params.pop('class')
# wrapper metadata should be a class path
if isinstance(wrapper, str):
tokens = wrapper.split('.')
mod = __import__('.'.join(tokens[:-1]), fromlist=[tokens[-1]])
wrapper = getattr(mod, tokens[-1])
# or directly a class object
if isclass(wrapper):
wrapper = wrapper(param, dialog, row, col, **params)
# or a wrapper instance
return wrapper
@staticmethod
def create_wrapper_from_class(param, dialog, row=0, col=0):
wrapper = None
if param.type() == 'boolean':
# deprecated, moved to c++
wrapper = BooleanWidgetWrapper
elif param.type() == 'crs':
# deprecated, moved to c++
wrapper = CrsWidgetWrapper
elif param.type() == 'extent':
wrapper = ExtentWidgetWrapper
elif param.type() == 'point':
# deprecated, moved to c++
wrapper = PointWidgetWrapper
elif param.type() == 'file':
# deprecated, moved to c++
wrapper = FileWidgetWrapper
elif param.type() == 'multilayer':
wrapper = MultipleLayerWidgetWrapper
elif param.type() == 'number':
# deprecated, moved to c++
wrapper = NumberWidgetWrapper
elif param.type() == 'distance':
# deprecated, moved to c++
wrapper = DistanceWidgetWrapper
elif param.type() == 'raster':
wrapper = RasterWidgetWrapper
elif param.type() == 'enum':
# deprecated, moved to c++
wrapper = EnumWidgetWrapper
elif param.type() == 'string':
# deprecated, moved to c++
wrapper = StringWidgetWrapper
elif param.type() == 'expression':
# deprecated, moved to c++
wrapper = ExpressionWidgetWrapper
elif param.type() == 'vector':
wrapper = VectorLayerWidgetWrapper
elif param.type() == 'field':
# deprecated, moved to c++
wrapper = TableFieldWidgetWrapper
elif param.type() == 'source':
wrapper = FeatureSourceWidgetWrapper
elif param.type() == 'band':
wrapper = BandWidgetWrapper
elif param.type() == 'layer':
wrapper = MapLayerWidgetWrapper
elif param.type() == 'range':
# deprecated, moved to c++
wrapper = RangeWidgetWrapper
elif param.type() == 'matrix':
# deprecated, moved to c++
wrapper = FixedTableWidgetWrapper
elif param.type() == 'mesh':
wrapper = MeshWidgetWrapper
else:
assert False, param.type()
return wrapper(param, dialog, row, col)