Move parameter retrieval logic from dialog to ParametersPanel

This is cleaner, because the panel owns all the wrappers required
to evaluate the parameter values
This commit is contained in:
Nyall Dawson 2020-04-04 10:26:06 +10:00
parent a80bdc47e5
commit d69519a91e
2 changed files with 98 additions and 96 deletions

View File

@ -21,33 +21,21 @@ __author__ = 'Victor Olaya'
__date__ = 'August 2012' __date__ = 'August 2012'
__copyright__ = '(C) 2012, Victor Olaya' __copyright__ = '(C) 2012, Victor Olaya'
import os
from pprint import pformat from pprint import pformat
import time import time
from qgis.PyQt.QtCore import QCoreApplication, Qt from qgis.PyQt.QtCore import QCoreApplication
from qgis.PyQt.QtWidgets import QMessageBox, QPushButton, QSizePolicy, QDialogButtonBox from qgis.PyQt.QtWidgets import QMessageBox, QPushButton, QDialogButtonBox
from qgis.PyQt.QtGui import QColor, QPalette from qgis.PyQt.QtGui import QColor, QPalette
from qgis.core import (Qgis, from qgis.core import (Qgis,
QgsProject,
QgsApplication, QgsApplication,
QgsProcessingUtils,
QgsProcessingParameterDefinition,
QgsProcessingAlgRunnerTask, QgsProcessingAlgRunnerTask,
QgsProcessingOutputHtml, QgsProcessingOutputHtml,
QgsProcessingParameterVectorDestination,
QgsProcessingOutputLayerDefinition,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterRasterDestination,
QgsProcessingAlgorithm, QgsProcessingAlgorithm,
QgsProcessingParameters,
QgsProxyProgressTask, QgsProxyProgressTask,
QgsTaskManager,
QgsProcessingFeatureSourceDefinition) QgsProcessingFeatureSourceDefinition)
from qgis.gui import (QgsGui, from qgis.gui import (QgsGui,
QgsMessageBar,
QgsProcessingLayerOutputDestinationWidget,
QgsProcessingAlgorithmDialogBase) QgsProcessingAlgorithmDialogBase)
from qgis.utils import iface from qgis.utils import iface
@ -59,7 +47,6 @@ from processing.gui.BatchAlgorithmDialog import BatchAlgorithmDialog
from processing.gui.AlgorithmDialogBase import AlgorithmDialogBase from processing.gui.AlgorithmDialogBase import AlgorithmDialogBase
from processing.gui.AlgorithmExecutor import executeIterating, execute, execute_in_place from processing.gui.AlgorithmExecutor import executeIterating, execute, execute_in_place
from processing.gui.Postprocessing import handleAlgorithmResults from processing.gui.Postprocessing import handleAlgorithmResults
from processing.gui.wrappers import WidgetWrapper
from processing.tools import dataobjects from processing.tools import dataobjects
@ -82,20 +69,23 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
if not self.in_place: if not self.in_place:
self.runAsBatchButton = QPushButton(QCoreApplication.translate("AlgorithmDialog", "Run as Batch Process…")) self.runAsBatchButton = QPushButton(QCoreApplication.translate("AlgorithmDialog", "Run as Batch Process…"))
self.runAsBatchButton.clicked.connect(self.runAsBatch) self.runAsBatchButton.clicked.connect(self.runAsBatch)
self.buttonBox().addButton(self.runAsBatchButton, QDialogButtonBox.ResetRole) # reset role to ensure left alignment self.buttonBox().addButton(self.runAsBatchButton,
QDialogButtonBox.ResetRole) # reset role to ensure left alignment
else: else:
self.active_layer = iface.activeLayer() self.active_layer = iface.activeLayer()
self.runAsBatchButton = None self.runAsBatchButton = None
has_selection = self.active_layer and (self.active_layer.selectedFeatureCount() > 0) has_selection = self.active_layer and (self.active_layer.selectedFeatureCount() > 0)
self.buttonBox().button(QDialogButtonBox.Ok).setText(QCoreApplication.translate("AlgorithmDialog", "Modify Selected Features") self.buttonBox().button(QDialogButtonBox.Ok).setText(
QCoreApplication.translate("AlgorithmDialog", "Modify Selected Features")
if has_selection else QCoreApplication.translate("AlgorithmDialog", "Modify All Features")) if has_selection else QCoreApplication.translate("AlgorithmDialog", "Modify All Features"))
self.buttonBox().button(QDialogButtonBox.Close).setText(QCoreApplication.translate("AlgorithmDialog", "Cancel")) self.buttonBox().button(QDialogButtonBox.Close).setText(
QCoreApplication.translate("AlgorithmDialog", "Cancel"))
self.setWindowTitle(self.windowTitle() + ' | ' + self.active_layer.name()) self.setWindowTitle(self.windowTitle() + ' | ' + self.active_layer.name())
self.updateRunButtonVisibility() self.updateRunButtonVisibility()
def getParametersPanel(self, alg, parent): def getParametersPanel(self, alg, parent):
panel = ParametersPanel(parent, alg, self.in_place, parameters_generator=self) panel = ParametersPanel(parent, alg, self.in_place)
return panel return panel
def runAsBatch(self): def runAsBatch(self):
@ -116,70 +106,10 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
self.mainWidget().setParameters(parameters) self.mainWidget().setParameters(parameters)
def createProcessingParameters(self): def createProcessingParameters(self):
parameters = {}
if self.mainWidget() is None: if self.mainWidget() is None:
return parameters return {}
for param in self.algorithm().parameterDefinitions():
if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
if not param.isDestination():
if self.in_place and param.name() == 'INPUT':
parameters[param.name()] = self.active_layer
continue
try:
wrapper = self.mainWidget().wrappers[param.name()]
except KeyError:
continue
# For compatibility with 3.x API, we need to check whether the wrapper is
# the deprecated WidgetWrapper class. If not, it's the newer
# QgsAbstractProcessingParameterWidgetWrapper class
# TODO QGIS 4.0 - remove
if issubclass(wrapper.__class__, WidgetWrapper):
widget = wrapper.widget
else: else:
widget = wrapper.wrappedWidget() return self.mainWidget().createProcessingParameters()
if widget is None:
continue
value = wrapper.parameterValue()
parameters[param.name()] = value
if not param.checkValueIsAcceptable(value):
raise AlgorithmDialogBase.InvalidParameterValue(param, widget)
else:
if self.in_place and param.name() == 'OUTPUT':
parameters[param.name()] = 'memory:'
continue
try:
wrapper = self.mainWidget().wrappers[param.name()]
except KeyError:
continue
widget = wrapper.wrappedWidget()
value = wrapper.parameterValue()
dest_project = None
if wrapper.customProperties().get('OPEN_AFTER_RUNNING'):
dest_project = QgsProject.instance()
if value and isinstance(value, QgsProcessingOutputLayerDefinition):
value.destinationProject = dest_project
if value:
parameters[param.name()] = value
if param.isDestination():
context = dataobjects.createContext()
ok, error = self.algorithm().provider().isSupportedOutputValue(value, param, context)
if not ok:
raise AlgorithmDialogBase.InvalidOutputExtension(widget, error)
return self.algorithm().preprocessParameters(parameters)
def runAlgorithm(self): def runAlgorithm(self):
self.feedback = self.createFeedback() self.feedback = self.createFeedback()
@ -211,7 +141,8 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
self.iterateParam = None self.iterateParam = None
for param in self.algorithm().parameterDefinitions(): for param in self.algorithm().parameterDefinitions():
if isinstance(parameters.get(param.name(), None), QgsProcessingFeatureSourceDefinition) and parameters[param.name()].flags & QgsProcessingFeatureSourceDefinition.FlagCreateIndividualOutputPerInputFeature: if isinstance(parameters.get(param.name(), None), QgsProcessingFeatureSourceDefinition) and parameters[
param.name()].flags & QgsProcessingFeatureSourceDefinition.FlagCreateIndividualOutputPerInputFeature:
self.iterateParam = param.name() self.iterateParam = param.name()
break break
@ -223,12 +154,14 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
self.setProgressText(QCoreApplication.translate('AlgorithmDialog', 'Processing algorithm…')) self.setProgressText(QCoreApplication.translate('AlgorithmDialog', 'Processing algorithm…'))
self.setInfo( self.setInfo(
QCoreApplication.translate('AlgorithmDialog', '<b>Algorithm \'{0}\' starting&hellip;</b>').format(self.algorithm().displayName()), escapeHtml=False) QCoreApplication.translate('AlgorithmDialog', '<b>Algorithm \'{0}\' starting&hellip;</b>').format(
self.algorithm().displayName()), escapeHtml=False)
self.feedback.pushInfo(self.tr('Input parameters:')) self.feedback.pushInfo(self.tr('Input parameters:'))
display_params = [] display_params = []
for k, v in parameters.items(): for k, v in parameters.items():
display_params.append("'" + k + "' : " + self.algorithm().parameterDefinition(k).valueAsPythonString(v, self.context)) display_params.append(
"'" + k + "' : " + self.algorithm().parameterDefinition(k).valueAsPythonString(v, self.context))
self.feedback.pushCommandInfo('{ ' + ', '.join(display_params) + ' }') self.feedback.pushCommandInfo('{ ' + ', '.join(display_params) + ' }')
self.feedback.pushInfo('') self.feedback.pushInfo('')
start_time = time.time() start_time = time.time()
@ -259,7 +192,8 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
def on_complete(ok, results): def on_complete(ok, results):
if ok: if ok:
self.feedback.pushInfo(self.tr('Execution completed in {0:0.2f} seconds').format(time.time() - start_time)) self.feedback.pushInfo(
self.tr('Execution completed in {0:0.2f} seconds').format(time.time() - start_time))
self.feedback.pushInfo(self.tr('Results:')) self.feedback.pushInfo(self.tr('Results:'))
r = {k: v for k, v in results.items() if k not in ('CHILD_RESULTS', 'CHILD_INPUTS')} r = {k: v for k, v in results.items() if k not in ('CHILD_RESULTS', 'CHILD_INPUTS')}
self.feedback.pushCommandInfo(pformat(r)) self.feedback.pushCommandInfo(pformat(r))
@ -291,7 +225,9 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
task.executed.connect(on_complete) task.executed.connect(on_complete)
self.setCurrentTask(task) self.setCurrentTask(task)
else: else:
self.proxy_progress = QgsProxyProgressTask(QCoreApplication.translate("AlgorithmDialog", "Executing “{}").format(self.algorithm().displayName())) self.proxy_progress = QgsProxyProgressTask(
QCoreApplication.translate("AlgorithmDialog", "Executing “{}").format(
self.algorithm().displayName()))
QgsApplication.taskManager().addTask(self.proxy_progress) QgsApplication.taskManager().addTask(self.proxy_progress)
self.feedback.progressChanged.connect(self.proxy_progress.setProxyProgress) self.feedback.progressChanged.connect(self.proxy_progress.setProxyProgress)
self.feedback_dialog = self.createProgressDialog() self.feedback_dialog = self.createProgressDialog()
@ -314,7 +250,8 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
except: except:
pass pass
self.messageBar().clearWidgets() self.messageBar().clearWidgets()
self.messageBar().pushMessage("", self.tr("Wrong or missing parameter value: {0}").format(e.parameter.description()), self.messageBar().pushMessage("", self.tr("Wrong or missing parameter value: {0}").format(
e.parameter.description()),
level=Qgis.Warning, duration=5) level=Qgis.Warning, duration=5)
except AlgorithmDialogBase.InvalidOutputExtension as e: except AlgorithmDialogBase.InvalidOutputExtension as e:
try: try:
@ -337,7 +274,8 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
# add html results to results dock # add html results to results dock
for out in self.algorithm().outputDefinitions(): for out in self.algorithm().outputDefinitions():
if isinstance(out, QgsProcessingOutputHtml) and out.name() in result and result[out.name()]: if isinstance(out, QgsProcessingOutputHtml) and out.name() in result and result[out.name()]:
resultsList.addResult(icon=self.algorithm().icon(), name=out.description(), timestamp=time.localtime(), resultsList.addResult(icon=self.algorithm().icon(), name=out.description(),
timestamp=time.localtime(),
result=result[out.name()]) result=result[out.name()])
if not handleAlgorithmResults(self.algorithm(), context, feedback, not keepOpen, result): if not handleAlgorithmResults(self.algorithm(), context, feedback, not keepOpen, result):
self.resetGui() self.resetGui()

