[processing][FEATURE] Add api for setting model component colors, and expose

the option to set manual colors for individual model comments
This commit is contained in:
Nyall Dawson 2020-04-01 09:48:06 +10:00
parent 25b2ba4158
commit e788be93fa
19 changed files with 397 additions and 43 deletions

View File

@ -69,6 +69,27 @@ Sets the ``size`` of the model component within the graphical modeler.
.. seealso:: :py:func:`size` .. seealso:: :py:func:`size`
.. versionadded:: 3.14
%End
QColor color() const;
%Docstring
Returns the color of the model component within the graphical modeler.
An invalid color indicates that the default color for the component should be used.
.. seealso:: :py:func:`setColor`
.. versionadded:: 3.14
%End
void setColor( const QColor &color );
%Docstring
Sets the ``color`` of the model component within the graphical modeler. An invalid ``color``
indicates that the default color for the component should be used.
.. seealso:: :py:func:`color`
.. versionadded:: 3.14 .. versionadded:: 3.14
%End %End

View File

@ -428,6 +428,7 @@
%Include auto_generated/processing/models/qgsprocessingmodelchildparametersource.sip %Include auto_generated/processing/models/qgsprocessingmodelchildparametersource.sip
%Include auto_generated/processing/models/qgsprocessingmodelcomment.sip %Include auto_generated/processing/models/qgsprocessingmodelcomment.sip
%Include auto_generated/processing/models/qgsprocessingmodelcomponent.sip %Include auto_generated/processing/models/qgsprocessingmodelcomponent.sip
%Include auto_generated/processing/models/qgsprocessingmodelgroupbox.sip
%Include auto_generated/processing/models/qgsprocessingmodeloutput.sip %Include auto_generated/processing/models/qgsprocessingmodeloutput.sip
%Include auto_generated/processing/models/qgsprocessingmodelparameter.sip %Include auto_generated/processing/models/qgsprocessingmodelparameter.sip
%Include auto_generated/processing/qgsprocessing.sip %Include auto_generated/processing/qgsprocessing.sip

View File

@ -157,6 +157,24 @@ Returns the comments for the parameter.
.. seealso:: :py:func:`setComments` .. seealso:: :py:func:`setComments`
.. versionadded:: 3.14
%End
void setCommentColor( const QColor &color );
%Docstring
Sets the color for the comments for the parameter.
.. seealso:: :py:func:`commentColor`
.. versionadded:: 3.14
%End
QColor commentColor() const;
%Docstring
Returns the color for the comments for the parameter.
.. seealso:: :py:func:`setCommentColor`
.. versionadded:: 3.14 .. versionadded:: 3.14
%End %End

View File

@ -210,11 +210,12 @@ Sets the string to use for the "no color" option in the button's drop-down menu.
dialog dialog
%End %End
void setShowNull( bool showNull ); void setShowNull( bool showNull, const QString &nullString = QString() );
%Docstring %Docstring
Sets whether a set to null (clear) option is shown in the button's drop-down menu. Sets whether a set to null (clear) option is shown in the button's drop-down menu.
:param showNull: set to ``True`` to show a null option :param showNull: set to ``True`` to show a null option
:param nullString: translated string to use for the null option. If not set, a default "Clear Color" string will be used.
.. seealso:: :py:func:`showNull` .. seealso:: :py:func:`showNull`

View File

@ -63,17 +63,20 @@ class ModelerInputGraphicItem(QgsModelParameterGraphicItem):
def edit(self, edit_comment=False): def edit(self, edit_comment=False):
existing_param = self.model().parameterDefinition(self.component().parameterName()) existing_param = self.model().parameterDefinition(self.component().parameterName())
comment = self.component().comment().description() comment = self.component().comment().description()
comment_color = self.component().comment().color()
new_param = None new_param = None
if ModelerParameterDefinitionDialog.use_legacy_dialog(param=existing_param): if ModelerParameterDefinitionDialog.use_legacy_dialog(param=existing_param):
# boo, old api # boo, old api
dlg = ModelerParameterDefinitionDialog(self.model(), dlg = ModelerParameterDefinitionDialog(self.model(),
param=existing_param) param=existing_param)
dlg.setComments(comment) dlg.setComments(comment)
dlg.setCommentColor(comment_color)
if edit_comment: if edit_comment:
dlg.switchToCommentTab() dlg.switchToCommentTab()
if dlg.exec_(): if dlg.exec_():
new_param = dlg.param new_param = dlg.param
comment = dlg.comments() comment = dlg.comments()
comment_color = dlg.commentColor()
else: else:
# yay, use new API! # yay, use new API!
context = createContext() context = createContext()
@ -84,12 +87,14 @@ class ModelerInputGraphicItem(QgsModelParameterGraphicItem):
definition=existing_param, definition=existing_param,
algorithm=self.model()) algorithm=self.model())
dlg.setComments(comment) dlg.setComments(comment)
dlg.setCommentColor(comment_color)
if edit_comment: if edit_comment:
dlg.switchToCommentTab() dlg.switchToCommentTab()
if dlg.exec_(): if dlg.exec_():
new_param = dlg.createParameter(existing_param.name()) new_param = dlg.createParameter(existing_param.name())
comment = dlg.comments() comment = dlg.comments()
comment_color = dlg.commentColor()
if new_param is not None: if new_param is not None:
self.aboutToChange.emit(self.tr('Edit {}').format(new_param.description())) self.aboutToChange.emit(self.tr('Edit {}').format(new_param.description()))
@ -97,6 +102,7 @@ class ModelerInputGraphicItem(QgsModelParameterGraphicItem):
self.component().setParameterName(new_param.name()) self.component().setParameterName(new_param.name())
self.component().setDescription(new_param.name()) self.component().setDescription(new_param.name())
self.component().comment().setDescription(comment) self.component().comment().setDescription(comment)
self.component().comment().setColor(comment_color)
self.model().addModelParameter(new_param, self.component()) self.model().addModelParameter(new_param, self.component())
self.setLabel(new_param.description()) self.setLabel(new_param.description())
self.requestModelRepaint.emit() self.requestModelRepaint.emit()
@ -125,6 +131,7 @@ class ModelerChildAlgorithmGraphicItem(QgsModelChildAlgorithmGraphicItem):
dlg = ModelerParametersDialog(elemAlg, self.model(), self.component().childId(), dlg = ModelerParametersDialog(elemAlg, self.model(), self.component().childId(),
self.component().configuration()) self.component().configuration())
dlg.setComments(self.component().comment().description()) dlg.setComments(self.component().comment().description())
dlg.setCommentColor(self.component().comment().color())
if edit_comment: if edit_comment:
dlg.switchToCommentTab() dlg.switchToCommentTab()
if dlg.exec_(): if dlg.exec_():
@ -160,6 +167,7 @@ class ModelerOutputGraphicItem(QgsModelOutputGraphicItem):
dlg = ModelerParameterDefinitionDialog(self.model(), dlg = ModelerParameterDefinitionDialog(self.model(),
param=self.model().parameterDefinition(param_name)) param=self.model().parameterDefinition(param_name))
dlg.setComments(self.component().comment().description()) dlg.setComments(self.component().comment().description())
dlg.setCommentColor(self.component().comment().color())
if edit_comment: if edit_comment:
dlg.switchToCommentTab() dlg.switchToCommentTab()
@ -169,6 +177,7 @@ class ModelerOutputGraphicItem(QgsModelOutputGraphicItem):
model_output.setDefaultValue(dlg.param.defaultValue()) model_output.setDefaultValue(dlg.param.defaultValue())
model_output.setMandatory(not (dlg.param.flags() & QgsProcessingParameterDefinition.FlagOptional)) model_output.setMandatory(not (dlg.param.flags() & QgsProcessingParameterDefinition.FlagOptional))
model_output.comment().setDescription(dlg.comments()) model_output.comment().setDescription(dlg.comments())
model_output.comment().setColor(dlg.commentColor())
self.aboutToChange.emit(self.tr('Edit {}').format(model_output.description())) self.aboutToChange.emit(self.tr('Edit {}').format(model_output.description()))
self.model().updateDestinationParameters() self.model().updateDestinationParameters()
self.requestModelRepaint.emit() self.requestModelRepaint.emit()

View File

@ -36,9 +36,12 @@ from qgis.PyQt.QtWidgets import (QDialog,
QMessageBox, QMessageBox,
QTabWidget, QTabWidget,
QWidget, QWidget,
QTextEdit) QTextEdit,
QHBoxLayout)
from qgis.PyQt.QtGui import QColor
from qgis.gui import (QgsProcessingLayerOutputDestinationWidget) from qgis.gui import (QgsProcessingLayerOutputDestinationWidget,
QgsColorButton)
from qgis.core import (QgsApplication, from qgis.core import (QgsApplication,
QgsSettings, QgsSettings,
QgsProcessing, QgsProcessing,
@ -210,8 +213,8 @@ class ModelerParameterDefinitionDialog(QDialog):
if self.param is not None: if self.param is not None:
self.shapetypeCombo.setCurrentIndex(self.shapetypeCombo.findData(self.param.dataTypes()[0])) self.shapetypeCombo.setCurrentIndex(self.shapetypeCombo.findData(self.param.dataTypes()[0]))
self.verticalLayout.addWidget(self.shapetypeCombo) self.verticalLayout.addWidget(self.shapetypeCombo)
elif (self.paramType == parameters.PARAMETER_MULTIPLE elif (self.paramType == parameters.PARAMETER_MULTIPLE or
or isinstance(self.param, QgsProcessingParameterMultipleLayers)): isinstance(self.param, QgsProcessingParameterMultipleLayers)):
self.verticalLayout.addWidget(QLabel(self.tr('Data type'))) self.verticalLayout.addWidget(QLabel(self.tr('Data type')))
self.datatypeCombo = QComboBox() self.datatypeCombo = QComboBox()
self.datatypeCombo.addItem(self.tr('Any Map Layer'), QgsProcessing.TypeMapLayer) self.datatypeCombo.addItem(self.tr('Any Map Layer'), QgsProcessing.TypeMapLayer)
@ -225,8 +228,8 @@ class ModelerParameterDefinitionDialog(QDialog):
if self.param is not None: if self.param is not None:
self.datatypeCombo.setCurrentIndex(self.datatypeCombo.findData(self.param.layerType())) self.datatypeCombo.setCurrentIndex(self.datatypeCombo.findData(self.param.layerType()))
self.verticalLayout.addWidget(self.datatypeCombo) self.verticalLayout.addWidget(self.datatypeCombo)
elif (self.paramType == parameters.PARAMETER_MAP_LAYER or elif (self.paramType == parameters.PARAMETER_MAP_LAYER
isinstance(self.param, QgsProcessingParameterMapLayer)): or isinstance(self.param, QgsProcessingParameterMapLayer)):
self.verticalLayout.addWidget(QLabel(self.tr('Data type'))) self.verticalLayout.addWidget(QLabel(self.tr('Data type')))
self.datatypeCombo = QComboBox() self.datatypeCombo = QComboBox()
self.datatypeCombo.addItem(self.tr('Any Map Layer'), QgsProcessing.TypeMapLayer) self.datatypeCombo.addItem(self.tr('Any Map Layer'), QgsProcessing.TypeMapLayer)
@ -239,11 +242,11 @@ class ModelerParameterDefinitionDialog(QDialog):
if self.param is not None: if self.param is not None:
self.datatypeCombo.setCurrentIndex(self.datatypeCombo.findData(self.param.dataTypes()[0])) self.datatypeCombo.setCurrentIndex(self.datatypeCombo.findData(self.param.dataTypes()[0]))
self.verticalLayout.addWidget(self.datatypeCombo) self.verticalLayout.addWidget(self.datatypeCombo)
elif (self.paramType in (parameters.PARAMETER_NUMBER, parameters.PARAMETER_DISTANCE, parameters.PARAMETER_SCALE) elif (self.paramType in (parameters.PARAMETER_NUMBER, parameters.PARAMETER_DISTANCE, parameters.PARAMETER_SCALE) or
or isinstance(self.param, (QgsProcessingParameterNumber, QgsProcessingParameterDistance, QgsProcessingParameterScale))): isinstance(self.param, (QgsProcessingParameterNumber, QgsProcessingParameterDistance, QgsProcessingParameterScale))):
if (self.paramType == parameters.PARAMETER_DISTANCE if (self.paramType == parameters.PARAMETER_DISTANCE or
or isinstance(self.param, QgsProcessingParameterDistance)): isinstance(self.param, QgsProcessingParameterDistance)):
self.verticalLayout.addWidget(QLabel(self.tr('Linked input'))) self.verticalLayout.addWidget(QLabel(self.tr('Linked input')))
self.parentCombo = QComboBox() self.parentCombo = QComboBox()
self.parentCombo.addItem('', '') self.parentCombo.addItem('', '')
@ -334,15 +337,26 @@ class ModelerParameterDefinitionDialog(QDialog):
self.commentLayout = QVBoxLayout() self.commentLayout = QVBoxLayout()
self.commentEdit = QTextEdit() self.commentEdit = QTextEdit()
self.commentEdit.setAcceptRichText(False) self.commentEdit.setAcceptRichText(False)
self.commentLayout.addWidget(self.commentEdit) self.commentLayout.addWidget(self.commentEdit, 1)
hl = QHBoxLayout()
hl.setContentsMargins(0, 0, 0, 0)
hl.addWidget(QLabel(self.tr('Color')))
self.comment_color_button = QgsColorButton()
self.comment_color_button.setAllowOpacity(True)
self.comment_color_button.setWindowTitle(self.tr('Comment Color'))
self.comment_color_button.setShowNull(True, self.tr('Default'))
hl.addWidget(self.comment_color_button)
self.commentLayout.addLayout(hl)
w2 = QWidget() w2 = QWidget()
w2.setLayout(self.commentLayout) w2.setLayout(self.commentLayout)
self.tab.addTab(w2, self.tr('Comments')) self.tab.addTab(w2, self.tr('Comments'))
self.buttonBox = QDialogButtonBox(self) self.buttonBox = QDialogButtonBox(self)
self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setOrientation(Qt.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel |
| QDialogButtonBox.Ok) QDialogButtonBox.Ok)
self.buttonBox.setObjectName('buttonBox') self.buttonBox.setObjectName('buttonBox')
self.buttonBox.accepted.connect(self.accept) self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject) self.buttonBox.rejected.connect(self.reject)
@ -357,6 +371,15 @@ class ModelerParameterDefinitionDialog(QDialog):
def comments(self): def comments(self):
return self.commentEdit.toPlainText() return self.commentEdit.toPlainText()
def setCommentColor(self, color):
if color.isValid():
self.comment_color_button.setColor(color)
else:
self.comment_color_button.setToNull()
def commentColor(self):
return self.comment_color_button.color() if not self.comment_color_button.isNull() else QColor()
def accept(self): def accept(self):
description = self.nameTextBox.text() description = self.nameTextBox.text()
if description.strip() == '': if description.strip() == '':
@ -374,8 +397,8 @@ class ModelerParameterDefinitionDialog(QDialog):
i += 1 i += 1
else: else:
name = self.param.name() name = self.param.name()
if (self.paramType == parameters.PARAMETER_TABLE_FIELD if (self.paramType == parameters.PARAMETER_TABLE_FIELD or
or isinstance(self.param, QgsProcessingParameterField)): isinstance(self.param, QgsProcessingParameterField)):
if self.parentCombo.currentIndex() < 0: if self.parentCombo.currentIndex() < 0:
QMessageBox.warning(self, self.tr('Unable to define parameter'), QMessageBox.warning(self, self.tr('Unable to define parameter'),
self.tr('Wrong or missing parameter values')) self.tr('Wrong or missing parameter values'))
@ -388,39 +411,39 @@ class ModelerParameterDefinitionDialog(QDialog):
self.param = QgsProcessingParameterField(name, description, defaultValue=default, self.param = QgsProcessingParameterField(name, description, defaultValue=default,
parentLayerParameterName=parent, type=datatype, parentLayerParameterName=parent, type=datatype,
allowMultiple=self.multipleCheck.isChecked()) allowMultiple=self.multipleCheck.isChecked())
elif (self.paramType == parameters.PARAMETER_BAND elif (self.paramType == parameters.PARAMETER_BAND or
or isinstance(self.param, QgsProcessingParameterBand)): isinstance(self.param, QgsProcessingParameterBand)):
if self.parentCombo.currentIndex() < 0: if self.parentCombo.currentIndex() < 0:
QMessageBox.warning(self, self.tr('Unable to define parameter'), QMessageBox.warning(self, self.tr('Unable to define parameter'),
self.tr('Wrong or missing parameter values')) self.tr('Wrong or missing parameter values'))
return return
parent = self.parentCombo.currentData() parent = self.parentCombo.currentData()
self.param = QgsProcessingParameterBand(name, description, None, parent) self.param = QgsProcessingParameterBand(name, description, None, parent)
elif (self.paramType == parameters.PARAMETER_MAP_LAYER elif (self.paramType == parameters.PARAMETER_MAP_LAYER or
or isinstance(self.param, QgsProcessingParameterMapLayer)): isinstance(self.param, QgsProcessingParameterMapLayer)):
self.param = QgsProcessingParameterMapLayer( self.param = QgsProcessingParameterMapLayer(
name, description, types=[self.datatypeCombo.currentData()]) name, description, types=[self.datatypeCombo.currentData()])
elif (self.paramType == parameters.PARAMETER_RASTER elif (self.paramType == parameters.PARAMETER_RASTER or
or isinstance(self.param, QgsProcessingParameterRasterLayer)): isinstance(self.param, QgsProcessingParameterRasterLayer)):
self.param = QgsProcessingParameterRasterLayer( self.param = QgsProcessingParameterRasterLayer(
name, description) name, description)
elif (self.paramType == parameters.PARAMETER_TABLE elif (self.paramType == parameters.PARAMETER_TABLE or
or isinstance(self.param, QgsProcessingParameterVectorLayer)): isinstance(self.param, QgsProcessingParameterVectorLayer)):
self.param = QgsProcessingParameterVectorLayer( self.param = QgsProcessingParameterVectorLayer(
name, description, name, description,
[self.shapetypeCombo.currentData()]) [self.shapetypeCombo.currentData()])
elif (self.paramType == parameters.PARAMETER_VECTOR elif (self.paramType == parameters.PARAMETER_VECTOR or
or isinstance(self.param, QgsProcessingParameterFeatureSource)): isinstance(self.param, QgsProcessingParameterFeatureSource)):
self.param = QgsProcessingParameterFeatureSource( self.param = QgsProcessingParameterFeatureSource(
name, description, name, description,
[self.shapetypeCombo.currentData()]) [self.shapetypeCombo.currentData()])
elif (self.paramType == parameters.PARAMETER_MULTIPLE elif (self.paramType == parameters.PARAMETER_MULTIPLE or
or isinstance(self.param, QgsProcessingParameterMultipleLayers)): isinstance(self.param, QgsProcessingParameterMultipleLayers)):
self.param = QgsProcessingParameterMultipleLayers( self.param = QgsProcessingParameterMultipleLayers(
name, description, name, description,
self.datatypeCombo.currentData()) self.datatypeCombo.currentData())
elif (self.paramType == parameters.PARAMETER_DISTANCE elif (self.paramType == parameters.PARAMETER_DISTANCE or
or isinstance(self.param, QgsProcessingParameterDistance)): isinstance(self.param, QgsProcessingParameterDistance)):
self.param = QgsProcessingParameterDistance(name, description, self.param = QgsProcessingParameterDistance(name, description,
self.defaultTextBox.text()) self.defaultTextBox.text())
try: try:
@ -442,12 +465,12 @@ class ModelerParameterDefinitionDialog(QDialog):
parent = self.parentCombo.currentData() parent = self.parentCombo.currentData()
if parent: if parent:
self.param.setParentParameterName(parent) self.param.setParentParameterName(parent)
elif (self.paramType == parameters.PARAMETER_SCALE elif (self.paramType == parameters.PARAMETER_SCALE or
or isinstance(self.param, QgsProcessingParameterScale)): isinstance(self.param, QgsProcessingParameterScale)):
self.param = QgsProcessingParameterScale(name, description, self.param = QgsProcessingParameterScale(name, description,
self.defaultTextBox.text()) self.defaultTextBox.text())
elif (self.paramType == parameters.PARAMETER_NUMBER elif (self.paramType == parameters.PARAMETER_NUMBER or
or isinstance(self.param, QgsProcessingParameterNumber)): isinstance(self.param, QgsProcessingParameterNumber)):
type = self.type_combo.currentData() type = self.type_combo.currentData()
self.param = QgsProcessingParameterNumber(name, description, type, self.param = QgsProcessingParameterNumber(name, description, type,

View File

@ -27,6 +27,7 @@ from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import (QDialog, QDialogButtonBox, QLabel, QLineEdit, from qgis.PyQt.QtWidgets import (QDialog, QDialogButtonBox, QLabel, QLineEdit,
QFrame, QPushButton, QSizePolicy, QVBoxLayout, QFrame, QPushButton, QSizePolicy, QVBoxLayout,
QHBoxLayout, QWidget, QTabWidget, QTextEdit) QHBoxLayout, QWidget, QTabWidget, QTextEdit)
from qgis.PyQt.QtGui import QColor
from qgis.core import (Qgis, from qgis.core import (Qgis,
QgsProject, QgsProject,
@ -50,7 +51,8 @@ from qgis.gui import (QgsGui,
QgsProcessingModelerParameterWidget, QgsProcessingModelerParameterWidget,
QgsProcessingParameterWidgetContext, QgsProcessingParameterWidgetContext,
QgsPanelWidget, QgsPanelWidget,
QgsPanelWidgetStack) QgsPanelWidgetStack,
QgsColorButton)
from qgis.utils import iface from qgis.utils import iface
from processing.gui.wrappers import WidgetWrapperFactory from processing.gui.wrappers import WidgetWrapperFactory
@ -101,6 +103,12 @@ class ModelerParametersDialog(QDialog):
def comments(self): def comments(self):
return self.widget.comments() return self.widget.comments()
def setCommentColor(self, color):
self.widget.setCommentColor(color)
def commentColor(self):
return self.widget.commentColor()
def switchToCommentTab(self): def switchToCommentTab(self):
self.widget.switchToCommentTab() self.widget.switchToCommentTab()
@ -534,7 +542,18 @@ class ModelerParametersWidget(QWidget):
self.commentLayout = QVBoxLayout() self.commentLayout = QVBoxLayout()
self.commentEdit = QTextEdit() self.commentEdit = QTextEdit()
self.commentEdit.setAcceptRichText(False) self.commentEdit.setAcceptRichText(False)
self.commentLayout.addWidget(self.commentEdit) self.commentLayout.addWidget(self.commentEdit, 1)
hl = QHBoxLayout()
hl.setContentsMargins(0, 0, 0, 0)
hl.addWidget(QLabel(self.tr('Color')))
self.comment_color_button = QgsColorButton()
self.comment_color_button.setAllowOpacity(True)
self.comment_color_button.setWindowTitle(self.tr('Comment Color'))
self.comment_color_button.setShowNull(True, self.tr('Default'))
hl.addWidget(self.comment_color_button)
self.commentLayout.addLayout(hl)
w2 = QWidget() w2 = QWidget()
w2.setLayout(self.commentLayout) w2.setLayout(self.commentLayout)
self.tab.addTab(w2, self.tr('Comments')) self.tab.addTab(w2, self.tr('Comments'))
@ -547,6 +566,15 @@ class ModelerParametersWidget(QWidget):
def comments(self): def comments(self):
return self.commentEdit.toPlainText() return self.commentEdit.toPlainText()
def setCommentColor(self, color):
if color.isValid():
self.comment_color_button.setColor(color)
else:
self.comment_color_button.setToNull()
def commentColor(self):
return self.comment_color_button.color() if not self.comment_color_button.isNull() else QColor()
def getAvailableDependencies(self): def getAvailableDependencies(self):
return self.widget.getAvailableDependencies() return self.widget.getAvailableDependencies()
@ -566,4 +594,5 @@ class ModelerParametersWidget(QWidget):
alg = self.widget.createAlgorithm() alg = self.widget.createAlgorithm()
if alg: if alg:
alg.comment().setDescription(self.comments()) alg.comment().setDescription(self.comments())
alg.comment().setColor(self.commentColor())
return alg return alg

View File

@ -155,6 +155,7 @@ SET(QGIS_CORE_SRCS
processing/models/qgsprocessingmodelchildparametersource.cpp processing/models/qgsprocessingmodelchildparametersource.cpp
processing/models/qgsprocessingmodelcomment.cpp processing/models/qgsprocessingmodelcomment.cpp
processing/models/qgsprocessingmodelcomponent.cpp processing/models/qgsprocessingmodelcomponent.cpp
processing/models/qgsprocessingmodelgroupbox.cpp
processing/models/qgsprocessingmodelparameter.cpp processing/models/qgsprocessingmodelparameter.cpp
processing/models/qgsprocessingmodeloutput.cpp processing/models/qgsprocessingmodeloutput.cpp
@ -1197,6 +1198,7 @@ SET(QGIS_CORE_HDRS
processing/models/qgsprocessingmodelchildparametersource.h processing/models/qgsprocessingmodelchildparametersource.h
processing/models/qgsprocessingmodelcomment.h processing/models/qgsprocessingmodelcomment.h
processing/models/qgsprocessingmodelcomponent.h processing/models/qgsprocessingmodelcomponent.h
processing/models/qgsprocessingmodelgroupbox.h
processing/models/qgsprocessingmodeloutput.h processing/models/qgsprocessingmodeloutput.h
processing/models/qgsprocessingmodelparameter.h processing/models/qgsprocessingmodelparameter.h
processing/qgsprocessing.h processing/qgsprocessing.h

View File

@ -68,7 +68,10 @@ void QgsProcessingModelChildAlgorithm::copyNonDefinitionPropertiesFromModel( Qgs
for ( auto it = mModelOutputs.begin(); it != mModelOutputs.end(); ++it ) for ( auto it = mModelOutputs.begin(); it != mModelOutputs.end(); ++it )
{ {
if ( !existingChild.modelOutputs().value( it.key() ).position().isNull() ) if ( !existingChild.modelOutputs().value( it.key() ).position().isNull() )
{
it.value().setPosition( existingChild.modelOutputs().value( it.key() ).position() ); it.value().setPosition( existingChild.modelOutputs().value( it.key() ).position() );
it.value().setSize( existingChild.modelOutputs().value( it.key() ).size() );
}
else else
it.value().setPosition( position() + QPointF( size().width(), ( i + 1.5 ) * size().height() ) ); it.value().setPosition( position() + QPointF( size().width(), ( i + 1.5 ) * size().height() ) );
@ -79,6 +82,7 @@ void QgsProcessingModelChildAlgorithm::copyNonDefinitionPropertiesFromModel( Qgs
comment->setDescription( existingComment->description() ); comment->setDescription( existingComment->description() );
comment->setSize( existingComment->size() ); comment->setSize( existingComment->size() );
comment->setPosition( existingComment->position() ); comment->setPosition( existingComment->position() );
comment->setColor( existingComment->color() );
} }
} }
i++; i++;

View File

@ -17,6 +17,7 @@
#include "qgsprocessingmodelcomponent.h" #include "qgsprocessingmodelcomponent.h"
#include "qgsprocessingmodelcomment.h" #include "qgsprocessingmodelcomment.h"
#include "qgssymbollayerutils.h"
///@cond NOT_STABLE ///@cond NOT_STABLE
@ -54,6 +55,16 @@ void QgsProcessingModelComponent::setSize( QSizeF size )
mSize = size; mSize = size;
} }
QColor QgsProcessingModelComponent::color() const
{
return mColor;
}
void QgsProcessingModelComponent::setColor( const QColor &color )
{
mColor = color;
}
bool QgsProcessingModelComponent::linksCollapsed( Qt::Edge edge ) const bool QgsProcessingModelComponent::linksCollapsed( Qt::Edge edge ) const
{ {
switch ( edge ) switch ( edge )
@ -103,6 +114,7 @@ void QgsProcessingModelComponent::saveCommonProperties( QVariantMap &map ) const
map.insert( QStringLiteral( "component_height" ), mSize.height() ); map.insert( QStringLiteral( "component_height" ), mSize.height() );
map.insert( QStringLiteral( "parameters_collapsed" ), mTopEdgeLinksCollapsed ); map.insert( QStringLiteral( "parameters_collapsed" ), mTopEdgeLinksCollapsed );
map.insert( QStringLiteral( "outputs_collapsed" ), mBottomEdgeLinksCollapsed ); map.insert( QStringLiteral( "outputs_collapsed" ), mBottomEdgeLinksCollapsed );
map.insert( QStringLiteral( "color" ), mColor.isValid() ? QgsSymbolLayerUtils::encodeColor( mColor ) : QString() );
if ( comment() ) if ( comment() )
map.insert( QStringLiteral( "comment" ), comment()->toVariant() ); map.insert( QStringLiteral( "comment" ), comment()->toVariant() );
} }
@ -116,6 +128,7 @@ void QgsProcessingModelComponent::restoreCommonProperties( const QVariantMap &ma
mDescription = map.value( QStringLiteral( "component_description" ) ).toString(); mDescription = map.value( QStringLiteral( "component_description" ) ).toString();
mSize.setWidth( map.value( QStringLiteral( "component_width" ), QString::number( DEFAULT_COMPONENT_WIDTH ) ).toDouble() ); mSize.setWidth( map.value( QStringLiteral( "component_width" ), QString::number( DEFAULT_COMPONENT_WIDTH ) ).toDouble() );
mSize.setHeight( map.value( QStringLiteral( "component_height" ), QString::number( DEFAULT_COMPONENT_HEIGHT ) ).toDouble() ); mSize.setHeight( map.value( QStringLiteral( "component_height" ), QString::number( DEFAULT_COMPONENT_HEIGHT ) ).toDouble() );
mColor = map.value( QStringLiteral( "color" ) ).toString().isEmpty() ? QColor() : QgsSymbolLayerUtils::decodeColor( map.value( QStringLiteral( "color" ) ).toString() );
mTopEdgeLinksCollapsed = map.value( QStringLiteral( "parameters_collapsed" ) ).toBool(); mTopEdgeLinksCollapsed = map.value( QStringLiteral( "parameters_collapsed" ) ).toBool();
mBottomEdgeLinksCollapsed = map.value( QStringLiteral( "outputs_collapsed" ) ).toBool(); mBottomEdgeLinksCollapsed = map.value( QStringLiteral( "outputs_collapsed" ) ).toBool();
if ( comment() ) if ( comment() )

View File

@ -22,6 +22,7 @@
#include "qgis.h" #include "qgis.h"
#include <QPointF> #include <QPointF>
#include <QSizeF> #include <QSizeF>
#include <QColor>
class QgsProcessingModelComment; class QgsProcessingModelComment;
@ -76,6 +77,25 @@ class CORE_EXPORT QgsProcessingModelComponent
*/ */
void setSize( QSizeF size ); void setSize( QSizeF size );
/**
* Returns the color of the model component within the graphical modeler.
*
* An invalid color indicates that the default color for the component should be used.
*
* \see setColor()
* \since QGIS 3.14
*/
QColor color() const;
/**
* Sets the \a color of the model component within the graphical modeler. An invalid \a color
* indicates that the default color for the component should be used.
*
* \see color()
* \since QGIS 3.14
*/
void setColor( const QColor &color );
/** /**
* Returns TRUE if the link points for the specified \a edge should be shown collapsed or not. * Returns TRUE if the link points for the specified \a edge should be shown collapsed or not.
* \see setLinksCollapsed() * \see setLinksCollapsed()
@ -158,6 +178,7 @@ class CORE_EXPORT QgsProcessingModelComponent
QString mDescription; QString mDescription;
QSizeF mSize = QSizeF( DEFAULT_COMPONENT_WIDTH, DEFAULT_COMPONENT_HEIGHT ); QSizeF mSize = QSizeF( DEFAULT_COMPONENT_WIDTH, DEFAULT_COMPONENT_HEIGHT );
QColor mColor;
bool mTopEdgeLinksCollapsed = true; bool mTopEdgeLinksCollapsed = true;
bool mBottomEdgeLinksCollapsed = true; bool mBottomEdgeLinksCollapsed = true;

View File

@ -0,0 +1,47 @@
/***************************************************************************
qgsprocessingmodelgroupbox.cpp
--------------------------
begin : March 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 "qgsprocessingmodelgroupbox.h"
///@cond NOT_STABLE
QgsProcessingModelGroupBox::QgsProcessingModelGroupBox( const QString &description )
: QgsProcessingModelComponent( description )
{
setSize( QSizeF( 100, 60 ) );
}
QgsProcessingModelGroupBox *QgsProcessingModelGroupBox::clone() const
{
return new QgsProcessingModelGroupBox( *this );
}
QVariant QgsProcessingModelGroupBox::toVariant() const
{
QVariantMap map;
saveCommonProperties( map );
return map;
}
bool QgsProcessingModelGroupBox::loadVariant( const QVariantMap &map )
{
restoreCommonProperties( map );
return true;
}
///@endcond

View File

@ -0,0 +1,59 @@
/***************************************************************************
qgsprocessingmodelgroupbox.h
--------------------------
begin : March 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 QGSPROCESSINGMODELGROUPBOX_H
#define QGSPROCESSINGMODELGROUPBOX_H
#include "qgis_core.h"
#include "qgis.h"
#include "qgsprocessingmodelcomponent.h"
#include "qgsprocessingparameters.h"
///@cond NOT_STABLE
/**
* Represents a group box in a model.
* \ingroup core
* \since QGIS 3.14
*/
class CORE_EXPORT QgsProcessingModelGroupBox : public QgsProcessingModelComponent
{
public:
/**
* Constructor for QgsProcessingModelGroupBox with the specified \a description.
*/
QgsProcessingModelGroupBox( const QString &description = QString() );
QgsProcessingModelGroupBox *clone() const override SIP_FACTORY;
/**
* Saves this group box to a QVariant.
* \see loadVariant()
*/
QVariant toVariant() const;
/**
* Loads this group box from a QVariantMap.
* \see toVariant()
*/
bool loadVariant( const QVariantMap &map );
};
///@endcond
#endif // QGSPROCESSINGMODELGROUPBOX_H

View File

@ -318,8 +318,33 @@ bool QgsModelComponentGraphicItem::contains( const QPointF &point ) const
void QgsModelComponentGraphicItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *, QWidget * ) void QgsModelComponentGraphicItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *, QWidget * )
{ {
const QRectF rect = itemRect(); const QRectF rect = itemRect();
QColor color = fillColor( state() ); QColor color;
QColor stroke = strokeColor( state() ); QColor stroke;
QColor foreColor;
if ( mComponent->color().isValid() )
{
color = mComponent->color();
switch ( state() )
{
case Selected:
color = color.darker( 110 );
break;
case Hover:
color = color.darker( 105 );
break;
case Normal:
break;
}
stroke = color.darker( 110 );
foreColor = color.lightness() > 150 ? QColor( 0, 0, 0 ) : QColor( 255, 255, 255 );
}
else
{
color = fillColor( state() );
stroke = strokeColor( state() );
foreColor = textColor( state() );
}
QPen strokePen = QPen( stroke, 0 ) ; // 0 width "cosmetic" pen QPen strokePen = QPen( stroke, 0 ) ; // 0 width "cosmetic" pen
strokePen.setStyle( strokeStyle( state() ) ); strokePen.setStyle( strokeStyle( state() ) );
@ -327,7 +352,7 @@ void QgsModelComponentGraphicItem::paint( QPainter *painter, const QStyleOptionG
painter->setBrush( QBrush( color, Qt::SolidPattern ) ); painter->setBrush( QBrush( color, Qt::SolidPattern ) );
painter->drawRect( rect ); painter->drawRect( rect );
painter->setFont( font() ); painter->setFont( font() );
painter->setPen( QPen( textColor( state() ) ) ); painter->setPen( QPen( foreColor ) );
QString text; QString text;

View File

@ -22,6 +22,7 @@
#include "qgsapplication.h" #include "qgsapplication.h"
#include "qgsprocessingregistry.h" #include "qgsprocessingregistry.h"
#include "qgsprocessingparametertype.h" #include "qgsprocessingparametertype.h"
#include "qgscolorbutton.h"
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
@ -145,7 +146,18 @@ QgsProcessingParameterDefinitionDialog::QgsProcessingParameterDefinitionDialog(
QVBoxLayout *commentLayout = new QVBoxLayout(); QVBoxLayout *commentLayout = new QVBoxLayout();
mCommentEdit = new QTextEdit(); mCommentEdit = new QTextEdit();
mCommentEdit->setAcceptRichText( false ); mCommentEdit->setAcceptRichText( false );
commentLayout->addWidget( mCommentEdit ); commentLayout->addWidget( mCommentEdit, 1 );
QHBoxLayout *hl = new QHBoxLayout();
hl->setContentsMargins( 0, 0, 0, 0 );
hl->addWidget( new QLabel( tr( "Color" ) ) );
mCommentColorButton = new QgsColorButton();
mCommentColorButton->setAllowOpacity( true );
mCommentColorButton->setWindowTitle( tr( "Comment Color" ) );
mCommentColorButton->setShowNull( true, tr( "Default" ) );
hl->addWidget( mCommentColorButton );
commentLayout->addLayout( hl );
QWidget *w2 = new QWidget(); QWidget *w2 = new QWidget();
w2->setLayout( commentLayout ); w2->setLayout( commentLayout );
mTabWidget->addTab( w2, tr( "Comments" ) ); mTabWidget->addTab( w2, tr( "Comments" ) );
@ -178,6 +190,19 @@ QString QgsProcessingParameterDefinitionDialog::comments() const
return mCommentEdit->toPlainText(); return mCommentEdit->toPlainText();
} }
void QgsProcessingParameterDefinitionDialog::setCommentColor( const QColor &color )
{
if ( color.isValid() )
mCommentColorButton->setColor( color );
else
mCommentColorButton->setToNull();
}
QColor QgsProcessingParameterDefinitionDialog::commentColor() const
{
return !mCommentColorButton->isNull() ? mCommentColorButton->color() : QColor();
}
void QgsProcessingParameterDefinitionDialog::switchToCommentTab() void QgsProcessingParameterDefinitionDialog::switchToCommentTab()
{ {
mTabWidget->setCurrentIndex( 1 ); mTabWidget->setCurrentIndex( 1 );

View File

@ -31,6 +31,7 @@ class QLineEdit;
class QCheckBox; class QCheckBox;
class QTabWidget; class QTabWidget;
class QTextEdit; class QTextEdit;
class QgsColorButton;
/** /**
* Abstract base class for widgets which allow users to specify the properties of a * Abstract base class for widgets which allow users to specify the properties of a
@ -183,6 +184,20 @@ class GUI_EXPORT QgsProcessingParameterDefinitionDialog: public QDialog
*/ */
QString comments() const; QString comments() const;
/**
* Sets the color for the comments for the parameter.
* \see commentColor()
* \since QGIS 3.14
*/
void setCommentColor( const QColor &color );
/**
* Returns the color for the comments for the parameter.
* \see setCommentColor()
* \since QGIS 3.14
*/
QColor commentColor() const;
/** /**
* Switches the dialog to the comments tab. * Switches the dialog to the comments tab.
*/ */
@ -195,6 +210,7 @@ class GUI_EXPORT QgsProcessingParameterDefinitionDialog: public QDialog
QTabWidget *mTabWidget = nullptr; QTabWidget *mTabWidget = nullptr;
QTextEdit *mCommentEdit = nullptr; QTextEdit *mCommentEdit = nullptr;
QgsColorButton *mCommentColorButton = nullptr;
QgsProcessingParameterDefinitionWidget *mWidget = nullptr; QgsProcessingParameterDefinitionWidget *mWidget = nullptr;
}; };

View File

@ -508,7 +508,7 @@ void QgsColorButton::prepareMenu()
{ {
if ( mShowNull ) if ( mShowNull )
{ {
QAction *nullAction = new QAction( tr( "Clear Color" ), this ); QAction *nullAction = new QAction( mNullColorString.isEmpty() ? tr( "Clear Color" ) : mNullColorString, this );
nullAction->setIcon( createMenuIcon( Qt::transparent, false ) ); nullAction->setIcon( createMenuIcon( Qt::transparent, false ) );
mMenu->addAction( nullAction ); mMenu->addAction( nullAction );
connect( nullAction, &QAction::triggered, this, &QgsColorButton::setToNull ); connect( nullAction, &QAction::triggered, this, &QgsColorButton::setToNull );
@ -825,9 +825,10 @@ void QgsColorButton::setDefaultColor( const QColor &color )
mDefaultColor = color; mDefaultColor = color;
} }
void QgsColorButton::setShowNull( bool showNull ) void QgsColorButton::setShowNull( bool showNull, const QString &nullString )
{ {
mShowNull = showNull; mShowNull = showNull;
mNullColorString = nullString;
} }
bool QgsColorButton::showNull() const bool QgsColorButton::showNull() const

View File

@ -202,11 +202,12 @@ class GUI_EXPORT QgsColorButton : public QToolButton
/** /**
* Sets whether a set to null (clear) option is shown in the button's drop-down menu. * Sets whether a set to null (clear) option is shown in the button's drop-down menu.
* \param showNull set to TRUE to show a null option * \param showNull set to TRUE to show a null option
* \param nullString translated string to use for the null option. If not set, a default "Clear Color" string will be used.
* \see showNull() * \see showNull()
* \see isNull() * \see isNull()
* \since QGIS 2.16 * \since QGIS 2.16
*/ */
void setShowNull( bool showNull ); void setShowNull( bool showNull, const QString &nullString = QString() );
/** /**
* Returns whether the set to null (clear) option is shown in the button's drop-down menu. * Returns whether the set to null (clear) option is shown in the button's drop-down menu.
@ -481,6 +482,7 @@ class GUI_EXPORT QgsColorButton : public QToolButton
bool mShowNoColorOption = false; bool mShowNoColorOption = false;
QString mNoColorString; QString mNoColorString;
bool mShowNull = false; bool mShowNull = false;
QString mNullColorString;
QPoint mDragStartPosition; QPoint mDragStartPosition;
bool mPickingColor = false; bool mPickingColor = false;

View File

@ -8266,16 +8266,20 @@ void TestQgsProcessing::modelerAlgorithm()
QCOMPARE( comment.position(), QPointF( 11, 14 ) ); QCOMPARE( comment.position(), QPointF( 11, 14 ) );
comment.setDescription( QStringLiteral( "a comment" ) ); comment.setDescription( QStringLiteral( "a comment" ) );
QCOMPARE( comment.description(), QStringLiteral( "a comment" ) ); QCOMPARE( comment.description(), QStringLiteral( "a comment" ) );
comment.setColor( QColor( 123, 45, 67 ) );
QCOMPARE( comment.color(), QColor( 123, 45, 67 ) );
std::unique_ptr< QgsProcessingModelComment > commentClone( comment.clone() ); std::unique_ptr< QgsProcessingModelComment > commentClone( comment.clone() );
QCOMPARE( commentClone->toVariant(), comment.toVariant() ); QCOMPARE( commentClone->toVariant(), comment.toVariant() );
QCOMPARE( commentClone->size(), QSizeF( 9, 8 ) ); QCOMPARE( commentClone->size(), QSizeF( 9, 8 ) );
QCOMPARE( commentClone->position(), QPointF( 11, 14 ) ); QCOMPARE( commentClone->position(), QPointF( 11, 14 ) );
QCOMPARE( commentClone->description(), QStringLiteral( "a comment" ) ); QCOMPARE( commentClone->description(), QStringLiteral( "a comment" ) );
QCOMPARE( commentClone->color(), QColor( 123, 45, 67 ) );
QgsProcessingModelComment comment2; QgsProcessingModelComment comment2;
comment2.loadVariant( comment.toVariant().toMap() ); comment2.loadVariant( comment.toVariant().toMap() );
QCOMPARE( comment2.size(), QSizeF( 9, 8 ) ); QCOMPARE( comment2.size(), QSizeF( 9, 8 ) );
QCOMPARE( comment2.position(), QPointF( 11, 14 ) ); QCOMPARE( comment2.position(), QPointF( 11, 14 ) );
QCOMPARE( comment2.description(), QStringLiteral( "a comment" ) ); QCOMPARE( comment2.description(), QStringLiteral( "a comment" ) );
QCOMPARE( comment2.color(), QColor( 123, 45, 67 ) );
QMap< QString, QString > friendlyOutputNames; QMap< QString, QString > friendlyOutputNames;
QgsProcessingModelChildAlgorithm child( QStringLiteral( "some_id" ) ); QgsProcessingModelChildAlgorithm child( QStringLiteral( "some_id" ) );
@ -8463,6 +8467,21 @@ void TestQgsProcessing::modelerAlgorithm()
a2.setDescription( QStringLiteral( "alg2" ) ); a2.setDescription( QStringLiteral( "alg2" ) );
a2.setPosition( QPointF( 112, 131 ) ); a2.setPosition( QPointF( 112, 131 ) );
a2.setSize( QSizeF( 44, 55 ) ); a2.setSize( QSizeF( 44, 55 ) );
a2.comment()->setSize( QSizeF( 111, 222 ) );
a2.comment()->setPosition( QPointF( 113, 114 ) );
a2.comment()->setDescription( QStringLiteral( "c" ) );
a2.comment()->setColor( QColor( 255, 254, 253 ) );
QgsProcessingModelOutput oo;
oo.setPosition( QPointF( 312, 331 ) );
oo.setSize( QSizeF( 344, 355 ) );
oo.comment()->setSize( QSizeF( 311, 322 ) );
oo.comment()->setPosition( QPointF( 313, 314 ) );
oo.comment()->setDescription( QStringLiteral( "c3" ) );
oo.comment()->setColor( QColor( 155, 14, 353 ) );
QMap< QString, QgsProcessingModelOutput > a2Outs;
a2Outs.insert( QStringLiteral( "out1" ), oo );
a2.setModelOutputs( a2Outs );
algs.insert( QStringLiteral( "a" ), a1 ); algs.insert( QStringLiteral( "a" ), a1 );
algs.insert( QStringLiteral( "b" ), a2 ); algs.insert( QStringLiteral( "b" ), a2 );
alg.setChildAlgorithms( algs ); alg.setChildAlgorithms( algs );
@ -8473,10 +8492,28 @@ void TestQgsProcessing::modelerAlgorithm()
QgsProcessingModelChildAlgorithm a2other; QgsProcessingModelChildAlgorithm a2other;
a2other.setChildId( QStringLiteral( "b" ) ); a2other.setChildId( QStringLiteral( "b" ) );
a2other.setDescription( QStringLiteral( "alg2 other" ) ); a2other.setDescription( QStringLiteral( "alg2 other" ) );
QgsProcessingModelOutput oo2;
QMap< QString, QgsProcessingModelOutput > a2Outs2;
a2Outs2.insert( QStringLiteral( "out1" ), oo2 );
a2other.setModelOutputs( a2Outs2 );
a2other.copyNonDefinitionPropertiesFromModel( &alg ); a2other.copyNonDefinitionPropertiesFromModel( &alg );
QCOMPARE( a2other.description(), QStringLiteral( "alg2 other" ) ); QCOMPARE( a2other.description(), QStringLiteral( "alg2 other" ) );
QCOMPARE( a2other.position(), QPointF( 112, 131 ) ); QCOMPARE( a2other.position(), QPointF( 112, 131 ) );
QCOMPARE( a2other.size(), QSizeF( 44, 55 ) ); QCOMPARE( a2other.size(), QSizeF( 44, 55 ) );
QCOMPARE( a2other.comment()->size(), QSizeF( 111, 222 ) );
QCOMPARE( a2other.comment()->position(), QPointF( 113, 114 ) );
// should not be copied
QCOMPARE( a2other.comment()->description(), QString() );
QVERIFY( !a2other.comment()->color().isValid() );
QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).position(), QPointF( 312, 331 ) );
QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).size(), QSizeF( 344, 355 ) );
QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).comment()->size(), QSizeF( 311, 322 ) );
QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).comment()->position(), QPointF( 313, 314 ) );
// should be copied for outputs
QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).comment()->description(), QStringLiteral( "c3" ) );
QCOMPARE( a2other.modelOutput( QStringLiteral( "out1" ) ).comment()->color(), QColor( 155, 14, 353 ) );
QgsProcessingModelChildAlgorithm a3; QgsProcessingModelChildAlgorithm a3;
a3.setChildId( QStringLiteral( "c" ) ); a3.setChildId( QStringLiteral( "c" ) );