Skip to content

Commit

Permalink
Messy messy refactoring to open model parameter options using inline …
Browse files Browse the repository at this point in the history
…panels

The "stable" old python api is really biting us here, and causes a heap of mess
Bring on QGIS 4.0!
  • Loading branch information
nyalldawson committed Mar 31, 2020
1 parent d2360da commit fbe36f8
Showing 1 changed file with 182 additions and 86 deletions.
268 changes: 182 additions & 86 deletions python/plugins/processing/modeler/ModelerParametersDialog.py
Expand Up @@ -23,31 +23,23 @@

import webbrowser

from qgis.PyQt.QtCore import (Qt,
QUrl,
QMetaObject,
QByteArray)
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.core import (Qgis,
QgsProject,
QgsProcessingParameterDefinition,
QgsProcessingParameterPoint,
QgsProcessingParameterExtent,
QgsProcessingModelAlgorithm,
QgsProcessingModelOutput,
QgsProcessingModelChildAlgorithm,
QgsProcessingModelChildParameterSource,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterMultipleLayers,
QgsProcessingParameterRasterDestination,
QgsProcessingParameterFileDestination,
QgsProcessingParameterFolderDestination,
QgsProcessingParameterVectorDestination,
QgsProcessingOutputDefinition,
QgsSettings)
QgsProcessingOutputDefinition)

from qgis.gui import (QgsGui,
QgsMessageBar,
Expand All @@ -56,7 +48,9 @@
QgsHelp,
QgsProcessingContextGenerator,
QgsProcessingModelerParameterWidget,
QgsProcessingParameterWidgetContext)
QgsProcessingParameterWidgetContext,
QgsPanelWidget,
QgsPanelWidgetStack)
from qgis.utils import iface

from processing.gui.wrappers import WidgetWrapperFactory
Expand All @@ -69,15 +63,86 @@
class ModelerParametersDialog(QDialog):

def __init__(self, alg, model, algName=None, configuration=None):
QDialog.__init__(self)
super().__init__()
self.setObjectName('ModelerParametersDialog')
self.setModal(True)

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
# 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.widget = ModelerParametersWidget(alg, model, algName, configuration, context=self.context, dialog=self)
QgsGui.enableAutoGeometryRestore(self)

self.buttonBox = QDialogButtonBox()
self.buttonBox.setOrientation(Qt.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok | QDialogButtonBox.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 setComments(self, text):
self.widget.setComments(text)

def comments(self):
return self.widget.comments()

def switchToCommentTab(self):
self.widget.switchToCommentTab()

def getAvailableDependencies(self): # spellok
return self.widget.getAvailableDependencies()

def getDependenciesPanel(self):
return self.widget.getDependenciesPanel()

def getAvailableValuesOfType(self, paramType, outTypes=[], dataTypes=[]):
return self.widget.getAvailableValuesOfType(paramType, outTypes, dataTypes)

def resolveValueDescription(self, value):
return self.widget.resolveValueDescription(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(),
"{}{}".format(self.algorithm().provider().helpId(), self.algorithm().name()))).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.setWindowTitle(self._alg.displayName())
self.widget_labels = {}

class ContextGenerator(QgsProcessingContextGenerator):
Expand All @@ -94,18 +159,8 @@ def processingContext(self):
self.setupUi()
self.params = None

settings = QgsSettings()
self.restoreGeometry(settings.value("/Processing/modelParametersDialogGeometry", QByteArray()))

def closeEvent(self, event):
settings = QgsSettings()
settings.setValue("/Processing/modelParametersDialogGeometry", self.saveGeometry())
super(ModelerParametersDialog, self).closeEvent(event)

def switchToCommentTab(self):
self.tab.setCurrentIndex(1)
self.commentEdit.setFocus()
self.commentEdit.selectAll()
def algorithm(self):
return self._alg

def setupUi(self):
self.checkBoxes = {}
Expand All @@ -115,27 +170,17 @@ def setupUi(self):
self.dependentItems = {}
self.algorithmItem = None

self.resize(650, 450)

self.mainLayout = QVBoxLayout()
self.tab = QTabWidget()
self.mainLayout.addWidget(self.tab)
self.mainLayout.setContentsMargins(0, 0, 0, 0)

self.buttonBox = QDialogButtonBox()
self.buttonBox.setOrientation(Qt.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok | QDialogButtonBox.Help)
self.setSizePolicy(QSizePolicy.Expanding,
QSizePolicy.Expanding)
self.verticalLayout = QVBoxLayout()
self.verticalLayout.setSpacing(5)

self.bar = QgsMessageBar()
self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
self.verticalLayout.addWidget(self.bar)

hLayout = QHBoxLayout()
hLayout.setSpacing(5)
hLayout.setMargin(0)
hLayout.setContentsMargins(0, 0, 0, 0)
descriptionLabel = QLabel(self.tr("Description"))
self.descriptionBox = QLineEdit()
self.descriptionBox.setText(self._alg.displayName())
Expand Down Expand Up @@ -179,7 +224,7 @@ def setupUi(self):
if param.isDestination() or param.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue

wrapper = WidgetWrapperFactory.create_wrapper(param, self)
wrapper = WidgetWrapperFactory.create_wrapper(param, self.dialog)
self.wrappers[param.name()] = wrapper