View File

@ -28,7 +28,8 @@ __copyright__ = '(C) 2012, Victor Olaya'
from qgis.core import (QgsProcessingParameterDefinition, from qgis.core import (QgsProcessingParameterDefinition,
QgsProcessingParameterExtent, QgsProcessingParameterExtent,
QgsProject, QgsProject,
QgsProcessingModelAlgorithm) QgsProcessingModelAlgorithm,
QgsProcessingOutputLayerDefinition)
from qgis.gui import (QgsProcessingContextGenerator, from qgis.gui import (QgsProcessingContextGenerator,
QgsProcessingParameterWidgetContext, QgsProcessingParameterWidgetContext,
QgsProcessingParametersWidget, QgsProcessingParametersWidget,
@ -38,19 +39,19 @@ from qgis.gui import (QgsProcessingContextGenerator,
from qgis.utils import iface from qgis.utils import iface
from processing.gui.wrappers import WidgetWrapperFactory, WidgetWrapper from processing.gui.wrappers import WidgetWrapperFactory, WidgetWrapper
from processing.gui.AlgorithmDialogBase import AlgorithmDialogBase
from processing.tools.dataobjects import createContext from processing.tools.dataobjects import createContext
class ParametersPanel(QgsProcessingParametersWidget, QgsProcessingParametersGenerator): class ParametersPanel(QgsProcessingParametersWidget, QgsProcessingParametersGenerator):
def __init__(self, parent, alg, in_place=False, parameters_generator=None): def __init__(self, parent, alg, in_place=False):
super().__init__(alg, parent) super().__init__(alg, parent)
self.in_place = in_place self.in_place = in_place
self.wrappers = {} self.wrappers = {}
self.processing_context = createContext() self.processing_context = createContext()
self.processing_parameters_generator = parameters_generator
class ContextGenerator(QgsProcessingContextGenerator): class ContextGenerator(QgsProcessingContextGenerator):
@ -100,7 +101,7 @@ class ParametersPanel(QgsProcessingParametersWidget, QgsProcessingParametersGene
wrapper = WidgetWrapperFactory.create_wrapper(param, self.parent()) wrapper = WidgetWrapperFactory.create_wrapper(param, self.parent())
wrapper.setWidgetContext(widget_context) wrapper.setWidgetContext(widget_context)
wrapper.registerProcessingContextGenerator(self.context_generator) wrapper.registerProcessingContextGenerator(self.context_generator)
wrapper.registerProcessingParametersGenerator(self.processing_parameters_generator) wrapper.registerProcessingParametersGenerator(self)
self.wrappers[param.name()] = wrapper self.wrappers[param.name()] = wrapper
# For compatibility with 3.x API, we need to check whether the wrapper is # For compatibility with 3.x API, we need to check whether the wrapper is
@ -152,7 +153,7 @@ class ParametersPanel(QgsProcessingParametersWidget, QgsProcessingParametersGene
wrapper = QgsGui.processingGuiRegistry().createParameterWidgetWrapper(output, QgsProcessingGui.Standard) wrapper = QgsGui.processingGuiRegistry().createParameterWidgetWrapper(output, QgsProcessingGui.Standard)
wrapper.setWidgetContext(widget_context) wrapper.setWidgetContext(widget_context)
wrapper.registerProcessingContextGenerator(self.context_generator) wrapper.registerProcessingContextGenerator(self.context_generator)
wrapper.registerProcessingParametersGenerator(self.processing_parameters_generator) wrapper.registerProcessingParametersGenerator(self)
self.wrappers[output.name()] = wrapper self.wrappers[output.name()] = wrapper
label = wrapper.createWrappedLabel() label = wrapper.createWrappedLabel()
@ -185,6 +186,69 @@ class ParametersPanel(QgsProcessingParametersWidget, QgsProcessingParametersGene
for wrapper in list(self.wrappers.values()): for wrapper in list(self.wrappers.values()):
wrapper.postInitialize(list(self.wrappers.values())) wrapper.postInitialize(list(self.wrappers.values()))
def createProcessingParameters(self):
parameters = {}
for param in self.algorithm().parameterDefinitions():
if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
if not param.isDestination():
if self.in_place and param.name() == 'INPUT':
parameters[param.name()] = self.active_layer
continue
try:
wrapper = self.wrappers[param.name()]
except KeyError:
continue
# For compatibility with 3.x API, we need to check whether the wrapper is
# the deprecated WidgetWrapper class. If not, it's the newer
# QgsAbstractProcessingParameterWidgetWrapper class
# TODO QGIS 4.0 - remove
if issubclass(wrapper.__class__, WidgetWrapper):
widget = wrapper.widget
else:
widget = wrapper.wrappedWidget()
if widget is None:
continue
value = wrapper.parameterValue()
parameters[param.name()] = value
if not param.checkValueIsAcceptable(value):
raise AlgorithmDialogBase.InvalidParameterValue(param, widget)
else:
if self.in_place and param.name() == 'OUTPUT':
parameters[param.name()] = 'memory:'
continue
try:
wrapper = self.wrappers[param.name()]
except KeyError:
continue
widget = wrapper.wrappedWidget()
value = wrapper.parameterValue()
dest_project = None
if wrapper.customProperties().get('OPEN_AFTER_RUNNING'):
dest_project = QgsProject.instance()
if value and isinstance(value, QgsProcessingOutputLayerDefinition):
value.destinationProject = dest_project
if value:
parameters[param.name()] = value
if param.isDestination():
context = createContext()
ok, error = self.algorithm().provider().isSupportedOutputValue(value, param, context)
if not ok:
raise AlgorithmDialogBase.InvalidOutputExtension(widget, error)
return self.algorithm().preprocessParameters(parameters)
def setParameters(self, parameters): def setParameters(self, parameters):
for param in self.algorithm().parameterDefinitions(): for param in self.algorithm().parameterDefinitions():
if param.flags() & QgsProcessingParameterDefinition.FlagHidden: if param.flags() & QgsProcessingParameterDefinition.FlagHidden: