diff --git a/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in b/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in index d395352fe3c..0de7ed00de7 100644 --- a/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in +++ b/python/core/auto_generated/processing/models/qgsprocessingmodelcomponent.sip.in @@ -69,6 +69,27 @@ Sets the ``size`` of the model component within the graphical modeler. .. 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 %End diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 81d7ce88aee..9c76609bebe 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -428,6 +428,7 @@ %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/qgsprocessingmodelgroupbox.sip %Include auto_generated/processing/models/qgsprocessingmodeloutput.sip %Include auto_generated/processing/models/qgsprocessingmodelparameter.sip %Include auto_generated/processing/qgsprocessing.sip diff --git a/python/gui/auto_generated/processing/qgsprocessingparameterdefinitionwidget.sip.in b/python/gui/auto_generated/processing/qgsprocessingparameterdefinitionwidget.sip.in index db3d6457d0e..8fb9dad414a 100644 --- a/python/gui/auto_generated/processing/qgsprocessingparameterdefinitionwidget.sip.in +++ b/python/gui/auto_generated/processing/qgsprocessingparameterdefinitionwidget.sip.in @@ -157,6 +157,24 @@ Returns the comments for the parameter. .. 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 %End diff --git a/python/gui/auto_generated/qgscolorbutton.sip.in b/python/gui/auto_generated/qgscolorbutton.sip.in index 13f086f34bf..2f048047cf0 100644 --- a/python/gui/auto_generated/qgscolorbutton.sip.in +++ b/python/gui/auto_generated/qgscolorbutton.sip.in @@ -210,11 +210,12 @@ Sets the string to use for the "no color" option in the button's drop-down menu. dialog %End - void setShowNull( bool showNull ); + void setShowNull( bool showNull, const QString &nullString = QString() ); %Docstring 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 nullString: translated string to use for the null option. If not set, a default "Clear Color" string will be used. .. seealso:: :py:func:`showNull` diff --git a/python/plugins/processing/modeler/ModelerGraphicItem.py b/python/plugins/processing/modeler/ModelerGraphicItem.py index 82a4480bb91..86cc1c0634a 100644 --- a/python/plugins/processing/modeler/ModelerGraphicItem.py +++ b/python/plugins/processing/modeler/ModelerGraphicItem.py @@ -63,17 +63,20 @@ class ModelerInputGraphicItem(QgsModelParameterGraphicItem): def edit(self, edit_comment=False): existing_param = self.model().parameterDefinition(self.component().parameterName()) comment = self.component().comment().description() + comment_color = self.component().comment().color() new_param = None if ModelerParameterDefinitionDialog.use_legacy_dialog(param=existing_param): # boo, old api dlg = ModelerParameterDefinitionDialog(self.model(), param=existing_param) dlg.setComments(comment) + dlg.setCommentColor(comment_color) if edit_comment: dlg.switchToCommentTab() if dlg.exec_(): new_param = dlg.param comment = dlg.comments() + comment_color = dlg.commentColor() else: # yay, use new API! context = createContext() @@ -84,12 +87,14 @@ class ModelerInputGraphicItem(QgsModelParameterGraphicItem): definition=existing_param, algorithm=self.model()) dlg.setComments(comment) + dlg.setCommentColor(comment_color) if edit_comment: dlg.switchToCommentTab() if dlg.exec_(): new_param = dlg.createParameter(existing_param.name()) comment = dlg.comments() + comment_color = dlg.commentColor() if new_param is not None: 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().setDescription(new_param.name()) self.component().comment().setDescription(comment) + self.component().comment().setColor(comment_color) self.model().addModelParameter(new_param, self.component()) self.setLabel(new_param.description()) self.requestModelRepaint.emit() @@ -125,6 +131,7 @@ class ModelerChildAlgorithmGraphicItem(QgsModelChildAlgorithmGraphicItem): dlg = ModelerParametersDialog(elemAlg, self.model(), self.component().childId(), self.component().configuration()) dlg.setComments(self.component().comment().description()) + dlg.setCommentColor(self.component().comment().color()) if edit_comment: dlg.switchToCommentTab() if dlg.exec_(): @@ -160,6 +167,7 @@ class ModelerOutputGraphicItem(QgsModelOutputGraphicItem): dlg = ModelerParameterDefinitionDialog(self.model(), param=self.model().parameterDefinition(param_name)) dlg.setComments(self.component().comment().description()) + dlg.setCommentColor(self.component().comment().color()) if edit_comment: dlg.switchToCommentTab() @@ -169,6 +177,7 @@ class ModelerOutputGraphicItem(QgsModelOutputGraphicItem): model_output.setDefaultValue(dlg.param.defaultValue()) model_output.setMandatory(not (dlg.param.flags() & QgsProcessingParameterDefinition.FlagOptional)) model_output.comment().setDescription(dlg.comments()) + model_output.comment().setColor(dlg.commentColor()) self.aboutToChange.emit(self.tr('Edit {}').format(model_output.description())) self.model().updateDestinationParameters() self.requestModelRepaint.emit() diff --git a/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py b/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py index 131afcb46ad..201c79119f1 100755 --- a/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py +++ b/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py @@ -36,9 +36,12 @@ from qgis.PyQt.QtWidgets import (QDialog, QMessageBox, QTabWidget, 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, QgsSettings, QgsProcessing, @@ -210,8 +213,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) @@ -225,8 +228,8 @@ 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 == parameters.PARAMETER_MAP_LAYER or - isinstance(self.param, QgsProcessingParameterMapLayer)): + elif (self.paramType == parameters.PARAMETER_MAP_LAYER + or isinstance(self.param, QgsProcessingParameterMapLayer)): self.verticalLayout.addWidget(QLabel(self.tr('Data type'))) self.datatypeCombo = QComboBox() self.datatypeCombo.addItem(self.tr('Any Map Layer'), QgsProcessing.TypeMapLayer) @@ -239,11 +242,11 @@ class ModelerParameterDefinitionDialog(QDialog): if self.param is not None: self.datatypeCombo.setCurrentIndex(self.datatypeCombo.findData(self.param.dataTypes()[0])) 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('', '') @@ -334,15 +337,26 @@ class ModelerParameterDefinitionDialog(QDialog): self.commentLayout = QVBoxLayout() self.commentEdit = QTextEdit() 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.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) @@ -357,6 +371,15 @@ class ModelerParameterDefinitionDialog(QDialog): def comments(self): 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): description = self.nameTextBox.text() if description.strip() == '': @@ -374,8 +397,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')) @@ -388,39 +411,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, types=[self.datatypeCombo.currentData()]) - 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: @@ -442,12 +465,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, diff --git a/python/plugins/processing/modeler/ModelerParametersDialog.py b/python/plugins/processing/modeler/ModelerParametersDialog.py index 299166da0e6..5d1dd8a812d 100644 --- a/python/plugins/processing/modeler/ModelerParametersDialog.py +++ b/python/plugins/processing/modeler/ModelerParametersDialog.py @@ -27,6 +27,7 @@ from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtWidgets import (QDialog, QDialogButtonBox, QLabel, QLineEdit, QFrame, QPushButton, QSizePolicy, QVBoxLayout, QHBoxLayout, QWidget, QTabWidget, QTextEdit) +from qgis.PyQt.QtGui import QColor from qgis.core import (Qgis, QgsProject, @@ -50,7 +51,8 @@ from qgis.gui import (QgsGui, QgsProcessingModelerParameterWidget, QgsProcessingParameterWidgetContext, QgsPanelWidget, - QgsPanelWidgetStack) + QgsPanelWidgetStack, + QgsColorButton) from qgis.utils import iface from processing.gui.wrappers import WidgetWrapperFactory @@ -101,6 +103,12 @@ class ModelerParametersDialog(QDialog): def comments(self): return self.widget.comments() + def setCommentColor(self, color): + self.widget.setCommentColor(color) + + def commentColor(self): + return self.widget.commentColor() + def switchToCommentTab(self): self.widget.switchToCommentTab() @@ -534,7 +542,18 @@ class ModelerParametersWidget(QWidget): self.commentLayout = QVBoxLayout() self.commentEdit = QTextEdit() 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.setLayout(self.commentLayout) self.tab.addTab(w2, self.tr('Comments')) @@ -547,6 +566,15 @@ class ModelerParametersWidget(QWidget): def comments(self): 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): return self.widget.getAvailableDependencies() @@ -566,4 +594,5 @@ class ModelerParametersWidget(QWidget): alg = self.widget.createAlgorithm() if alg: alg.comment().setDescription(self.comments()) + alg.comment().setColor(self.commentColor()) return alg diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8d1ac52ca2e..2f633461faa 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -155,6 +155,7 @@ SET(QGIS_CORE_SRCS processing/models/qgsprocessingmodelchildparametersource.cpp processing/models/qgsprocessingmodelcomment.cpp processing/models/qgsprocessingmodelcomponent.cpp + processing/models/qgsprocessingmodelgroupbox.cpp processing/models/qgsprocessingmodelparameter.cpp processing/models/qgsprocessingmodeloutput.cpp @@ -1197,6 +1198,7 @@ SET(QGIS_CORE_HDRS processing/models/qgsprocessingmodelchildparametersource.h processing/models/qgsprocessingmodelcomment.h processing/models/qgsprocessingmodelcomponent.h + processing/models/qgsprocessingmodelgroupbox.h processing/models/qgsprocessingmodeloutput.h processing/models/qgsprocessingmodelparameter.h processing/qgsprocessing.h diff --git a/src/core/processing/models/qgsprocessingmodelchildalgorithm.cpp b/src/core/processing/models/qgsprocessingmodelchildalgorithm.cpp index ab2b1c50dfb..9dcd2d67d0b 100644 --- a/src/core/processing/models/qgsprocessingmodelchildalgorithm.cpp +++ b/src/core/processing/models/qgsprocessingmodelchildalgorithm.cpp @@ -68,7 +68,10 @@ void QgsProcessingModelChildAlgorithm::copyNonDefinitionPropertiesFromModel( Qgs for ( auto it = mModelOutputs.begin(); it != mModelOutputs.end(); ++it ) { if ( !existingChild.modelOutputs().value( it.key() ).position().isNull() ) + { it.value().setPosition( existingChild.modelOutputs().value( it.key() ).position() ); + it.value().setSize( existingChild.modelOutputs().value( it.key() ).size() ); + } else 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->setSize( existingComment->size() ); comment->setPosition( existingComment->position() ); + comment->setColor( existingComment->color() ); } } i++; diff --git a/src/core/processing/models/qgsprocessingmodelcomponent.cpp b/src/core/processing/models/qgsprocessingmodelcomponent.cpp index bd835ff6165..6f3e4bd97dc 100644 --- a/src/core/processing/models/qgsprocessingmodelcomponent.cpp +++ b/src/core/processing/models/qgsprocessingmodelcomponent.cpp @@ -17,6 +17,7 @@ #include "qgsprocessingmodelcomponent.h" #include "qgsprocessingmodelcomment.h" +#include "qgssymbollayerutils.h" ///@cond NOT_STABLE @@ -54,6 +55,16 @@ void QgsProcessingModelComponent::setSize( QSizeF size ) mSize = size; } +QColor QgsProcessingModelComponent::color() const +{ + return mColor; +} + +void QgsProcessingModelComponent::setColor( const QColor &color ) +{ + mColor = color; +} + bool QgsProcessingModelComponent::linksCollapsed( Qt::Edge edge ) const { switch ( edge ) @@ -103,6 +114,7 @@ void QgsProcessingModelComponent::saveCommonProperties( QVariantMap &map ) const map.insert( QStringLiteral( "component_height" ), mSize.height() ); map.insert( QStringLiteral( "parameters_collapsed" ), mTopEdgeLinksCollapsed ); map.insert( QStringLiteral( "outputs_collapsed" ), mBottomEdgeLinksCollapsed ); + map.insert( QStringLiteral( "color" ), mColor.isValid() ? QgsSymbolLayerUtils::encodeColor( mColor ) : QString() ); if ( comment() ) map.insert( QStringLiteral( "comment" ), comment()->toVariant() ); } @@ -116,6 +128,7 @@ void QgsProcessingModelComponent::restoreCommonProperties( const QVariantMap &ma mDescription = map.value( QStringLiteral( "component_description" ) ).toString(); 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() ); + mColor = map.value( QStringLiteral( "color" ) ).toString().isEmpty() ? QColor() : QgsSymbolLayerUtils::decodeColor( map.value( QStringLiteral( "color" ) ).toString() ); mTopEdgeLinksCollapsed = map.value( QStringLiteral( "parameters_collapsed" ) ).toBool(); mBottomEdgeLinksCollapsed = map.value( QStringLiteral( "outputs_collapsed" ) ).toBool(); if ( comment() ) diff --git a/src/core/processing/models/qgsprocessingmodelcomponent.h b/src/core/processing/models/qgsprocessingmodelcomponent.h index 5b709195d96..b2b8425145b 100644 --- a/src/core/processing/models/qgsprocessingmodelcomponent.h +++ b/src/core/processing/models/qgsprocessingmodelcomponent.h @@ -22,6 +22,7 @@ #include "qgis.h" #include #include +#include class QgsProcessingModelComment; @@ -76,6 +77,25 @@ class CORE_EXPORT QgsProcessingModelComponent */ 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. * \see setLinksCollapsed() @@ -158,6 +178,7 @@ class CORE_EXPORT QgsProcessingModelComponent QString mDescription; QSizeF mSize = QSizeF( DEFAULT_COMPONENT_WIDTH, DEFAULT_COMPONENT_HEIGHT ); + QColor mColor; bool mTopEdgeLinksCollapsed = true; bool mBottomEdgeLinksCollapsed = true; diff --git a/src/core/processing/models/qgsprocessingmodelgroupbox.cpp b/src/core/processing/models/qgsprocessingmodelgroupbox.cpp new file mode 100644 index 00000000000..1ca7fde16fa --- /dev/null +++ b/src/core/processing/models/qgsprocessingmodelgroupbox.cpp @@ -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 diff --git a/src/core/processing/models/qgsprocessingmodelgroupbox.h b/src/core/processing/models/qgsprocessingmodelgroupbox.h new file mode 100644 index 00000000000..c6ce12022c2 --- /dev/null +++ b/src/core/processing/models/qgsprocessingmodelgroupbox.h @@ -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 diff --git a/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp b/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp index ec3d99af52d..da9f39e9199 100644 --- a/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp +++ b/src/gui/processing/models/qgsmodelcomponentgraphicitem.cpp @@ -318,8 +318,33 @@ bool QgsModelComponentGraphicItem::contains( const QPointF &point ) const void QgsModelComponentGraphicItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *, QWidget * ) { const QRectF rect = itemRect(); - QColor color = fillColor( state() ); - QColor stroke = strokeColor( state() ); + QColor color; + 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 strokePen.setStyle( strokeStyle( state() ) ); @@ -327,7 +352,7 @@ void QgsModelComponentGraphicItem::paint( QPainter *painter, const QStyleOptionG painter->setBrush( QBrush( color, Qt::SolidPattern ) ); painter->drawRect( rect ); painter->setFont( font() ); - painter->setPen( QPen( textColor( state() ) ) ); + painter->setPen( QPen( foreColor ) ); QString text; diff --git a/src/gui/processing/qgsprocessingparameterdefinitionwidget.cpp b/src/gui/processing/qgsprocessingparameterdefinitionwidget.cpp index 3690fb1ea3b..6ba8c7b37a6 100644 --- a/src/gui/processing/qgsprocessingparameterdefinitionwidget.cpp +++ b/src/gui/processing/qgsprocessingparameterdefinitionwidget.cpp @@ -22,6 +22,7 @@ #include "qgsapplication.h" #include "qgsprocessingregistry.h" #include "qgsprocessingparametertype.h" +#include "qgscolorbutton.h" #include #include #include @@ -145,7 +146,18 @@ QgsProcessingParameterDefinitionDialog::QgsProcessingParameterDefinitionDialog( QVBoxLayout *commentLayout = new QVBoxLayout(); mCommentEdit = new QTextEdit(); 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(); w2->setLayout( commentLayout ); mTabWidget->addTab( w2, tr( "Comments" ) ); @@ -178,6 +190,19 @@ QString QgsProcessingParameterDefinitionDialog::comments() const 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() { mTabWidget->setCurrentIndex( 1 ); diff --git a/src/gui/processing/qgsprocessingparameterdefinitionwidget.h b/src/gui/processing/qgsprocessingparameterdefinitionwidget.h index 7f964aa97eb..c47cef60cc8 100644 --- a/src/gui/processing/qgsprocessingparameterdefinitionwidget.h +++ b/src/gui/processing/qgsprocessingparameterdefinitionwidget.h @@ -31,6 +31,7 @@ class QLineEdit; class QCheckBox; class QTabWidget; class QTextEdit; +class QgsColorButton; /** * 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; + /** + * 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. */ @@ -195,6 +210,7 @@ class GUI_EXPORT QgsProcessingParameterDefinitionDialog: public QDialog QTabWidget *mTabWidget = nullptr; QTextEdit *mCommentEdit = nullptr; + QgsColorButton *mCommentColorButton = nullptr; QgsProcessingParameterDefinitionWidget *mWidget = nullptr; }; diff --git a/src/gui/qgscolorbutton.cpp b/src/gui/qgscolorbutton.cpp index bbb35b72cb6..e87ab097327 100644 --- a/src/gui/qgscolorbutton.cpp +++ b/src/gui/qgscolorbutton.cpp @@ -508,7 +508,7 @@ void QgsColorButton::prepareMenu() { 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 ) ); mMenu->addAction( nullAction ); connect( nullAction, &QAction::triggered, this, &QgsColorButton::setToNull ); @@ -825,9 +825,10 @@ void QgsColorButton::setDefaultColor( const QColor &color ) mDefaultColor = color; } -void QgsColorButton::setShowNull( bool showNull ) +void QgsColorButton::setShowNull( bool showNull, const QString &nullString ) { mShowNull = showNull; + mNullColorString = nullString; } bool QgsColorButton::showNull() const diff --git a/src/gui/qgscolorbutton.h b/src/gui/qgscolorbutton.h index c3c9277b585..20a724f21a0 100644 --- a/src/gui/qgscolorbutton.h +++ b/src/gui/qgscolorbutton.h @@ -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. * \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 isNull() * \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. @@ -481,6 +482,7 @@ class GUI_EXPORT QgsColorButton : public QToolButton bool mShowNoColorOption = false; QString mNoColorString; bool mShowNull = false; + QString mNullColorString; QPoint mDragStartPosition; bool mPickingColor = false; diff --git a/tests/src/analysis/testqgsprocessing.cpp b/tests/src/analysis/testqgsprocessing.cpp index 1e8d4e7698f..b3e0a433369 100644 --- a/tests/src/analysis/testqgsprocessing.cpp +++ b/tests/src/analysis/testqgsprocessing.cpp @@ -8266,16 +8266,20 @@ void TestQgsProcessing::modelerAlgorithm() QCOMPARE( comment.position(), QPointF( 11, 14 ) ); comment.setDescription( 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() ); QCOMPARE( commentClone->toVariant(), comment.toVariant() ); QCOMPARE( commentClone->size(), QSizeF( 9, 8 ) ); QCOMPARE( commentClone->position(), QPointF( 11, 14 ) ); QCOMPARE( commentClone->description(), QStringLiteral( "a comment" ) ); + QCOMPARE( commentClone->color(), QColor( 123, 45, 67 ) ); QgsProcessingModelComment comment2; comment2.loadVariant( comment.toVariant().toMap() ); QCOMPARE( comment2.size(), QSizeF( 9, 8 ) ); QCOMPARE( comment2.position(), QPointF( 11, 14 ) ); QCOMPARE( comment2.description(), QStringLiteral( "a comment" ) ); + QCOMPARE( comment2.color(), QColor( 123, 45, 67 ) ); QMap< QString, QString > friendlyOutputNames; QgsProcessingModelChildAlgorithm child( QStringLiteral( "some_id" ) ); @@ -8463,6 +8467,21 @@ void TestQgsProcessing::modelerAlgorithm() a2.setDescription( QStringLiteral( "alg2" ) ); a2.setPosition( QPointF( 112, 131 ) ); 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( "b" ), a2 ); alg.setChildAlgorithms( algs ); @@ -8473,10 +8492,28 @@ void TestQgsProcessing::modelerAlgorithm() QgsProcessingModelChildAlgorithm a2other; a2other.setChildId( QStringLiteral( "b" ) ); a2other.setDescription( QStringLiteral( "alg2 other" ) ); + QgsProcessingModelOutput oo2; + QMap< QString, QgsProcessingModelOutput > a2Outs2; + a2Outs2.insert( QStringLiteral( "out1" ), oo2 ); + a2other.setModelOutputs( a2Outs2 ); + a2other.copyNonDefinitionPropertiesFromModel( &alg ); QCOMPARE( a2other.description(), QStringLiteral( "alg2 other" ) ); QCOMPARE( a2other.position(), QPointF( 112, 131 ) ); 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; a3.setChildId( QStringLiteral( "c" ) );