diff --git a/python/plugins/processing/modeler/ModelerGraphicItem.py b/python/plugins/processing/modeler/ModelerGraphicItem.py index c1a18edef39..4dec5838f07 100644 --- a/python/plugins/processing/modeler/ModelerGraphicItem.py +++ b/python/plugins/processing/modeler/ModelerGraphicItem.py @@ -17,7 +17,6 @@ *************************************************************************** """ - __author__ = 'Victor Olaya' __date__ = 'August 2012' __copyright__ = '(C) 2012, Victor Olaya' @@ -49,7 +48,6 @@ pluginPath = os.path.split(os.path.dirname(__file__))[0] class ModelerGraphicItem(QgsModelComponentGraphicItem): - BOX_HEIGHT = 30 BOX_WIDTH = 200 @@ -65,38 +63,6 @@ class ModelerGraphicItem(QgsModelComponentGraphicItem): self.pixmap = None self.picture = None self.hover_over_item = False - if isinstance(element, QgsProcessingModelParameter): - svg = QSvgRenderer(os.path.join(pluginPath, 'images', 'input.svg')) - self.picture = QPicture() - painter = QPainter(self.picture) - svg.render(painter) - painter.end() - paramDef = self.model().parameterDefinition(element.parameterName()) - if paramDef: - self.text = paramDef.description() - else: - self.text = 'Error ({})'.format(element.parameterName()) - elif isinstance(element, QgsProcessingModelOutput): - # Output name - svg = QSvgRenderer(os.path.join(pluginPath, 'images', 'output.svg')) - self.picture = QPicture() - painter = QPainter(self.picture) - svg.render(painter) - painter.end() - self.text = element.name() - else: - if element.algorithm().svgIconPath(): - svg = QSvgRenderer(element.algorithm().svgIconPath()) - size = svg.defaultSize() - self.picture = QPicture() - painter = QPainter(self.picture) - painter.scale(16 / size.width(), 16 / size.width()) - svg.render(painter) - painter.end() - self.pixmap = None - else: - self.pixmap = element.algorithm().icon().pixmap(15, 15) - self.text = element.description() self.arrows = [] svg = QSvgRenderer(os.path.join(pluginPath, 'images', 'edit.svg')) @@ -109,7 +75,7 @@ class ModelerGraphicItem(QgsModelComponentGraphicItem): self.box_height / 2 - ModelerGraphicItem.BUTTON_HEIGHT / 2) self.editButton = QgsModelDesignerFlatButtonGraphicItem(self, picture, pt) - self.editButton.clicked.connect(self.editElement) + self.editButton.clicked.connect(self.editComponent) svg = QSvgRenderer(os.path.join(pluginPath, 'images', 'delete.svg')) picture = QPicture() painter = QPainter(picture) @@ -120,51 +86,19 @@ class ModelerGraphicItem(QgsModelComponentGraphicItem): ModelerGraphicItem.BUTTON_HEIGHT / 2 - self.box_height / 2) self.deleteButton = QgsModelDesignerFlatButtonGraphicItem(self, picture, pt) - self.deleteButton.clicked.connect(self.removeElement) - - if isinstance(element, QgsProcessingModelChildAlgorithm): - alg = element.algorithm() - if [a for a in alg.parameterDefinitions() if not a.isDestination()]: - pt = self.getLinkPointForParameter(-1) - pt = QPointF(0, pt.y()) - self.inButton = QgsModelDesignerFoldButtonGraphicItem(self, self.component().parametersCollapsed(), pt) - self.inButton.folded.connect(self.foldInput) - if alg.outputDefinitions(): - pt = self.getLinkPointForOutput(-1) - pt = QPointF(0, pt.y()) - self.outButton = QgsModelDesignerFoldButtonGraphicItem(self, self.component().outputsCollapsed(), pt) - self.outButton.folded.connect(self.foldOutput) - - def foldInput(self, folded): - self.component().setParametersCollapsed(folded) - #also need to update the model's stored component - self.model().childAlgorithm(self.component().childId()).setParametersCollapsed(folded) - self.prepareGeometryChange() - if self.component().algorithm().outputDefinitions(): - pt = self.getLinkPointForOutput(-1) - pt = QPointF(0, pt.y()) - self.outButton.position = pt - for arrow in self.arrows: - arrow.updatePath() - self.update() - - def foldOutput(self, folded): - self.component().setOutputsCollapsed(folded) - # also need to update the model's stored component - self.model().childAlgorithm(self.component().childId()).setOutputsCollapsed(folded) - self.prepareGeometryChange() - for arrow in self.arrows: - arrow.updatePath() - self.update() + self.deleteButton.clicked.connect(self.removeComponent) def addArrow(self, arrow): self.arrows.append(arrow) def boundingRect(self): fm = QFontMetricsF(self.item_font) - unfolded = isinstance(self.component(), QgsProcessingModelChildAlgorithm) and not self.component().parametersCollapsed() - numParams = len([a for a in self.component().algorithm().parameterDefinitions() if not a.isDestination()]) if unfolded else 0 - unfolded = isinstance(self.component(), QgsProcessingModelChildAlgorithm) and not self.component().outputsCollapsed() + unfolded = isinstance(self.component(), + QgsProcessingModelChildAlgorithm) and not self.component().parametersCollapsed() + numParams = len([a for a in self.component().algorithm().parameterDefinitions() if + not a.isDestination()]) if unfolded else 0 + unfolded = isinstance(self.component(), + QgsProcessingModelChildAlgorithm) and not self.component().outputsCollapsed() numOutputs = len(self.component().algorithm().outputDefinitions()) if unfolded else 0 hUp = fm.height() * 1.2 * (numParams + 2) @@ -176,7 +110,7 @@ class ModelerGraphicItem(QgsModelComponentGraphicItem): return rect def mouseDoubleClickEvent(self, event): - self.editElement() + self.editComponent() def hoverEnterEvent(self, event): self.updateToolTip(event) @@ -203,136 +137,6 @@ class ModelerGraphicItem(QgsModelComponentGraphicItem): self.update() self.repaintArrows() - def contextMenuEvent(self, event): - if isinstance(self.component(), QgsProcessingModelOutput): - return - popupmenu = QMenu() - removeAction = popupmenu.addAction('Remove') - removeAction.triggered.connect(self.removeElement) - editAction = popupmenu.addAction('Edit') - editAction.triggered.connect(self.editElement) - if isinstance(self.component(), QgsProcessingModelChildAlgorithm): - if not self.component().isActive(): - removeAction = popupmenu.addAction('Activate') - removeAction.triggered.connect(self.activateAlgorithm) - else: - deactivateAction = popupmenu.addAction('Deactivate') - deactivateAction.triggered.connect(self.deactivateAlgorithm) - popupmenu.exec_(event.screenPos()) - - def deactivateAlgorithm(self): - self.model().deactivateChildAlgorithm(self.component().childId()) - self.requestModelRepaint.emit() - - def activateAlgorithm(self): - if self.model().activateChildAlgorithm(self.component().childId()): - self.requestModelRepaint.emit() - else: - QMessageBox.warning(None, 'Could not activate Algorithm', - 'The selected algorithm depends on other currently non-active algorithms.\n' - 'Activate them them before trying to activate it.') - - def create_widget_context(self): - """ - Returns a new widget context for use in the model editor - """ - widget_context = QgsProcessingParameterWidgetContext() - widget_context.setProject(QgsProject.instance()) - if iface is not None: - widget_context.setMapCanvas(iface.mapCanvas()) - widget_context.setModel(self.model()) - return widget_context - - def editElement(self): - if isinstance(self.component(), QgsProcessingModelParameter): - existing_param = self.model().parameterDefinition(self.component().parameterName()) - new_param = None - if ModelerParameterDefinitionDialog.use_legacy_dialog(param=existing_param): - # boo, old api - dlg = ModelerParameterDefinitionDialog(self.model(), - param=existing_param) - if dlg.exec_(): - new_param = dlg.param - else: - # yay, use new API! - context = createContext() - widget_context = self.create_widget_context() - dlg = QgsProcessingParameterDefinitionDialog(type=existing_param.type(), - context=context, - widgetContext=widget_context, - definition=existing_param, - algorithm=self.model()) - if dlg.exec_(): - new_param = dlg.createParameter(existing_param.name()) - - if new_param is not None: - self.model().removeModelParameter(self.component().parameterName()) - self.component().setParameterName(new_param.name()) - self.component().setDescription(new_param.name()) - self.model().addModelParameter(new_param, self.component()) - self.text = new_param.description() - self.requestModelRepaint.emit() - elif isinstance(self.component(), QgsProcessingModelChildAlgorithm): - elemAlg = self.component().algorithm() - dlg = ModelerParametersDialog(elemAlg, self.model(), self.component().childId(), self.component().configuration()) - if dlg.exec_(): - alg = dlg.createAlgorithm() - alg.setChildId(self.component().childId()) - self.updateAlgorithm(alg) - self.requestModelRepaint.emit() - - elif isinstance(self.component(), QgsProcessingModelOutput): - child_alg = self.model().childAlgorithm(self.component().childId()) - param_name = '{}:{}'.format(self.component().childId(), self.component().name()) - dlg = ModelerParameterDefinitionDialog(self.model(), - param=self.model().parameterDefinition(param_name)) - if dlg.exec_() and dlg.param is not None: - model_output = child_alg.modelOutput(self.component().name()) - model_output.setDescription(dlg.param.description()) - model_output.setDefaultValue(dlg.param.defaultValue()) - model_output.setMandatory(not (dlg.param.flags() & QgsProcessingParameterDefinition.FlagOptional)) - self.model().updateDestinationParameters() - - def updateAlgorithm(self, alg): - existing_child = self.model().childAlgorithm(alg.childId()) - alg.setPosition(existing_child.position()) - alg.setParametersCollapsed(existing_child.parametersCollapsed()) - alg.setOutputsCollapsed(existing_child.outputsCollapsed()) - for i, out in enumerate(alg.modelOutputs().keys()): - alg.modelOutput(out).setPosition(alg.modelOutput(out).position() or - alg.position() + QPointF( - self.box_width, - (i + 1.5) * self.box_height)) - self.model().setChildAlgorithm(alg) - - def removeElement(self): - if isinstance(self.component(), QgsProcessingModelParameter): - if self.model().childAlgorithmsDependOnParameter(self.component().parameterName()): - QMessageBox.warning(None, 'Could not remove input', - 'Algorithms depend on the selected input.\n' - 'Remove them before trying to remove it.') - elif self.model().otherParametersDependOnParameter(self.component().parameterName()): - QMessageBox.warning(None, 'Could not remove input', - 'Other inputs depend on the selected input.\n' - 'Remove them before trying to remove it.') - else: - self.model().removeModelParameter(self.component().parameterName()) - self.changed.emit() - self.requestModelRepaint.emit() - elif isinstance(self.component(), QgsProcessingModelChildAlgorithm): - if not self.model().removeChildAlgorithm(self.component().childId()): - QMessageBox.warning(None, 'Could not remove element', - 'Other elements depend on the selected one.\n' - 'Remove them before trying to remove it.') - else: - self.changed.emit() - self.requestModelRepaint.emit() - elif isinstance(self.component(), QgsProcessingModelOutput): - self.model().childAlgorithm(self.component().childId()).removeModelOutput(self.component().name()) - self.model().updateDestinationParameters() - self.changed.emit() - self.requestModelRepaint.emit() - def getAdjustedText(self, text): fm = QFontMetricsF(self.item_font) w = fm.width(text) @@ -437,7 +241,8 @@ class ModelerGraphicItem(QgsModelComponentGraphicItem): return QPointF(-self.box_width / 2 + offsetX, h) def getLinkPointForOutput(self, outputIndex): - if isinstance(self.component(), QgsProcessingModelChildAlgorithm) and self.component().algorithm().outputDefinitions(): + if isinstance(self.component(), + QgsProcessingModelChildAlgorithm) and self.component().algorithm().outputDefinitions(): outputIndex = (outputIndex if not self.component().outputsCollapsed() else -1) text = self.getAdjustedText(self.component().algorithm().outputDefinitions()[outputIndex].description()) fm = QFontMetricsF(self.item_font) @@ -467,8 +272,235 @@ class ModelerGraphicItem(QgsModelComponentGraphicItem): elif isinstance(self.component(), QgsProcessingModelParameter): self.model().parameterComponent(self.component().parameterName()).setPosition(self.pos()) elif isinstance(self.component(), QgsProcessingModelOutput): - self.model().childAlgorithm(self.component().childId()).modelOutput(self.component().name()).setPosition(self.pos()) + self.model().childAlgorithm(self.component().childId()).modelOutput( + self.component().name()).setPosition(self.pos()) elif change == QGraphicsItem.ItemSelectedChange: self.repaintArrows() return super().itemChange(change, value) + + +class ModelerInputGraphicItem(ModelerGraphicItem): + + def __init__(self, element, model): + super().__init__(element, model) + + svg = QSvgRenderer(os.path.join(pluginPath, 'images', 'input.svg')) + self.picture = QPicture() + painter = QPainter(self.picture) + svg.render(painter) + painter.end() + paramDef = self.model().parameterDefinition(element.parameterName()) + if paramDef: + self.text = paramDef.description() + else: + self.text = 'Error ({})'.format(element.parameterName()) + + def create_widget_context(self): + """ + Returns a new widget context for use in the model editor + """ + widget_context = QgsProcessingParameterWidgetContext() + widget_context.setProject(QgsProject.instance()) + if iface is not None: + widget_context.setMapCanvas(iface.mapCanvas()) + widget_context.setModel(self.model()) + return widget_context + + def editComponent(self): + existing_param = self.model().parameterDefinition(self.component().parameterName()) + new_param = None + if ModelerParameterDefinitionDialog.use_legacy_dialog(param=existing_param): + # boo, old api + dlg = ModelerParameterDefinitionDialog(self.model(), + param=existing_param) + if dlg.exec_(): + new_param = dlg.param + else: + # yay, use new API! + context = createContext() + widget_context = self.create_widget_context() + dlg = QgsProcessingParameterDefinitionDialog(type=existing_param.type(), + context=context, + widgetContext=widget_context, + definition=existing_param, + algorithm=self.model()) + if dlg.exec_(): + new_param = dlg.createParameter(existing_param.name()) + + if new_param is not None: + self.model().removeModelParameter(self.component().parameterName()) + self.component().setParameterName(new_param.name()) + self.component().setDescription(new_param.name()) + self.model().addModelParameter(new_param, self.component()) + self.text = new_param.description() + self.requestModelRepaint.emit() + + def removeComponent(self): + if self.model().childAlgorithmsDependOnParameter(self.component().parameterName()): + QMessageBox.warning(None, 'Could not remove input', + 'Algorithms depend on the selected input.\n' + 'Remove them before trying to remove it.') + elif self.model().otherParametersDependOnParameter(self.component().parameterName()): + QMessageBox.warning(None, 'Could not remove input', + 'Other inputs depend on the selected input.\n' + 'Remove them before trying to remove it.') + else: + self.model().removeModelParameter(self.component().parameterName()) + self.changed.emit() + self.requestModelRepaint.emit() + + def contextMenuEvent(self, event): + popupmenu = QMenu() + removeAction = popupmenu.addAction('Remove') + removeAction.triggered.connect(self.removeComponent) + editAction = popupmenu.addAction('Edit') + editAction.triggered.connect(self.editComponent) + popupmenu.exec_(event.screenPos()) + + +class ModelerChildAlgorithmGraphicItem(ModelerGraphicItem): + + def __init__(self, element, model): + super().__init__(element, model) + + if element.algorithm().svgIconPath(): + svg = QSvgRenderer(element.algorithm().svgIconPath()) + size = svg.defaultSize() + self.picture = QPicture() + painter = QPainter(self.picture) + painter.scale(16 / size.width(), 16 / size.width()) + svg.render(painter) + painter.end() + self.pixmap = None + else: + self.pixmap = element.algorithm().icon().pixmap(15, 15) + self.text = element.description() + + alg = element.algorithm() + if [a for a in alg.parameterDefinitions() if not a.isDestination()]: + pt = self.getLinkPointForParameter(-1) + pt = QPointF(0, pt.y()) + self.inButton = QgsModelDesignerFoldButtonGraphicItem(self, self.component().parametersCollapsed(), pt) + self.inButton.folded.connect(self.foldInput) + if alg.outputDefinitions(): + pt = self.getLinkPointForOutput(-1) + pt = QPointF(0, pt.y()) + self.outButton = QgsModelDesignerFoldButtonGraphicItem(self, self.component().outputsCollapsed(), pt) + self.outButton.folded.connect(self.foldOutput) + + def foldInput(self, folded): + self.component().setParametersCollapsed(folded) + # also need to update the model's stored component + self.model().childAlgorithm(self.component().childId()).setParametersCollapsed(folded) + self.prepareGeometryChange() + if self.component().algorithm().outputDefinitions(): + pt = self.getLinkPointForOutput(-1) + pt = QPointF(0, pt.y()) + self.outButton.position = pt + for arrow in self.arrows: + arrow.updatePath() + self.update() + + def foldOutput(self, folded): + self.component().setOutputsCollapsed(folded) + # also need to update the model's stored component + self.model().childAlgorithm(self.component().childId()).setOutputsCollapsed(folded) + self.prepareGeometryChange() + for arrow in self.arrows: + arrow.updatePath() + self.update() + + def editComponent(self): + elemAlg = self.component().algorithm() + dlg = ModelerParametersDialog(elemAlg, self.model(), self.component().childId(), + self.component().configuration()) + if dlg.exec_(): + alg = dlg.createAlgorithm() + alg.setChildId(self.component().childId()) + self.updateAlgorithm(alg) + self.requestModelRepaint.emit() + + def updateAlgorithm(self, alg): + existing_child = self.model().childAlgorithm(alg.childId()) + alg.setPosition(existing_child.position()) + alg.setParametersCollapsed(existing_child.parametersCollapsed()) + alg.setOutputsCollapsed(existing_child.outputsCollapsed()) + for i, out in enumerate(alg.modelOutputs().keys()): + alg.modelOutput(out).setPosition(alg.modelOutput(out).position() or + alg.position() + QPointF( + self.box_width, + (i + 1.5) * self.box_height)) + self.model().setChildAlgorithm(alg) + + def removeComponent(self): + if not self.model().removeChildAlgorithm(self.component().childId()): + QMessageBox.warning(None, 'Could not remove element', + 'Other elements depend on the selected one.\n' + 'Remove them before trying to remove it.') + else: + self.changed.emit() + self.requestModelRepaint.emit() + + def contextMenuEvent(self, event): + popupmenu = QMenu() + removeAction = popupmenu.addAction('Remove') + removeAction.triggered.connect(self.removeComponent) + editAction = popupmenu.addAction('Edit') + editAction.triggered.connect(self.editComponent) + + if not self.component().isActive(): + removeAction = popupmenu.addAction('Activate') + removeAction.triggered.connect(self.activateAlgorithm) + else: + deactivateAction = popupmenu.addAction('Deactivate') + deactivateAction.triggered.connect(self.deactivateAlgorithm) + + popupmenu.exec_(event.screenPos()) + + def deactivateAlgorithm(self): + self.model().deactivateChildAlgorithm(self.component().childId()) + self.requestModelRepaint.emit() + + def activateAlgorithm(self): + if self.model().activateChildAlgorithm(self.component().childId()): + self.requestModelRepaint.emit() + else: + QMessageBox.warning(None, 'Could not activate Algorithm', + 'The selected algorithm depends on other currently non-active algorithms.\n' + 'Activate them them before trying to activate it.') + + +class ModelerOutputGraphicItem(ModelerGraphicItem): + + def __init__(self, element, model): + super().__init__(element, model) + + # Output name + svg = QSvgRenderer(os.path.join(pluginPath, 'images', 'output.svg')) + self.picture = QPicture() + painter = QPainter(self.picture) + svg.render(painter) + painter.end() + self.text = element.name() + + def editComponent(self): + child_alg = self.model().childAlgorithm(self.component().childId()) + param_name = '{}:{}'.format(self.component().childId(), self.component().name()) + dlg = ModelerParameterDefinitionDialog(self.model(), + param=self.model().parameterDefinition(param_name)) + if dlg.exec_() and dlg.param is not None: + model_output = child_alg.modelOutput(self.component().name()) + model_output.setDescription(dlg.param.description()) + model_output.setDefaultValue(dlg.param.defaultValue()) + model_output.setMandatory(not (dlg.param.flags() & QgsProcessingParameterDefinition.FlagOptional)) + self.model().updateDestinationParameters() + + def removeComponent(self): + self.model().childAlgorithm(self.component().childId()).removeModelOutput(self.component().name()) + self.model().updateDestinationParameters() + self.changed.emit() + self.requestModelRepaint.emit() + + def contextMenuEvent(self, event): + return diff --git a/python/plugins/processing/modeler/ModelerScene.py b/python/plugins/processing/modeler/ModelerScene.py index 469794764b0..c859d5ef0a3 100644 --- a/python/plugins/processing/modeler/ModelerScene.py +++ b/python/plugins/processing/modeler/ModelerScene.py @@ -22,17 +22,19 @@ __date__ = 'August 2012' __copyright__ = '(C) 2012, Victor Olaya' from qgis.PyQt.QtCore import QPointF, Qt -from qgis.PyQt.QtWidgets import QGraphicsItem, QGraphicsScene +from qgis.PyQt.QtWidgets import QGraphicsScene from qgis.core import (QgsProcessingParameterDefinition, QgsProcessingModelChildParameterSource, QgsExpression) from qgis.gui import ( - QgsModelGraphicsScene, - QgsModelParameterGraphicItem, - QgsModelChildAlgorithmGraphicItem, - QgsModelOutputGraphicItem + QgsModelGraphicsScene +) +from processing.modeler.ModelerGraphicItem import ( + ModelerGraphicItem, + ModelerInputGraphicItem, + ModelerOutputGraphicItem, + ModelerChildAlgorithmGraphicItem ) -from processing.modeler.ModelerGraphicItem import ModelerGraphicItem from processing.modeler.ModelerArrowItem import ModelerArrowItem from processing.tools.dataobjects import createContext @@ -99,7 +101,7 @@ class ModelerScene(QgsModelGraphicsScene): context = createContext() # Inputs for inp in list(model.parameterComponents().values()): - item = ModelerGraphicItem(inp.clone(), model) + item = ModelerInputGraphicItem(inp.clone(), model) self.addItem(item) item.setPos(inp.position().x(), inp.position().y()) self.paramItems[inp.parameterName()] = item @@ -135,7 +137,7 @@ class ModelerScene(QgsModelGraphicsScene): # We add the algs for alg in list(model.childAlgorithms().values()): - item = ModelerGraphicItem(alg.clone(), model) + item = ModelerChildAlgorithmGraphicItem(alg.clone(), model) self.addItem(item) item.setPos(alg.position().x(), alg.position().y()) self.algItems[alg.childId()] = item @@ -177,7 +179,7 @@ class ModelerScene(QgsModelGraphicsScene): for key, out in outputs.items(): if out is not None: - item = ModelerGraphicItem(out.clone(), model) + item = ModelerOutputGraphicItem(out.clone(), model) item.requestModelRepaint.connect(self.requestModelRepaint) item.changed.connect(self.componentChanged)