QGIS/python/plugins/processing/modeler/ModelerParametersDialog.py
2024-12-17 02:33:45 +01:00

687 lines
26 KiB
Python

"""
***************************************************************************
ModelerParametersDialog.py
---------------------
Date : August 2012
Copyright : (C) 2012 by Victor Olaya
Email : 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__ = "Victor Olaya"
__date__ = "August 2012"
__copyright__ = "(C) 2012, Victor Olaya"
import webbrowser
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import (
QDialog,
QDialogButtonBox,
QLabel,
QLineEdit,
QFrame,
QPushButton,
QSizePolicy,
QVBoxLayout,
QHBoxLayout,
QWidget,
QTabWidget,
QTextEdit,
)
from qgis.PyQt.QtGui import QColor
from qgis.core import (
Qgis,
QgsProject,
QgsProcessingParameterDefinition,
QgsProcessingModelOutput,
QgsProcessingModelChildAlgorithm,
QgsProcessingModelChildParameterSource,
QgsProcessingOutputDefinition,
)
from qgis.gui import (
QgsGui,
QgsMessageBar,
QgsScrollArea,
QgsFilterLineEdit,
QgsHelp,
QgsProcessingContextGenerator,
QgsProcessingModelerParameterWidget,
QgsProcessingParameterWidgetContext,
QgsPanelWidget,
QgsPanelWidgetStack,
QgsColorButton,
QgsModelChildDependenciesWidget,
)
from qgis.utils import iface
from processing.gui.wrappers import WidgetWrapperFactory
from processing.gui.wrappers import InvalidParameterValue
from processing.tools.dataobjects import createContext
from processing.gui.wrappers import WidgetWrapper
class ModelerParametersDialog(QDialog):
def __init__(self, alg, model, algName=None, configuration=None):
super().__init__()
self.setObjectName("ModelerParametersDialog")
self.setModal(True)
if iface is not None:
self.setStyleSheet(iface.mainWindow().styleSheet())
# dammit this is SUCH as mess... stupid stable API
self._alg = alg # The algorithm to define in this dialog. It is an instance of QgsProcessingAlgorithm
self.model = model # The model this algorithm is going to be added to. It is an instance of QgsProcessingModelAlgorithm
self.childId = algName # The name of the algorithm in the model, in case we are editing it and not defining it for the first time
self.configuration = configuration
self.context = createContext()
self.setWindowTitle(
" - ".join([self._alg.group(), self._alg.displayName()])
if self._alg.group()
else self._alg.displayName()
)
self.widget = ModelerParametersWidget(
alg, model, algName, configuration, context=self.context, dialog=self
)
QgsGui.enableAutoGeometryRestore(self)
self.buttonBox = QDialogButtonBox()
self.buttonBox.setOrientation(Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(
QDialogButtonBox.StandardButton.Cancel
| QDialogButtonBox.StandardButton.Ok
| QDialogButtonBox.StandardButton.Help
)
self.buttonBox.accepted.connect(self.okPressed)
self.buttonBox.rejected.connect(self.reject)
self.buttonBox.helpRequested.connect(self.openHelp)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.widget, 1)
mainLayout.addWidget(self.buttonBox)
self.setLayout(mainLayout)
def algorithm(self):
return self._alg
def setComments(self, text):
self.widget.setComments(text)
def comments(self):
return self.widget.comments()
def setCommentColor(self, color):
self.widget.setCommentColor(color)
def commentColor(self):
return self.widget.commentColor()
def switchToCommentTab(self):
self.widget.switchToCommentTab()
def getAvailableValuesOfType(self, paramType, outTypes=[], dataTypes=[]):
# upgrade paramType to list
if paramType is None:
paramType = []
elif not isinstance(paramType, (tuple, list)):
paramType = [paramType]
if outTypes is None:
outTypes = []
elif not isinstance(outTypes, (tuple, list)):
outTypes = [outTypes]
return self.model.availableSourcesForChild(
self.childId,
[
p.typeName()
for p in paramType
if issubclass(p, QgsProcessingParameterDefinition)
],
[
o.typeName()
for o in outTypes
if issubclass(o, QgsProcessingOutputDefinition)
],
dataTypes,
)
def resolveValueDescription(self, value):
if isinstance(value, QgsProcessingModelChildParameterSource):
if value.source() == Qgis.ProcessingModelChildParameterSource.StaticValue:
return value.staticValue()
elif (
value.source()
== Qgis.ProcessingModelChildParameterSource.ModelParameter
):
return self.model.parameterDefinition(
value.parameterName()
).description()
elif value.source() == Qgis.ProcessingModelChildParameterSource.ChildOutput:
alg = self.model.childAlgorithm(value.outputChildId())
output_name = (
alg.algorithm().outputDefinition(value.outputName()).description()
)
# see if this output has been named by the model designer -- if so, we use that friendly name
for name, output in alg.modelOutputs().items():
if output.childOutputName() == value.outputName():
output_name = name
break
return self.tr("'{0}' from algorithm '{1}'").format(
output_name, alg.description()
)
return value
def setPreviousValues(self):
self.widget.setPreviousValues()
def createAlgorithm(self):
return self.widget.createAlgorithm()
def okPressed(self):
if self.createAlgorithm() is not None:
self.accept()
def openHelp(self):
algHelp = self.widget.algorithm().helpUrl()
if not algHelp:
algHelp = QgsHelp.helpUrl(
"processing_algs/{}/{}.html#{}".format(
self.widget.algorithm().provider().helpId(),
self.algorithm().groupId(),
f"{self.algorithm().provider().helpId()}{self.algorithm().name().replace('_', '-')}",
)
).toString()
if algHelp not in [None, ""]:
webbrowser.open(algHelp)
class ModelerParametersPanelWidget(QgsPanelWidget):
def __init__(
self, alg, model, algName=None, configuration=None, dialog=None, context=None
):
super().__init__()
self._alg = alg # The algorithm to define in this dialog. It is an instance of QgsProcessingAlgorithm
self.model = model # The model this algorithm is going to be added to. It is an instance of QgsProcessingModelAlgorithm
self.childId = algName # The name of the algorithm in the model, in case we are editing it and not defining it for the first time
self.configuration = configuration
self.context = context
self.dialog = dialog
self.widget_labels = {}
self.previous_output_definitions = {}
class ContextGenerator(QgsProcessingContextGenerator):
def __init__(self, context):
super().__init__()
self.processing_context = context
def processingContext(self):
return self.processing_context
self.context_generator = ContextGenerator(self.context)
self.setupUi()
self.params = None
def algorithm(self):
return self._alg
def setupUi(self):
self.showAdvanced = False
self.wrappers = {}
self.algorithmItem = None
self.mainLayout = QVBoxLayout()
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout = QVBoxLayout()
self.bar = QgsMessageBar()
self.bar.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
self.verticalLayout.addWidget(self.bar)
hLayout = QHBoxLayout()
hLayout.setContentsMargins(0, 0, 0, 0)
descriptionLabel = QLabel(self.tr("Description"))
self.descriptionBox = QLineEdit()
self.descriptionBox.setText(self._alg.displayName())
hLayout.addWidget(descriptionLabel)
hLayout.addWidget(self.descriptionBox)
self.verticalLayout.addLayout(hLayout)
line = QFrame()
line.setFrameShape(QFrame.Shape.HLine)
line.setFrameShadow(QFrame.Shadow.Sunken)
self.verticalLayout.addWidget(line)
widget_context = QgsProcessingParameterWidgetContext()
widget_context.setProject(QgsProject.instance())
if iface is not None:
widget_context.setMapCanvas(iface.mapCanvas())
widget_context.setActiveLayer(iface.activeLayer())
widget_context.setModel(self.model)
widget_context.setModelChildAlgorithmId(self.childId)
self.algorithmItem = (
QgsGui.instance()
.processingGuiRegistry()
.algorithmConfigurationWidget(self._alg)
)
if self.algorithmItem:
self.algorithmItem.setWidgetContext(widget_context)
self.algorithmItem.registerProcessingContextGenerator(
self.context_generator
)
if self.configuration:
self.algorithmItem.setConfiguration(self.configuration)
self.verticalLayout.addWidget(self.algorithmItem)
for param in self._alg.parameterDefinitions():
if param.flags() & QgsProcessingParameterDefinition.Flag.FlagAdvanced:
self.advancedButton = QPushButton()
self.advancedButton.setText(self.tr("Show advanced parameters"))
self.advancedButton.clicked.connect(self.showAdvancedParametersClicked)
advancedButtonHLayout = QHBoxLayout()
advancedButtonHLayout.addWidget(self.advancedButton)
advancedButtonHLayout.addStretch()
self.verticalLayout.addLayout(advancedButtonHLayout)
break
for param in self._alg.parameterDefinitions():
if (
param.isDestination()
or param.flags() & QgsProcessingParameterDefinition.Flag.FlagHidden
):
continue
wrapper = WidgetWrapperFactory.create_wrapper(param, self.dialog)
self.wrappers[param.name()] = wrapper
wrapper.setWidgetContext(widget_context)
wrapper.registerProcessingContextGenerator(self.context_generator)
if issubclass(wrapper.__class__, QgsProcessingModelerParameterWidget):
widget = wrapper
else:
widget = wrapper.widget
if widget is not None:
if issubclass(wrapper.__class__, QgsProcessingModelerParameterWidget):
label = wrapper.createLabel()
else:
tooltip = param.description()
widget.setToolTip(tooltip)
label = wrapper.label
self.widget_labels[param.name()] = label
if param.flags() & QgsProcessingParameterDefinition.Flag.FlagAdvanced:
label.setVisible(self.showAdvanced)
widget.setVisible(self.showAdvanced)
self.verticalLayout.addWidget(label)
self.verticalLayout.addWidget(widget)
for output in self._alg.destinationParameterDefinitions():
if output.flags() & QgsProcessingParameterDefinition.Flag.FlagHidden:
continue
widget = QgsGui.processingGuiRegistry().createModelerParameterWidget(
self.model, self.childId, output, self.context
)
widget.setDialog(self.dialog)
widget.setWidgetContext(widget_context)
widget.registerProcessingContextGenerator(self.context_generator)
self.wrappers[output.name()] = widget
item = QgsFilterLineEdit()
if hasattr(item, "setPlaceholderText"):
item.setPlaceholderText(
self.tr("[Enter name if this is a final result]")
)
label = widget.createLabel()
if label is not None:
self.verticalLayout.addWidget(label)
self.verticalLayout.addWidget(widget)
label = QLabel(" ")
self.verticalLayout.addWidget(label)
label = QLabel(self.tr("Dependencies"))
self.dependencies_panel = QgsModelChildDependenciesWidget(
self, self.model, self.childId
)
self.verticalLayout.addWidget(label)
self.verticalLayout.addWidget(self.dependencies_panel)
self.verticalLayout.addStretch(1000)
self.setPreviousValues()
self.verticalLayout2 = QVBoxLayout()
self.verticalLayout2.setSpacing(2)
self.verticalLayout2.setMargin(0)
self.paramPanel = QWidget()
self.paramPanel.setLayout(self.verticalLayout)
self.scrollArea = QgsScrollArea()
self.scrollArea.setWidget(self.paramPanel)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setFrameStyle(QFrame.Shape.NoFrame)
self.verticalLayout2.addWidget(self.scrollArea)
w = QWidget()
w.setLayout(self.verticalLayout2)
self.mainLayout.addWidget(w)
self.setLayout(self.mainLayout)
def showAdvancedParametersClicked(self):
self.showAdvanced = not self.showAdvanced
if self.showAdvanced:
self.advancedButton.setText(self.tr("Hide advanced parameters"))
else:
self.advancedButton.setText(self.tr("Show advanced parameters"))
for param in self._alg.parameterDefinitions():
if param.flags() & QgsProcessingParameterDefinition.Flag.FlagAdvanced:
wrapper = self.wrappers[param.name()]
if issubclass(wrapper.__class__, QgsProcessingModelerParameterWidget):
wrapper.setVisible(self.showAdvanced)
else:
wrapper.widget.setVisible(self.showAdvanced)
self.widget_labels[param.name()].setVisible(self.showAdvanced)
def setPreviousValues(self):
if self.childId is not None:
alg = self.model.childAlgorithm(self.childId)
self.descriptionBox.setText(alg.description())
for param in alg.algorithm().parameterDefinitions():
if (
param.isDestination()
or param.flags() & QgsProcessingParameterDefinition.Flag.FlagHidden
):
continue
value = None
if param.name() in alg.parameterSources():
value = alg.parameterSources()[param.name()]
if isinstance(value, list) and len(value) == 1:
value = value[0]
elif isinstance(value, list) and len(value) == 0:
value = None
wrapper = self.wrappers[param.name()]
if issubclass(wrapper.__class__, QgsProcessingModelerParameterWidget):
if value is None:
value = QgsProcessingModelChildParameterSource.fromStaticValue(
param.defaultValue()
)
wrapper.setWidgetValue(value)
else:
if value is None:
value = param.defaultValue()
if (
isinstance(value, QgsProcessingModelChildParameterSource)
and value.source()
== Qgis.ProcessingModelChildParameterSource.StaticValue
):
value = value.staticValue()
wrapper.setValue(value)
for output in self.algorithm().destinationParameterDefinitions():
if output.flags() & QgsProcessingParameterDefinition.Flag.FlagHidden:
continue
model_output_name = None
for name, out in alg.modelOutputs().items():
if (
out.childId() == self.childId
and out.childOutputName() == output.name()
):
# this destination parameter is linked to a model output
model_output_name = out.name()
self.previous_output_definitions[output.name()] = out
break
value = None
if (
model_output_name is None
and output.name() in alg.parameterSources()
):
value = alg.parameterSources()[output.name()]
if isinstance(value, list) and len(value) == 1:
value = value[0]
elif isinstance(value, list) and len(value) == 0:
value = None
wrapper = self.wrappers[output.name()]
if model_output_name is not None:
wrapper.setToModelOutput(model_output_name)
elif value is not None or output.defaultValue() is not None:
if value is None:
value = QgsProcessingModelChildParameterSource.fromStaticValue(
output.defaultValue()
)
wrapper.setWidgetValue(value)
self.dependencies_panel.setValue(alg.dependencies())
def createAlgorithm(self):
alg = QgsProcessingModelChildAlgorithm(self._alg.id())
if not self.childId:
alg.generateChildId(self.model)
else:
alg.setChildId(self.childId)
alg.setDescription(self.descriptionBox.text())
if self.algorithmItem:
alg.setConfiguration(self.algorithmItem.configuration())
self._alg = alg.algorithm().create(self.algorithmItem.configuration())
for param in self._alg.parameterDefinitions():
if (
param.isDestination()
or param.flags() & QgsProcessingParameterDefinition.Flag.FlagHidden
):
continue
try:
wrapper = self.wrappers[param.name()]
if issubclass(wrapper.__class__, WidgetWrapper):
val = wrapper.value()
elif issubclass(wrapper.__class__, QgsProcessingModelerParameterWidget):
val = wrapper.value()
else:
val = wrapper.parameterValue()
except InvalidParameterValue:
val = None
if isinstance(val, QgsProcessingModelChildParameterSource):
val = [val]
elif not (
isinstance(val, list)
and all(
[
isinstance(subval, QgsProcessingModelChildParameterSource)
for subval in val
]
)
):
val = [QgsProcessingModelChildParameterSource.fromStaticValue(val)]
valid = True
for subval in val:
if (
isinstance(subval, QgsProcessingModelChildParameterSource)
and subval.source()
== Qgis.ProcessingModelChildParameterSource.StaticValue
and not param.checkValueIsAcceptable(subval.staticValue())
) or (
subval is None
and not param.flags()
& QgsProcessingParameterDefinition.Flag.FlagOptional
):
valid = False
break
if valid:
alg.addParameterSources(param.name(), val)
outputs = {}
for output in self._alg.destinationParameterDefinitions():
if not output.flags() & QgsProcessingParameterDefinition.Flag.FlagHidden:
wrapper = self.wrappers[output.name()]
if wrapper.isModelOutput():
name = wrapper.modelOutputName()
if name:
# if there was a previous output definition already for this output, we start with it,
# otherwise we'll lose any existing output comments, coloring, position, etc
model_output = self.previous_output_definitions.get(
output.name(), QgsProcessingModelOutput(name, name)
)
model_output.setDescription(name)
model_output.setChildId(alg.childId())
model_output.setChildOutputName(output.name())
outputs[name] = model_output
else:
val = wrapper.value()
if isinstance(val, QgsProcessingModelChildParameterSource):
val = [val]
alg.addParameterSources(output.name(), val)
if output.flags() & QgsProcessingParameterDefinition.Flag.FlagIsModelOutput:
if output.name() not in outputs:
model_output = QgsProcessingModelOutput(
output.name(), output.name()
)
model_output.setChildId(alg.childId())
model_output.setChildOutputName(output.name())
outputs[output.name()] = model_output
alg.setModelOutputs(outputs)
alg.setDependencies(self.dependencies_panel.value())
return alg
class ModelerParametersWidget(QWidget):
def __init__(
self, alg, model, algName=None, configuration=None, dialog=None, context=None
):
super().__init__()
self._alg = alg # The algorithm to define in this dialog. It is an instance of QgsProcessingAlgorithm
self.model = model # The model this algorithm is going to be added to. It is an instance of QgsProcessingModelAlgorithm
self.childId = algName # The name of the algorithm in the model, in case we are editing it and not defining it for the first time
self.configuration = configuration
self.context = context
self.dialog = dialog
self.widget = ModelerParametersPanelWidget(
alg, model, algName, configuration, dialog, context
)
class ContextGenerator(QgsProcessingContextGenerator):
def __init__(self, context):
super().__init__()
self.processing_context = context
def processingContext(self):
return self.processing_context
self.context_generator = ContextGenerator(self.context)
self.setupUi()
self.params = None
def algorithm(self):
return self._alg
def switchToCommentTab(self):
self.tab.setCurrentIndex(1)
self.commentEdit.setFocus()
self.commentEdit.selectAll()
def setupUi(self):
self.mainLayout = QVBoxLayout()
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.tab = QTabWidget()
self.mainLayout.addWidget(self.tab)
self.param_widget = QgsPanelWidgetStack()
self.widget.setDockMode(True)
self.param_widget.setMainPanel(self.widget)
self.tab.addTab(self.param_widget, self.tr("Properties"))
self.commentLayout = QVBoxLayout()
self.commentEdit = QTextEdit()
self.commentEdit.setAcceptRichText(False)
self.commentLayout.addWidget(self.commentEdit, 1)
hl = QHBoxLayout()
hl.setContentsMargins(0, 0, 0, 0)
hl.addWidget(QLabel(self.tr("Color")))
self.comment_color_button = QgsColorButton()
self.comment_color_button.setAllowOpacity(True)
self.comment_color_button.setWindowTitle(self.tr("Comment Color"))
self.comment_color_button.setShowNull(True, self.tr("Default"))
hl.addWidget(self.comment_color_button)
self.commentLayout.addLayout(hl)
w2 = QWidget()
w2.setLayout(self.commentLayout)
self.tab.addTab(w2, self.tr("Comments"))
self.setLayout(self.mainLayout)
def setComments(self, text):
self.commentEdit.setPlainText(text)
def comments(self):
return self.commentEdit.toPlainText()
def setCommentColor(self, color):
if color.isValid():
self.comment_color_button.setColor(color)
else:
self.comment_color_button.setToNull()
def commentColor(self):
return (
self.comment_color_button.color()
if not self.comment_color_button.isNull()
else QColor()
)
def setPreviousValues(self):
self.widget.setPreviousValues()
def createAlgorithm(self):
alg = self.widget.createAlgorithm()
if alg:
alg.comment().setDescription(self.comments())
alg.comment().setColor(self.commentColor())
return alg