diff --git a/Gui/ColorSelectorWidget.cpp b/Gui/ColorSelectorWidget.cpp
new file mode 100644
index 0000000000..154b370773
--- /dev/null
+++ b/Gui/ColorSelectorWidget.cpp
@@ -0,0 +1,886 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * This file is part of Natron ,
+ * (C) 2018-2021 The Natron developers
+ * (C) 2013-2018 INRIA and Alexandre Gauthier-Foichat
+ *
+ * Natron 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.
+ *
+ * Natron is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Natron. If not, see
+ * ***** END LICENSE BLOCK ***** */
+
+#include "ColorSelectorWidget.h"
+
+CLANG_DIAG_OFF(deprecated)
+CLANG_DIAG_OFF(uninitialized)
+#include
+#include
+#include
+CLANG_DIAG_ON(deprecated)
+CLANG_DIAG_ON(uninitialized)
+
+#include "Gui/Label.h"
+#include "Engine/Lut.h"
+
+NATRON_NAMESPACE_ENTER
+
+ColorSelectorWidget::ColorSelectorWidget(QWidget *parent)
+ : QWidget(parent)
+ , _spinR(0)
+ , _spinG(0)
+ , _spinB(0)
+ , _spinH(0)
+ , _spinS(0)
+ , _spinV(0)
+ , _spinA(0)
+ , _slideR(0)
+ , _slideG(0)
+ , _slideB(0)
+ , _slideH(0)
+ , _slideS(0)
+ , _slideV(0)
+ , _slideA(0)
+ , _triangle(0)
+ , _hex(0)
+ , _button(0)
+ , _stack(0)
+{
+ QVBoxLayout *mainLayout = new QVBoxLayout(this);
+
+ // colortriangle
+ _triangle = new QtColorTriangle(this);
+ // position the triangle properly before usage
+ _triangle->setColor( QColor::fromHsvF(0.0, 0.0, 0.0, 1.0) );
+ _triangle->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ // sliders
+ _slideR = new ScaleSliderQWidget(0.,
+ 1.,
+ 0.,
+ false,
+ ScaleSliderQWidget::eDataTypeDouble,
+ NULL,
+ eScaleTypeLinear,
+ this);
+ _slideG = new ScaleSliderQWidget(0.,
+ 1.,
+ 0.,
+ false,
+ ScaleSliderQWidget::eDataTypeDouble,
+ NULL,
+ eScaleTypeLinear,
+ this);
+ _slideB = new ScaleSliderQWidget(0.,
+ 1.,
+ 0.,
+ false,
+ ScaleSliderQWidget::eDataTypeDouble,
+ NULL,
+ eScaleTypeLinear,
+ this);
+ _slideH = new ScaleSliderQWidget(0.,
+ 1.,
+ 0.,
+ false,
+ ScaleSliderQWidget::eDataTypeDouble,
+ NULL,
+ eScaleTypeLinear,
+ this);
+ _slideS = new ScaleSliderQWidget(0.,
+ 1.,
+ 0.,
+ false,
+ ScaleSliderQWidget::eDataTypeDouble,
+ NULL,
+ eScaleTypeLinear,
+ this);
+ _slideV = new ScaleSliderQWidget(0.,
+ 1.,
+ 0.,
+ false,
+ ScaleSliderQWidget::eDataTypeDouble,
+ NULL,
+ eScaleTypeLinear,
+ this);
+ _slideA = new ScaleSliderQWidget(0.,
+ 1.,
+ 0.,
+ false,
+ ScaleSliderQWidget::eDataTypeDouble,
+ NULL,
+ eScaleTypeLinear,
+ this);
+
+ _slideR->setMinimumAndMaximum(0., 1.);
+ _slideG->setMinimumAndMaximum(0., 1.);
+ _slideB->setMinimumAndMaximum(0., 1.);
+ _slideH->setMinimumAndMaximum(0., 1.);
+ _slideS->setMinimumAndMaximum(0., 1.);
+ _slideV->setMinimumAndMaximum(0., 1.);
+ _slideA->setMinimumAndMaximum(0., 1.);
+
+ _slideR->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ _slideG->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ _slideB->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ _slideH->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ _slideS->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ _slideV->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ _slideA->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+
+ // set line color to match channel (R/G/B/A)
+ _slideR->setUseLineColor(true, QColor(200, 70, 70) );
+ _slideG->setUseLineColor(true, QColor(86, 166, 66) );
+ _slideB->setUseLineColor(true, QColor(83, 121, 180) );
+ _slideA->setUseLineColor(true, QColor(215, 215, 215) );
+
+ // override "knob" color on sliders
+ _slideR->setUseSliderColor(true, Qt::white);
+ _slideG->setUseSliderColor(true, Qt::white);
+ _slideB->setUseSliderColor(true, Qt::white);
+ _slideH->setUseSliderColor(true, Qt::white);
+ _slideS->setUseSliderColor(true, Qt::white);
+ _slideV->setUseSliderColor(true, Qt::white);
+ _slideA->setUseSliderColor(true, Qt::white);
+
+ // spinboxes
+ _spinR = new SpinBox(this, SpinBox::eSpinBoxTypeDouble);
+ _spinG = new SpinBox(this, SpinBox::eSpinBoxTypeDouble);
+ _spinB = new SpinBox(this, SpinBox::eSpinBoxTypeDouble);
+ _spinH = new SpinBox(this, SpinBox::eSpinBoxTypeDouble);
+ _spinS = new SpinBox(this, SpinBox::eSpinBoxTypeDouble);
+ _spinV = new SpinBox(this, SpinBox::eSpinBoxTypeDouble);
+ _spinA = new SpinBox(this, SpinBox::eSpinBoxTypeDouble);
+
+ _spinR->decimals(3);
+ _spinG->decimals(3);
+ _spinB->decimals(3);
+ _spinH->decimals(3);
+ _spinS->decimals(3);
+ _spinV->decimals(3);
+ _spinA->decimals(3);
+
+ _spinR->setIncrement(0.01);
+ _spinG->setIncrement(0.01);
+ _spinB->setIncrement(0.01);
+ _spinH->setIncrement(0.01);
+ _spinS->setIncrement(0.01);
+ _spinV->setIncrement(0.01);
+ _spinA->setIncrement(0.01);
+
+ _spinR->setMaximum(1.);
+ _spinR->setMinimum(0.);
+ _spinG->setMaximum(1.);
+ _spinG->setMinimum(0.);
+ _spinB->setMaximum(1.);
+ _spinB->setMinimum(0.);
+ _spinH->setMaximum(1.);
+ _spinH->setMinimum(0.);
+ _spinS->setMaximum(1.);
+ _spinS->setMinimum(0.);
+ _spinV->setMaximum(1.);
+ _spinV->setMinimum(0.);
+ _spinA->setMaximum(1.);
+ _spinA->setMinimum(0.);
+
+ // set color to match channel (R/G/B/A)
+ _spinR->setUseLineColor(true, QColor(200, 70, 70) );
+ _spinG->setUseLineColor(true, QColor(86, 166, 66) );
+ _spinB->setUseLineColor(true, QColor(83, 121, 180) );
+ _spinA->setUseLineColor(true, QColor(215, 215, 215) );
+
+ // hex
+ _hex = new LineEdit(this);
+
+ // button
+ _button = new Button(QString::fromUtf8("RGB"), this);
+ _button->setCheckable(true);
+
+ // set triangle size
+ setTriangleSize();
+
+ // set object names (for stylesheet)
+ _spinR->setObjectName( QString::fromUtf8("ColorSelectorRed") );
+ _spinG->setObjectName( QString::fromUtf8("ColorSelectorGreen") );
+ _spinB->setObjectName( QString::fromUtf8("ColorSelectorBlue") );
+ _spinA->setObjectName( QString::fromUtf8("ColorSelectorAlpha") );
+ _spinH->setObjectName( QString::fromUtf8("ColorSelectorHue") );
+ _spinS->setObjectName( QString::fromUtf8("ColorSelectorSat") );
+ _spinV->setObjectName( QString::fromUtf8("ColorSelectorVal") );
+ _hex->setObjectName( QString::fromUtf8("ColorSelectorHex") );
+ _button->setObjectName( QString::fromUtf8("ColorSelectorSwitch") );
+
+ // labels
+ Label *labelR = new Label(QString::fromUtf8("R"), this);
+ Label *labelG = new Label(QString::fromUtf8("G"), this);
+ Label *labelB = new Label(QString::fromUtf8("B"), this);
+ Label *labelA = new Label(QString::fromUtf8("A"), this);
+ Label *labelH = new Label(QString::fromUtf8("H"), this);
+ Label *labelS = new Label(QString::fromUtf8("S"), this);
+ Label *labelV = new Label(QString::fromUtf8("V"), this);
+ Label *labelHex = new Label(QString::fromUtf8("Hex"), this);
+
+ labelR->setMinimumWidth(10);
+ labelG->setMinimumWidth(10);
+ labelB->setMinimumWidth(10);
+ labelA->setMinimumWidth(10);
+ labelH->setMinimumWidth(10);
+ labelS->setMinimumWidth(10);
+ labelV->setMinimumWidth(10);
+
+ // tooltips
+ _spinR->setToolTip( QObject::tr("Red color value") );
+ _spinG->setToolTip( QObject::tr("Green color value") );
+ _spinB->setToolTip( QObject::tr("Blue color value") );
+ _spinA->setToolTip( QObject::tr("Alpha value") );
+ _spinH->setToolTip( QObject::tr("Hue color value") );
+ _spinS->setToolTip( QObject::tr("Saturation value") );
+ _spinV->setToolTip( QObject::tr("Brightness/Intensity value") );
+ _hex->setToolTip( QObject::tr("A HTML hexadecimal color is specified with: #RRGGBB, where the RR (red), GG (green) and BB (blue) hexadecimal integers specify the components of the color.") );
+ _button->setToolTip( QObject::tr("Switch to HSV") );
+
+ // layout
+ _stack = new QStackedWidget(this);
+
+ QWidget *rWidget = new QWidget(this);
+ QHBoxLayout *rLayout = new QHBoxLayout(rWidget);
+
+ QWidget *gWidget = new QWidget(this);
+ QHBoxLayout *gLayout = new QHBoxLayout(gWidget);
+
+ QWidget *bWidget = new QWidget(this);
+ QHBoxLayout *bLayout = new QHBoxLayout(bWidget);
+
+ QWidget *hWidget = new QWidget(this);
+ QHBoxLayout *hLayout = new QHBoxLayout(hWidget);
+
+ QWidget *sWidget = new QWidget(this);
+ QHBoxLayout *sLayout = new QHBoxLayout(sWidget);
+
+ QWidget *vWidget = new QWidget(this);
+ QHBoxLayout *vLayout = new QHBoxLayout(vWidget);
+
+ QWidget *aWidget = new QWidget(this);
+ QHBoxLayout *aLayout = new QHBoxLayout(aWidget);
+
+ QWidget *hexWidget = new QWidget(this);
+ QHBoxLayout *hexLayout = new QHBoxLayout(hexWidget);
+
+ QWidget *rgbaWidget = new QWidget(this);
+ QVBoxLayout *rgbaLayout = new QVBoxLayout(rgbaWidget);
+
+ QWidget *hsvWidget = new QWidget(this);
+ QVBoxLayout *hsvLayout = new QVBoxLayout(hsvWidget);
+
+ QWidget *topWidget = new QWidget(this);
+ QHBoxLayout *topLayout = new QHBoxLayout(topWidget);
+
+ QWidget *leftWidget = new QWidget(this);
+ QVBoxLayout *leftLayout = new QVBoxLayout(leftWidget);
+
+ QWidget *rightWidget = new QWidget(this);
+ QVBoxLayout *rightLayout = new QVBoxLayout(rightWidget);
+
+ QWidget *bottomWidget = new QWidget(this);
+ QHBoxLayout *bottomLayout = new QHBoxLayout(bottomWidget);
+
+ _stack->setContentsMargins(0, 0, 0, 0);
+ hexWidget->setContentsMargins(0, 0, 0, 0);
+ topWidget->setContentsMargins(0, 0, 0, 0);
+ leftWidget->setContentsMargins(0, 0, 0, 0);
+ rightWidget->setContentsMargins(0, 0, 0, 0);
+ bottomWidget->setContentsMargins(0, 0, 10, 0);
+
+ _stack->layout()->setContentsMargins(0, 0, 0, 0);
+ mainLayout->setContentsMargins(5, 5, 5, 5);
+ rLayout->setContentsMargins(0, 0, 0, 0);
+ gLayout->setContentsMargins(0, 0, 0, 0);
+ bLayout->setContentsMargins(0, 0, 0, 0);
+ hLayout->setContentsMargins(0, 0, 0, 0);
+ sLayout->setContentsMargins(0, 0, 0, 0);
+ vLayout->setContentsMargins(0, 0, 0, 0);
+ hexLayout->setContentsMargins(0, 0, 0, 0);
+ topLayout->setContentsMargins(0, 0, 0, 0);
+ leftLayout->setContentsMargins(0, 0, 0, 0);
+ rightLayout->setContentsMargins(0, 0, 0, 0);
+ bottomLayout->setContentsMargins(0, 0, 0, 0);
+
+ QMargins aMargin = hsvLayout->contentsMargins();
+ aMargin.setTop(0);
+ aLayout->setContentsMargins(aMargin);
+
+ _stack->layout()->setSpacing(0);
+ mainLayout->setSpacing(0);
+ topLayout->setSpacing(0);
+ leftLayout->setSpacing(0);
+ rightLayout->setSpacing(0);
+
+ rLayout->addWidget(labelR);
+ rLayout->addWidget(_spinR);
+ rLayout->addWidget(_slideR);
+ gLayout->addWidget(labelG);
+ gLayout->addWidget(_spinG);
+ gLayout->addWidget(_slideG);
+ bLayout->addWidget(labelB);
+ bLayout->addWidget(_spinB);
+ bLayout->addWidget(_slideB);
+
+ hLayout->addWidget(labelH);
+ hLayout->addWidget(_spinH);
+ hLayout->addWidget(_slideH);
+ sLayout->addWidget(labelS);
+ sLayout->addWidget(_spinS);
+ sLayout->addWidget(_slideS);
+ vLayout->addWidget(labelV);
+ vLayout->addWidget(_spinV);
+ vLayout->addWidget(_slideV);
+
+ aLayout->addWidget(labelA);
+ aLayout->addWidget(_spinA);
+ aLayout->addWidget(_slideA);
+
+ hexLayout->addWidget(labelHex);
+ hexLayout->addWidget(_hex);
+
+ rgbaLayout->addWidget(rWidget);
+ rgbaLayout->addWidget(gWidget);
+ rgbaLayout->addWidget(bWidget);
+
+ hsvLayout->addWidget(hWidget);
+ hsvLayout->addWidget(sWidget);
+ hsvLayout->addWidget(vWidget);
+
+ leftLayout->addWidget(_triangle);
+
+ _stack->addWidget(rgbaWidget);
+ _stack->addWidget(hsvWidget);
+
+ rightLayout->addWidget(_stack);
+ rightLayout->addWidget(aWidget);
+
+ topLayout->addWidget(leftWidget);
+ topLayout->addWidget(rightWidget);
+
+ bottomLayout->addWidget(_button);
+ bottomLayout->addStretch();
+ bottomLayout->addWidget(hexWidget);
+
+ mainLayout->addWidget(topWidget);
+ mainLayout->addWidget(bottomWidget);
+
+ // connect the widgets
+ QObject::connect( _triangle, SIGNAL( colorChanged(QColor) ),
+ this, SLOT( handleTriangleColorChanged(QColor) ) );
+
+ QObject::connect( _spinR, SIGNAL( valueChanged(double) ),
+ this, SLOT( handleSpinRChanged(double) ) );
+ QObject::connect( _spinG, SIGNAL( valueChanged(double) ),
+ this, SLOT( handleSpinGChanged(double) ) );
+ QObject::connect( _spinB, SIGNAL( valueChanged(double) ),
+ this, SLOT( handleSpinBChanged(double) ) );
+ QObject::connect( _spinH, SIGNAL( valueChanged(double) ),
+ this, SLOT( handleSpinHChanged(double) ) );
+ QObject::connect( _spinS, SIGNAL( valueChanged(double) ),
+ this, SLOT( handleSpinSChanged(double) ) );
+ QObject::connect( _spinV, SIGNAL( valueChanged(double) ),
+ this, SLOT( handleSpinVChanged(double) ) );
+ QObject::connect( _spinA, SIGNAL( valueChanged(double) ),
+ this, SLOT( handleSpinAChanged(double) ) );
+
+ QObject::connect( _hex, SIGNAL( returnPressed() ),
+ this, SLOT( handleHexChanged() ) );
+
+ QObject::connect( _slideR, SIGNAL( positionChanged(double) ),
+ this, SLOT( handleSliderRMoved(double) ) );
+ QObject::connect( _slideG, SIGNAL( positionChanged(double) ),
+ this, SLOT( handleSliderGMoved(double) ) );
+ QObject::connect( _slideB, SIGNAL( positionChanged(double) ),
+ this, SLOT( handleSliderBMoved(double) ) );
+ QObject::connect( _slideH, SIGNAL( positionChanged(double) ),
+ this, SLOT( handleSliderHMoved(double) ) );
+ QObject::connect( _slideS, SIGNAL( positionChanged(double) ),
+ this, SLOT( handleSliderSMoved(double) ) );
+ QObject::connect( _slideV, SIGNAL( positionChanged(double) ),
+ this, SLOT( handleSliderVMoved(double) ) );
+ QObject::connect( _slideA, SIGNAL( positionChanged(double) ),
+ this, SLOT( handleSliderAMoved(double) ) );
+
+ QObject::connect( _button, SIGNAL( clicked(bool) ),
+ this, SLOT( handleButtonClicked(bool) ) );
+}
+
+void
+ColorSelectorWidget::getColor(float *r,
+ float *g,
+ float *b,
+ float *a)
+{
+ *r = _spinR->value();
+ *g = _spinG->value();
+ *b = _spinB->value();
+ *a = _spinA->value();
+}
+
+void
+ColorSelectorWidget::setColor(float r,
+ float g,
+ float b,
+ float a)
+{
+ float h, s, v;
+ Color::rgb_to_hsv(r, g, b, &h, &s, &v);
+
+ setRedChannel(r);
+ setGreenChannel(g);
+ setBlueChannel(b);
+ setHueChannel(h);
+ setSaturationChannel(s);
+ setValueChannel(v);
+ setAlphaChannel(a);
+ setTriangle(r, g, b, a);
+ setHex( _triangle->color() );
+}
+
+void
+ColorSelectorWidget::setRedChannel(float value)
+{
+ _spinR->blockSignals(true);
+ _slideR->blockSignals(true);
+
+ _spinR->setValue(value);
+ _slideR->seekScalePosition(value);
+
+ _spinR->blockSignals(false);
+ _slideR->blockSignals(false);
+}
+
+void
+ColorSelectorWidget::setGreenChannel(float value)
+{
+ _spinG->blockSignals(true);
+ _slideG->blockSignals(true);
+
+ _spinG->setValue(value);
+ _slideG->seekScalePosition(value);
+
+ _spinG->blockSignals(false);
+ _slideG->blockSignals(false);
+}
+
+void
+ColorSelectorWidget::setBlueChannel(float value)
+{
+ _spinB->blockSignals(true);
+ _slideB->blockSignals(true);
+
+ _spinB->setValue(value);
+ _slideB->seekScalePosition(value);
+
+ _spinB->blockSignals(false);
+ _slideB->blockSignals(false);
+}
+
+void
+ColorSelectorWidget::setHueChannel(float value)
+{
+ _spinH->blockSignals(true);
+ _slideH->blockSignals(true);
+
+ _spinH->setValue(value);
+ _slideH->seekScalePosition(value);
+
+ setSliderHColor();
+
+ _spinH->blockSignals(false);
+ _slideH->blockSignals(false);
+}
+
+void
+ColorSelectorWidget::setSaturationChannel(float value)
+{
+ _spinS->blockSignals(true);
+ _slideS->blockSignals(true);
+
+ _spinS->setValue(value);
+ _slideS->seekScalePosition(value);
+
+ setSliderSColor();
+
+ _spinS->blockSignals(false);
+ _slideS->blockSignals(false);
+}
+
+void
+ColorSelectorWidget::setValueChannel(float value)
+{
+ _spinV->blockSignals(true);
+ _slideV->blockSignals(true);
+
+ _spinV->setValue(value);
+ _slideV->seekScalePosition(value);
+
+ setSliderVColor();
+
+ _spinV->blockSignals(false);
+ _slideV->blockSignals(false);
+}
+
+void
+ColorSelectorWidget::setAlphaChannel(float value)
+{
+ _spinA->blockSignals(true);
+ _slideA->blockSignals(true);
+
+ _spinA->setValue(value);
+ _slideA->seekScalePosition(value);
+
+ _spinA->blockSignals(false);
+ _slideA->blockSignals(false);
+}
+
+void
+ColorSelectorWidget::setTriangle(float r,
+ float g,
+ float b,
+ float a)
+{
+ QColor color = _triangle->color();
+ color.setRgbF( Color::to_func_srgb(r),
+ Color::to_func_srgb(g),
+ Color::to_func_srgb(b) );
+ color.setAlphaF(a);
+
+ _triangle->blockSignals(true);
+ _triangle->setColor(color);
+ _triangle->blockSignals(false);
+}
+
+void
+ColorSelectorWidget::setHex(const QColor &color)
+{
+ if ( !color.isValid() ) {
+ return;
+ }
+
+ _hex->blockSignals(true);
+ _hex->setText( color.name() );
+ _hex->blockSignals(false);
+}
+
+void
+ColorSelectorWidget::announceColorChange()
+{
+ Q_EMIT colorChanged( _spinR->value(),
+ _spinG->value(),
+ _spinB->value(),
+ _spinA->value() );
+}
+
+void
+ColorSelectorWidget::setTriangleSize()
+{
+ int triangleSize = 4;
+ int padding = 10;
+
+ triangleSize = (_spinR->size().height() * triangleSize) + (triangleSize * padding);
+
+ _triangle->setMinimumSize(triangleSize, triangleSize);
+ _triangle->setMaximumSize(triangleSize, triangleSize);
+}
+
+void
+ColorSelectorWidget::handleTriangleColorChanged(const QColor &color,
+ bool announce)
+{
+ setRedChannel( Color::from_func_srgb( color.redF() ) );
+ setGreenChannel( Color::from_func_srgb( color.greenF() ) );
+ setBlueChannel( Color::from_func_srgb( color.blueF() ) );
+ setHueChannel( color.toHsv().hueF() );
+ setSaturationChannel( color.toHsv().saturationF() );
+ setValueChannel( color.toHsv().valueF() );
+ setHex(color);
+
+ if (announce) {
+ announceColorChange();
+ }
+}
+
+void ColorSelectorWidget::manageColorRGBChanged(bool announce)
+{
+ float r = _spinR->value();
+ float g = _spinG->value();
+ float b = _spinB->value();
+ float a = _spinA->value();
+ float h, s, v;
+ Color::rgb_to_hsv(r, g, b, &h, &s, &v);
+
+ setHueChannel(h);
+ setSaturationChannel(s);
+ setValueChannel(v);
+ setTriangle(r, g, b, a);
+ setHex( _triangle->color() );
+
+ if (announce) {
+ announceColorChange();
+ }
+}
+
+void
+ColorSelectorWidget::manageColorHSVChanged(bool announce)
+{
+ float h = _spinH->value();
+ float s = _spinS->value();
+ float v = _spinV->value();
+ float a = _spinA->value();
+ float r, g, b;
+ Color::hsv_to_rgb(h, s, v, &r, &g, &b);
+
+ setRedChannel(r);
+ setGreenChannel(g);
+ setBlueChannel(b);
+
+ QColor color = _triangle->color();
+ color.setHsvF(h, s, v);
+ color.setAlphaF(a);
+
+ _triangle->blockSignals(true);
+ _triangle->setColor(color);
+ _triangle->blockSignals(false);
+
+ setHex(color);
+
+ setSliderHColor();
+ setSliderSColor();
+ setSliderVColor();
+
+ if (announce) {
+ announceColorChange();
+ }
+}
+
+void
+ColorSelectorWidget::manageColorAlphaChanged(bool announce)
+{
+ if (announce) {
+ announceColorChange();
+ }
+}
+
+void
+ColorSelectorWidget::handleSpinRChanged(double value)
+{
+ _slideR->blockSignals(true);
+ _slideR->seekScalePosition(value);
+ _slideR->blockSignals(false);
+
+ manageColorRGBChanged();
+}
+
+void
+ColorSelectorWidget::handleSpinGChanged(double value)
+{
+ _slideG->blockSignals(true);
+ _slideG->seekScalePosition(value);
+ _slideG->blockSignals(false);
+
+ manageColorRGBChanged();
+}
+
+void
+ColorSelectorWidget::handleSpinBChanged(double value)
+{
+ _slideB->blockSignals(true);
+ _slideB->seekScalePosition(value);
+ _slideB->blockSignals(false);
+
+ manageColorRGBChanged();
+}
+
+void
+ColorSelectorWidget::handleSpinHChanged(double value)
+{
+ _slideH->blockSignals(true);
+ _slideH->seekScalePosition(value);
+ _slideH->blockSignals(false);
+
+ manageColorHSVChanged();
+}
+
+void
+ColorSelectorWidget::handleSpinSChanged(double value)
+{
+ _slideS->blockSignals(true);
+ _slideS->seekScalePosition(value);
+ _slideS->blockSignals(false);
+
+ manageColorHSVChanged();
+}
+
+void
+ColorSelectorWidget::handleSpinVChanged(double value)
+{
+ _slideV->blockSignals(true);
+ _slideV->seekScalePosition(value);
+ _slideV->blockSignals(false);
+
+ manageColorHSVChanged();
+}
+
+void
+ColorSelectorWidget::handleSpinAChanged(double value)
+{
+ _slideA->blockSignals(true);
+ _slideA->seekScalePosition(value);
+ _slideA->blockSignals(false);
+
+ manageColorAlphaChanged();
+}
+
+void ColorSelectorWidget::handleHexChanged()
+{
+ QString value = _hex->text();
+ if ( !value.startsWith( QString::fromUtf8("#") ) ) {
+ value.prepend( QString::fromUtf8("#") );
+ }
+
+ QColor color;
+ color.setNamedColor( _hex->text() );
+ if ( !color.isValid() ) {
+ return;
+ }
+
+ _triangle->setColor(color);
+}
+
+void
+ColorSelectorWidget::handleSliderRMoved(double value)
+{
+ _spinR->blockSignals(true);
+ _spinR->setValue(value);
+ _spinR->blockSignals(false);
+
+ manageColorRGBChanged();
+}
+
+void
+ColorSelectorWidget::handleSliderGMoved(double value)
+{
+ _spinG->blockSignals(true);
+ _spinG->setValue(value);
+ _spinG->blockSignals(false);
+
+ manageColorRGBChanged();
+}
+
+void
+ColorSelectorWidget::handleSliderBMoved(double value)
+{
+ _spinB->blockSignals(true);
+ _spinB->setValue(value);
+ _spinB->blockSignals(false);
+
+ manageColorRGBChanged();
+}
+
+void
+ColorSelectorWidget::handleSliderHMoved(double value)
+{
+ _spinH->blockSignals(true);
+ _spinH->setValue(value);
+ _spinH->blockSignals(false);
+
+ manageColorHSVChanged();
+}
+
+void
+ColorSelectorWidget::handleSliderSMoved(double value)
+{
+ _spinS->blockSignals(true);
+ _spinS->setValue(value);
+ _spinS->blockSignals(false);
+
+ manageColorHSVChanged();
+}
+
+void
+ColorSelectorWidget::handleSliderVMoved(double value)
+{
+ _spinV->blockSignals(true);
+ _spinV->setValue(value);
+ _spinV->blockSignals(false);
+
+ manageColorHSVChanged();
+}
+
+
+void
+ColorSelectorWidget::handleSliderAMoved(double value)
+{
+ _spinA->blockSignals(true);
+ _spinA->setValue(value);
+ _spinA->blockSignals(false);
+
+ manageColorAlphaChanged();
+}
+
+void
+ColorSelectorWidget::setSliderHColor()
+{
+ QColor color;
+ color.setHsvF( _spinH->value(),
+ 1.0,
+ 1.0,
+ 1.0);
+ _spinH->setUseLineColor(true, color);
+ _slideH->setUseLineColor(true, color);
+}
+
+void
+ColorSelectorWidget::setSliderSColor()
+{
+ QColor color;
+ color.setHsvF( _spinH->value(),
+ _spinS->value(),
+ 1.0,
+ 1.0);
+ _spinS->setUseLineColor(true, color);
+ _slideS->setUseLineColor(true, color);
+}
+
+void
+ColorSelectorWidget::setSliderVColor()
+{
+ QColor color;
+ color.setHsvF( _spinH->value(),
+ _spinS->value(),
+ _spinV->value(),
+ 1.0);
+ _spinV->setUseLineColor(true, color);
+ _slideV->setUseLineColor(true, color);
+}
+
+void
+ColorSelectorWidget::handleButtonClicked(bool checked)
+{
+ if (checked) {
+ _button->setText( QString::fromUtf8("HSV") );
+ _button->setToolTip( QObject::tr("Switch to RGB") );
+ _stack->setCurrentIndex(1);
+ } else {
+ _button->setText( QString::fromUtf8("RGB") );
+ _button->setToolTip( QObject::tr("Switch to HSV") );
+ _stack->setCurrentIndex(0);
+ }
+}
+
+NATRON_NAMESPACE_EXIT
+
+NATRON_NAMESPACE_USING
+#include "moc_ColorSelectorWidget.cpp"
diff --git a/Gui/ColorSelectorWidget.h b/Gui/ColorSelectorWidget.h
new file mode 100644
index 0000000000..e973ce478b
--- /dev/null
+++ b/Gui/ColorSelectorWidget.h
@@ -0,0 +1,156 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * This file is part of Natron ,
+ * (C) 2018-2021 The Natron developers
+ * (C) 2013-2018 INRIA and Alexandre Gauthier-Foichat
+ *
+ * Natron 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.
+ *
+ * Natron is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Natron. If not, see
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef Gui_ColorSelectorWidget_h
+#define Gui_ColorSelectorWidget_h
+
+#include "Global/Macros.h"
+
+CLANG_DIAG_OFF(deprecated)
+CLANG_DIAG_OFF(uninitialized)
+#include
+#include
+#include "Gui/QtColorTriangle.h" // from Qt Solutions
+#include
+#include
+#include
+CLANG_DIAG_ON(deprecated)
+CLANG_DIAG_ON(uninitialized)
+
+#include "Gui/ScaleSliderQWidget.h"
+#include "Gui/SpinBox.h"
+#include "Gui/LineEdit.h"
+#include "Gui/Button.h"
+
+NATRON_NAMESPACE_ENTER
+
+class ColorSelectorWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+
+ explicit ColorSelectorWidget(QWidget *parent = NULL);
+ void getColor(float *r, float *g, float *b, float *a);
+
+Q_SIGNALS:
+
+ void colorChanged(float r, float g, float b, float a);
+ void updateColor();
+
+public Q_SLOTS:
+
+ void setColor(float r, float g, float b, float a);
+
+private:
+
+ SpinBox *_spinR;
+ SpinBox *_spinG;
+ SpinBox *_spinB;
+ SpinBox *_spinH;
+ SpinBox *_spinS;
+ SpinBox *_spinV;
+ SpinBox *_spinA;
+
+ ScaleSliderQWidget *_slideR;
+ ScaleSliderQWidget *_slideG;
+ ScaleSliderQWidget *_slideB;
+ ScaleSliderQWidget *_slideH;
+ ScaleSliderQWidget *_slideS;
+ ScaleSliderQWidget *_slideV;
+ ScaleSliderQWidget *_slideA;
+
+ QtColorTriangle *_triangle;
+
+ LineEdit *_hex;
+
+ Button *_button;
+
+ QStackedWidget *_stack;
+
+ void setRedChannel(float value);
+ void setGreenChannel(float value);
+ void setBlueChannel(float value);
+ void setHueChannel(float value);
+ void setSaturationChannel(float value);
+ void setValueChannel(float value);
+ void setAlphaChannel(float value);
+ void setTriangle(float r, float g, float b, float a);
+ void setHex(const QColor &color);
+
+ void announceColorChange();
+
+ void setTriangleSize();
+
+private Q_SLOTS:
+
+ void handleTriangleColorChanged(const QColor &color, bool announce = true);
+
+ void manageColorRGBChanged(bool announce = true);
+ void manageColorHSVChanged(bool announce = true);
+ void manageColorAlphaChanged(bool announce = true);
+
+ void handleSpinRChanged(double value);
+ void handleSpinGChanged(double value);
+ void handleSpinBChanged(double value);
+ void handleSpinHChanged(double value);
+ void handleSpinSChanged(double value);
+ void handleSpinVChanged(double value);
+ void handleSpinAChanged(double value);
+
+ void handleHexChanged();
+
+ void handleSliderRMoved(double value);
+ void handleSliderGMoved(double value);
+ void handleSliderBMoved(double value);
+ void handleSliderHMoved(double value);
+ void handleSliderSMoved(double value);
+ void handleSliderVMoved(double value);
+ void handleSliderAMoved(double value);
+
+ void setSliderHColor();
+ void setSliderSColor();
+ void setSliderVColor();
+
+ void handleButtonClicked(bool checked);
+
+ // workaround for QToolButton+QWidgetAction
+ // triggered signal(s) are never emitted!?
+ bool event(QEvent*e) override
+ {
+ if (e->type() == QEvent::Show) {
+ Q_EMIT updateColor();
+ }
+ return QWidget::event(e);
+ }
+
+ // https://bugreports.qt.io/browse/QTBUG-47406
+ void mousePressEvent(QMouseEvent *e) override
+ {
+ e->accept();
+ }
+ void mouseReleaseEvent(QMouseEvent *e) override
+ {
+ e->accept();
+ }
+};
+
+NATRON_NAMESPACE_EXIT
+
+#endif // Gui_ColorSelectorWidget_h
diff --git a/Gui/Gui.pro b/Gui/Gui.pro
index 6f305f1e28..4ffe3a2177 100644
--- a/Gui/Gui.pro
+++ b/Gui/Gui.pro
@@ -80,6 +80,7 @@ SOURCES += \
ChannelsComboBox.cpp \
ClickableLabel.cpp \
ColoredFrame.cpp \
+ ColorSelectorWidget.cpp \
ComboBox.cpp \
CurveEditor.cpp \
CurveEditorUndoRedo.cpp \
@@ -183,6 +184,7 @@ SOURCES += \
PropertiesBinWrapper.cpp \
PyGuiApp.cpp \
PythonPanels.cpp \
+ QtColorTriangle.cpp \
QtEnumConvert.cpp \
RenderStatsDialog.cpp \
ResizableMessageBox.cpp \
@@ -235,6 +237,7 @@ HEADERS += \
ChannelsComboBox.h \
ClickableLabel.h \
ColoredFrame.h \
+ ColorSelectorWidget.h \
ComboBox.h \
CurveEditor.h \
CurveEditorUndoRedo.h \
@@ -324,6 +327,7 @@ HEADERS += \
PyGuiApp.h \
Pyside_Gui_Python.h \
PythonPanels.h \
+ QtColorTriangle.h \
QtEnumConvert.h \
RegisteredTabs.h \
RenderStatsDialog.h \
diff --git a/Gui/KnobGuiColor.cpp b/Gui/KnobGuiColor.cpp
index 51feaaed71..38c6612b39 100644
--- a/Gui/KnobGuiColor.cpp
+++ b/Gui/KnobGuiColor.cpp
@@ -34,13 +34,13 @@ CLANG_DIAG_OFF(uninitialized)
#include
#include
#include
-#include
#include
#include
#include
#include
#include
#include
+#include
GCC_DIAG_UNUSED_PRIVATE_FIELD_OFF
// /opt/local/include/QtGui/qmime.h:119:10: warning: private field 'type' is not used [-Wunused-private-field]
#include
@@ -207,7 +207,8 @@ KnobGuiColor::KnobGuiColor(KnobIPtr knob,
: KnobGuiValue(knob, container)
, _knob( boost::dynamic_pointer_cast(knob) )
, _colorLabel(0)
- , _colorDialogButton(0)
+ , _colorSelector(0)
+ , _colorSelectorButton(0)
, _lastColor()
, _useSimplifiedUI(true)
{
@@ -302,15 +303,32 @@ KnobGuiColor::addExtraWidgets(QHBoxLayout* containerLayout)
containerLayout->addSpacing( TO_DPIX(5) );
}
- QPixmap buttonPix;
- appPTR->getIcon(NATRON_PIXMAP_COLORWHEEL, NATRON_MEDIUM_BUTTON_ICON_SIZE, &buttonPix);
- _colorDialogButton = new Button( QIcon(buttonPix), QString(), containerLayout->widget() );
- _colorDialogButton->setFixedSize(medSize);
- _colorDialogButton->setIconSize(medIconSize);
- _colorDialogButton->setToolTip( NATRON_NAMESPACE::convertFromPlainText(tr("Open the color dialog."), NATRON_NAMESPACE::WhiteSpaceNormal) );
- _colorDialogButton->setFocusPolicy(Qt::NoFocus);
- QObject::connect( _colorDialogButton, SIGNAL(clicked()), this, SLOT(showColorDialog()) );
- containerLayout->addWidget(_colorDialogButton);
+ // add color selector popup
+ QPixmap colorSelectorPix;
+ appPTR->getIcon(NATRON_PIXMAP_COLORWHEEL, NATRON_MEDIUM_BUTTON_ICON_SIZE, &colorSelectorPix);
+
+ _colorSelectorButton = new QToolButton( containerLayout->widget() );
+ _colorSelectorButton->setObjectName( QString::fromUtf8("ColorSelectorButton") );
+ _colorSelectorButton->setIcon( QIcon(colorSelectorPix) );
+ _colorSelectorButton->setFixedSize(medSize);
+ _colorSelectorButton->setIconSize(medIconSize);
+ _colorSelectorButton->setPopupMode(QToolButton::InstantPopup);
+ _colorSelectorButton->setArrowType(Qt::NoArrow);
+ _colorSelectorButton->setAutoRaise(false);
+ _colorSelectorButton->setCheckable(false);
+ _colorSelectorButton->setToolTip( NATRON_NAMESPACE::convertFromPlainText(tr("Open Color Selector"), NATRON_NAMESPACE::WhiteSpaceNormal) );
+ _colorSelectorButton->setFocusPolicy(Qt::NoFocus);
+
+ _colorSelector = new ColorSelectorWidget( containerLayout->widget() );
+ QObject::connect( _colorSelector, SIGNAL( colorChanged(float, float, float, float) ),
+ this, SLOT( onColorSelectorChanged(float, float, float, float) ) );
+ QObject::connect( _colorSelector, SIGNAL( updateColor() ),
+ this, SLOT( updateColorSelector() ) );
+
+ QWidgetAction *colorPopupAction = new QWidgetAction( containerLayout->widget() );
+ colorPopupAction->setDefaultWidget(_colorSelector);
+ _colorSelectorButton->addAction(colorPopupAction);
+ containerLayout->addWidget(_colorSelectorButton);
if (_useSimplifiedUI) {
KnobGuiValue::_hide();
@@ -324,6 +342,36 @@ KnobGuiColor::onMustShowAllDimension()
onDimensionSwitchClicked(true);
}
+void
+KnobGuiColor::onColorSelectorChanged(float r,
+ float g,
+ float b,
+ float a)
+{
+ KnobColorPtr knob = _knob.lock();
+ int nDims = knob->getDimension();
+
+ assert(nDims == 1 || nDims == 3 || nDims == 4);
+ if (nDims != 1 && nDims != 3 && nDims != 4) {
+ throw std::logic_error("A color Knob can only have dimension 1, 3 or 4");
+ }
+
+ if (nDims == 1) {
+ knob->setValue(r, ViewSpec::all(), 0);
+ } else if (nDims == 3) {
+ knob->setValues(r, g, b,
+ ViewSpec::all(),
+ eValueChangedReasonNatronInternalEdited);
+ } else if (nDims == 4) {
+ knob->setValues(r, g, b, a,
+ ViewSpec::all(),
+ eValueChangedReasonNatronInternalEdited);
+ }
+ if ( getGui() ) {
+ getGui()->setDraftRenderEnabled(true);
+ }
+}
+
void
KnobGuiColor::updateLabel(double r,
double g,
@@ -373,7 +421,7 @@ KnobGuiColor::_hide()
KnobGuiValue::_hide();
}
_colorLabel->hide();
- _colorDialogButton->hide();
+ _colorSelectorButton->hide();
}
void
@@ -383,7 +431,7 @@ KnobGuiColor::_show()
KnobGuiValue::_show();
}
_colorLabel->show();
- _colorDialogButton->show();
+ _colorSelectorButton->show();
}
void
@@ -457,49 +505,13 @@ KnobGuiColor::onDimensionsExpanded()
void
KnobGuiColor::setEnabledExtraGui(bool enabled)
{
- _colorDialogButton->setEnabled(enabled);
_colorLabel->setEnabledMode(enabled);
+ _colorSelectorButton->setEnabled(enabled);
}
void
-KnobGuiColor::onDialogCurrentColorChanged(const QColor & color)
+KnobGuiColor::updateColorSelector()
{
- KnobColorPtr knob = _knob.lock();
- bool isSimple = _useSimplifiedUI;
- int nDims = knob->getDimension();
-
- assert(nDims == 1 || nDims == 3 || nDims == 4);
- if (nDims != 1 && nDims != 3 && nDims != 4) {
- throw std::logic_error("A color Knob can only have dimension 1, 3 or 4");
- }
-
- if (nDims == 1) {
- knob->setValue(color.redF(), ViewSpec::all(), 0);
- } else if (nDims == 3) {
- knob->setValues(isSimple ? color.redF() : Color::from_func_srgb( color.redF() ),
- isSimple ? color.greenF() : Color::from_func_srgb( color.greenF() ),
- isSimple ? color.blueF() : Color::from_func_srgb( color.blueF() ),
- ViewSpec::all(),
- eValueChangedReasonNatronInternalEdited);
- } else if (nDims == 4) {
- knob->setValues(isSimple ? color.redF() : Color::from_func_srgb( color.redF() ),
- isSimple ? color.greenF() : Color::from_func_srgb( color.greenF() ),
- isSimple ? color.blueF() : Color::from_func_srgb( color.blueF() ),
- color.alphaF(),
- ViewSpec::all(),
- eValueChangedReasonNatronInternalEdited);
- }
- if ( getGui() ) {
- getGui()->setDraftRenderEnabled(true);
- }
-}
-
-void
-KnobGuiColor::showColorDialog()
-{
- QColorDialog dialog( _colorLabel->parentWidget() );
-
- dialog.setOption(QColorDialog::DontUseNativeDialog);
KnobColorPtr knob = _knob.lock();
const int nDims = knob->getDimension();
double curR = knob->getValue(0);
@@ -509,71 +521,22 @@ KnobGuiColor::showColorDialog()
throw std::logic_error("A color Knob can only have dimension 1, 3 or 4");
}
- _lastColor[0] = curR;
double curG = curR;
double curB = curR;
double curA = 1.;
if (nDims > 1) {
curG = knob->getValue(1);
- _lastColor[1] = curG;
curB = knob->getValue(2);
- _lastColor[2] = curB;
}
if (nDims > 3) {
- dialog.setOption(QColorDialog::ShowAlphaChannel);
curA = knob->getValue(3);
- _lastColor[3] = curA;
- }
-
- bool isSimple = _useSimplifiedUI;
- QColor curColor;
- curColor.setRgbF( Image::clamp(isSimple ? curR : Color::to_func_srgb(curR), 0., 1.),
- Image::clamp(isSimple ? curG : Color::to_func_srgb(curG), 0., 1.),
- Image::clamp(isSimple ? curB : Color::to_func_srgb(curB), 0., 1.),
- Image::clamp(curA, 0., 1.) );
- dialog.setCurrentColor(curColor);
- QObject::connect( &dialog, SIGNAL(currentColorChanged(QColor)), this, SLOT(onDialogCurrentColorChanged(QColor)) );
- if ( !dialog.exec() ) {
- if (nDims == 3) {
- knob->setValues(_lastColor[0], _lastColor[1], _lastColor[2], ViewSpec::all(), eValueChangedReasonNatronGuiEdited);
- } else if (nDims == 4) {
- knob->setValues(_lastColor[0], _lastColor[1], _lastColor[2], _lastColor[3], ViewSpec::all(), eValueChangedReasonNatronGuiEdited);
- } else if (nDims == 1) {
- knob->setValue(_lastColor[0], ViewSpec::all(), 0, eValueChangedReasonNatronGuiEdited, NULL);
- }
- } else {
- QColor userColor = dialog.currentColor();
- std::vector color(4);
- color[0] = isSimple ? userColor.redF() : Color::from_func_srgb( userColor.redF() );
- color[1] = isSimple ? userColor.greenF() : Color::from_func_srgb( userColor.greenF() );
- color[2] = isSimple ? userColor.blueF() : Color::from_func_srgb( userColor.blueF() );
- color[3] = userColor.alphaF();
-
- for (int i = 0; i < 3; ++i) {
- SpinBox* sb = 0;
- getSpinBox(i, &sb);
- assert(sb);
- sb->setValue(color[i]);
- }
-
- // Refresh the last value so that the undo command retrieves the value that was prior to opening the dialog
- if (nDims == 3) {
- knob->setValues(_lastColor[0], _lastColor[1], _lastColor[2], ViewSpec::all(), eValueChangedReasonUserEdited);
- } else if (nDims == 4) {
- knob->setValues(_lastColor[0], _lastColor[1], _lastColor[2], _lastColor[3], ViewSpec::all(), eValueChangedReasonUserEdited);
- } else if (nDims == 1) {
- knob->setValue(_lastColor[0], ViewSpec::all(), 0, eValueChangedReasonUserEdited);
- }
-
- onSpinBoxValueChanged();
-
}
- if ( getGui() ) {
- getGui()->setDraftRenderEnabled(false);
- }
- //knob->evaluateValueChange(0, knob->getCurrentTime(), ViewIdx(0), eValueChangedReasonNatronGuiEdited);
-} // showColorDialog
+ _colorSelector->setColor( Image::clamp(curR, 0., 1.),
+ Image::clamp(curG, 0., 1.),
+ Image::clamp(curB, 0., 1.),
+ Image::clamp(curA, 0., 1.) );
+}
bool
KnobGuiColor::isAutoFoldDimensionsEnabled() const
diff --git a/Gui/KnobGuiColor.h b/Gui/KnobGuiColor.h
index fc9b7367ec..933424f8a4 100644
--- a/Gui/KnobGuiColor.h
+++ b/Gui/KnobGuiColor.h
@@ -36,6 +36,7 @@ CLANG_DIAG_OFF(uninitialized)
#include
#include
#include
+#include
CLANG_DIAG_ON(deprecated)
CLANG_DIAG_ON(uninitialized)
@@ -48,6 +49,7 @@ CLANG_DIAG_ON(uninitialized)
#include "Gui/CurveSelection.h"
#include "Gui/KnobGuiValue.h"
+#include "Gui/ColorSelectorWidget.h"
#include "Gui/AnimatedCheckBox.h"
#include "Gui/Label.h"
#include "Gui/GuiFwd.h"
@@ -129,7 +131,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON
public Q_SLOTS:
- void showColorDialog();
+ void updateColorSelector();
void setPickingEnabled(bool enabled);
@@ -137,8 +139,7 @@ public Q_SLOTS:
void onMustShowAllDimension();
- void onDialogCurrentColorChanged(const QColor & color);
-
+ void onColorSelectorChanged(float r, float g, float b, float a);
Q_SIGNALS:
@@ -185,7 +186,8 @@ public Q_SLOTS:
KnobColorWPtr _knob;
ColorPickerLabel *_colorLabel;
- Button *_colorDialogButton;
+ ColorSelectorWidget *_colorSelector;
+ QToolButton *_colorSelectorButton;
std::vector _lastColor;
bool _useSimplifiedUI;
};
diff --git a/Gui/QtColorTriangle.cpp b/Gui/QtColorTriangle.cpp
new file mode 100644
index 0000000000..bee6f20a17
--- /dev/null
+++ b/Gui/QtColorTriangle.cpp
@@ -0,0 +1,1516 @@
+/****************************************************************************
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+/*
+ This widget is maintained in Natron under the GNU Lesser General Public 2.1 license.
+*/
+
+#include "QtColorTriangle.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+/*! \class QtColorTriangle
+
+ \brief The QtColorTriangle class provides a triangular color
+ selection widget.
+
+ This widget uses the HSV color model, and is therefore useful for
+ selecting colors by eye.
+
+ The triangle in the center of the widget is used for selecting
+ saturation and value, and the surrounding circle is used for
+ selecting hue.
+
+ Use setColor() and color() to set and get the current color.
+
+ \img colortriangle.png
+*/
+
+/*! \fn QtColorTriangle::colorChanged(const QColor &color)
+
+ Whenever the color triangles color changes this signal is emitted
+ with the new \a color.
+*/
+
+const double PI = 3.14159265358979323846264338327950288419717;
+const double TWOPI = 2.0*PI;
+
+/*
+ Used to store color values in the range 0..255 as doubles.
+*/
+struct DoubleColor
+{
+ double r, g, b;
+
+ DoubleColor() : r(0.0), g(0.0), b(0.0) {}
+ DoubleColor(double red, double green, double blue) : r(red), g(green), b(blue) {}
+ DoubleColor(const DoubleColor &c) : r(c.r), g(c.g), b(c.b) {}
+};
+
+/*
+ Used to store pairs of DoubleColor and DoublePoint in one structure.
+*/
+struct Vertex {
+ DoubleColor color;
+ QPointF point;
+
+ Vertex(const DoubleColor &c, const QPointF &p) : color(c), point(p) {}
+ Vertex(const QColor &c, const QPointF &p)
+ : color(DoubleColor((double) c.red(), (double) c.green(),
+ (double) c.blue())), point(p) {}
+};
+
+/*! \internal
+
+Swaps the Vertex at *a with the one at *b.
+ */
+static void swap(Vertex **a, Vertex **b)
+{
+ Vertex *tmp = *a;
+ *a = *b;
+ *b = tmp;
+}
+
+/*!
+ Constructs a color triangle widget with the given \a parent.
+*/
+QtColorTriangle::QtColorTriangle(QWidget *parent)
+ : QWidget(parent), bg(sizeHint(), QImage::Format_RGB32), selMode(Idle)
+{
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ setFocusPolicy(Qt::StrongFocus);
+
+ mustGenerateBackground = true;
+
+ QColor tmp;
+ tmp.setHsv(76, 184, 206);
+ setColor(tmp);
+}
+
+/*!
+ Destructs the color triangle.
+*/
+QtColorTriangle::~QtColorTriangle()
+{
+}
+
+/*!
+ \internal
+
+ Generates the first background image.
+*/
+void QtColorTriangle::polish()
+{
+ outerRadius = (contentsRect().width() - 1) / 2;
+ if ((contentsRect().height() - 1) / 2 < outerRadius)
+ outerRadius = (contentsRect().height() - 1) / 2;
+
+ penWidth = (int) floor(outerRadius / 50.0);
+ ellipseSize = (int) floor(outerRadius / 12.5);
+
+ double cx = (double) contentsRect().center().x();
+ double cy = (double) contentsRect().center().y();
+
+ pa = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(a) * (outerRadius - (outerRadius / 5.0))));
+ pb = QPointF(cx + (cos(b) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(b) * (outerRadius - (outerRadius / 5.0))));
+ pc = QPointF(cx + (cos(c) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(c) * (outerRadius - (outerRadius / 5.0))));
+ pd = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 10.0))),
+ cy - (sin(a) * (outerRadius - (outerRadius / 10.0))));
+
+ // Find the current position of the selector
+ selectorPos = pointFromColor(curColor);
+
+ update();
+}
+
+/*! \reimp
+ */
+QSize QtColorTriangle::sizeHint() const
+{
+ return QSize(100, 100);
+}
+
+/*!
+ Forces the triangle widget to always be square. Returns the value
+ \a w.
+*/
+int QtColorTriangle::heightForWidth(int w) const
+{
+ return w;
+}
+
+/*!
+ \internal
+
+ Generates a new static background image. This function is called
+ initially, and in resizeEvent.
+*/
+void QtColorTriangle::genBackground()
+{
+ // Find the inner radius of the hue donut.
+ double innerRadius = outerRadius - outerRadius / 5;
+
+ // Create an image of the same size as the contents rect.
+ bg = QImage(contentsRect().size(), QImage::Format_RGB32);
+ QPainter p(&bg);
+ p.setRenderHint(QPainter::HighQualityAntialiasing);
+ p.fillRect(bg.rect(), palette().mid());
+
+ QConicalGradient gradient(bg.rect().center(), 90);
+ QColor color;
+ for (double i = 0; i <= 1.0; i += 0.1) {
+#if QT_VERSION < 0x040100
+ color.setHsv(int(i * 360.0), 255, 255);
+#else
+ color.setHsv(int(360.0 - (i * 360.0)), 255, 255);
+#endif
+ gradient.setColorAt(i, color);
+ }
+
+ QRectF innerRadiusRect(bg.rect().center().x() - innerRadius, bg.rect().center().y() - innerRadius,
+ innerRadius * 2 + 1, innerRadius * 2 + 1);
+ QRectF outerRadiusRect(bg.rect().center().x() - outerRadius, bg.rect().center().y() - outerRadius,
+ outerRadius * 2 + 1, outerRadius * 2 + 1);
+ QPainterPath path;
+ path.addEllipse(innerRadiusRect);
+ path.addEllipse(outerRadiusRect);
+
+ p.save();
+ p.setClipPath(path);
+ p.fillRect(bg.rect(), gradient);
+ p.restore();
+
+ double penThickness = bg.width() / 400.0;
+ for (int f = 0; f <= 5760; f += 20) {
+ int value = int((0.5 + cos(((f - 1800) / 5760.0) * TWOPI) / 2) * 255.0);
+
+ color.setHsv(int((f / 5760.0) * 360.0), 128 + (255 - value)/2, 255 - (255 - value)/4);
+ p.setPen(QPen(color, penThickness));
+ p.drawArc(innerRadiusRect, 1440 - f, 20);
+
+ color.setHsv(int((f / 5760.0) * 360.0), 128 + value/2, 255 - value/4);
+ p.setPen(QPen(color, penThickness));
+ p.drawArc(outerRadiusRect, 2880 - 1440 - f, 20);
+ }
+ return;
+}
+
+/*!
+ \internal
+
+ Selects new hue or saturation/value values, depending on where the
+ mouse button was pressed initially.
+*/
+void QtColorTriangle::mouseMoveEvent(QMouseEvent *e)
+{
+ if ((e->buttons() & Qt::LeftButton) == 0)
+ return;
+
+ QPointF depos((double) e->pos().x(), (double) e->pos().y());
+ bool newColor = false;
+
+ if (selMode == SelectingHue) {
+ // If selecting hue, find the new angles for the points a,b,c
+ // of the triangle. The following update() will then redraw
+ // the triangle.
+ a = angleAt(depos, contentsRect());
+ b = a + TWOPI / 3.0;
+ c = b + TWOPI / 3.0;
+ if (b > TWOPI) b -= TWOPI;
+ if (c > TWOPI) c -= TWOPI;
+
+ double am = a - PI/2;
+ if (am < 0) am += TWOPI;
+
+ curHue = 360 - (int) (((am) * 360.0) / TWOPI);
+ int h,s,v;
+ curColor.getHsv(&h, &s, &v);
+
+ if (curHue != h) {
+ newColor = true;
+ curColor.setHsv(curHue, s, v);
+ }
+
+ double cx = (double) contentsRect().center().x();
+ double cy = (double) contentsRect().center().y();
+
+ pa = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(a) * (outerRadius - (outerRadius / 5.0))));
+ pb = QPointF(cx + (cos(b) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(b) * (outerRadius - (outerRadius / 5.0))));
+ pc = QPointF(cx + (cos(c) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(c) * (outerRadius - (outerRadius / 5.0))));
+ pd = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 10.0))),
+ cy - (sin(a) * (outerRadius - (outerRadius / 10.0))));
+
+ selectorPos = pointFromColor(curColor);
+ } else {
+ Vertex aa(Qt::black, pa);
+ Vertex bb(Qt::black, pb);
+ Vertex cc(Qt::black, pc);
+
+ Vertex *p1 = &aa;
+ Vertex *p2 = &bb;
+ Vertex *p3 = &cc;
+ if (p1->point.y() > p2->point.y()) swap(&p1, &p2);
+ if (p1->point.y() > p3->point.y()) swap(&p1, &p3);
+ if (p2->point.y() > p3->point.y()) swap(&p2, &p3);
+
+ selectorPos = movePointToTriangle(depos.x(), depos.y(), aa, bb, cc);
+ QColor col = colorFromPoint(selectorPos);
+ if (col != curColor) {
+ // Ensure that hue does not change when selecting
+ // saturation and value.
+ int h,s,v;
+ col.getHsv(&h, &s, &v);
+ curColor.setHsv(curHue, s, v);
+ newColor = true;
+ }
+ }
+
+ if (newColor)
+ Q_EMIT colorChanged(curColor);
+
+ update();
+}
+
+/*!
+ \internal
+
+ When the left mouse button is pressed, this function determines
+ what part of the color triangle the cursor is, and from that it
+ initiates either selecting the hue (outside the triangle's area)
+ or the saturation/value (inside the triangle's area).
+*/
+void QtColorTriangle::mousePressEvent(QMouseEvent *e)
+{
+ // Only respond to the left mouse button.
+ if (e->button() != Qt::LeftButton)
+ return;
+
+ QPointF depos((double) e->pos().x(), (double) e->pos().y());
+ double rad = radiusAt(depos, contentsRect());
+ bool newColor = false;
+
+ // As in mouseMoveEvent, either find the a,b,c angles or the
+ // radian position of the selector, then order an update.
+ if (rad > (outerRadius - (outerRadius / 5))) {
+ selMode = SelectingHue;
+
+ a = angleAt(depos, contentsRect());
+ b = a + TWOPI / 3.0;
+ c = b + TWOPI / 3.0;
+ if (b > TWOPI) b -= TWOPI;
+ if (c > TWOPI) c -= TWOPI;
+
+ double am = a - PI/2;
+ if (am < 0) am += TWOPI;
+
+ curHue = 360 - (int) ((am * 360.0) / TWOPI);
+ int h,s,v;
+ curColor.getHsv(&h, &s, &v);
+
+ if (h != curHue) {
+ newColor = true;
+ curColor.setHsv(curHue, s, v);
+ }
+
+ double cx = (double) contentsRect().center().x();
+ double cy = (double) contentsRect().center().y();
+
+ pa = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(a) * (outerRadius - (outerRadius / 5.0))));
+ pb = QPointF(cx + (cos(b) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(b) * (outerRadius - (outerRadius / 5.0))));
+ pc = QPointF(cx + (cos(c) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(c) * (outerRadius - (outerRadius / 5.0))));
+ pd = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 10.0))),
+ cy - (sin(a) * (outerRadius - (outerRadius / 10.0))));
+
+ selectorPos = pointFromColor(curColor);
+ Q_EMIT colorChanged(curColor);
+ } else {
+ selMode = SelectingSatValue;
+
+ Vertex aa(Qt::black, pa);
+ Vertex bb(Qt::black, pb);
+ Vertex cc(Qt::black, pc);
+
+ Vertex *p1 = &aa;
+ Vertex *p2 = &bb;
+ Vertex *p3 = &cc;
+ if (p1->point.y() > p2->point.y()) swap(&p1, &p2);
+ if (p1->point.y() > p3->point.y()) swap(&p1, &p3);
+ if (p2->point.y() > p3->point.y()) swap(&p2, &p3);
+
+ selectorPos = movePointToTriangle(depos.x(), depos.y(), aa, bb, cc);
+ QColor col = colorFromPoint(selectorPos);
+ if (col != curColor) {
+ curColor = col;
+ newColor = true;
+ }
+ }
+
+ if (newColor)
+ Q_EMIT colorChanged(curColor);
+
+ update();
+}
+
+/*!
+ \internal
+
+ Stops selecting of colors with the mouse.
+*/
+void QtColorTriangle::mouseReleaseEvent(QMouseEvent *e)
+{
+ if (e->button() == Qt::LeftButton)
+ selMode = Idle;
+}
+
+/*!
+ \internal
+*/
+void QtColorTriangle::keyPressEvent(QKeyEvent *e)
+{
+ switch (e->key()) {
+ case Qt::Key_Left: {
+ --curHue;
+ if (curHue < 0) curHue += 360;
+ int h,s,v;
+ curColor.getHsv(&h, &s, &v);
+ QColor tmp;
+ tmp.setHsv(curHue, s, v);
+ setColor(tmp);
+ }
+ break;
+ case Qt::Key_Right: {
+ ++curHue;
+ if (curHue > 359) curHue -= 360;
+ int h,s,v;
+ curColor.getHsv(&h, &s, &v);
+ QColor tmp;
+ tmp.setHsv(curHue, s, v);
+ setColor(tmp);
+ }
+ break;
+ case Qt::Key_Up: {
+ int h,s,v;
+ curColor.getHsv(&h, &s, &v);
+ QColor tmp;
+ if (e->modifiers() & Qt::ShiftModifier) {
+ if (s > 5) s -= 5;
+ else s = 0;
+ } else {
+ if (v > 5) v -= 5;
+ else v = 0;
+ }
+ tmp.setHsv(curHue, s, v);
+ setColor(tmp);
+ }
+ break;
+ case Qt::Key_Down: {
+ int h,s,v;
+ curColor.getHsv(&h, &s, &v);
+ QColor tmp;
+ if (e->modifiers() & Qt::ShiftModifier) {
+ if (s < 250) s += 5;
+ else s = 255;
+ } else {
+ if (v < 250) v += 5;
+ else v = 255;
+ }
+ tmp.setHsv(curHue, s, v);
+ setColor(tmp);
+ }
+ break;
+ };
+}
+
+/*!
+ \internal
+
+ Regenerates the background image and sends an update.
+*/
+void QtColorTriangle::resizeEvent(QResizeEvent *)
+{
+ outerRadius = (contentsRect().width() - 1) / 2;
+ if ((contentsRect().height() - 1) / 2 < outerRadius)
+ outerRadius = (contentsRect().height() - 1) / 2;
+
+ penWidth = (int) floor(outerRadius / 50.0);
+ ellipseSize = (int) floor(outerRadius / 12.5);
+
+ double cx = (double) contentsRect().center().x();
+ double cy = (double) contentsRect().center().y();
+
+ pa = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(a) * (outerRadius - (outerRadius / 5.0))));
+ pb = QPointF(cx + (cos(b) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(b) * (outerRadius - (outerRadius / 5.0))));
+ pc = QPointF(cx + (cos(c) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(c) * (outerRadius - (outerRadius / 5.0))));
+ pd = QPointF(cx + (cos(a) * (outerRadius - (outerRadius / 10.0))),
+ cy - (sin(a) * (outerRadius - (outerRadius / 10.0))));
+
+ // Find the current position of the selector
+ selectorPos = pointFromColor(curColor);
+
+ mustGenerateBackground = true;
+ update();
+}
+
+/*! \reimp
+
+First copies a background image of the hue donut and its
+ background color onto the frame, then draws the color triangle,
+ and finally the selectors.
+*/
+void QtColorTriangle::paintEvent(QPaintEvent *e)
+{
+ QPainter p(this);
+ if (e->rect().intersects(contentsRect()))
+ p.setClipRegion(e->region().intersected(contentsRect()));
+ if (mustGenerateBackground) {
+ genBackground();
+ mustGenerateBackground = false;
+ }
+
+ // Blit the static generated background with the hue gradient onto
+ // the double buffer.
+ QImage buf = bg.copy();
+
+ // Draw the trigon
+ int h,s,v;
+ curColor.getHsv(&h, &s, &v);
+
+ // Find the color with only the hue, and max value and saturation
+ QColor hueColor;
+ hueColor.setHsv(curHue, 255, 255);
+
+ // Draw the triangle
+ drawTrigon(&buf, pa, pb, pc, hueColor);
+
+ // Slow step: convert the image to a pixmap
+ QPixmap pix = QPixmap::fromImage(buf);
+ QPainter painter(&pix);
+ painter.setRenderHint(QPainter::Antialiasing);
+
+ // Draw an outline of the triangle
+ QColor halfAlpha(0, 0, 0, 128);
+ painter.setPen(QPen(halfAlpha, 0));
+ painter.drawLine(pa, pb);
+ painter.drawLine(pb, pc);
+ painter.drawLine(pc, pa);
+
+ int ri, gi, bi;
+ hueColor.getRgb(&ri, &gi, &bi);
+ if ((ri * 30) + (gi * 59) + (bi * 11) > 12800)
+ painter.setPen(QPen(Qt::black, penWidth));
+ else
+ painter.setPen(QPen(Qt::white, penWidth));
+ painter.drawEllipse((int) (pd.x() - ellipseSize / 2.0),
+ (int) (pd.y() - ellipseSize / 2.0),
+ ellipseSize, ellipseSize);
+
+ curColor.getRgb(&ri, &gi, &bi);
+
+ // Find a color for painting the selector based on the brightness
+ // value of the color.
+ if ((ri * 30) + (gi * 59) + (bi * 11) > 12800)
+ painter.setPen(QPen(Qt::black, penWidth));
+ else
+ painter.setPen(QPen(Qt::white, penWidth));
+
+ // Draw the selector ellipse.
+ painter.drawEllipse(QRectF(selectorPos.x() - ellipseSize / 2.0,
+ selectorPos.y() - ellipseSize / 2.0,
+ ellipseSize + 0.5, ellipseSize + 0.5));
+
+ // Blit
+ p.drawPixmap(contentsRect().topLeft(), pix);
+}
+
+/*! \internal
+
+Draws a trigon (polygon with three corners \a pa, \a pb and \a pc
+ and three edges), using \a painter.
+
+ Fills the trigon with a gradient, where the \a pa point has the
+ color \a color, \a pb is black and \a bc is white. Bilinear
+ gradient.
+*/
+void QtColorTriangle::drawTrigon(QImage *buf, const QPointF &pa,
+ const QPointF &pb, const QPointF &pc,
+ const QColor &color)
+{
+ // Create three Vertex objects. A Vertex contains a double-point
+ // coordinate and a color.
+ // pa is the tip of the arrow
+ // pb is the black corner
+ // pc is the white corner
+ Vertex aa(color, pa);
+ Vertex bb(Qt::black, pb);
+ Vertex cc(Qt::white, pc);
+
+ // Sort. Make p1 above p2, which is above p3 (using y coordinate).
+ // Bubble sorting is fastest here.
+ Vertex *p1 = &aa;
+ Vertex *p2 = &bb;
+ Vertex *p3 = &cc;
+ if (p1->point.y() > p2->point.y()) swap(&p1, &p2);
+ if (p1->point.y() > p3->point.y()) swap(&p1, &p3);
+ if (p2->point.y() > p3->point.y()) swap(&p2, &p3);
+
+ // All the three y deltas are >= 0
+ double p1p2ydist = p2->point.y() - p1->point.y();
+ double p1p3ydist = p3->point.y() - p1->point.y();
+ double p2p3ydist = p3->point.y() - p2->point.y();
+ double p1p2xdist = p2->point.x() - p1->point.x();
+ double p1p3xdist = p3->point.x() - p1->point.x();
+ double p2p3xdist = p3->point.x() - p2->point.x();
+
+ // The first x delta decides wether we have a lefty or a righty
+ // trigon.
+ bool lefty = p1p2xdist < 0;
+
+ // Left and right colors and X values. The key in this map is the
+ // y values. Our goal is to fill these structures with all the
+ // information needed to do a single pass top-to-bottom,
+ // left-to-right drawing of the trigon.
+ QVarLengthArray leftColors;
+ QVarLengthArray rightColors;
+ QVarLengthArray leftX;
+ QVarLengthArray rightX;
+
+ leftColors.resize(int(floor(p3->point.y() + 1)));
+ rightColors.resize(int(floor(p3->point.y() + 1)));
+ leftX.resize(int(floor(p3->point.y() + 1)));
+ rightX.resize(int(floor(p3->point.y() + 1)));
+
+ // Scan longy - find all left and right colors and X-values for
+ // the tallest edge (p1-p3).
+ DoubleColor source;
+ DoubleColor dest;
+ double r, g, b;
+ double rdelta, gdelta, bdelta;
+ double x;
+ double xdelta;
+ int y1, y2;
+
+ // Initialize with known values
+ x = p1->point.x();
+ source = p1->color;
+ dest = p3->color;
+ r = source.r;
+ g = source.g;
+ b = source.b;
+ y1 = (int) floor(p1->point.y());
+ y2 = (int) floor(p3->point.y());
+
+ // Find slopes (notice that if the y dists are 0, we don't care
+ // about the slopes)
+ xdelta = p1p3ydist == 0.0 ? 0.0 : p1p3xdist / p1p3ydist;
+ rdelta = p1p3ydist == 0.0 ? 0.0 : (dest.r - r) / p1p3ydist;
+ gdelta = p1p3ydist == 0.0 ? 0.0 : (dest.g - g) / p1p3ydist;
+ bdelta = p1p3ydist == 0.0 ? 0.0 : (dest.b - b) / p1p3ydist;
+
+ // Calculate gradients using linear approximation
+ int y;
+ for (y = y1; y < y2; ++y) {
+ if (lefty) {
+ rightColors[y] = DoubleColor(r, g, b);
+ rightX[y] = x;
+ } else {
+ leftColors[y] = DoubleColor(r, g, b);
+ leftX[y] = x;
+ }
+
+ r += rdelta;
+ g += gdelta;
+ b += bdelta;
+ x += xdelta;
+ }
+
+ // Scan top shorty - find all left and right colors and x-values
+ // for the topmost of the two not-tallest short edges.
+ x = p1->point.x();
+ source = p1->color;
+ dest = p2->color;
+ r = source.r;
+ g = source.g;
+ b = source.b;
+ y1 = (int) floor(p1->point.y());
+ y2 = (int) floor(p2->point.y());
+
+ // Find slopes (notice that if the y dists are 0, we don't care
+ // about the slopes)
+ xdelta = p1p2ydist == 0.0 ? 0.0 : p1p2xdist / p1p2ydist;
+ rdelta = p1p2ydist == 0.0 ? 0.0 : (dest.r - r) / p1p2ydist;
+ gdelta = p1p2ydist == 0.0 ? 0.0 : (dest.g - g) / p1p2ydist;
+ bdelta = p1p2ydist == 0.0 ? 0.0 : (dest.b - b) / p1p2ydist;
+
+ // Calculate gradients using linear approximation
+ for (y = y1; y < y2; ++y) {
+ if (lefty) {
+ leftColors[y] = DoubleColor(r, g, b);
+ leftX[y] = x;
+ } else {
+ rightColors[y] = DoubleColor(r, g, b);
+ rightX[y] = x;
+ }
+
+ r += rdelta;
+ g += gdelta;
+ b += bdelta;
+ x += xdelta;
+ }
+
+ // Scan bottom shorty - find all left and right colors and
+ // x-values for the bottommost of the two not-tallest short edges.
+ x = p2->point.x();
+ source = p2->color;
+ dest = p3->color;
+ r = source.r;
+ g = source.g;
+ b = source.b;
+ y1 = (int) floor(p2->point.y());
+ y2 = (int) floor(p3->point.y());
+
+ // Find slopes (notice that if the y dists are 0, we don't care
+ // about the slopes)
+ xdelta = p2p3ydist == 0.0 ? 0.0 : p2p3xdist / p2p3ydist;
+ rdelta = p2p3ydist == 0.0 ? 0.0 : (dest.r - r) / p2p3ydist;
+ gdelta = p2p3ydist == 0.0 ? 0.0 : (dest.g - g) / p2p3ydist;
+ bdelta = p2p3ydist == 0.0 ? 0.0 : (dest.b - b) / p2p3ydist;
+
+ // Calculate gradients using linear approximation
+ for (y = y1; y < y2; ++y) {
+ if (lefty) {
+ leftColors[y] = DoubleColor(r, g, b);
+ leftX[y] = x;
+ } else {
+ rightColors[y] = DoubleColor(r, g, b);
+ rightX[y] = x;
+ }
+
+ r += rdelta;
+ g += gdelta;
+ b += bdelta;
+ x += xdelta;
+ }
+
+ // Inner loop. For each y in the left map of x-values, draw one
+ // line from left to right.
+ const int p3yfloor = int(floor(p3->point.y()));
+ for (int y = int(floor(p1->point.y())); y < p3yfloor; ++y) {
+ double lx = leftX[y];
+ double rx = rightX[y];
+
+ int lxi = (int) floor(lx);
+ int rxi = (int) floor(rx);
+ DoubleColor rc = rightColors[y];
+ DoubleColor lc = leftColors[y];
+
+ // if the xdist is 0, don't draw anything.
+ double xdist = rx - lx;
+ if (xdist != 0.0) {
+ double r = lc.r;
+ double g = lc.g;
+ double b = lc.b;
+ double rdelta = (rc.r - r) / xdist;
+ double gdelta = (rc.g - g) / xdist;
+ double bdelta = (rc.b - b) / xdist;
+
+ QRgb *scanline = reinterpret_cast(buf->scanLine(y));
+ scanline += lxi;
+
+ // Inner loop 2. Draws the line from left to right.
+ for (int i = lxi; i < rxi; ++i) {
+ *scanline++ = qRgb((int) r, (int) g, (int) b);
+ r += rdelta;
+ g += gdelta;
+ b += bdelta;
+ }
+ }
+ }
+}
+
+/*! \internal
+
+Sets the color of the triangle to \a col.
+ */
+void QtColorTriangle::setColor(const QColor &col)
+{
+ if (col == curColor)
+ return;
+
+ curColor = col;
+
+ int h, s, v;
+ curColor.getHsv(&h, &s, &v);
+
+ // Never use an invalid hue to display colors
+ if (h != -1)
+ curHue = h;
+
+ a = (((360 - curHue) * TWOPI) / 360.0);
+ a += PI / 2.0;
+ if (a > TWOPI) a -= TWOPI;
+
+ b = a + TWOPI/3;
+ c = b + TWOPI/3;
+
+ if (b > TWOPI) b -= TWOPI;
+ if (c > TWOPI) c -= TWOPI;
+
+ double cx = (double) contentsRect().center().x();
+ double cy = (double) contentsRect().center().y();
+ double innerRadius = outerRadius - (outerRadius / 5.0);
+ double pointerRadius = outerRadius - (outerRadius / 10.0);
+
+ pa = QPointF(cx + (cos(a) * innerRadius), cy - (sin(a) * innerRadius));
+ pb = QPointF(cx + (cos(b) * innerRadius), cy - (sin(b) * innerRadius));
+ pc = QPointF(cx + (cos(c) * innerRadius), cy - (sin(c) * innerRadius));
+ pd = QPointF(cx + (cos(a) * pointerRadius), cy - (sin(a) * pointerRadius));
+
+ selectorPos = pointFromColor(curColor);
+ update();
+
+ Q_EMIT colorChanged(curColor);
+}
+
+/*! \internal
+
+Returns the current color of the triangle.
+ */
+QColor QtColorTriangle::color() const
+{
+ return curColor;
+}
+
+/*!
+ \internal
+
+ Returns the distance from \a pos to the center of \a rect.
+*/
+double QtColorTriangle::radiusAt(const QPointF &pos, const QRect &rect) const
+{
+ double mousexdist = pos.x() - (double) rect.center().x();
+ double mouseydist = pos.y() - (double) rect.center().y();
+ return sqrt(mousexdist * mousexdist + mouseydist * mouseydist);
+}
+
+/*!
+ \internal
+
+ With origin set to the center of \a rect, this function returns
+ the angle in radians between the line that starts at (0,0) and
+ ends at (1,0) and the line that stars at (0,0) and ends at \a pos.
+*/
+double QtColorTriangle::angleAt(const QPointF &pos, const QRect &rect) const
+{
+ double mousexdist = pos.x() - (double) rect.center().x();
+ double mouseydist = pos.y() - (double) rect.center().y();
+ double mouserad = sqrt(mousexdist * mousexdist + mouseydist * mouseydist);
+ if (mouserad == 0.0)
+ return 0.0;
+
+ double angle = acos(mousexdist / mouserad);
+ if (mouseydist >= 0)
+ angle = TWOPI - angle;
+
+ return angle;
+}
+
+/*! \internal
+
+Returns a * a.
+ */
+inline double qsqr(double a)
+{
+ return a * a;
+}
+
+/*! \internal
+
+Returns the length of the vector (x,y).
+ */
+inline double vlen(double x, double y)
+{
+ return sqrt(qsqr(x) + qsqr(y));
+}
+
+/*! \internal
+
+Returns the vector product of (x1,y1) and (x2,y2).
+ */
+inline double vprod(double x1, double y1, double x2, double y2)
+{
+ return x1 * x2 + y1 * y2;
+}
+
+/*! \internal
+
+Returns true if the point cos(p),sin(p) is on the arc between
+ cos(a1),sin(a1) and cos(a2),sin(a2); otherwise returns false.
+*/
+bool angleBetweenAngles(double p, double a1, double a2)
+{
+ if (a1 > a2) {
+ a2 += TWOPI;
+ if (p < PI) p += TWOPI;
+ }
+
+ return p >= a1 && p < a2;
+}
+
+/*! \internal
+
+ A line from a to b is one of several lines in an equilateral
+ polygon, and they are drawn counter clockwise. This line therefore
+ has one side facing in and one facing out of the polygon. This
+ function determines wether (x,y) is on the inside or outside of
+ the given line, defined by the "from" coordinate (ax,ay) and the
+ "to" coordinate (bx,by).
+
+ The point (px,py) is the intersection between the a-b line and the
+ perpendicular projection of (x,y) onto that line.
+
+ Returns true if (x,y) is above the line; otherwise returns false.
+
+ If ax and bx are equal and ay and by are equal (line is a point),
+ this function will return true if (x,y) is equal to this point.
+*/
+static bool pointAbovePoint(double x, double y, double px, double py,
+ double ax, double ay, double bx, double by)
+{
+ bool result = false;
+
+ if (floor(ax) > floor(bx)) {
+ if (floor(ay) < floor(by)) {
+ // line is draw upright-to-downleft
+ if (floor(x) < floor(px) || floor(y) < floor(py))
+ result = true;
+ } else if (floor(ay) > floor(by)) {
+ // line is draw downright-to-upleft
+ if (floor(x) > floor(px) || floor(y) < floor(py))
+ result = true;
+ } else {
+ // line is flat horizontal
+ if (y < ay) result = true;
+ }
+ } else if (floor(ax) < floor(bx)) {
+ if (floor(ay) < floor(by)) {
+ // line is draw upleft-to-downright
+ if (floor(x) < floor(px) || floor(y) > floor(py))
+ result = true;
+ } else if (floor(ay) > floor(by)) {
+ // line is draw downleft-to-upright
+ if (floor(x) > floor(px) || floor(y) > floor(py))
+ result = true;
+ } else {
+ // line is flat horizontal
+ if (y > ay)
+ result = true;
+ }
+ } else {
+ // line is vertical
+ if (floor(ay) < floor(by)) {
+ if (x < ax) result = true;
+ } else if (floor(ay) > floor(by)) {
+ if (x > ax) result = true;
+ } else {
+ if (!(x == ax && y == ay))
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+/*! \internal
+
+if (ax,ay) to (bx,by) describes a line, and (x,y) is a point on
+ that line, returns -1 if (x,y) is outside the (ax,ay) bounds, 1 if
+ it is outside the (bx,by) bounds and 0 if (x,y) is within (ax,ay)
+ and (bx,by).
+*/
+static int pointInLine(double x, double y, double ax, double ay,
+ double bx, double by)
+{
+ if (ax > bx) {
+ if (ay < by) {
+ // line is draw upright-to-downleft
+
+ // if (x,y) is in on or above the upper right point,
+ // return -1.
+ if (y <= ay && x >= ax)
+ return -1;
+
+ // if (x,y) is in on or below the lower left point,
+ // return 1.
+ if (y >= by && x <= bx)
+ return 1;
+ } else {
+ // line is draw downright-to-upleft
+
+ // If the line is flat, only use the x coordinate.
+ if (floor(ay) == floor(by)) {
+ // if (x is to the right of the rightmost point,
+ // return -1. otherwise if x is to the left of the
+ // leftmost point, return 1.
+ if (x >= ax)
+ return -1;
+ else if (x <= bx)
+ return 1;
+ } else {
+ // if (x,y) is on or below the lower right point,
+ // return -1.
+ if (y >= ay && x >= ax)
+ return -1;
+
+ // if (x,y) is on or above the upper left point,
+ // return 1.
+ if (y <= by && x <= bx)
+ return 1;
+ }
+ }
+ } else {
+ if (ay < by) {
+ // line is draw upleft-to-downright
+
+ // If (x,y) is on or above the upper left point, return
+ // -1.
+ if (y <= ay && x <= ax)
+ return -1;
+
+ // If (x,y) is on or below the lower right point, return
+ // 1.
+ if (y >= by && x >= bx)
+ return 1;
+ } else {
+ // line is draw downleft-to-upright
+
+ // If the line is flat, only use the x coordinate.
+ if (floor(ay) == floor(by)) {
+ if (x <= ax)
+ return -1;
+ else if (x >= bx)
+ return 1;
+ } else {
+ // If (x,y) is on or below the lower left point, return
+ // -1.
+ if (y >= ay && x <= ax)
+ return -1;
+
+ // If (x,y) is on or above the upper right point, return
+ // 1.
+ if (y <= by && x >= bx)
+ return 1;
+ }
+ }
+ }
+
+ // No tests proved that (x,y) was outside [(ax,ay),(bx,by)], so we
+ // assume it's inside the line's bounds.
+ return 0;
+}
+
+/*! \internal
+
+ \a a, \a b and \a c are corner points of an equilateral triangle.
+ (\a x,\a y) is an arbitrary point inside or outside this triangle.
+
+ If (x,y) is inside the triangle, this function returns the double
+ point (x,y).
+
+ Otherwise, the intersection of the perpendicular projection of
+ (x,y) onto the closest triangle edge is returned, unless this
+ intersection is outside the triangle's bounds, in which case the
+ corner closest to the intersection is returned instead.
+
+ Yes, it's trigonometry.
+*/
+QPointF QtColorTriangle::movePointToTriangle(double x, double y, const Vertex &a,
+ const Vertex &b, const Vertex &c) const
+{
+ // Let v1A be the vector from (x,y) to a.
+ // Let v2A be the vector from a to b.
+ // Find the angle alphaA between v1A and v2A.
+ double v1xA = x - a.point.x();
+ double v1yA = y - a.point.y();
+ double v2xA = b.point.x() - a.point.x();
+ double v2yA = b.point.y() - a.point.y();
+ double vpA = vprod(v1xA, v1yA, v2xA, v2yA);
+ double cosA = vpA / (vlen(v1xA, v1yA) * vlen(v2xA, v2yA));
+ double alphaA = acos(cosA);
+
+ // Let v1B be the vector from x to b.
+ // Let v2B be the vector from b to c.
+ double v1xB = x - b.point.x();
+ double v1yB = y - b.point.y();
+ double v2xB = c.point.x() - b.point.x();
+ double v2yB = c.point.y() - b.point.y();
+ double vpB = vprod(v1xB, v1yB, v2xB, v2yB);
+ double cosB = vpB / (vlen(v1xB, v1yB) * vlen(v2xB, v2yB));
+ double alphaB = acos(cosB);
+
+ // Let v1C be the vector from x to c.
+ // Let v2C be the vector from c back to a.
+ double v1xC = x - c.point.x();
+ double v1yC = y - c.point.y();
+ double v2xC = a.point.x() - c.point.x();
+ double v2yC = a.point.y() - c.point.y();
+ double vpC = vprod(v1xC, v1yC, v2xC, v2yC);
+ double cosC = vpC / (vlen(v1xC, v1yC) * vlen(v2xC, v2yC));
+ double alphaC = acos(cosC);
+
+ // Find the radian angles between the (1,0) vector and the points
+ // A, B, C and (x,y). Use this information to determine which of
+ // the edges we should project (x,y) onto.
+ double angleA = angleAt(a.point, contentsRect());
+ double angleB = angleAt(b.point, contentsRect());
+ double angleC = angleAt(c.point, contentsRect());
+ double angleP = angleAt(QPointF(x, y), contentsRect());
+
+ // If (x,y) is in the a-b area, project onto the a-b vector.
+ if (angleBetweenAngles(angleP, angleA, angleB)) {
+ // Find the distance from (x,y) to a. Then use the slope of
+ // the a-b vector with this distance and the angle between a-b
+ // and a-(x,y) to determine the point of intersection of the
+ // perpendicular projection from (x,y) onto a-b.
+ double pdist = sqrt(qsqr(x - a.point.x()) + qsqr(y - a.point.y()));
+
+ // the length of all edges is always > 0
+ double p0x = a.point.x() + ((b.point.x() - a.point.x()) / vlen(v2xB, v2yB)) * cos(alphaA) * pdist;
+ double p0y = a.point.y() + ((b.point.y() - a.point.y()) / vlen(v2xB, v2yB)) * cos(alphaA) * pdist;
+
+ // If (x,y) is above the a-b line, which basically means it's
+ // outside the triangle, then return its projection onto a-b.
+ if (pointAbovePoint(x, y, p0x, p0y, a.point.x(), a.point.y(), b.point.x(), b.point.y())) {
+ // If the projection is "outside" a, return a. If it is
+ // outside b, return b. Otherwise return the projection.
+ int n = pointInLine(p0x, p0y, a.point.x(), a.point.y(), b.point.x(), b.point.y());
+ if (n < 0)
+ return a.point;
+ else if (n > 0)
+ return b.point;
+
+ return QPointF(p0x, p0y);
+ }
+ } else if (angleBetweenAngles(angleP, angleB, angleC)) {
+ // If (x,y) is in the b-c area, project onto the b-c vector.
+ double pdist = sqrt(qsqr(x - b.point.x()) + qsqr(y - b.point.y()));
+
+ // the length of all edges is always > 0
+ double p0x = b.point.x() + ((c.point.x() - b.point.x()) / vlen(v2xC, v2yC)) * cos(alphaB) * pdist;
+ double p0y = b.point.y() + ((c.point.y() - b.point.y()) / vlen(v2xC, v2yC)) * cos(alphaB) * pdist;
+
+ if (pointAbovePoint(x, y, p0x, p0y, b.point.x(), b.point.y(), c.point.x(), c.point.y())) {
+ int n = pointInLine(p0x, p0y, b.point.x(), b.point.y(), c.point.x(), c.point.y());
+ if (n < 0)
+ return b.point;
+ else if (n > 0)
+ return c.point;
+ return QPointF(p0x, p0y);
+ }
+ } else if (angleBetweenAngles(angleP, angleC, angleA)) {
+ // If (x,y) is in the c-a area, project onto the c-a vector.
+ double pdist = sqrt(qsqr(x - c.point.x()) + qsqr(y - c.point.y()));
+
+ // the length of all edges is always > 0
+ double p0x = c.point.x() + ((a.point.x() - c.point.x()) / vlen(v2xA, v2yA)) * cos(alphaC) * pdist;
+ double p0y = c.point.y() + ((a.point.y() - c.point.y()) / vlen(v2xA, v2yA)) * cos(alphaC) * pdist;
+
+ if (pointAbovePoint(x, y, p0x, p0y, c.point.x(), c.point.y(), a.point.x(), a.point.y())) {
+ int n = pointInLine(p0x, p0y, c.point.x(), c.point.y(), a.point.x(), a.point.y());
+ if (n < 0)
+ return c.point;
+ else if (n > 0)
+ return a.point;
+ return QPointF(p0x, p0y);
+ }
+ }
+
+ // (x,y) is inside the triangle (inside a-b, b-c and a-c).
+ return QPointF(x, y);
+}
+
+/*! \internal
+
+ Given the color \a col, this function determines the point in the
+ equilateral triangle defined with (pa, pb, pc) that displays this
+ color. The function assumes the color at pa has a hue equal to the
+ hue of \a col, and that pb is black and pc is white.
+
+ In this certain type of triangle, we observe that saturation grows
+ from the black-color edge towards the black-white edge. The value
+ grows from the black corner towards the white-color edge. Using
+ the intersection of the saturation and value points on the three
+ edges, we are able to determine the point with the same saturation
+ and value as \a col.
+*/
+QPointF QtColorTriangle::pointFromColor(const QColor &col) const
+{
+ // Simplifications for the corner cases.
+ if (col == Qt::black)
+ return pb;
+ else if (col == Qt::white)
+ return pc;
+
+ // Find the x and y slopes
+ double ab_deltax = pb.x() - pa.x();
+ double ab_deltay = pb.y() - pa.y();
+ double bc_deltax = pc.x() - pb.x();
+ double bc_deltay = pc.y() - pb.y();
+ double ac_deltax = pc.x() - pa.x();
+ double ac_deltay = pc.y() - pa.y();
+
+ // Extract the h,s,v values of col.
+ int hue,sat,val;
+ col.getHsv(&hue, &sat, &val);
+
+ // Find the line that passes through the triangle where the value
+ // is equal to our color's value.
+ double p1 = pa.x() + (ab_deltax * (double) (255 - val)) / 255.0;
+ double q1 = pa.y() + (ab_deltay * (double) (255 - val)) / 255.0;
+ double p2 = pb.x() + (bc_deltax * (double) val) / 255.0;
+ double q2 = pb.y() + (bc_deltay * (double) val) / 255.0;
+
+ // Find the line that passes through the triangle where the
+ // saturation is equal to our color's value.
+ double p3 = pa.x() + (ac_deltax * (double) (255 - sat)) / 255.0;
+ double q3 = pa.y() + (ac_deltay * (double) (255 - sat)) / 255.0;
+ double p4 = pb.x();
+ double q4 = pb.y();
+
+ // Find the intersection between these lines.
+ double x = 0;
+ double y = 0;
+ if (p1 != p2) {
+ double a = (q2 - q1) / (p2 - p1);
+ double c = (q4 - q3) / (p4 - p3);
+ double b = q1 - a * p1;
+ double d = q3 - c * p3;
+
+ x = (d - b) / (a - c);
+ y = a * x + b;
+ }
+ else {
+ x = p1;
+ y = q3 + (x - p3) * (q4 - q3) / (p4 - p3);
+ }
+
+ return QPointF(x, y);
+}
+
+/*! \internal
+
+ Determines the color in the color triangle at the point \a p. Uses
+ linear interpolation to find the colors to the left and right of
+ \a p, then uses the same technique to find the color at \a p using
+ these two colors.
+*/
+QColor QtColorTriangle::colorFromPoint(const QPointF &p) const
+{
+ // Find the outer radius of the hue gradient.
+ int outerRadius = (contentsRect().width() - 1) / 2;
+ if ((contentsRect().height() - 1) / 2 < outerRadius)
+ outerRadius = (contentsRect().height() - 1) / 2;
+
+ // Find the center coordinates
+ double cx = (double) contentsRect().center().x();
+ double cy = (double) contentsRect().center().y();
+
+ // Find the a, b and c from their angles, the center of the rect
+ // and the radius of the hue gradient donut.
+ QPointF pa(cx + (cos(a) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(a) * (outerRadius - (outerRadius / 5.0))));
+ QPointF pb(cx + (cos(b) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(b) * (outerRadius - (outerRadius / 5.0))));
+ QPointF pc(cx + (cos(c) * (outerRadius - (outerRadius / 5.0))),
+ cy - (sin(c) * (outerRadius - (outerRadius / 5.0))));
+
+ // Find the hue value from the angle of the 'a' point.
+ double angle = a - PI/2.0;
+ if (angle < 0) angle += TWOPI;
+ double hue = (360.0 * angle) / TWOPI;
+
+ // Create the color of the 'a' corner point. We know that b is
+ // black and c is white.
+ QColor color;
+ color.setHsv(360 - (int) floor(hue), 255, 255);
+
+ // See also drawTrigon(), which basically does exactly the same to
+ // determine all colors in the trigon.
+ Vertex aa(color, pa);
+ Vertex bb(Qt::black, pb);
+ Vertex cc(Qt::white, pc);
+
+ // Make sure p1 is above p2, which is above p3.
+ Vertex *p1 = &aa;
+ Vertex *p2 = &bb;
+ Vertex *p3 = &cc;
+ if (p1->point.y() > p2->point.y()) swap(&p1, &p2);
+ if (p1->point.y() > p3->point.y()) swap(&p1, &p3);
+ if (p2->point.y() > p3->point.y()) swap(&p2, &p3);
+
+ // Find the slopes of all edges in the trigon. All the three y
+ // deltas here are positive because of the above sorting.
+ double p1p2ydist = p2->point.y() - p1->point.y();
+ double p1p3ydist = p3->point.y() - p1->point.y();
+ double p2p3ydist = p3->point.y() - p2->point.y();
+ double p1p2xdist = p2->point.x() - p1->point.x();
+ double p1p3xdist = p3->point.x() - p1->point.x();
+ double p2p3xdist = p3->point.x() - p2->point.x();
+
+ // The first x delta decides wether we have a lefty or a righty
+ // trigon. A lefty trigon has its tallest edge on the right hand
+ // side of the trigon. The righty trigon has it on its left side.
+ // This property determines wether the left or the right set of x
+ // coordinates will be continuous.
+ bool lefty = p1p2xdist < 0;
+
+ // Find whether the selector's y is in the first or second shorty,
+ // counting from the top and downwards. This is used to find the
+ // color at the selector point.
+ bool firstshorty = (p.y() >= p1->point.y() && p.y() < p2->point.y());
+
+ // From the y value of the selector's position, find the left and
+ // right x values.
+ double leftx;
+ double rightx;
+ if (lefty) {
+ if (firstshorty) {
+ leftx = p1->point.x();
+ if (floor(p1p2ydist) != 0.0) {
+ leftx += (p1p2xdist * (p.y() - p1->point.y())) / p1p2ydist;
+ } else {
+ leftx = qMin(p1->point.x(), p2->point.x());
+ }
+ } else {
+ leftx = p2->point.x();
+ if (floor(p2p3ydist) != 0.0) {
+ leftx += (p2p3xdist * (p.y() - p2->point.y())) / p2p3ydist;
+ } else {
+ leftx = qMin(p2->point.x(), p3->point.x());
+ }
+ }
+
+ rightx = p1->point.x();
+ rightx += (p1p3xdist * (p.y() - p1->point.y())) / p1p3ydist;
+ } else {
+ leftx = p1->point.x();
+ leftx += (p1p3xdist * (p.y() - p1->point.y())) / p1p3ydist;
+
+ if (firstshorty) {
+ rightx = p1->point.x();
+ if (floor(p1p2ydist) != 0.0) {
+ rightx += (p1p2xdist * (p.y() - p1->point.y())) / p1p2ydist;
+ } else {
+ rightx = qMax(p1->point.x(), p2->point.x());
+ }
+ } else {
+ rightx = p2->point.x();
+ if (floor(p2p3ydist) != 0.0) {
+ rightx += (p2p3xdist * (p.y() - p2->point.y())) / p2p3ydist;
+ } else {
+ rightx = qMax(p2->point.x(), p3->point.x());
+ }
+ }
+ }
+
+ // Find the r,g,b values of the points on the trigon's edges that
+ // are to the left and right of the selector.
+ double rshort = 0, gshort = 0, bshort = 0;
+ double rlong = 0, glong = 0, blong = 0;
+ if (firstshorty) {
+ if (floor(p1p2ydist) != 0.0) {
+ rshort = p2->color.r * (p.y() - p1->point.y()) / p1p2ydist;
+ gshort = p2->color.g * (p.y() - p1->point.y()) / p1p2ydist;
+ bshort = p2->color.b * (p.y() - p1->point.y()) / p1p2ydist;
+ rshort += p1->color.r * (p2->point.y() - p.y()) / p1p2ydist;
+ gshort += p1->color.g * (p2->point.y() - p.y()) / p1p2ydist;
+ bshort += p1->color.b * (p2->point.y() - p.y()) / p1p2ydist;
+ } else {
+ if (lefty) {
+ if (p1->point.x() <= p2->point.x()) {
+ rshort = p1->color.r;
+ gshort = p1->color.g;
+ bshort = p1->color.b;
+ } else {
+ rshort = p2->color.r;
+ gshort = p2->color.g;
+ bshort = p2->color.b;
+ }
+ } else {
+ if (p1->point.x() > p2->point.x()) {
+ rshort = p1->color.r;
+ gshort = p1->color.g;
+ bshort = p1->color.b;
+ } else {
+ rshort = p2->color.r;
+ gshort = p2->color.g;
+ bshort = p2->color.b;
+ }
+ }
+ }
+ } else {
+ if (floor(p2p3ydist) != 0.0) {
+ rshort = p3->color.r * (p.y() - p2->point.y()) / p2p3ydist;
+ gshort = p3->color.g * (p.y() - p2->point.y()) / p2p3ydist;
+ bshort = p3->color.b * (p.y() - p2->point.y()) / p2p3ydist;
+ rshort += p2->color.r * (p3->point.y() - p.y()) / p2p3ydist;
+ gshort += p2->color.g * (p3->point.y() - p.y()) / p2p3ydist;
+ bshort += p2->color.b * (p3->point.y() - p.y()) / p2p3ydist;
+ } else {
+ if (lefty) {
+ if (p2->point.x() <= p3->point.x()) {
+ rshort = p2->color.r;
+ gshort = p2->color.g;
+ bshort = p2->color.b;
+ } else {
+ rshort = p3->color.r;
+ gshort = p3->color.g;
+ bshort = p3->color.b;
+ }
+ } else {
+ if (p2->point.x() > p3->point.x()) {
+ rshort = p2->color.r;
+ gshort = p2->color.g;
+ bshort = p2->color.b;
+ } else {
+ rshort = p3->color.r;
+ gshort = p3->color.g;
+ bshort = p3->color.b;
+ }
+ }
+ }
+ }
+
+ // p1p3ydist is never 0
+ rlong = p3->color.r * (p.y() - p1->point.y()) / p1p3ydist;
+ glong = p3->color.g * (p.y() - p1->point.y()) / p1p3ydist;
+ blong = p3->color.b * (p.y() - p1->point.y()) / p1p3ydist;
+ rlong += p1->color.r * (p3->point.y() - p.y()) / p1p3ydist;
+ glong += p1->color.g * (p3->point.y() - p.y()) / p1p3ydist;
+ blong += p1->color.b * (p3->point.y() - p.y()) / p1p3ydist;
+
+ // rshort,gshort,bshort is the color on one of the shortys.
+ // rlong,glong,blong is the color on the longy. So depending on
+ // wether we have a lefty trigon or not, we can determine which
+ // colors are on the left and right edge.
+ double rl, gl, bl, rr, gr, br;
+ if (lefty) {
+ rl = rshort; gl = gshort; bl = bshort;
+ rr = rlong; gr = glong; br = blong;
+ } else {
+ rl = rlong; gl = glong; bl = blong;
+ rr = rshort; gr = gshort; br = bshort;
+ }
+
+ // Find the distance from the left x to the right x (xdist). Then
+ // find the distances from the selector to each of these (saxdist
+ // and saxdist2). These distances are used to find the color at
+ // the selector.
+ double xdist = rightx - leftx;
+ double saxdist = p.x() - leftx;
+ double saxdist2 = xdist - saxdist;
+
+ // Now determine the r,g,b values of the selector using a linear
+ // approximation.
+ double r, g, b;
+ if (xdist != 0.0) {
+ r = (saxdist2 * rl / xdist) + (saxdist * rr / xdist);
+ g = (saxdist2 * gl / xdist) + (saxdist * gr / xdist);
+ b = (saxdist2 * bl / xdist) + (saxdist * br / xdist);
+ } else {
+ // In theory, the left and right color will be equal here. But
+ // because of the loss of precision, we get an error on both
+ // colors. The best approximation we can get is from adding
+ // the two errors, which in theory will eliminate the error
+ // but in practise will only minimize it.
+ r = (rl + rr) / 2;
+ g = (gl + gr) / 2;
+ b = (bl + br) / 2;
+ }
+
+ // Now floor the color components and fit them into proper
+ // boundaries. This again is to compensate for the error caused by
+ // loss of precision.
+ int ri = (int) floor(r);
+ int gi = (int) floor(g);
+ int bi = (int) floor(b);
+ if (ri < 0) ri = 0;
+ else if (ri > 255) ri = 255;
+ if (gi < 0) gi = 0;
+ else if (gi > 255) gi = 255;
+ if (bi < 0) bi = 0;
+ else if (bi > 255) bi = 255;
+
+ // Voila, we have the color at the point of the selector.
+ return QColor(ri, gi, bi);
+}
diff --git a/Gui/QtColorTriangle.h b/Gui/QtColorTriangle.h
new file mode 100644
index 0000000000..5d420733f2
--- /dev/null
+++ b/Gui/QtColorTriangle.h
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact Nokia at qt-info@nokia.com.
+**
+****************************************************************************/
+
+/*
+ This widget is maintained in Natron under the GNU Lesser General Public 2.1 license.
+*/
+
+#ifndef QTCOLORTRIANGLE_H
+#define QTCOLORTRIANGLE_H
+#include
+#include
+
+class QPointF;
+struct Vertex;
+
+class QtColorTriangle : public QWidget
+{
+ Q_OBJECT
+
+public:
+ QtColorTriangle(QWidget *parent = 0);
+ ~QtColorTriangle();
+
+ QSize sizeHint() const;
+ int heightForWidth(int w) const;
+
+ void polish();
+ QColor color() const;
+
+Q_SIGNALS:
+ void colorChanged(const QColor &col);
+
+public Q_SLOTS:
+ void setColor(const QColor &col);
+
+protected:
+ void paintEvent(QPaintEvent *);
+ void mouseMoveEvent(QMouseEvent *);
+ void mousePressEvent(QMouseEvent *);
+ void mouseReleaseEvent(QMouseEvent *);
+ void keyPressEvent(QKeyEvent *e);
+ void resizeEvent(QResizeEvent *);
+ void drawTrigon(QImage *p, const QPointF &a, const QPointF &b,
+ const QPointF &c, const QColor &color);
+
+private:
+ double radiusAt(const QPointF &pos, const QRect &rect) const;
+ double angleAt(const QPointF &pos, const QRect &rect) const;
+ QPointF movePointToTriangle(double x, double y, const Vertex &a,
+ const Vertex &b, const Vertex &c) const;
+
+ QPointF pointFromColor(const QColor &col) const;
+ QColor colorFromPoint(const QPointF &p) const;
+
+ void genBackground();
+
+ QImage bg;
+ double a, b, c;
+ QPointF pa, pb, pc, pd;
+
+ QColor curColor;
+ int curHue;
+
+ bool mustGenerateBackground;
+ int penWidth;
+ int ellipseSize;
+
+ int outerRadius;
+ QPointF selectorPos;
+
+ enum SelectionMode {
+ Idle,
+ SelectingHue,
+ SelectingSatValue
+ } selMode;
+};
+
+#endif
diff --git a/Gui/Resources/Stylesheets/mainstyle.qss b/Gui/Resources/Stylesheets/mainstyle.qss
index 145ea4338e..89d8b5ba77 100644
--- a/Gui/Resources/Stylesheets/mainstyle.qss
+++ b/Gui/Resources/Stylesheets/mainstyle.qss
@@ -289,6 +289,9 @@ QPushButton:focus {
QPushButton:!enabled {
color: %8;
}
+QPushButton:checked {
+ background-color: %4;
+}
QHeaderView:section {
color: %5;
@@ -734,3 +737,20 @@ OutputScriptTextEdit {
selection-background-color: %1;
}
+/*
+ Color Selector Button/Widget
+*/
+
+QToolButton#ColorSelectorButton {
+ border: none;
+}
+
+QToolButton#ColorSelectorButton:menu-indicator {
+ image: none;
+}
+
+QToolButton#ColorSelectorButton:pressed {
+ padding-left: 0px;
+ top: 0px;
+ left: 0px;
+}
diff --git a/Gui/ScaleSliderQWidget.cpp b/Gui/ScaleSliderQWidget.cpp
index 8482d2c524..470d7dfcbd 100644
--- a/Gui/ScaleSliderQWidget.cpp
+++ b/Gui/ScaleSliderQWidget.cpp
@@ -73,6 +73,7 @@ struct ScaleSliderQWidgetPrivate
bool dragging;
QFont font;
QColor sliderColor;
+ QColor customSliderColor;
bool initialized;
bool mustInitializeSliderPosition;
bool readOnly;
@@ -82,6 +83,7 @@ struct ScaleSliderQWidgetPrivate
ScaleSliderQWidget::DataTypeEnum dataType;
bool altered;
bool useLineColor;
+ bool useSliderColor;
QColor lineColor;
bool allowDraftModeSetting;
@@ -112,6 +114,7 @@ struct ScaleSliderQWidgetPrivate
, dataType(dataType)
, altered(false)
, useLineColor(false)
+ , useSliderColor(false)
, lineColor(Qt::black)
, allowDraftModeSetting(allowDraftModeSetting)
{
@@ -572,9 +575,13 @@ ScaleSliderQWidget::paintEvent(QPaintEvent* /*e*/)
double positionValue = _imp->zoomCtx.toWidgetCoordinates(_imp->value, 0).x();
SettingsPtr settings = appPTR->getCurrentSettings();
- double sliderColorRGB[3];
- settings->getSliderColor(&sliderColorRGB[0], &sliderColorRGB[1], &sliderColorRGB[2]);
- _imp->sliderColor.setRgbF(sliderColorRGB[0], sliderColorRGB[1], sliderColorRGB[2]);
+ if (!_imp->useSliderColor) {
+ double sliderColorRGB[3];
+ settings->getSliderColor(&sliderColorRGB[0], &sliderColorRGB[1], &sliderColorRGB[2]);
+ _imp->sliderColor.setRgbF(sliderColorRGB[0], sliderColorRGB[1], sliderColorRGB[2]);
+ } else {
+ _imp->sliderColor = _imp->customSliderColor;
+ }
double selectionColorRGB[3];
settings->getSelectionColor(&selectionColorRGB[0], &selectionColorRGB[1], &selectionColorRGB[2]);
@@ -652,6 +659,15 @@ ScaleSliderQWidget::setUseLineColor(bool use,
update();
}
+void
+ScaleSliderQWidget::setUseSliderColor(bool use,
+ const QColor &color)
+{
+ _imp->useSliderColor = use;
+ _imp->customSliderColor = color;
+ update();
+}
+
NATRON_NAMESPACE_EXIT
NATRON_NAMESPACE_USING
diff --git a/Gui/ScaleSliderQWidget.h b/Gui/ScaleSliderQWidget.h
index c7e09767c4..1e010b7d63 100644
--- a/Gui/ScaleSliderQWidget.h
+++ b/Gui/ScaleSliderQWidget.h
@@ -96,6 +96,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON
bool getAltered() const;
void setUseLineColor(bool use, const QColor& color);
+ void setUseSliderColor(bool use, const QColor &color);
Q_SIGNALS:
void editingFinished(bool hasMovedOnce);