diff --git a/python/core/auto_generated/processing/models/qgsprocessingmodelchildalgorithm.sip.in b/python/core/auto_generated/processing/models/qgsprocessingmodelchildalgorithm.sip.in index 645b9fe7dda..574dbed4d13 100644 --- a/python/core/auto_generated/processing/models/qgsprocessingmodelchildalgorithm.sip.in +++ b/python/core/auto_generated/processing/models/qgsprocessingmodelchildalgorithm.sip.in @@ -275,6 +275,9 @@ The ``currentIndent`` and ``indentSize`` are used to set the base line indent an The ``friendlyChildNames`` argument gives a map of child id to a friendly algorithm name, to be used in the code to identify that algorithm instead of the raw child id. %End + virtual QgsProcessingModelComment *comment(); + virtual void setComment( const QgsProcessingModelComment &comment ); + }; diff --git a/python/core/auto_generated/processing/models/qgsprocessingmodelcomment.sip.in b/python/core/auto_generated/processing/models/qgsprocessingmodelcomment.sip.in new file mode 100644 index 00000000000..b1c00441f5b --- /dev/null +++ b/python/core/auto_generated/processing/models/qgsprocessingmodelcomment.sip.in @@ -0,0 +1,56 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/processing/models/qgsprocessingmodelcomment.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsProcessingModelComment : QgsProcessingModelComponent +{ +%Docstring +Represents a comment in a model. + +.. versionadded:: 3.14 +%End + +%TypeHeaderCode +#include "qgsprocessingmodelcomment.h" +%End + public: + + QgsProcessingModelComment( const QString &description = QString() ); +%Docstring +Constructor for QgsProcessingModelComment with the specified ``description``. +%End + + virtual QgsProcessingModelComment *clone() const /Factory/; + + + QVariant toVariant() const; +%Docstring +Saves this comment to a QVariant. + +.. seealso:: :py:func:`loadVariant` +%End + + bool loadVariant( const QVariantMap &map ); +%Docstring +Loads this comment from a QVariantMap. + +.. seealso:: :py:func:`toVariant` +%End +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/processing/models/qgsprocessingmodelcomment.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in b/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in index e1c3385662d..faa9e21577c 100644 --- a/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in +++ b/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in @@ -10,6 +10,7 @@ + class QgsProcessingModelComponent { %Docstring @@ -86,6 +87,21 @@ in the graphical modeler. .. seealso:: :py:func:`linksCollapsed` %End + + virtual QgsProcessingModelComment *comment(); +%Docstring +Returns the comment attached to this component (may be ``None``) + +.. seealso:: :py:func:`setComment` +%End + + virtual void setComment( const QgsProcessingModelComment &comment ); +%Docstring +Sets the ``comment`` attached to this component. + +.. seealso:: :py:func:`comment` +%End + virtual QgsProcessingModelComponent *clone() const = 0 /Factory/; %Docstring Clones the component. diff --git a/python/core/auto_generated/processing/models/qgsprocessingmodeloutput.sip.in b/python/core/auto_generated/processing/models/qgsprocessingmodeloutput.sip.in index 97c153991f9..dfa373e2591 100644 --- a/python/core/auto_generated/processing/models/qgsprocessingmodeloutput.sip.in +++ b/python/core/auto_generated/processing/models/qgsprocessingmodeloutput.sip.in @@ -127,6 +127,9 @@ Loads this output from a QVariantMap. .. seealso:: :py:func:`toVariant` %End + virtual QgsProcessingModelComment *comment(); + virtual void setComment( const QgsProcessingModelComment &comment ); + }; diff --git a/python/core/auto_generated/processing/models/qgsprocessingmodelparameter.sip.in b/python/core/auto_generated/processing/models/qgsprocessingmodelparameter.sip.in index 2c42438cb1c..cb30079323f 100644 --- a/python/core/auto_generated/processing/models/qgsprocessingmodelparameter.sip.in +++ b/python/core/auto_generated/processing/models/qgsprocessingmodelparameter.sip.in @@ -63,6 +63,9 @@ Loads this parameter from a QVariantMap. .. seealso:: :py:func:`toVariant` %End + virtual QgsProcessingModelComment *comment(); + virtual void setComment( const QgsProcessingModelComment &comment ); + }; diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index d9dd8e4b463..224ee3552a2 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -415,6 +415,7 @@ %Include auto_generated/processing/models/qgsprocessingmodelalgorithm.sip %Include auto_generated/processing/models/qgsprocessingmodelchildalgorithm.sip %Include auto_generated/processing/models/qgsprocessingmodelchildparametersource.sip +%Include auto_generated/processing/models/qgsprocessingmodelcomment.sip %Include auto_generated/processing/models/qgsprocessingmodelcomponent.sip %Include auto_generated/processing/models/qgsprocessingmodeloutput.sip %Include auto_generated/processing/models/qgsprocessingmodelparameter.sip diff --git a/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in b/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in index 2c2722f52c4..59ccab55a38 100644 --- a/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in +++ b/python/gui/auto_generated/processing/models/qgsmodelcomponentgraphicitem.sip.in @@ -147,6 +147,13 @@ Returns the best link point to use for a link originating at a specified ``other - edge: item edge for calculated best link point %End + virtual void editComment(); +%Docstring +Called when the comment attached to the item should be edited. + +The default implementation does nothing. +%End + signals: @@ -208,6 +215,11 @@ Returns the stroke color for the item for the specified ``state``. virtual QColor textColor( State state ) const = 0; %Docstring Returns the label text color for the item for the specified ``state``. +%End + + virtual Qt::PenStyle strokeStyle( State state ) const; +%Docstring +Returns the stroke style to use while rendering the outline of the item. %End virtual QPicture iconPicture() const; @@ -218,6 +230,11 @@ Returns a QPicture version of the item's icon, if available. virtual QPixmap iconPixmap() const; %Docstring Returns a QPixmap version of the item's icon, if available. +%End + + virtual void updateStoredComponentPosition( const QPointF &pos ) = 0; +%Docstring +Updates the position stored in the model for the associated comment %End }; @@ -264,6 +281,8 @@ Ownership of ``parameter`` is transferred to the item. virtual QPicture iconPicture() const; + virtual void updateStoredComponentPosition( const QPointF &pos ); + protected slots: @@ -320,6 +339,8 @@ Ownership of ``child`` is transferred to the item. virtual QString linkPointText( Qt::Edge edge, int index ) const; + virtual void updateStoredComponentPosition( const QPointF &pos ); + protected slots: @@ -368,6 +389,8 @@ Ownership of ``output`` is transferred to the item. virtual QPicture iconPicture() const; + virtual void updateStoredComponentPosition( const QPointF &pos ); + protected slots: @@ -376,6 +399,62 @@ Ownership of ``output`` is transferred to the item. }; + + +class QgsModelCommentGraphicItem : QgsModelComponentGraphicItem +{ +%Docstring +A graphic item representing a model comment in the model designer. + +.. warning:: + + Not stable API + +.. versionadded:: 3.14 +%End + +%TypeHeaderCode +#include "qgsmodelcomponentgraphicitem.h" +%End + public: + + QgsModelCommentGraphicItem( QgsProcessingModelComment *comment /Transfer/, + QgsModelComponentGraphicItem *parentItem, + QgsProcessingModelAlgorithm *model, + QGraphicsItem *parent /TransferThis/ ); +%Docstring +Constructor for QgsModelCommentGraphicItem for the specified ``comment``, with the specified ``parent`` item. + +The ``model`` argument specifies the associated processing model. Ownership of ``model`` is not transferred, and +it must exist for the lifetime of this object. + +Ownership of ``output`` is transferred to the item. +%End + ~QgsModelCommentGraphicItem(); + virtual void contextMenuEvent( QGraphicsSceneContextMenuEvent *event ); + + + protected: + + virtual QColor fillColor( State state ) const; + + virtual QColor strokeColor( State state ) const; + + virtual QColor textColor( State state ) const; + + virtual Qt::PenStyle strokeStyle( State state ) const; + + virtual void updateStoredComponentPosition( const QPointF &pos ); + + + protected slots: + + virtual void deleteComponent(); + + virtual void editComponent(); + +}; + /************************************************************************ * This file has been generated automatically from * * * diff --git a/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in b/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in index 1a1dbe602cc..5c38ba7757e 100644 --- a/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in +++ b/python/gui/auto_generated/processing/models/qgsmodelgraphicsscene.sip.in @@ -110,6 +110,13 @@ Creates a new graphic item for a model child algorithm. Creates a new graphic item for a model output. %End + virtual QgsModelComponentGraphicItem *createCommentGraphicItem( QgsProcessingModelAlgorithm *model, QgsProcessingModelComment *comment, + QgsModelComponentGraphicItem *parentItem ) const /Factory/; +%Docstring +Creates a new graphic item for a model comment. +%End + + }; diff --git a/python/plugins/processing/modeler/ModelerGraphicItem.py b/python/plugins/processing/modeler/ModelerGraphicItem.py index 377985f0a82..53d6a8bd916 100644 --- a/python/plugins/processing/modeler/ModelerGraphicItem.py +++ b/python/plugins/processing/modeler/ModelerGraphicItem.py @@ -29,7 +29,8 @@ from qgis.gui import ( QgsProcessingParameterWidgetContext, QgsModelParameterGraphicItem, QgsModelChildAlgorithmGraphicItem, - QgsModelOutputGraphicItem + QgsModelOutputGraphicItem, + QgsModelCommentGraphicItem ) from processing.modeler.ModelerParameterDefinitionDialog import ModelerParameterDefinitionDialog from processing.modeler.ModelerParametersDialog import ModelerParametersDialog @@ -59,15 +60,20 @@ class ModelerInputGraphicItem(QgsModelParameterGraphicItem): widget_context.setModel(self.model()) return widget_context - def editComponent(self): + def edit(self, edit_comment=False): existing_param = self.model().parameterDefinition(self.component().parameterName()) + comment = self.component().comment().description() new_param = None if ModelerParameterDefinitionDialog.use_legacy_dialog(param=existing_param): # boo, old api dlg = ModelerParameterDefinitionDialog(self.model(), param=existing_param) + dlg.setComments(comment) + if edit_comment: + dlg.switchToCommentTab() if dlg.exec_(): new_param = dlg.param + comment = dlg.comments() else: # yay, use new API! context = createContext() @@ -84,11 +90,18 @@ class ModelerInputGraphicItem(QgsModelParameterGraphicItem): self.model().removeModelParameter(self.component().parameterName()) self.component().setParameterName(new_param.name()) self.component().setDescription(new_param.name()) + self.component().comment().setDescription(comment) self.model().addModelParameter(new_param, self.component()) self.setLabel(new_param.description()) self.requestModelRepaint.emit() self.changed.emit() + def editComponent(self): + self.edit() + + def editComment(self): + self.edit(edit_comment=True) + class ModelerChildAlgorithmGraphicItem(QgsModelChildAlgorithmGraphicItem): """ @@ -101,10 +114,13 @@ class ModelerChildAlgorithmGraphicItem(QgsModelChildAlgorithmGraphicItem): def __init__(self, element, model): super().__init__(element, model, None) - def editComponent(self): + def edit(self, edit_comment=False): elemAlg = self.component().algorithm() dlg = ModelerParametersDialog(elemAlg, self.model(), self.component().childId(), self.component().configuration()) + dlg.setComments(self.component().comment().description()) + if edit_comment: + dlg.switchToCommentTab() if dlg.exec_(): alg = dlg.createAlgorithm() alg.setChildId(self.component().childId()) @@ -112,11 +128,18 @@ class ModelerChildAlgorithmGraphicItem(QgsModelChildAlgorithmGraphicItem): self.requestModelRepaint.emit() self.changed.emit() + def editComponent(self): + self.edit() + + def editComment(self): + self.edit(edit_comment=True) + def updateAlgorithm(self, alg): existing_child = self.model().childAlgorithm(alg.childId()) alg.setPosition(existing_child.position()) alg.setLinksCollapsed(Qt.TopEdge, existing_child.linksCollapsed(Qt.TopEdge)) alg.setLinksCollapsed(Qt.BottomEdge, existing_child.linksCollapsed(Qt.BottomEdge)) + alg.comment().setPosition(existing_child.comment().position()) for i, out in enumerate(alg.modelOutputs().keys()): alg.modelOutput(out).setPosition(alg.modelOutput(out).position() or alg.position() + QPointF( @@ -136,15 +159,42 @@ class ModelerOutputGraphicItem(QgsModelOutputGraphicItem): def __init__(self, element, model): super().__init__(element, model, None) - def editComponent(self): + def edit(self, edit_comment=False): 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: + dlg.setComments(self.component().comment().description()) + if edit_comment: + dlg.switchToCommentTab() + + if dlg.exec_(): 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)) + model_output.comment().setDescription(dlg.comments()) self.model().updateDestinationParameters() + self.requestModelRepaint.emit() self.changed.emit() + + def editComponent(self): + self.edit() + + def editComment(self): + self.edit(edit_comment=True) + + +class ModelerCommentGraphicItem(QgsModelCommentGraphicItem): + """ + IMPORTANT! This is intentionally a MINIMAL class, only containing code which HAS TO BE HERE + because it contains Python code for compatibility with deprecated methods ONLY. + + Don't add anything here -- edit the c++ base class instead! + """ + + def __init__(self, model, element, parent_component): + super().__init__(element, parent_component, model, None) + + def editComponent(self): + pass diff --git a/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py b/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py index c95a794ba5b..ce102384026 100755 --- a/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py +++ b/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py @@ -33,7 +33,10 @@ from qgis.PyQt.QtWidgets import (QDialog, QComboBox, QCheckBox, QDialogButtonBox, - QMessageBox) + QMessageBox, + QTabWidget, + QWidget, + QTextEdit) from qgis.gui import QgsExpressionLineEdit, QgsProjectionSelectionWidget from qgis.core import (QgsApplication, @@ -124,13 +127,20 @@ class ModelerParameterDefinitionDialog(QDialog): settings.setValue("/Processing/modelParametersDefinitionDialogGeometry", self.saveGeometry()) super(ModelerParameterDefinitionDialog, self).closeEvent(event) + def switchToCommentTab(self): + self.tab.setCurrentIndex(1) + def setupUi(self): type_metadata = QgsApplication.processingRegistry().parameterType(self.param.type() if self.param else self.paramType) self.setWindowTitle(self.tr('{} Parameter Definition').format(type_metadata.name())) + + self.mainLayout = QVBoxLayout() + self.tab = QTabWidget() + self.mainLayout.addWidget(self.tab) + self.setMinimumWidth(300) - self.verticalLayout = QVBoxLayout(self) - self.verticalLayout.setMargin(20) + self.verticalLayout = QVBoxLayout() self.label = QLabel(self.tr('Parameter name')) self.verticalLayout.addWidget(self.label) @@ -214,8 +224,8 @@ class ModelerParameterDefinitionDialog(QDialog): if self.param is not None: self.shapetypeCombo.setCurrentIndex(self.shapetypeCombo.findData(self.param.dataTypes()[0])) self.verticalLayout.addWidget(self.shapetypeCombo) - elif (self.paramType == parameters.PARAMETER_MULTIPLE or - isinstance(self.param, QgsProcessingParameterMultipleLayers)): + elif (self.paramType == parameters.PARAMETER_MULTIPLE + or isinstance(self.param, QgsProcessingParameterMultipleLayers)): self.verticalLayout.addWidget(QLabel(self.tr('Data type'))) self.datatypeCombo = QComboBox() self.datatypeCombo.addItem(self.tr('Any Map Layer'), QgsProcessing.TypeMapLayer) @@ -229,11 +239,11 @@ class ModelerParameterDefinitionDialog(QDialog): if self.param is not None: self.datatypeCombo.setCurrentIndex(self.datatypeCombo.findData(self.param.layerType())) self.verticalLayout.addWidget(self.datatypeCombo) - elif (self.paramType in (parameters.PARAMETER_NUMBER, parameters.PARAMETER_DISTANCE, parameters.PARAMETER_SCALE) or - isinstance(self.param, (QgsProcessingParameterNumber, QgsProcessingParameterDistance, QgsProcessingParameterScale))): + elif (self.paramType in (parameters.PARAMETER_NUMBER, parameters.PARAMETER_DISTANCE, parameters.PARAMETER_SCALE) + or isinstance(self.param, (QgsProcessingParameterNumber, QgsProcessingParameterDistance, QgsProcessingParameterScale))): - if (self.paramType == parameters.PARAMETER_DISTANCE or - isinstance(self.param, QgsProcessingParameterDistance)): + if (self.paramType == parameters.PARAMETER_DISTANCE + or isinstance(self.param, QgsProcessingParameterDistance)): self.verticalLayout.addWidget(QLabel(self.tr('Linked input'))) self.parentCombo = QComboBox() self.parentCombo.addItem('', '') @@ -281,8 +291,8 @@ class ModelerParameterDefinitionDialog(QDialog): if default: self.defaultTextBox.setText(str(default)) self.verticalLayout.addWidget(self.defaultTextBox) - elif (self.paramType == parameters.PARAMETER_EXPRESSION or - isinstance(self.param, QgsProcessingParameterExpression)): + elif (self.paramType == parameters.PARAMETER_EXPRESSION + or isinstance(self.param, QgsProcessingParameterExpression)): self.verticalLayout.addWidget(QLabel(self.tr('Default value'))) self.defaultEdit = QgsExpressionLineEdit() if self.param is not None: @@ -302,15 +312,15 @@ class ModelerParameterDefinitionDialog(QDialog): self.parentCombo.setCurrentIndex(idx) idx += 1 self.verticalLayout.addWidget(self.parentCombo) - elif (self.paramType == parameters.PARAMETER_POINT or - isinstance(self.param, QgsProcessingParameterPoint)): + elif (self.paramType == parameters.PARAMETER_POINT + or isinstance(self.param, QgsProcessingParameterPoint)): self.verticalLayout.addWidget(QLabel(self.tr('Default value'))) self.defaultTextBox = QLineEdit() if self.param is not None: self.defaultTextBox.setText(self.param.defaultValue()) self.verticalLayout.addWidget(self.defaultTextBox) - elif (self.paramType == parameters.PARAMETER_CRS or - isinstance(self.param, QgsProcessingParameterCrs)): + elif (self.paramType == parameters.PARAMETER_CRS + or isinstance(self.param, QgsProcessingParameterCrs)): self.verticalLayout.addWidget(QLabel(self.tr('Default value'))) self.selector = QgsProjectionSelectionWidget() if self.param is not None: @@ -367,18 +377,37 @@ class ModelerParameterDefinitionDialog(QDialog): self.advancedCheck.setEnabled(False) self.advancedCheck.setChecked(False) + self.verticalLayout.addStretch() + + w = QWidget() + w.setLayout(self.verticalLayout) + 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.buttonBox = QDialogButtonBox(self) self.buttonBox.setOrientation(Qt.Horizontal) - self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | - QDialogButtonBox.Ok) + self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel + | QDialogButtonBox.Ok) self.buttonBox.setObjectName('buttonBox') self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) - self.verticalLayout.addStretch() - self.verticalLayout.addWidget(self.buttonBox) + self.mainLayout.addWidget(self.buttonBox) - self.setLayout(self.verticalLayout) + self.setLayout(self.mainLayout) + + def setComments(self, text): + self.commentEdit.setPlainText(text) + + def comments(self): + return self.commentEdit.toPlainText() def accept(self): description = self.nameTextBox.text() @@ -397,8 +426,8 @@ class ModelerParameterDefinitionDialog(QDialog): i += 1 else: name = self.param.name() - if (self.paramType == parameters.PARAMETER_TABLE_FIELD or - isinstance(self.param, QgsProcessingParameterField)): + if (self.paramType == parameters.PARAMETER_TABLE_FIELD + or isinstance(self.param, QgsProcessingParameterField)): if self.parentCombo.currentIndex() < 0: QMessageBox.warning(self, self.tr('Unable to define parameter'), self.tr('Wrong or missing parameter values')) @@ -411,39 +440,39 @@ class ModelerParameterDefinitionDialog(QDialog): self.param = QgsProcessingParameterField(name, description, defaultValue=default, parentLayerParameterName=parent, type=datatype, allowMultiple=self.multipleCheck.isChecked()) - elif (self.paramType == parameters.PARAMETER_BAND or - isinstance(self.param, QgsProcessingParameterBand)): + elif (self.paramType == parameters.PARAMETER_BAND + or isinstance(self.param, QgsProcessingParameterBand)): if self.parentCombo.currentIndex() < 0: QMessageBox.warning(self, self.tr('Unable to define parameter'), self.tr('Wrong or missing parameter values')) return parent = self.parentCombo.currentData() self.param = QgsProcessingParameterBand(name, description, None, parent) - elif (self.paramType == parameters.PARAMETER_MAP_LAYER or - isinstance(self.param, QgsProcessingParameterMapLayer)): + elif (self.paramType == parameters.PARAMETER_MAP_LAYER + or isinstance(self.param, QgsProcessingParameterMapLayer)): self.param = QgsProcessingParameterMapLayer( name, description) - elif (self.paramType == parameters.PARAMETER_RASTER or - isinstance(self.param, QgsProcessingParameterRasterLayer)): + elif (self.paramType == parameters.PARAMETER_RASTER + or isinstance(self.param, QgsProcessingParameterRasterLayer)): self.param = QgsProcessingParameterRasterLayer( name, description) - elif (self.paramType == parameters.PARAMETER_TABLE or - isinstance(self.param, QgsProcessingParameterVectorLayer)): + elif (self.paramType == parameters.PARAMETER_TABLE + or isinstance(self.param, QgsProcessingParameterVectorLayer)): self.param = QgsProcessingParameterVectorLayer( name, description, [self.shapetypeCombo.currentData()]) - elif (self.paramType == parameters.PARAMETER_VECTOR or - isinstance(self.param, QgsProcessingParameterFeatureSource)): + elif (self.paramType == parameters.PARAMETER_VECTOR + or isinstance(self.param, QgsProcessingParameterFeatureSource)): self.param = QgsProcessingParameterFeatureSource( name, description, [self.shapetypeCombo.currentData()]) - elif (self.paramType == parameters.PARAMETER_MULTIPLE or - isinstance(self.param, QgsProcessingParameterMultipleLayers)): + elif (self.paramType == parameters.PARAMETER_MULTIPLE + or isinstance(self.param, QgsProcessingParameterMultipleLayers)): self.param = QgsProcessingParameterMultipleLayers( name, description, self.datatypeCombo.currentData()) - elif (self.paramType == parameters.PARAMETER_DISTANCE or - isinstance(self.param, QgsProcessingParameterDistance)): + elif (self.paramType == parameters.PARAMETER_DISTANCE + or isinstance(self.param, QgsProcessingParameterDistance)): self.param = QgsProcessingParameterDistance(name, description, self.defaultTextBox.text()) try: @@ -465,12 +494,12 @@ class ModelerParameterDefinitionDialog(QDialog): parent = self.parentCombo.currentData() if parent: self.param.setParentParameterName(parent) - elif (self.paramType == parameters.PARAMETER_SCALE or - isinstance(self.param, QgsProcessingParameterScale)): + elif (self.paramType == parameters.PARAMETER_SCALE + or isinstance(self.param, QgsProcessingParameterScale)): self.param = QgsProcessingParameterScale(name, description, self.defaultTextBox.text()) - elif (self.paramType == parameters.PARAMETER_NUMBER or - isinstance(self.param, QgsProcessingParameterNumber)): + elif (self.paramType == parameters.PARAMETER_NUMBER + or isinstance(self.param, QgsProcessingParameterNumber)): type = self.type_combo.currentData() self.param = QgsProcessingParameterNumber(name, description, type, @@ -486,27 +515,27 @@ class ModelerParameterDefinitionDialog(QDialog): QMessageBox.warning(self, self.tr('Unable to define parameter'), self.tr('Wrong or missing parameter values')) return - elif (self.paramType == parameters.PARAMETER_EXPRESSION or - isinstance(self.param, QgsProcessingParameterExpression)): + elif (self.paramType == parameters.PARAMETER_EXPRESSION + or isinstance(self.param, QgsProcessingParameterExpression)): parent = self.parentCombo.currentData() self.param = QgsProcessingParameterExpression(name, description, str(self.defaultEdit.expression()), parent) - elif (self.paramType == parameters.PARAMETER_EXTENT or - isinstance(self.param, QgsProcessingParameterExtent)): + elif (self.paramType == parameters.PARAMETER_EXTENT + or isinstance(self.param, QgsProcessingParameterExtent)): self.param = QgsProcessingParameterExtent(name, description) - elif (self.paramType == parameters.PARAMETER_POINT or - isinstance(self.param, QgsProcessingParameterPoint)): + elif (self.paramType == parameters.PARAMETER_POINT + or isinstance(self.param, QgsProcessingParameterPoint)): self.param = QgsProcessingParameterPoint(name, description, str(self.defaultTextBox.text())) - elif (self.paramType == parameters.PARAMETER_CRS or - isinstance(self.param, QgsProcessingParameterCrs)): + elif (self.paramType == parameters.PARAMETER_CRS + or isinstance(self.param, QgsProcessingParameterCrs)): self.param = QgsProcessingParameterCrs(name, description, self.selector.crs().authid()) - elif (self.paramType == parameters.PARAMETER_ENUM or - isinstance(self.param, QgsProcessingParameterEnum)): + elif (self.paramType == parameters.PARAMETER_ENUM + or isinstance(self.param, QgsProcessingParameterEnum)): self.param = QgsProcessingParameterEnum(name, description, self.widget.options(), self.widget.allowMultiple(), self.widget.defaultOptions()) - elif (self.paramType == parameters.PARAMETER_MATRIX or - isinstance(self.param, QgsProcessingParameterMatrix)): + elif (self.paramType == parameters.PARAMETER_MATRIX + or isinstance(self.param, QgsProcessingParameterMatrix)): self.param = QgsProcessingParameterMatrix(name, description, hasFixedNumberRows=self.widget.fixedRows(), headers=self.widget.headers(), defaultValue=self.widget.value()) # Destination parameter diff --git a/python/plugins/processing/modeler/ModelerParametersDialog.py b/python/plugins/processing/modeler/ModelerParametersDialog.py index bcea2f5157f..81aaed815da 100644 --- a/python/plugins/processing/modeler/ModelerParametersDialog.py +++ b/python/plugins/processing/modeler/ModelerParametersDialog.py @@ -29,7 +29,7 @@ from qgis.PyQt.QtCore import (Qt, QByteArray) from qgis.PyQt.QtWidgets import (QDialog, QDialogButtonBox, QLabel, QLineEdit, QFrame, QPushButton, QSizePolicy, QVBoxLayout, - QHBoxLayout, QWidget) + QHBoxLayout, QWidget, QTabWidget, QTextEdit) from qgis.core import (Qgis, QgsProject, @@ -102,6 +102,9 @@ class ModelerParametersDialog(QDialog): settings.setValue("/Processing/modelParametersDialogGeometry", self.saveGeometry()) super(ModelerParametersDialog, self).closeEvent(event) + def switchToCommentTab(self): + self.tab.setCurrentIndex(1) + def setupUi(self): self.checkBoxes = {} self.showAdvanced = False @@ -111,6 +114,11 @@ class ModelerParametersDialog(QDialog): self.algorithmItem = None self.resize(650, 450) + + self.mainLayout = QVBoxLayout() + self.tab = QTabWidget() + self.mainLayout.addWidget(self.tab) + self.buttonBox = QDialogButtonBox() self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok | QDialogButtonBox.Help) @@ -118,7 +126,6 @@ class ModelerParametersDialog(QDialog): QSizePolicy.Expanding) self.verticalLayout = QVBoxLayout() self.verticalLayout.setSpacing(5) - self.verticalLayout.setMargin(20) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) @@ -229,13 +236,33 @@ class ModelerParametersDialog(QDialog): self.scrollArea.setWidgetResizable(True) self.verticalLayout2.addWidget(self.scrollArea) - self.verticalLayout2.addWidget(self.buttonBox) - self.setLayout(self.verticalLayout2) 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.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 = [] @@ -378,9 +405,9 @@ class ModelerParametersDialog(QDialog): [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()), @@ -419,6 +446,7 @@ class ModelerParametersDialog(QDialog): #except: # pass + alg.comment().setDescription(self.comments()) return alg def okPressed(self): diff --git a/python/plugins/processing/modeler/ModelerScene.py b/python/plugins/processing/modeler/ModelerScene.py index dd5b3352289..aaf1acc078f 100644 --- a/python/plugins/processing/modeler/ModelerScene.py +++ b/python/plugins/processing/modeler/ModelerScene.py @@ -25,7 +25,8 @@ from qgis.gui import QgsModelGraphicsScene from processing.modeler.ModelerGraphicItem import ( ModelerInputGraphicItem, ModelerOutputGraphicItem, - ModelerChildAlgorithmGraphicItem + ModelerChildAlgorithmGraphicItem, + ModelerCommentGraphicItem ) @@ -48,3 +49,6 @@ class ModelerScene(QgsModelGraphicsScene): def createOutputGraphicItem(self, model, output): return ModelerOutputGraphicItem(output.clone(), model) + + #def createCommentGraphicItem(self, model, comment, parent): + # return ModelerCommentGraphicItem(model, comment.clone(), parent) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 24f0a88c643..127670642aa 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -152,6 +152,7 @@ SET(QGIS_CORE_SRCS processing/models/qgsprocessingmodelalgorithm.cpp processing/models/qgsprocessingmodelchildalgorithm.cpp processing/models/qgsprocessingmodelchildparametersource.cpp + processing/models/qgsprocessingmodelcomment.cpp processing/models/qgsprocessingmodelcomponent.cpp processing/models/qgsprocessingmodelparameter.cpp processing/models/qgsprocessingmodeloutput.cpp @@ -1169,6 +1170,7 @@ SET(QGIS_CORE_HDRS processing/models/qgsprocessingmodelalgorithm.h processing/models/qgsprocessingmodelchildalgorithm.h processing/models/qgsprocessingmodelchildparametersource.h + processing/models/qgsprocessingmodelcomment.h processing/models/qgsprocessingmodelcomponent.h processing/models/qgsprocessingmodeloutput.h processing/models/qgsprocessingmodelparameter.h diff --git a/src/core/processing/models/qgsprocessingmodelchildalgorithm.cpp b/src/core/processing/models/qgsprocessingmodelchildalgorithm.cpp index 92d27a83db5..f385c606946 100644 --- a/src/core/processing/models/qgsprocessingmodelchildalgorithm.cpp +++ b/src/core/processing/models/qgsprocessingmodelchildalgorithm.cpp @@ -35,6 +35,7 @@ QgsProcessingModelChildAlgorithm::QgsProcessingModelChildAlgorithm( const QgsPro , mModelOutputs( other.mModelOutputs ) , mActive( other.mActive ) , mDependencies( other.mDependencies ) + , mComment( other.mComment ) { setAlgorithmId( other.algorithmId() ); } @@ -49,6 +50,7 @@ QgsProcessingModelChildAlgorithm &QgsProcessingModelChildAlgorithm::operator=( c mModelOutputs = other.mModelOutputs; mActive = other.mActive; mDependencies = other.mDependencies; + mComment = other.mComment; return *this; } @@ -89,6 +91,7 @@ QVariant QgsProcessingModelChildAlgorithm::toVariant() const map.insert( QStringLiteral( "alg_config" ), mConfiguration ); map.insert( QStringLiteral( "active" ), mActive ); map.insert( QStringLiteral( "dependencies" ), mDependencies ); + map.insert( QStringLiteral( "comment" ), mComment.toVariant() ); saveCommonProperties( map ); @@ -126,6 +129,7 @@ bool QgsProcessingModelChildAlgorithm::loadVariant( const QVariant &child ) setAlgorithmId( map.value( QStringLiteral( "alg_id" ) ).toString() ); mActive = map.value( QStringLiteral( "active" ) ).toBool(); mDependencies = map.value( QStringLiteral( "dependencies" ) ).toStringList(); + mComment.loadVariant( map.value( QStringLiteral( "comment" ) ).toMap() ); restoreCommonProperties( map ); diff --git a/src/core/processing/models/qgsprocessingmodelchildalgorithm.h b/src/core/processing/models/qgsprocessingmodelchildalgorithm.h index aa0b17b28ed..63cfbeda68b 100644 --- a/src/core/processing/models/qgsprocessingmodelchildalgorithm.h +++ b/src/core/processing/models/qgsprocessingmodelchildalgorithm.h @@ -23,6 +23,7 @@ #include "qgsprocessingmodelcomponent.h" #include "qgsprocessingmodelchildparametersource.h" #include "qgsprocessingmodeloutput.h" +#include "qgsprocessingmodelcomment.h" #include class QgsProcessingModelAlgorithm; @@ -261,6 +262,10 @@ class CORE_EXPORT QgsProcessingModelChildAlgorithm : public QgsProcessingModelCo QStringList asPythonCode( QgsProcessing::PythonOutputType outputType, const QgsStringMap &extraParameters, int currentIndent, int indentSize, const QMap &friendlyChildNames, const QMap &friendlyOutputNames ) const; + SIP_SKIP const QgsProcessingModelComment *comment() const override { return &mComment; } + QgsProcessingModelComment *comment() override { return &mComment; } + void setComment( const QgsProcessingModelComment &comment ) override { mComment = comment; } + private: QString mId; @@ -281,6 +286,8 @@ class CORE_EXPORT QgsProcessingModelChildAlgorithm : public QgsProcessingModelCo //! List of child algorithms from the parent model on which this algorithm is dependent QStringList mDependencies; + QgsProcessingModelComment mComment; + friend class TestQgsProcessing; }; diff --git a/src/core/processing/models/qgsprocessingmodelcomment.cpp b/src/core/processing/models/qgsprocessingmodelcomment.cpp new file mode 100644 index 00000000000..d003803d056 --- /dev/null +++ b/src/core/processing/models/qgsprocessingmodelcomment.cpp @@ -0,0 +1,47 @@ +/*************************************************************************** + qgsprocessingmodelcomment.cpp + -------------------------- + begin : February 2020 + copyright : (C) 2020 by Nyall Dawson + email : nyall dot dawson 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. * + * * + ***************************************************************************/ + +#include "qgsprocessingmodelcomment.h" + +///@cond NOT_STABLE + +QgsProcessingModelComment::QgsProcessingModelComment( const QString &description ) + : QgsProcessingModelComponent( description ) +{ + setSize( QSizeF( 100, 60 ) ); +} + +QgsProcessingModelComment *QgsProcessingModelComment::clone() const +{ + return new QgsProcessingModelComment( *this ); +} + +QVariant QgsProcessingModelComment::toVariant() const +{ + QVariantMap map; + saveCommonProperties( map ); + return map; +} + +bool QgsProcessingModelComment::loadVariant( const QVariantMap &map ) +{ + restoreCommonProperties( map ); + return true; +} + + +///@endcond diff --git a/src/core/processing/models/qgsprocessingmodelcomment.h b/src/core/processing/models/qgsprocessingmodelcomment.h new file mode 100644 index 00000000000..f5e2d155668 --- /dev/null +++ b/src/core/processing/models/qgsprocessingmodelcomment.h @@ -0,0 +1,59 @@ +/*************************************************************************** + qgsprocessingmodelcomment.h + -------------------------- + begin : February 2020 + copyright : (C) 2020 by Nyall Dawson + email : nyall dot dawson 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. * + * * + ***************************************************************************/ + +#ifndef QGSPROCESSINGMODELCOMMENT_H +#define QGSPROCESSINGMODELCOMMENT_H + +#include "qgis_core.h" +#include "qgis.h" +#include "qgsprocessingmodelcomponent.h" +#include "qgsprocessingparameters.h" + +///@cond NOT_STABLE + +/** + * Represents a comment in a model. + * \ingroup core + * \since QGIS 3.14 + */ +class CORE_EXPORT QgsProcessingModelComment : public QgsProcessingModelComponent +{ + public: + + /** + * Constructor for QgsProcessingModelComment with the specified \a description. + */ + QgsProcessingModelComment( const QString &description = QString() ); + + QgsProcessingModelComment *clone() const override SIP_FACTORY; + + /** + * Saves this comment to a QVariant. + * \see loadVariant() + */ + QVariant toVariant() const; + + /** + * Loads this comment from a QVariantMap. + * \see toVariant() + */ + bool loadVariant( const QVariantMap &map ); +}; + +///@endcond + +#endif // QGSPROCESSINGMODELCOMMENT_H diff --git a/src/core/processing/models/qgsprocessingmodelcomponent.cpp b/src/core/processing/models/qgsprocessingmodelcomponent.cpp index 723261f78e4..f6d00136038 100644 --- a/src/core/processing/models/qgsprocessingmodelcomponent.cpp +++ b/src/core/processing/models/qgsprocessingmodelcomponent.cpp @@ -88,6 +88,11 @@ void QgsProcessingModelComponent::setLinksCollapsed( Qt::Edge edge, bool collaps } } +void QgsProcessingModelComponent::setComment( const QgsProcessingModelComment & ) +{ + +} + void QgsProcessingModelComponent::saveCommonProperties( QVariantMap &map ) const { map.insert( QStringLiteral( "component_pos_x" ), mPosition.x() ); diff --git a/src/core/processing/models/qgsprocessingmodelcomponent.h b/src/core/processing/models/qgsprocessingmodelcomponent.h index 51acdfbd27f..38dc49e23d2 100644 --- a/src/core/processing/models/qgsprocessingmodelcomponent.h +++ b/src/core/processing/models/qgsprocessingmodelcomponent.h @@ -23,6 +23,8 @@ #include #include +class QgsProcessingModelComment; + ///@cond NOT_STABLE /** @@ -87,6 +89,24 @@ class CORE_EXPORT QgsProcessingModelComponent */ void setLinksCollapsed( Qt::Edge edge, bool collapsed ); + /** + * Returns the comment attached to this component (may be NULLPTR) + * \see setComment() + */ + SIP_SKIP virtual const QgsProcessingModelComment *comment() const { return nullptr; } + + /** + * Returns the comment attached to this component (may be NULLPTR) + * \see setComment() + */ + virtual QgsProcessingModelComment *comment() { return nullptr; } + + /** + * Sets the \a comment attached to this component. + * \see comment() + */ + virtual void setComment( const QgsProcessingModelComment &comment ); + /** * Clones the component. * diff --git a/src/core/processing/models/qgsprocessingmodeloutput.cpp b/src/core/processing/models/qgsprocessingmodeloutput.cpp index 18bddc85f35..a5eaa357f33 100644 --- a/src/core/processing/models/qgsprocessingmodeloutput.cpp +++ b/src/core/processing/models/qgsprocessingmodeloutput.cpp @@ -48,6 +48,7 @@ QVariant QgsProcessingModelOutput::toVariant() const map.insert( QStringLiteral( "child_id" ), mChildId ); map.insert( QStringLiteral( "output_name" ), mOutputName ); map.insert( QStringLiteral( "mandatory" ), mMandatory ); + map.insert( QStringLiteral( "comment" ), mComment.toVariant() ); saveCommonProperties( map ); return map; } @@ -79,6 +80,7 @@ bool QgsProcessingModelOutput::loadVariant( const QVariantMap &map ) mChildId = map.value( QStringLiteral( "child_id" ) ).toString(); mOutputName = map.value( QStringLiteral( "output_name" ) ).toString(); mMandatory = map.value( QStringLiteral( "mandatory" ), false ).toBool(); + mComment.loadVariant( map.value( QStringLiteral( "comment" ) ).toMap() ); restoreCommonProperties( map ); return true; } diff --git a/src/core/processing/models/qgsprocessingmodeloutput.h b/src/core/processing/models/qgsprocessingmodeloutput.h index 1e2541d9f87..f64b6905ee1 100644 --- a/src/core/processing/models/qgsprocessingmodeloutput.h +++ b/src/core/processing/models/qgsprocessingmodeloutput.h @@ -22,6 +22,7 @@ #include "qgis.h" #include "qgsprocessingmodelcomponent.h" #include "qgsprocessingparameters.h" +#include "qgsprocessingmodelcomment.h" ///@cond NOT_STABLE @@ -121,6 +122,10 @@ class CORE_EXPORT QgsProcessingModelOutput : public QgsProcessingModelComponent */ bool loadVariant( const QVariantMap &map ); + SIP_SKIP const QgsProcessingModelComment *comment() const override { return &mComment; } + QgsProcessingModelComment *comment() override { return &mComment; } + void setComment( const QgsProcessingModelComment &comment ) override { mComment = comment; } + private: QString mName; @@ -128,6 +133,9 @@ class CORE_EXPORT QgsProcessingModelOutput : public QgsProcessingModelComponent QString mChildId; QString mOutputName; bool mMandatory = false; + + QgsProcessingModelComment mComment; + }; ///@endcond diff --git a/src/core/processing/models/qgsprocessingmodelparameter.cpp b/src/core/processing/models/qgsprocessingmodelparameter.cpp index b721dcfa8b3..4cd52cafa59 100644 --- a/src/core/processing/models/qgsprocessingmodelparameter.cpp +++ b/src/core/processing/models/qgsprocessingmodelparameter.cpp @@ -34,6 +34,7 @@ QVariant QgsProcessingModelParameter::toVariant() const { QVariantMap map; map.insert( QStringLiteral( "name" ), mParameterName ); + map.insert( QStringLiteral( "comment" ), mComment.toVariant() ); saveCommonProperties( map ); return map; } @@ -41,6 +42,7 @@ QVariant QgsProcessingModelParameter::toVariant() const bool QgsProcessingModelParameter::loadVariant( const QVariantMap &map ) { mParameterName = map.value( QStringLiteral( "name" ) ).toString(); + mComment.loadVariant( map.value( QStringLiteral( "comment" ) ).toMap() ); restoreCommonProperties( map ); return true; } diff --git a/src/core/processing/models/qgsprocessingmodelparameter.h b/src/core/processing/models/qgsprocessingmodelparameter.h index 6b54d346be1..f6d63973e35 100644 --- a/src/core/processing/models/qgsprocessingmodelparameter.h +++ b/src/core/processing/models/qgsprocessingmodelparameter.h @@ -21,6 +21,7 @@ #include "qgis_core.h" #include "qgis.h" #include "qgsprocessingmodelcomponent.h" +#include "qgsprocessingmodelcomment.h" ///@cond NOT_STABLE @@ -68,10 +69,16 @@ class CORE_EXPORT QgsProcessingModelParameter : public QgsProcessingModelCompone */ bool loadVariant( const QVariantMap &map ); + SIP_SKIP const QgsProcessingModelComment *comment() const override { return &mComment; } + QgsProcessingModelComment *comment() override { return &mComment; } + void setComment( const QgsProcessingModelComment &comment ) override { mComment = comment; } + private: QString mParameterName; + QgsProcessingModelComment mComment; + }; ///@endcond diff --git a/src/core/qgsxmlutils.cpp b/src/core/qgsxmlutils.cpp index a3e0ce0ef29..13d32b59455 100644 --- a/src/core/qgsxmlutils.cpp +++ b/src/core/qgsxmlutils.cpp @@ -20,6 +20,7 @@ #include "qgsrectangle.h" #include "qgsproperty.h" #include "qgssymbollayerutils.h" +#include "qgsprocessingparameters.h" QgsUnitTypes::DistanceUnit QgsXmlUtils::readMapUnits( const QDomElement &element ) { @@ -213,7 +214,15 @@ QDomElement QgsXmlUtils::writeVariant( const QVariant &value, QDomDocument &doc element.setAttribute( QStringLiteral( "value" ), geom.asWkt() ); break; } - FALLTHROUGH + else if ( value.canConvert< QgsProcessingOutputLayerDefinition >() ) + { + QDomElement valueElement = writeVariant( value.value< QgsProcessingOutputLayerDefinition >().toVariant(), doc ); + element.appendChild( valueElement ); + element.setAttribute( QStringLiteral( "type" ), QStringLiteral( "QgsProcessingOutputLayerDefinition" ) ); + break; + } + Q_ASSERT_X( false, "QgsXmlUtils::writeVariant", QStringLiteral( "unsupported user variant type %1" ).arg( QMetaType::typeName( value.userType() ) ).toLocal8Bit() ); + break; } default: @@ -338,6 +347,18 @@ QVariant QgsXmlUtils::readVariant( const QDomElement &element ) { return QgsGeometry::fromWkt( element.attribute( "value" ) ); } + else if ( type == QLatin1String( "QgsProcessingOutputLayerDefinition" ) ) + { + QgsProcessingOutputLayerDefinition res; + const QDomNodeList values = element.childNodes(); + if ( values.isEmpty() ) + return QVariant(); + + if ( res.loadVariant( QgsXmlUtils::readVariant( values.at( 0 ).toElement() ).toMap() ) ) + return res; + + return QVariant(); + } else { return QVariant(); diff --git a/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp b/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp index 933ff21fdbb..898ddbf39bb 100644 --- a/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp +++ b/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp @@ -131,14 +131,7 @@ QVariant QgsModelComponentGraphicItem::itemChange( QGraphicsItem::GraphicsItemCh mComponent->setPosition( pos() ); // also need to update the model's stored component's position - // TODO - this is not so nice, consider moving this to model class - if ( QgsProcessingModelChildAlgorithm *child = dynamic_cast< QgsProcessingModelChildAlgorithm * >( mComponent.get() ) ) - mModel->childAlgorithm( child->childId() ).setPosition( pos() ); - else if ( QgsProcessingModelParameter *param = dynamic_cast< QgsProcessingModelParameter * >( mComponent.get() ) ) - mModel->parameterComponent( param->parameterName() ).setPosition( pos() ); - else if ( QgsProcessingModelOutput *output = dynamic_cast< QgsProcessingModelOutput * >( mComponent.get() ) ) - mModel->childAlgorithm( output->childId() ).modelOutput( output->name() ).setPosition( pos() ); - + updateStoredComponentPosition( pos() ); break; } case QGraphicsItem::ItemSelectedChange: @@ -197,7 +190,9 @@ void QgsModelComponentGraphicItem::paint( QPainter *painter, const QStyleOptionG QColor color = fillColor( state() ); QColor stroke = strokeColor( state() ); - painter->setPen( QPen( stroke, 0 ) ); // 0 width "cosmetic" pen + QPen strokePen = QPen( stroke, 0 ) ; // 0 width "cosmetic" pen + strokePen.setStyle( strokeStyle( state() ) ); + painter->setPen( strokePen ); painter->setBrush( QBrush( color, Qt::SolidPattern ) ); painter->drawRect( rect ); painter->setFont( font() ); @@ -291,6 +286,11 @@ QString QgsModelComponentGraphicItem::truncatedTextForItem( const QString &text return t; } +Qt::PenStyle QgsModelComponentGraphicItem::strokeStyle( QgsModelComponentGraphicItem::State ) const +{ + return Qt::SolidLine; +} + QPicture QgsModelComponentGraphicItem::iconPicture() const { return QPicture(); @@ -558,6 +558,12 @@ QPicture QgsModelParameterGraphicItem::iconPicture() const return mPicture; } +void QgsModelParameterGraphicItem::updateStoredComponentPosition( const QPointF &pos ) +{ + if ( QgsProcessingModelParameter *param = dynamic_cast< QgsProcessingModelParameter * >( component() ) ) + model()->parameterComponent( param->parameterName() ).setPosition( pos ); +} + void QgsModelParameterGraphicItem::deleteComponent() { if ( const QgsProcessingModelParameter *param = dynamic_cast< const QgsProcessingModelParameter * >( component() ) ) @@ -733,6 +739,12 @@ QString QgsModelChildAlgorithmGraphicItem::linkPointText( Qt::Edge edge, int ind return QString(); } +void QgsModelChildAlgorithmGraphicItem::updateStoredComponentPosition( const QPointF &pos ) +{ + if ( QgsProcessingModelChildAlgorithm *child = dynamic_cast< QgsProcessingModelChildAlgorithm * >( component() ) ) + model()->childAlgorithm( child->childId() ).setPosition( pos ); +} + void QgsModelChildAlgorithmGraphicItem::deleteComponent() { if ( const QgsProcessingModelChildAlgorithm *child = dynamic_cast< const QgsProcessingModelChildAlgorithm * >( component() ) ) @@ -830,6 +842,12 @@ QPicture QgsModelOutputGraphicItem::iconPicture() const return mPicture; } +void QgsModelOutputGraphicItem::updateStoredComponentPosition( const QPointF &pos ) +{ + if ( QgsProcessingModelOutput *output = dynamic_cast< QgsProcessingModelOutput * >( component() ) ) + model()->childAlgorithm( output->childId() ).modelOutput( output->name() ).setPosition( pos ); +} + void QgsModelOutputGraphicItem::deleteComponent() { if ( const QgsProcessingModelOutput *output = dynamic_cast< const QgsProcessingModelOutput * >( component() ) ) @@ -842,4 +860,115 @@ void QgsModelOutputGraphicItem::deleteComponent() } + + +QgsModelCommentGraphicItem::QgsModelCommentGraphicItem( QgsProcessingModelComment *comment, QgsModelComponentGraphicItem *parentItem, QgsProcessingModelAlgorithm *model, QGraphicsItem *parent ) + : QgsModelComponentGraphicItem( comment, model, parent ) + , mParentComponent( parentItem->component()->clone() ) + , mParentItem( parentItem ) +{ + setLabel( comment->description() ); + + QFont f = font(); + f.setPixelSize( 9 ); + setFont( f ); +} + +void QgsModelCommentGraphicItem::contextMenuEvent( QGraphicsSceneContextMenuEvent *event ) +{ + QMenu *popupmenu = new QMenu( event->widget() ); + QAction *removeAction = popupmenu->addAction( QObject::tr( "Remove" ) ); + connect( removeAction, &QAction::triggered, this, &QgsModelCommentGraphicItem::deleteComponent ); + QAction *editAction = popupmenu->addAction( QObject::tr( "Edit" ) ); + connect( editAction, &QAction::triggered, this, &QgsModelCommentGraphicItem::editComponent ); + popupmenu->exec( event->screenPos() ); +} + +QgsModelCommentGraphicItem::~QgsModelCommentGraphicItem() = default; + +QColor QgsModelCommentGraphicItem::fillColor( QgsModelComponentGraphicItem::State state ) const +{ + QColor c( 230, 230, 230 ); + switch ( state ) + { + case Selected: + c = c.darker( 110 ); + break; + case Hover: + c = c.darker( 105 ); + break; + + case Normal: + break; + } + return c; +} + +QColor QgsModelCommentGraphicItem::strokeColor( QgsModelComponentGraphicItem::State state ) const +{ + switch ( state ) + { + case Selected: + return QColor( 50, 50, 50 ); + case Hover: + case Normal: + return QColor( 150, 150, 150 ); + } + return QColor(); +} + +QColor QgsModelCommentGraphicItem::textColor( QgsModelComponentGraphicItem::State ) const +{ + return QColor( 100, 100, 100 ); +} + +Qt::PenStyle QgsModelCommentGraphicItem::strokeStyle( QgsModelComponentGraphicItem::State ) const +{ + return Qt::DotLine; +} + +void QgsModelCommentGraphicItem::updateStoredComponentPosition( const QPointF &pos ) +{ + if ( QgsProcessingModelComment *comment = modelComponent() ) + { + comment->setPosition( pos ); + } +} + +void QgsModelCommentGraphicItem::deleteComponent() +{ + if ( QgsProcessingModelComment *comment = modelComponent() ) + { + comment->setDescription( QString() ); + emit changed(); + emit requestModelRepaint(); + } +} + +void QgsModelCommentGraphicItem::editComponent() +{ + if ( mParentItem ) + { + mParentItem->editComment(); + } +} + +QgsProcessingModelComment *QgsModelCommentGraphicItem::modelComponent() +{ + if ( const QgsProcessingModelChildAlgorithm *child = dynamic_cast< const QgsProcessingModelChildAlgorithm * >( mParentComponent.get() ) ) + { + return model()->childAlgorithm( child->childId() ).comment(); + } + else if ( const QgsProcessingModelParameter *param = dynamic_cast< const QgsProcessingModelParameter * >( mParentComponent.get() ) ) + { + return model()->parameterComponent( param->parameterName() ).comment(); + } + else if ( const QgsProcessingModelOutput *output = dynamic_cast< const QgsProcessingModelOutput * >( mParentComponent.get() ) ) + { + return model()->childAlgorithm( output->childId() ).modelOutput( output->name() ).comment(); + } + return nullptr; +} + + ///@endcond diff --git a/src/gui/processing/models/qgsmodelcomponentgraphicitem.h b/src/gui/processing/models/qgsmodelcomponentgraphicitem.h index a6e5dfdadc6..ab75a3201a3 100644 --- a/src/gui/processing/models/qgsmodelcomponentgraphicitem.h +++ b/src/gui/processing/models/qgsmodelcomponentgraphicitem.h @@ -21,11 +21,13 @@ #include #include #include +#include class QgsProcessingModelComponent; class QgsProcessingModelParameter; class QgsProcessingModelChildAlgorithm; class QgsProcessingModelOutput; +class QgsProcessingModelComment; class QgsProcessingModelAlgorithm; class QgsModelDesignerFlatButtonGraphicItem; class QgsModelDesignerFoldButtonGraphicItem; @@ -158,6 +160,13 @@ class GUI_EXPORT QgsModelComponentGraphicItem : public QGraphicsObject */ QPointF calculateAutomaticLinkPoint( const QPointF &point, Qt::Edge &edge SIP_OUT ) const; + /** + * Called when the comment attached to the item should be edited. + * + * The default implementation does nothing. + */ + virtual void editComment() {} + signals: // TODO - rework this, should be triggered externally when the model actually changes! @@ -222,6 +231,11 @@ class GUI_EXPORT QgsModelComponentGraphicItem : public QGraphicsObject */ virtual QColor textColor( State state ) const = 0; + /** + * Returns the stroke style to use while rendering the outline of the item. + */ + virtual Qt::PenStyle strokeStyle( State state ) const; + /** * Returns a QPicture version of the item's icon, if available. */ @@ -232,6 +246,11 @@ class GUI_EXPORT QgsModelComponentGraphicItem : public QGraphicsObject */ virtual QPixmap iconPixmap() const; + /** + * Updates the position stored in the model for the associated comment + */ + virtual void updateStoredComponentPosition( const QPointF &pos ) = 0; + private: void updateToolTip( const QPointF &pos ); @@ -292,6 +311,7 @@ class GUI_EXPORT QgsModelParameterGraphicItem : public QgsModelComponentGraphicI QColor strokeColor( State state ) const override; QColor textColor( State state ) const override; QPicture iconPicture() const override; + void updateStoredComponentPosition( const QPointF &pos ) override; protected slots: @@ -337,6 +357,7 @@ class GUI_EXPORT QgsModelChildAlgorithmGraphicItem : public QgsModelComponentGra int linkPointCount( Qt::Edge edge ) const override; QString linkPointText( Qt::Edge edge, int index ) const override; + void updateStoredComponentPosition( const QPointF &pos ) override; protected slots: @@ -382,6 +403,7 @@ class GUI_EXPORT QgsModelOutputGraphicItem : public QgsModelComponentGraphicItem QColor strokeColor( State state ) const override; QColor textColor( State state ) const override; QPicture iconPicture() const override; + void updateStoredComponentPosition( const QPointF &pos ) override; protected slots: @@ -391,6 +413,57 @@ class GUI_EXPORT QgsModelOutputGraphicItem : public QgsModelComponentGraphicItem QPicture mPicture; }; + + + +/** + * \ingroup gui + * \brief A graphic item representing a model comment in the model designer. + * \warning Not stable API + * \since QGIS 3.14 + */ +class GUI_EXPORT QgsModelCommentGraphicItem : public QgsModelComponentGraphicItem +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsModelCommentGraphicItem for the specified \a comment, with the specified \a parent item. + * + * The \a model argument specifies the associated processing model. Ownership of \a model is not transferred, and + * it must exist for the lifetime of this object. + * + * Ownership of \a output is transferred to the item. + */ + QgsModelCommentGraphicItem( QgsProcessingModelComment *comment SIP_TRANSFER, + QgsModelComponentGraphicItem *parentItem, + QgsProcessingModelAlgorithm *model, + QGraphicsItem *parent SIP_TRANSFERTHIS ); + ~QgsModelCommentGraphicItem() override; + void contextMenuEvent( QGraphicsSceneContextMenuEvent *event ) override; + + protected: + + QColor fillColor( State state ) const override; + QColor strokeColor( State state ) const override; + QColor textColor( State state ) const override; + Qt::PenStyle strokeStyle( State state ) const override; + void updateStoredComponentPosition( const QPointF &pos ) override; + + protected slots: + + void deleteComponent() override; + void editComponent() override; + private: + + QgsProcessingModelComment *modelComponent(); + + std::unique_ptr< QgsProcessingModelComponent > mParentComponent; + QPointer< QgsModelComponentGraphicItem > mParentItem; + + +}; ///@endcond #endif // QGSMODELCOMPONENTGRAPHICITEM_H diff --git a/src/gui/processing/models/qgsmodelgraphicsscene.cpp b/src/gui/processing/models/qgsmodelgraphicsscene.cpp index c4d72ee35d7..4e2e0263c67 100644 --- a/src/gui/processing/models/qgsmodelgraphicsscene.cpp +++ b/src/gui/processing/models/qgsmodelgraphicsscene.cpp @@ -58,6 +58,11 @@ QgsModelComponentGraphicItem *QgsModelGraphicsScene::createOutputGraphicItem( Qg return new QgsModelOutputGraphicItem( output, model, nullptr ); } +QgsModelComponentGraphicItem *QgsModelGraphicsScene::createCommentGraphicItem( QgsProcessingModelAlgorithm *model, QgsProcessingModelComment *comment, QgsModelComponentGraphicItem *parentItem ) const +{ + return new QgsModelCommentGraphicItem( comment, parentItem, model, nullptr ); +} + void QgsModelGraphicsScene::createItems( QgsProcessingModelAlgorithm *model, QgsProcessingContext &context ) { // model input parameters @@ -70,6 +75,8 @@ void QgsModelGraphicsScene::createItems( QgsProcessingModelAlgorithm *model, Qgs mParameterItems.insert( it.value().parameterName(), item ); connect( item, &QgsModelComponentGraphicItem::requestModelRepaint, this, &QgsModelGraphicsScene::rebuildRequired ); connect( item, &QgsModelComponentGraphicItem::changed, this, &QgsModelGraphicsScene::componentChanged ); + + addCommentItemForComponent( model, it.value(), item ); } // input dependency arrows @@ -98,6 +105,8 @@ void QgsModelGraphicsScene::createItems( QgsProcessingModelAlgorithm *model, Qgs mChildAlgorithmItems.insert( it.value().childId(), item ); connect( item, &QgsModelComponentGraphicItem::requestModelRepaint, this, &QgsModelGraphicsScene::rebuildRequired ); connect( item, &QgsModelComponentGraphicItem::changed, this, &QgsModelGraphicsScene::componentChanged ); + + addCommentItemForComponent( model, it.value(), item ); } // arrows linking child algorithms @@ -148,6 +157,8 @@ void QgsModelGraphicsScene::createItems( QgsProcessingModelAlgorithm *model, Qgs connect( item, &QgsModelComponentGraphicItem::requestModelRepaint, this, &QgsModelGraphicsScene::rebuildRequired ); connect( item, &QgsModelComponentGraphicItem::changed, this, &QgsModelGraphicsScene::componentChanged ); + addCommentItemForComponent( model, outputIt.value(), item ); + QPointF pos = outputIt.value().position(); // find the actual index of the linked output from the child algorithm it comes from @@ -164,12 +175,7 @@ void QgsModelGraphicsScene::createItems( QgsProcessingModelAlgorithm *model, Qgs i++; } -#if 0 - if pos is None: - pos = ( alg.position() + QPointF( alg.size().width(), 0 ) - + self.algItems[alg.childId()].linkPoint( Qt.BottomEdge, idx ) ) -#endif - item->setPos( pos ); + item->setPos( pos ); outputItems.insert( outputIt.key(), item ); addItem( new QgsModelArrowItem( mChildAlgorithmItems[it.value().childId()], Qt::BottomEdge, idx, item ) ); } @@ -249,5 +255,21 @@ QList QgsModelGraphicsScene::linkSourcesForPa return res; } +void QgsModelGraphicsScene::addCommentItemForComponent( QgsProcessingModelAlgorithm *model, const QgsProcessingModelComponent &component, QgsModelComponentGraphicItem *parentItem ) +{ + if ( !component.comment() || component.comment()->description().isEmpty() ) + return; + + QgsModelComponentGraphicItem *commentItem = createCommentGraphicItem( model, component.comment()->clone(), parentItem ); + commentItem->setPos( component.comment()->position().x(), component.comment()->position().y() ); + addItem( commentItem ); + connect( commentItem, &QgsModelComponentGraphicItem::requestModelRepaint, this, &QgsModelGraphicsScene::rebuildRequired ); + connect( commentItem, &QgsModelComponentGraphicItem::changed, this, &QgsModelGraphicsScene::componentChanged ); + + std::unique_ptr< QgsModelArrowItem > arrow = qgis::make_unique< QgsModelArrowItem >( parentItem, commentItem ); + arrow->setPenStyle( Qt::DotLine ); + addItem( arrow.release() ); +} + ///@endcond diff --git a/src/gui/processing/models/qgsmodelgraphicsscene.h b/src/gui/processing/models/qgsmodelgraphicsscene.h index 5830db8ca2c..b35a25d5376 100644 --- a/src/gui/processing/models/qgsmodelgraphicsscene.h +++ b/src/gui/processing/models/qgsmodelgraphicsscene.h @@ -26,6 +26,8 @@ class QgsModelComponentGraphicItem; class QgsProcessingModelParameter; class QgsProcessingModelChildAlgorithm; class QgsProcessingModelOutput; +class QgsProcessingModelComponent; +class QgsProcessingModelComment; ///@cond NOT_STABLE @@ -118,6 +120,13 @@ class GUI_EXPORT QgsModelGraphicsScene : public QGraphicsScene */ virtual QgsModelComponentGraphicItem *createOutputGraphicItem( QgsProcessingModelAlgorithm *model, QgsProcessingModelOutput *output ) const SIP_FACTORY; + /** + * Creates a new graphic item for a model comment. + */ + virtual QgsModelComponentGraphicItem *createCommentGraphicItem( QgsProcessingModelAlgorithm *model, QgsProcessingModelComment *comment, + QgsModelComponentGraphicItem *parentItem ) const SIP_FACTORY; + + private: struct LinkSource @@ -128,6 +137,8 @@ class GUI_EXPORT QgsModelGraphicsScene : public QGraphicsScene }; QList< LinkSource > linkSourcesForParameterValue( QgsProcessingModelAlgorithm *model, const QVariant &value, const QString &childId, QgsProcessingContext &context ) const; + void addCommentItemForComponent( QgsProcessingModelAlgorithm *model, const QgsProcessingModelComponent &component, QgsModelComponentGraphicItem *parentItem ); + Flags mFlags = nullptr; QMap< QString, QgsModelComponentGraphicItem * > mParameterItems;