File indexing completed on 2024-05-12 04:44:33

0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT
0003 
0004 // Own headers
0005 // First the interface, which forces the header to be self-contained.
0006 #include "gradientslider.h"
0007 // Second, the private implementation.
0008 #include "gradientslider_p.h" // IWYU pragma: associated
0009 
0010 #include "abstractdiagram.h"
0011 #include "constpropagatingrawpointer.h"
0012 #include "constpropagatinguniquepointer.h"
0013 #include "gradientimageparameters.h"
0014 #include "helperconstants.h"
0015 #include "lchadouble.h"
0016 #include <helper.h>
0017 #include <qevent.h>
0018 #include <qguiapplication.h>
0019 #include <qimage.h>
0020 #include <qpainter.h>
0021 #include <qpen.h>
0022 #include <qpoint.h>
0023 #include <qsharedpointer.h>
0024 #include <qsizepolicy.h>
0025 #include <qtransform.h>
0026 #include <qwidget.h>
0027 
0028 namespace PerceptualColor
0029 {
0030 /** @brief Constructs a vertical slider.
0031  * @param colorSpace The color space within which this widget should operate.
0032  * Can be created with @ref RgbColorSpaceFactory.
0033  * Can be created with @ref RgbColorSpaceFactory.
0034  * @param parent parent widget (if any) */
0035 GradientSlider::GradientSlider(const QSharedPointer<PerceptualColor::RgbColorSpace> &colorSpace, QWidget *parent)
0036     : AbstractDiagram(parent)
0037     , d_pointer(new GradientSliderPrivate(this))
0038 {
0039     d_pointer->initialize(colorSpace, Qt::Orientation::Vertical);
0040 }
0041 
0042 /** @brief Constructs a slider.
0043  * @param colorSpace The color space within which this widget should operate.
0044  * Can be created with @ref RgbColorSpaceFactory.
0045  * @param orientation The orientation parameter determines whether
0046  * the slider is horizontal or vertical; the valid values
0047  * are <tt>Qt::Vertical</tt> and <tt>Qt::Horizontal</tt>.
0048  * @param parent parent widget (if any) */
0049 GradientSlider::GradientSlider(const QSharedPointer<PerceptualColor::RgbColorSpace> &colorSpace, Qt::Orientation orientation, QWidget *parent)
0050     : AbstractDiagram(parent)
0051     , d_pointer(new GradientSliderPrivate(this))
0052 {
0053     d_pointer->initialize(colorSpace, orientation);
0054 }
0055 
0056 /** @brief Default destructor */
0057 GradientSlider::~GradientSlider() noexcept
0058 {
0059 }
0060 
0061 /** @brief Constructor
0062  *
0063  * @param backLink Pointer to the object from which <em>this</em> object
0064  * is the private implementation. */
0065 GradientSliderPrivate::GradientSliderPrivate(GradientSlider *backLink)
0066     : m_firstColor{0, 0, 0, 0} // dummy value
0067     , m_secondColor{0, 0, 0, 0} // dummy value
0068     , q_pointer(backLink)
0069 {
0070 }
0071 
0072 /** @brief Basic initialization.
0073  *
0074  * Code that is shared between the various overloaded constructors
0075  * of @ref GradientSlider.
0076  *
0077  * @note This function requires that @ref q_pointer points to a completely
0078  * initialized object. Therefore, this function may <em>not</em> be called
0079  * within the constructor of @ref GradientSliderPrivate because in this
0080  * moment the @ref GradientSlider object is still not fully initialized.
0081  * However, a call from the <em>function body</em> of a constructor of
0082  * @ref GradientSlider should be okay.
0083  *
0084  * @param colorSpace the color space
0085  * @param orientation determines whether the slider is horizontal or
0086  * vertical */
0087 void GradientSliderPrivate::initialize(const QSharedPointer<RgbColorSpace> &colorSpace, Qt::Orientation orientation)
0088 {
0089     q_pointer->setFocusPolicy(Qt::StrongFocus);
0090     m_gradientImageParameters.rgbColorSpace = colorSpace;
0091     setOrientationWithoutSignalAndForceNewSizePolicy(orientation);
0092     constexpr LchaDouble first{75, 65, 90, 1};
0093     constexpr LchaDouble second{50, 75, 45, 1};
0094     q_pointer->setColors(first, second);
0095 
0096     // Connections
0097     q_pointer->connect( //
0098         &m_gradientImage, //
0099         &AsyncImageProvider<GradientImageParameters>::interlacingPassCompleted, //
0100         q_pointer,
0101         &GradientSlider::callUpdate);
0102 }
0103 
0104 // No documentation here (documentation of properties
0105 // and its getters are in the header)
0106 LchaDouble GradientSlider::firstColor() const
0107 {
0108     return d_pointer->m_firstColor;
0109 }
0110 
0111 /** @brief Setter for @ref firstColor property.
0112  *
0113  * @param newFirstColor the new @ref firstColor */
0114 void GradientSlider::setFirstColor(const PerceptualColor::LchaDouble &newFirstColor)
0115 {
0116     if (!d_pointer->m_firstColor.hasSameCoordinates(newFirstColor)) {
0117         d_pointer->m_firstColor = newFirstColor;
0118         d_pointer->m_gradientImageParameters.setFirstColor(newFirstColor);
0119         d_pointer->m_gradientImage.setImageParameters( //
0120             d_pointer->m_gradientImageParameters);
0121         update();
0122         Q_EMIT firstColorChanged(newFirstColor);
0123     }
0124 }
0125 
0126 // No documentation here (documentation of properties
0127 // and its getters are in the header)
0128 LchaDouble GradientSlider::secondColor() const
0129 {
0130     return d_pointer->m_secondColor;
0131 }
0132 
0133 /** @brief Setter for @ref secondColor property.
0134  *
0135  * @param newSecondColor the new @ref secondColor */
0136 void GradientSlider::setSecondColor(const PerceptualColor::LchaDouble &newSecondColor)
0137 {
0138     if (!d_pointer->m_secondColor.hasSameCoordinates(newSecondColor)) {
0139         d_pointer->m_secondColor = newSecondColor;
0140         d_pointer->m_gradientImageParameters.setSecondColor(newSecondColor);
0141         d_pointer->m_gradientImage.setImageParameters( //
0142             d_pointer->m_gradientImageParameters);
0143         update();
0144         Q_EMIT secondColorChanged(newSecondColor);
0145     }
0146 }
0147 
0148 /** @brief Setter for both, @ref firstColor property and @ref secondColor
0149  * property.
0150  *
0151  * @param newFirstColor the new @ref firstColor
0152  * @param newSecondColor the new @ref secondColor */
0153 void GradientSlider::setColors(const PerceptualColor::LchaDouble &newFirstColor, const PerceptualColor::LchaDouble &newSecondColor)
0154 {
0155     setFirstColor(newFirstColor);
0156     setSecondColor(newSecondColor);
0157     update();
0158 }
0159 
0160 /** @brief React on a resize event.
0161  *
0162  * Reimplemented from base class.
0163  *
0164  * @param event The corresponding resize event */
0165 void GradientSlider::resizeEvent(QResizeEvent *event)
0166 {
0167     Q_UNUSED(event)
0168     d_pointer->m_gradientImageParameters.setGradientLength( //
0169         d_pointer->physicalPixelLength());
0170     d_pointer->m_gradientImageParameters.setGradientThickness(
0171         // Normally, this should not change, but maybe on Hight-DPI
0172         // devices there might be some differences.
0173         d_pointer->physicalPixelThickness());
0174     d_pointer->m_gradientImage.setImageParameters( //
0175         d_pointer->m_gradientImageParameters);
0176     update();
0177 }
0178 
0179 /** @brief Recommended size for the widget
0180  *
0181  * Reimplemented from base class.
0182  *
0183  * @returns Recommended size for the widget.
0184  *
0185  * @sa @ref sizeHint() */
0186 QSize GradientSlider::sizeHint() const
0187 {
0188     QSize result = minimumSizeHint();
0189     if (d_pointer->m_orientation == Qt::Orientation::Horizontal) {
0190         result.setWidth( //
0191             qRound(result.width() * scaleFromMinumumSizeHintToSizeHint));
0192     } else {
0193         result.setHeight( //
0194             qRound(result.height() * scaleFromMinumumSizeHintToSizeHint));
0195     }
0196     return result;
0197 }
0198 
0199 /** @brief Recommended minimum size for the widget.
0200  *
0201  * Reimplemented from base class.
0202  *
0203  * @returns Recommended minimum size for the widget.
0204  *
0205  * @sa @ref minimumSizeHint() */
0206 QSize GradientSlider::minimumSizeHint() const
0207 {
0208     QSize result;
0209     if (d_pointer->m_orientation == Qt::Orientation::Horizontal) {
0210         result.setWidth(gradientMinimumLength());
0211         result.setHeight(gradientThickness());
0212     } else {
0213         result.setWidth(gradientThickness());
0214         result.setHeight(gradientMinimumLength());
0215     }
0216     return result;
0217 }
0218 
0219 // No documentation here (documentation of properties
0220 // and its getters are in the header)
0221 qreal GradientSlider::singleStep() const
0222 {
0223     return d_pointer->m_singleStep;
0224 }
0225 
0226 /** @brief Setter for @ref singleStep property.
0227  *
0228  * @param newSingleStep the new @ref singleStep. Is bound to the valid
0229  * range of the property. */
0230 void GradientSlider::setSingleStep(qreal newSingleStep)
0231 {
0232     // Do not use negative value
0233     const qreal boundedSingleStep = qBound<qreal>(0.0, newSingleStep, 1.0);
0234     if (boundedSingleStep != d_pointer->m_singleStep) {
0235         d_pointer->m_singleStep = boundedSingleStep;
0236         Q_EMIT singleStepChanged(d_pointer->m_singleStep);
0237     }
0238 }
0239 
0240 // No documentation here (documentation of properties
0241 // and its getters are in the header)
0242 qreal GradientSlider::pageStep() const
0243 {
0244     return d_pointer->m_pageStep;
0245 }
0246 
0247 /** @brief Setter for @ref pageStep property.
0248  *
0249  * @param newPageStep the new @ref pageStep. Is bound to the valid
0250  * range of the property. */
0251 void GradientSlider::setPageStep(qreal newPageStep)
0252 {
0253     // Do not use negative altkluge
0254     const qreal boundedNewPageStep = qBound<qreal>(0.0, newPageStep, 1.0);
0255     if (boundedNewPageStep != d_pointer->m_pageStep) {
0256         d_pointer->m_pageStep = boundedNewPageStep;
0257         Q_EMIT pageStepChanged(d_pointer->m_pageStep);
0258     }
0259 }
0260 
0261 // No documentation here (documentation of properties
0262 // and its getters are in the header)
0263 qreal GradientSlider::value() const
0264 {
0265     return d_pointer->m_value;
0266 }
0267 
0268 /** @brief Setter for @ref value property.
0269  *
0270  * @param newValue the new @ref value. Is bound to the valid
0271  * range of the property. */
0272 void GradientSlider::setValue(qreal newValue)
0273 {
0274     qreal temp = qBound<qreal>(0, newValue, 1);
0275     if (d_pointer->m_value != temp) {
0276         d_pointer->m_value = temp;
0277         update();
0278         Q_EMIT valueChanged(temp);
0279     }
0280 }
0281 
0282 /** @brief React on a mouse press event.
0283  *
0284  * Reimplemented from base class.
0285  *
0286  * @param event The corresponding mouse event */
0287 void GradientSlider::mousePressEvent(QMouseEvent *event)
0288 {
0289     setValue(d_pointer->fromWidgetPixelPositionToValue(event->pos()));
0290 }
0291 
0292 /** @brief React on a mouse release event.
0293  *
0294  * Reimplemented from base class.
0295  *
0296  * @param event The corresponding mouse event */
0297 void GradientSlider::mouseReleaseEvent(QMouseEvent *event)
0298 {
0299     setValue(d_pointer->fromWidgetPixelPositionToValue(event->pos()));
0300 }
0301 
0302 /** @brief React on a mouse move event.
0303  *
0304  * Reimplemented from base class.
0305  *
0306  * @param event The corresponding mouse event */
0307 void GradientSlider::mouseMoveEvent(QMouseEvent *event)
0308 {
0309     setValue(d_pointer->fromWidgetPixelPositionToValue(event->pos()));
0310 }
0311 
0312 /** @brief React on a mouse wheel event.
0313  *
0314  * Reimplemented from base class.
0315  *
0316  * @param event The corresponding mouse event */
0317 void GradientSlider::wheelEvent(QWheelEvent *event)
0318 {
0319     qreal steps = standardWheelStepCount(event);
0320     //  Only react on good old vertical wheels, and not on horizontal wheels
0321     if (steps != 0) {
0322         qreal stepSize;
0323         if ( //
0324             QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) //
0325             || QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier) //
0326         ) {
0327             stepSize = pageStep();
0328         } else {
0329             stepSize = singleStep();
0330         }
0331         setValue(d_pointer->m_value + steps * stepSize);
0332     } else {
0333         // Don’t accept the event and let it up to the default treatment:
0334         event->ignore();
0335     }
0336 }
0337 
0338 /** @brief React on key press events.
0339  *
0340  * Reimplemented from base class.
0341  *
0342  * The user can change the @ref value of this widget by the following
0343  * key strokes:
0344  *
0345  * - Qt::Key_Up and Qt::Key_Plus increments a @ref singleStep.
0346  * - Qt::Key_Down and Qt::Key_Minus decrements a @ref singleStep.
0347  * - Qt::Key_Left increments and Qt::Key_Right increment or decrement
0348  *   a @ref singleStep, depending on the layout direction (LTR or RTL).
0349  * - Qt::Key_PageUp increments a @ref pageStep
0350  * - Qt::Key_PageDown decrements a @ref pageStep
0351  * - Qt::Key_Home increments to the maximum @ref value
0352  * - Qt::Key_End decrements to the minimum @ref value
0353  *
0354  * @param event the event  */
0355 void GradientSlider::keyPressEvent(QKeyEvent *event)
0356 {
0357     switch (event->key()) {
0358     case Qt::Key_Up:
0359     case Qt::Key_Plus:
0360         setValue(d_pointer->m_value + d_pointer->m_singleStep);
0361         break;
0362     case Qt::Key_Down:
0363     case Qt::Key_Minus:
0364         setValue(d_pointer->m_value - d_pointer->m_singleStep);
0365         break;
0366     case Qt::Key_Left:
0367         if (layoutDirection() == Qt::LayoutDirection::LeftToRight) {
0368             setValue(d_pointer->m_value - d_pointer->m_singleStep);
0369         } else {
0370             setValue(d_pointer->m_value + d_pointer->m_singleStep);
0371         }
0372         break;
0373     case Qt::Key_Right:
0374         if (layoutDirection() == Qt::LayoutDirection::LeftToRight) {
0375             setValue(d_pointer->m_value + d_pointer->m_singleStep);
0376         } else {
0377             setValue(d_pointer->m_value - d_pointer->m_singleStep);
0378         }
0379         break;
0380     case Qt::Key_PageUp:
0381         setValue(d_pointer->m_value + d_pointer->m_pageStep);
0382         break;
0383     case Qt::Key_PageDown:
0384         setValue(d_pointer->m_value - d_pointer->m_pageStep);
0385         break;
0386     case Qt::Key_Home:
0387         setValue(0);
0388         break;
0389     case Qt::Key_End:
0390         setValue(1);
0391         break;
0392     default:
0393         /* Quote from Qt documentation:
0394          *
0395          *     “If you reimplement this handler, it is very important that
0396          *      you call the base class implementation if you do not act
0397          *      upon the key.
0398          *
0399          *      The default implementation closes popup widgets if the
0400          *      user presses the key sequence for QKeySequence::Cancel
0401          *      (typically the Escape key). Otherwise the event is
0402          *      ignored, so that the widget’s parent can interpret it.“ */
0403         QWidget::keyPressEvent(event);
0404     }
0405 }
0406 
0407 // No documentation here (documentation of properties
0408 // and its getters are in the header)
0409 Qt::Orientation GradientSlider::orientation() const
0410 {
0411     return d_pointer->m_orientation;
0412 }
0413 
0414 /** @brief Forces a new orientation and a corresponding size policy.
0415  *
0416  * @param newOrientation The new orientation for the widget.
0417  *
0418  * @post The new orientation is stored. The signal
0419  * @ref GradientSlider::orientationChanged is <em>not</em> emitted.
0420  * The <tt>sizePolicy</tt> property is updated corresponding to the
0421  * <em>new</em> orientation; this happens even if the new
0422  * orientation is identical to the old @ref m_orientation! */
0423 void GradientSliderPrivate::setOrientationWithoutSignalAndForceNewSizePolicy(Qt::Orientation newOrientation)
0424 {
0425     if (newOrientation == Qt::Orientation::Vertical) {
0426         q_pointer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
0427     } else {
0428         q_pointer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0429     }
0430     m_orientation = newOrientation;
0431     m_gradientImageParameters.setGradientLength(physicalPixelLength());
0432     m_gradientImageParameters.setGradientThickness(
0433         // Normally, this should not change, but maybe on Hight-DPI
0434         // devices there are some differences.
0435         physicalPixelThickness());
0436     m_gradientImage.setImageParameters(m_gradientImageParameters);
0437     // Notify the layout system the the geometry has changed
0438     q_pointer->updateGeometry();
0439     q_pointer->update();
0440 }
0441 
0442 /** @brief Setter for @ref orientation property.
0443  *
0444  * @param newOrientation the new @ref orientation. */
0445 void GradientSlider::setOrientation(Qt::Orientation newOrientation)
0446 {
0447     if (newOrientation != d_pointer->m_orientation) {
0448         d_pointer->setOrientationWithoutSignalAndForceNewSizePolicy( //
0449             newOrientation);
0450         Q_EMIT orientationChanged(d_pointer->m_orientation);
0451     }
0452 }
0453 
0454 /** @brief The rounded length of the widget
0455  * measured in <em>physical pixels</em>.
0456  *
0457  * @returns The rounded length of the widget
0458  * measured in <em>physical pixels</em>.
0459  *
0460  * This is a convenience function to access
0461  * @ref GradientSlider::physicalPixelSize(). The length is the
0462  * size of the widget in the direction of the gradient.
0463  *
0464  * @sa @ref physicalPixelThickness() */
0465 int GradientSliderPrivate::physicalPixelLength() const
0466 {
0467     if (m_orientation == Qt::Orientation::Vertical) {
0468         return q_pointer->physicalPixelSize().height();
0469     } else {
0470         return q_pointer->physicalPixelSize().width();
0471     }
0472 }
0473 
0474 /** @brief The rounded thickness of the widget
0475  * measured in <em>physical pixels</em>.
0476  *
0477  * @returns The rounded thickness of the widget
0478  * measured in <em>physical pixels</em>.
0479  *
0480  * This is a convenience function to access
0481  * @ref GradientSlider::physicalPixelSize(). The thickness is the
0482  * size of the widget orthogonal to the direction of the gradient.
0483  *
0484  * @sa @ref physicalPixelLength() */
0485 int GradientSliderPrivate::physicalPixelThickness() const
0486 {
0487     if (m_orientation == Qt::Orientation::Horizontal) {
0488         return q_pointer->physicalPixelSize().height();
0489     } else {
0490         return q_pointer->physicalPixelSize().width();
0491     }
0492 }
0493 
0494 /** @brief Converts widget pixel positions to @ref GradientSlider::value
0495  * @param pixelPosition The position of a pixel of the widget coordinate
0496  * system. The given value  does not necessarily need to
0497  * be within the actual displayed widget. It might even be negative.
0498  * @returns The corresponding @ref GradientSlider::value for the (center of
0499  * the) given widget pixel position.
0500  * @sa @ref measurementdetails */
0501 qreal GradientSliderPrivate::fromWidgetPixelPositionToValue(QPoint pixelPosition)
0502 {
0503     // We are interested in the point in the middle of the given pixel.
0504     const QPointF coordinatePoint = pixelPosition + QPointF(0.5, 0.5);
0505     qreal temp;
0506     if (m_orientation == Qt::Orientation::Vertical) {
0507         temp = (q_pointer->size().height() - coordinatePoint.y()) //
0508             / static_cast<qreal>(q_pointer->size().height());
0509     } else {
0510         if (q_pointer->layoutDirection() == Qt::LayoutDirection::LeftToRight) {
0511             temp = coordinatePoint.x() //
0512                 / static_cast<qreal>(q_pointer->size().width());
0513         } else {
0514             temp = (q_pointer->size().width() - coordinatePoint.x()) //
0515                 / static_cast<qreal>(q_pointer->size().width());
0516         }
0517     }
0518     return qBound<qreal>(0, temp, 1);
0519 }
0520 
0521 /** @brief Paint the widget.
0522  *
0523  * Reimplemented from base class.
0524  *
0525  * @param event the paint event */
0526 void GradientSlider::paintEvent(QPaintEvent *event)
0527 {
0528     Q_UNUSED(event)
0529     // We do not paint directly on the widget, but on a QImage buffer first:
0530     // Render anti-aliased looks better. But as Qt documentation says:
0531     //
0532     //      “Renderhints are used to specify flags to QPainter that may or
0533     //       may not be respected by any given engine.”
0534     //
0535     // Painting here directly on the widget might lead to different
0536     // anti-aliasing results depending on the underlying window system. This
0537     // is especially problematic as anti-aliasing might shift or not a pixel
0538     // to the left or to the right. So we paint on a QImage first. As QImage
0539     // (at difference to QPixmap and a QWidget) is independent of native
0540     // platform rendering, it guarantees identical anti-aliasing results on
0541     // all platforms. Here the quote from QPainter class documentation:
0542     //
0543     //      “To get the optimal rendering result using QPainter, you should
0544     //       use the platform independent QImage as paint device; i.e. using
0545     //       QImage will ensure that the result has an identical pixel
0546     //       representation on any platform.”
0547     QImage paintBuffer;
0548 
0549     // Paint the gradient itself.
0550     // Make sure the image will be correct. We set length and thickness,
0551     // just to be sure (we might have missed a resize event). Also,
0552     // the device pixel ratio float might have changed because the
0553     // window has been moved to another screen. We do not update the
0554     // first and the second color because we have complete control
0555     // about these values and are sure the any changes have yet been
0556     // applied.
0557     d_pointer->m_gradientImageParameters.setDevicePixelRatioF( //
0558         devicePixelRatioF());
0559     d_pointer->m_gradientImageParameters.setGradientLength( //
0560         d_pointer->physicalPixelLength());
0561     d_pointer->m_gradientImageParameters.setGradientThickness(
0562         // Normally, this should not change, but maybe on Hight-DPI
0563         // devices there are some differences.
0564         d_pointer->physicalPixelThickness());
0565     d_pointer->m_gradientImage.setImageParameters( //
0566         d_pointer->m_gradientImageParameters);
0567     d_pointer->m_gradientImage.refreshAsync();
0568     paintBuffer = d_pointer->m_gradientImage.getCache();
0569     if (paintBuffer.isNull()) {
0570         return;
0571     }
0572 
0573     // Draw slider handle
0574     QPainter bufferPainter(&paintBuffer);
0575     // We use antialiasing. As our current handle is just a horizontal or
0576     // vertical line, it might be slightly sharper without antialiasing.
0577     // But all other widgets of this library WILL USE antialiasing because
0578     // their handles are not perfectly horizontal or vertical and without
0579     // antialiasing they might look terrible. Now, when antialiasing is NOT
0580     // used, the line thickness is rounded. This would lead to a different
0581     // thickness in this widget compared to the other widgets. This is not
0582     // a good idea. Therefore, we USE antialiasing here. Anyway, in practical
0583     // tests, it seems almost as sharp as without antialiasing, and
0584     // additionally the position is more exact!
0585     bufferPainter.setRenderHint(QPainter::Antialiasing, true);
0586     QPen pen;
0587     const qreal handleCoordinatePoint = d_pointer->physicalPixelLength() //
0588         / devicePixelRatioF() //
0589         * d_pointer->m_value;
0590     if (hasFocus()) {
0591         pen.setWidthF(handleOutlineThickness() * 3);
0592         pen.setColor(focusIndicatorColor());
0593         bufferPainter.setPen(pen);
0594         bufferPainter.drawLine(QPointF(handleCoordinatePoint, 0), //
0595                                QPointF(handleCoordinatePoint, gradientThickness()));
0596     }
0597     pen.setWidthF(handleOutlineThickness());
0598     pen.setColor( //
0599         handleColorFromBackgroundLightness( //
0600             d_pointer->m_gradientImageParameters
0601                 .colorFromValue( //
0602                     d_pointer->m_value)
0603                 .l));
0604     bufferPainter.setPen(pen);
0605     bufferPainter.drawLine(QPointF(handleCoordinatePoint, 0), //
0606                            QPointF(handleCoordinatePoint, gradientThickness()));
0607 
0608     // Paint the buffer to the actual widget
0609     QTransform transform;
0610     // The m_gradientImageProvider contains the gradient always
0611     // in a default form, independent of the actual orientation
0612     // of this widget and independent of its actual layout direction:
0613     // In the default form, the first color is always on the left, and the
0614     // second color is always on the right. To paint it, we have to
0615     // rotate it if our actual orientation is vertical. And we have to
0616     // mirror it when our actual layout direction is RTL.
0617     if (d_pointer->m_orientation == Qt::Orientation::Vertical) {
0618         if (layoutDirection() == Qt::LayoutDirection::RightToLeft) {
0619             // Even on vertical gradients, we mirror the image, so that
0620             // the well-aligned edge of the transparency background is
0621             // always aligned according to the writing direction.
0622             transform.scale(-1, 1);
0623             transform.rotate(270);
0624             transform.translate(size().height() * (-1), size().width() * (-1));
0625         } else {
0626             transform.rotate(270);
0627             transform.translate(size().height() * (-1), 0);
0628         }
0629     } else {
0630         if (layoutDirection() == Qt::LayoutDirection::RightToLeft) {
0631             transform.scale(-1, 1);
0632             transform.translate(size().width() * (-1), 0);
0633         }
0634     }
0635     QPainter widgetPainter(this);
0636     widgetPainter.setTransform(transform);
0637     widgetPainter.drawImage(0, 0, paintBuffer);
0638 
0639     //     // TODO Draw a focus rectangle like this?:
0640     //     widgetPainter.setTransform(QTransform());
0641     //     if (hasFocus()) {
0642     //         QStyleOptionFocusRect opt;
0643     //         opt.palette = palette();
0644     //         opt.rect = rect();
0645     //         opt.state = QStyle::State_None |
0646     //         QStyle::State_KeyboardFocusChange; style()->drawPrimitive(
0647     //             QStyle::PE_FrameFocusRect,
0648     //             &opt,
0649     //             &widgetPainter,
0650     //             this
0651     //         );
0652     //     }
0653 }
0654 
0655 } // namespace PerceptualColor