wrapper.setWidgetContext(widget_context)
Expand Down Expand Up @@ -210,7 +255,8 @@ def setupUi(self):
if dest.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
if isinstance(dest, (QgsProcessingParameterRasterDestination, QgsProcessingParameterVectorDestination,
QgsProcessingParameterFeatureSink, QgsProcessingParameterFileDestination, QgsProcessingParameterFolderDestination)):
QgsProcessingParameterFeatureSink, QgsProcessingParameterFileDestination,
QgsProcessingParameterFolderDestination)):
label = QLabel(dest.description())
item = QgsFilterLineEdit()
if hasattr(item, 'setPlaceholderText'):
Expand All @@ -228,7 +274,6 @@ def setupUi(self):
self.verticalLayout.addStretch(1000)

self.setPreviousValues()
self.setWindowTitle(self._alg.displayName())
self.verticalLayout2 = QVBoxLayout()
self.verticalLayout2.setSpacing(2)
self.verticalLayout2.setMargin(0)
Expand All @@ -238,35 +283,15 @@ def setupUi(self):
self.scrollArea = QgsScrollArea()
self.scrollArea.setWidget(self.paramPanel)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setFrameStyle(QFrame.NoFrame)

self.verticalLayout2.addWidget(self.scrollArea)
self.buttonBox.accepted.connect(self.okPressed)
self.buttonBox.rejected.connect(self.cancelPressed)
self.buttonBox.helpRequested.connect(self.openHelp)

w = QWidget()
w.setLayout(self.verticalLayout2)
self.tab.addTab(w, self.tr('Properties'))

self.commentLayout = QVBoxLayout()
self.commentEdit = QTextEdit()
self.commentEdit.setAcceptRichText(False)
self.commentLayout.addWidget(self.commentEdit)
w2 = QWidget()
w2.setLayout(self.commentLayout)
self.tab.addTab(w2, self.tr('Comments'))

self.mainLayout.addWidget(self.buttonBox)
self.mainLayout.addWidget(w)
self.setLayout(self.mainLayout)

QMetaObject.connectSlotsByName(self)

def setComments(self, text):
self.commentEdit.setPlainText(text)

def comments(self):
return self.commentEdit.toPlainText()

def getAvailableDependencies(self): # spellok
if self.childId is None:
dependent = []
Expand Down Expand Up @@ -409,9 +434,9 @@ def createAlgorithm(self):
[isinstance(subval, QgsProcessingModelChildParameterSource) for subval in val])):
val = [QgsProcessingModelChildParameterSource.fromStaticValue(val)]
for subval in val:
if (isinstance(subval, QgsProcessingModelChildParameterSource)
and subval.source() == QgsProcessingModelChildParameterSource.StaticValue
and not param.checkValueIsAcceptable(subval.staticValue())) \
if (isinstance(subval, QgsProcessingModelChildParameterSource) and
subval.source() == QgsProcessingModelChildParameterSource.StaticValue and
not param.checkValueIsAcceptable(subval.staticValue())) \
or (subval is None and not param.flags() & QgsProcessingParameterDefinition.FlagOptional):
self.bar.pushMessage(self.tr("Error"), self.tr("Wrong or missing value for parameter '{}'").format(
param.description()),
Expand Down Expand Up @@ -445,27 +470,98 @@ def createAlgorithm(self):
dep_ids.append(availableDependencies[selected].childId()) # spellok
alg.setDependencies(dep_ids)

#try:
# self._alg.processBeforeAddingToModeler(alg, self.model)
#except:
# pass

alg.comment().setDescription(self.comments())
return alg

def okPressed(self):
alg = self.createAlgorithm()
if alg is not None:
self.accept()

def cancelPressed(self):
self.reject()
class ModelerParametersWidget(QWidget):

def openHelp(self):
algHelp = self._alg.helpUrl()
if not algHelp:
algHelp = QgsHelp.helpUrl("processing_algs/{}/{}.html#{}".format(
self._alg.provider().helpId(), self._alg.groupId(), "{}{}".format(self._alg.provider().helpId(), self._alg.name()))).toString()
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

if algHelp not in [None, ""]:
webbrowser.open(algHelp)
self.widget = ModelerParametersPanelWidget(alg, model, algName, configuration, dialog, context)

self.widget_labels = {}

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.checkBoxes = {}
self.showAdvanced = False
self.wrappers = {}
self.valueItems = {}
self.dependentItems = {}
self.algorithmItem = None

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)
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 getAvailableDependencies(self): # spellok
return self.widget.getAvailableDependencies()

def getDependenciesPanel(self):
return self.widget.getDependenciesPanel()

def getAvailableValuesOfType(self, paramType, outTypes=[], dataTypes=[]):
return self.widget.getAvailableValuesOfType(paramType, outTypes, dataTypes)

def resolveValueDescription(self, value):
return self.widget.resolveValueDescription(value)

def setPreviousValues(self):
self.widget.setPreviousValues()

def createAlgorithm(self):
alg = self.widget.createAlgorithm()
if alg:
alg.comment().setDescription(self.comments())
return alg

0 comments on commit fbe36f8

Please sign in to comment.