/* ============================================================
 *
 * This file is a part of digiKam project
 * https://www.digikam.org
 *
 * Date        : 2014-11-30
 * Description : Save space slider widget
 *
 * SPDX-FileCopyrightText: 2014-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
 * SPDX-FileCopyrightText:      2010 by Justin Noel <justin at ics dot com>
 * SPDX-FileCopyrightText:      2010 by Cyrille Berger <cberger at cberger dot net>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * ============================================================ */

#include "dsliderspinbox.h"

// C++ includes

#include <cmath>

// Qt includes

#include <QStyle>
#include <QPainter>
#include <QLineEdit>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QApplication>
#include <QIntValidator>
#include <QDoubleSpinBox>

namespace Digikam
{

class Q_DECL_HIDDEN DAbstractSliderSpinBoxPrivate
{
public:

    enum Style
    {
        STYLE_NOQUIRK,
        STYLE_PLASTIQUE,
        STYLE_WINDOWS,
        STYLE_BREEZE,
        STYLE_FUSION,
        STYLE_OXYGEN,
        STYLE_MACOS,
        STYLE_GTK2
    };

public:

    DAbstractSliderSpinBoxPrivate() = default;

public:

    QLineEdit*        edit                      = nullptr;
    QDoubleValidator* validator                 = nullptr;
    QSpinBox*         dummySpinBox              = nullptr;
    bool              upButtonDown              = false;
    bool              downButtonDown            = false;
    int               factor                    = 1;
    int               fastSliderStep            = 5;
    double            slowFactor                = 0.1;
    double            shiftPercent              = 0.0;
    bool              shiftMode                 = false;
    QString           prefix;
    QString           suffix;
    double            exponentRatio             = 0.0;
    int               value                     = 0;
    int               maximum                   = 100;
    int               minimum                   = 0;
    int               singleStep                = 1;
    Style             style                     = STYLE_NOQUIRK;
    bool              blockUpdateSignalOnDrag   = false;
    bool              isDragging                = false;
};

DAbstractSliderSpinBox::DAbstractSliderSpinBox(QWidget* const parent, DAbstractSliderSpinBoxPrivate* const q)
    : QWidget(parent),
      d_ptr  (q)
{
    Q_D(DAbstractSliderSpinBox);

    QEvent e(QEvent::StyleChange);
    this->changeEvent(&e);

    d->edit     = new QLineEdit(this);
    d->edit->setContentsMargins(QMargins());
    d->edit->setAlignment(Qt::AlignCenter);
    d->edit->installEventFilter(this);
    d->edit->setFrame(false);
    d->edit->hide();

    // Make edit transparent

    d->edit->setAutoFillBackground(false);
    QPalette pal = d->edit->palette();
    pal.setColor(QPalette::Base, Qt::transparent);
    d->edit->setPalette(pal);

    connect(d->edit, SIGNAL(editingFinished()),
            this, SLOT(editLostFocus()));

    d->validator = new QDoubleValidator(d->edit);
    d->edit->setValidator(d->validator);

    setExponentRatio(1.0);

    // Set sane defaults

    setFocusPolicy(Qt::StrongFocus);
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);

    // dummy needed to fix a bug in the polyester theme

    d->dummySpinBox = new QSpinBox(this);
    d->dummySpinBox->hide();
}

DAbstractSliderSpinBox::~DAbstractSliderSpinBox()
{
    Q_D(DAbstractSliderSpinBox);

    delete d;
}

void DAbstractSliderSpinBox::showEdit()
{
    Q_D(DAbstractSliderSpinBox);

    if (d->edit->isVisible())
    {
        return;
    }

    if (d->style == DAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE)
    {
        d->edit->setGeometry(progressRect(spinBoxOptions()).adjusted(0, 0, -2, 0));
    }
    else
    {
        d->edit->setGeometry(progressRect(spinBoxOptions()));
    }

    d->edit->setText(valueString());
    d->edit->selectAll();
    d->edit->show();
    d->edit->setFocus(Qt::OtherFocusReason);
    update();
}

void DAbstractSliderSpinBox::hideEdit()
{
    Q_D(DAbstractSliderSpinBox);

    d->edit->hide();
    update();
}

void DAbstractSliderSpinBox::paintEvent(QPaintEvent* e)
{
    Q_D(DAbstractSliderSpinBox);
    Q_UNUSED(e)

    QPainter painter(this);

    switch (d->style)
    {
        case DAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
        {
            paintPlastique(painter);
            break;
        }

        case DAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
        {
            paintBreeze(painter);
            break;
        }

        case DAbstractSliderSpinBoxPrivate::STYLE_FUSION:
        {
            paintFusion(painter);
            break;
        }

        default:
        {
            paint(painter);
            break;
        }
    }

    painter.end();
}

void DAbstractSliderSpinBox::paint(QPainter& painter)
{
    Q_D(DAbstractSliderSpinBox);

    // Create options to draw spin box parts

    QStyleOptionSpinBox spinOpts = spinBoxOptions();
    spinOpts.rect.adjust(0, 2, 0, -2);

    // Draw "SpinBox".Clip off the area of the lineEdit to avoid double
    // borders being drawn

    painter.save();
    painter.setClipping(true);

    QRect eraseRect(QPoint(rect().x(), rect().y()),
                    QPoint(progressRect(spinOpts).right(), rect().bottom()));

    painter.setClipRegion(QRegion(rect()).subtracted(eraseRect));
    style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
    painter.setClipping(false);
    painter.restore();

    QStyleOptionProgressBar progressOpts = progressBarOptions();

    if (
        (d->style == DAbstractSliderSpinBoxPrivate::STYLE_GTK2)    ||
        (d->style == DAbstractSliderSpinBoxPrivate::STYLE_MACOS)   ||
        (d->style == DAbstractSliderSpinBoxPrivate::STYLE_OXYGEN)  ||
        (d->style == DAbstractSliderSpinBoxPrivate::STYLE_WINDOWS)
       )
    {
        progressOpts.state |= QStyle::State_Horizontal;
    }

    progressOpts.rect.adjust(0, 2, 0, -2);
    style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, nullptr);

    // Draw focus if necessary

    if (hasFocus() && d->edit->hasFocus())
    {
        QStyleOptionFocusRect focusOpts;
        focusOpts.initFrom(this);
        focusOpts.rect = progressOpts.rect;
        focusOpts.backgroundColor = palette().color(QPalette::Window);
        style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this);
    }
}

void DAbstractSliderSpinBox::paintFusion(QPainter& painter)
{
    Q_D(DAbstractSliderSpinBox);

    QStyleOptionSpinBox spinOpts         = spinBoxOptions();
    QStyleOptionProgressBar progressOpts = progressBarOptions();
    spinOpts.frame                       = true;
    spinOpts.rect.adjust(0, -1, 0, 1);
/*
    spinOpts.palette().setBrush(QPalette::Base, palette().highlight());
*/
    style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);

    painter.save();

    QRect rect = progressOpts.rect.adjusted(1, 2, -4, -2);
    QRect leftRect;

    int progressIndicatorPos = (progressOpts.progress - double(progressOpts.minimum)) / qMax(double(1.0),
                                double(progressOpts.maximum) - progressOpts.minimum) * rect.width();

    if      ((progressIndicatorPos >= 0) && (progressIndicatorPos <= rect.width()))
    {
        leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height());
    }
    else if (progressIndicatorPos > rect.width())
    {
        painter.setPen(palette().highlightedText().color());
    }
    else
    {
        painter.setPen(palette().buttonText().color());
    }

    QRegion rightRect = rect;
    rightRect         = rightRect.subtracted(leftRect);

    QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
    textOption.setWrapMode(QTextOption::NoWrap);

    if (!(d->edit && d->edit->isVisible()))
    {
        painter.setClipRegion(rightRect);
        painter.setClipping(true);
        painter.drawText(rect.adjusted(-2, 0, 2, 0), progressOpts.text, textOption);
        painter.setClipping(false);
    }

    if (!leftRect.isNull())
    {
        painter.setClipRect(leftRect.adjusted(0, -1, 1, 1));
        painter.setPen(palette().highlight().color());
        painter.setBrush(palette().highlight());

        spinOpts.palette.setBrush(QPalette::Base, palette().highlight());
        style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);

        if (!(d->edit && d->edit->isVisible()))
        {
            painter.setPen(palette().highlightedText().color());
            painter.setClipping(true);
            painter.drawText(rect.adjusted(-2, 0, 2, 0), progressOpts.text, textOption);
        }

        painter.setClipping(false);
    }

    painter.restore();
}

void DAbstractSliderSpinBox::paintPlastique(QPainter& painter)
{
    Q_D(DAbstractSliderSpinBox);

    QStyleOptionSpinBox spinOpts         = spinBoxOptions();
    QStyleOptionProgressBar progressOpts = progressBarOptions();

    style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);

    painter.save();

    QRect rect               = progressOpts.rect.adjusted(2, 0, -2, 0);
    QRect leftRect;

    int progressIndicatorPos = (progressOpts.progress - double(progressOpts.minimum)) / qMax(double(1.0),
                                double(progressOpts.maximum) - progressOpts.minimum) * rect.width();

    if      ((progressIndicatorPos >= 0) && (progressIndicatorPos <= rect.width()))
    {
        leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height());
    }
    else if (progressIndicatorPos > rect.width())
    {
        painter.setPen(palette().highlightedText().color());
    }
    else
    {
        painter.setPen(palette().buttonText().color());
    }

    QRegion rightRect = rect;
    rightRect         = rightRect.subtracted(leftRect);

    QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
    textOption.setWrapMode(QTextOption::NoWrap);

    if (!(d->edit && d->edit->isVisible()))
    {
        painter.setClipRegion(rightRect);
        painter.setClipping(true);
        painter.drawText(rect.adjusted(-2, 0, 2, 0), progressOpts.text, textOption);
        painter.setClipping(false);
    }

    if (!leftRect.isNull())
    {
        painter.setPen(palette().highlight().color());
        painter.setBrush(palette().highlight());
        painter.drawRect(leftRect.adjusted(0, 0, 0, -1));

        if (!(d->edit && d->edit->isVisible()))
        {
            painter.setPen(palette().highlightedText().color());
            painter.setClipRect(leftRect.adjusted(0, 0, 1, 0));
            painter.setClipping(true);
            painter.drawText(rect.adjusted(-2, 0, 2, 0), progressOpts.text, textOption);
            painter.setClipping(false);
        }
    }

    painter.restore();
}

void DAbstractSliderSpinBox::paintBreeze(QPainter& painter)
{
    Q_D(DAbstractSliderSpinBox);

    QStyleOptionSpinBox spinOpts         = spinBoxOptions();
    QStyleOptionProgressBar progressOpts = progressBarOptions();
    QString valueText                    = progressOpts.text;
    progressOpts.text                    = QLatin1String("");
    progressOpts.rect.adjust(0, 1, 0, -1);

    style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, this);
    style()->drawControl(QStyle::CE_ProgressBarGroove, &progressOpts, &painter, this);

    painter.save();

    QRect leftRect;

    int progressIndicatorPos = (progressOpts.progress - double(progressOpts.minimum)) / qMax(double(1.0),
                                double(progressOpts.maximum) - progressOpts.minimum) * progressOpts.rect.width();

    if      ((progressIndicatorPos >= 0) && (progressIndicatorPos <= progressOpts.rect.width()))
    {
        leftRect = QRect(progressOpts.rect.left(), progressOpts.rect.top(), progressIndicatorPos, progressOpts.rect.height());
    }
    else if (progressIndicatorPos > progressOpts.rect.width())
    {
        painter.setPen(palette().highlightedText().color());
    }
    else
    {
        painter.setPen(palette().buttonText().color());
    }

    QRegion rightRect = progressOpts.rect;
    rightRect         = rightRect.subtracted(leftRect);
    painter.setClipRegion(rightRect);

    QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
    textOption.setWrapMode(QTextOption::NoWrap);

    if (!(d->edit && d->edit->isVisible()))
    {
        painter.drawText(progressOpts.rect, valueText, textOption);
    }

    if (!leftRect.isNull())
    {
        painter.setPen(palette().highlightedText().color());
        painter.setClipRect(leftRect);
        style()->drawControl(QStyle::CE_ProgressBarContents, &progressOpts, &painter, this);

        if (!(d->edit && d->edit->isVisible()))
        {
            painter.drawText(progressOpts.rect, valueText, textOption);
        }
    }

    painter.restore();
}

void DAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e)
{
    Q_D(DAbstractSliderSpinBox);

    QStyleOptionSpinBox spinOpts = spinBoxOptions();

    // Depress buttons or highlight slider
    // Also used to emulate mouse grab...

    if      (e->buttons() & Qt::LeftButton)
    {
        if      (upButtonRect(spinOpts).contains(e->pos()))
        {
            d->upButtonDown = true;
        }
        else if (downButtonRect(spinOpts).contains(e->pos()))
        {
            d->downButtonDown = true;
        }
    }
    else if (e->buttons() & Qt::RightButton)
    {
        showEdit();
    }

    update();
}

void DAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e)
{
    Q_D(DAbstractSliderSpinBox);

    QStyleOptionSpinBox spinOpts = spinBoxOptions();

    d->isDragging = false;

    // Step up/down for buttons
    // Emulating mouse grab too

    if      (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown)
    {
        setInternalValue(d->value + d->singleStep);
    }
    else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown)
    {
        setInternalValue(d->value - d->singleStep);
    }
    else if (
             progressRect(spinOpts).contains(e->pos()) &&
             !(d->edit->isVisible())                   &&
             !(d->upButtonDown || d->downButtonDown)
            )
    {
        // Snap to percentage for progress area

        setInternalValue(valueForX(e->pos().x(),e->modifiers()));
    }
    else
    {
        // Confirm the last known value, since we might be ignoring move events

        setInternalValue(d->value);
    }

    d->upButtonDown   = false;
    d->downButtonDown = false;
    update();
}

void DAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e)
{
    Q_D(DAbstractSliderSpinBox);

    if (e->modifiers() & Qt::ShiftModifier)
    {
        if (!d->shiftMode)
        {
            d->shiftPercent = pow(double(d->value - d->minimum) / double(d->maximum - d->minimum), 1 / double(d->exponentRatio));
            d->shiftMode    = true;
        }
    }
    else
    {
        d->shiftMode = false;
    }

    // Respect emulated mouse grab.

    if (
        (e->buttons() & Qt::LeftButton) &&
        !(d->downButtonDown || d->upButtonDown)
       )
    {
        d->isDragging = true;
        setInternalValue(valueForX(e->pos().x(),e->modifiers()), d->blockUpdateSignalOnDrag);
        update();
    }
}

void DAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e)
{
    Q_D(DAbstractSliderSpinBox);

    switch (e->key())
    {
        case Qt::Key_Up:
        case Qt::Key_Right:
        {
            setInternalValue(d->value + d->singleStep);

            if (d->edit->isVisible())
            {
                d->edit->setText(valueString());
            }

            update();
            break;
        }

        case Qt::Key_Down:
        case Qt::Key_Left:
        {
            setInternalValue(d->value - d->singleStep);

            if (d->edit->isVisible())
            {
                d->edit->setText(valueString());
            }

            update();
            break;
        }

        case Qt::Key_Shift:
        {
            d->shiftPercent = pow(double(d->value - d->minimum) / double(d->maximum - d->minimum), 1 / double(d->exponentRatio));
            d->shiftMode    = true;
            break;
        }

        case Qt::Key_Enter: // Line edit isn't "accepting" key strokes...
        case Qt::Key_Return:
        case Qt::Key_Escape:
        case Qt::Key_Control:
        case Qt::Key_Alt:
        case Qt::Key_AltGr:
        case Qt::Key_Super_L:
        case Qt::Key_Super_R:
        {
            break;
        }

        default:
        {
            showEdit();
            d->edit->event(e);
            break;
        }
    }
}

void DAbstractSliderSpinBox::wheelEvent(QWheelEvent* e)
{
    Q_D(DAbstractSliderSpinBox);

    if      (e->angleDelta().y() > 0)
    {
        setInternalValue(d->value + d->singleStep);
    }
    else if (e->angleDelta().y() < 0)
    {
        setInternalValue(d->value - d->singleStep);
    }

    update();
    e->accept();
}

void DAbstractSliderSpinBox::focusInEvent(QFocusEvent* e)
{
    if (e->reason() == Qt::TabFocusReason)
    {
        showEdit();
    }

    e->accept();
}

bool DAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e)
{
    Q_D(DAbstractSliderSpinBox);

    if (
        (recv == static_cast<QObject*>(d->edit)) &&
        (e->type() == QEvent::KeyRelease)
       )
    {
        QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);

        switch (keyEvent->key())
        {
            case Qt::Key_Enter:
            case Qt::Key_Return:
            {
                setInternalValue(QLocale::system().toDouble(d->edit->text()) * d->factor);
                hideEdit();
                return true;
            }

            case Qt::Key_Escape:
            {
                d->edit->setText(valueString());
                hideEdit();
                return true;
            }

            default:
            {
                break;
            }
        }
    }
    else if (d->edit->isVisible() && e->type() == QEvent::ShortcutOverride)
    {
        QKeyEvent* const keyEvent = static_cast<QKeyEvent*>(e);

        switch (keyEvent->key())
        {
            case Qt::Key_Tab:
            case Qt::Key_Enter:
            case Qt::Key_Return:
            case Qt::Key_Escape:
            {
                e->accept();
                return true;
            }

            default:
            {
                break;
            }
        }
    }

    return false;
}

QSize DAbstractSliderSpinBox::sizeHint() const
{
    const Q_D(DAbstractSliderSpinBox);

    QStyleOptionSpinBox spinOpts = spinBoxOptions();
    QFont ft(font());

    if (d->style == DAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK)
    {
        // Some styles use bold font in progressbars
        // unfortunately there is no reliable way to check for that

        ft.setBold(true);
    }

    QFontMetrics fm(ft);
    QSize hint(fm.boundingRect(d->prefix + QString::number(d->maximum) + d->suffix).size());
    hint += QSize(0, 2);

    switch (d->style)
    {
        case DAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
        {
            hint += QSize(8, 0);
            break;
        }

        case DAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
        {
            hint += QSize(2, 0);
            break;
        }

        case DAbstractSliderSpinBoxPrivate::STYLE_WINDOWS:
        case DAbstractSliderSpinBoxPrivate::STYLE_FUSION:
        case DAbstractSliderSpinBoxPrivate::STYLE_OXYGEN:
        case DAbstractSliderSpinBoxPrivate::STYLE_GTK2:
        {
            hint += QSize(8, 8);
            break;
        }

        case DAbstractSliderSpinBoxPrivate::STYLE_MACOS:
        {
            hint += QSize(8, 16);
            break;
        }

        case DAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK:
        {
            // almost all "modern" styles have a margin around controls
            hint += QSize(6, 6);
            break;
        }

        default:
        {
            break;
        }
    }

    // Getting the size of the buttons is a pain as the calcs require a rect
    // that is "big enough". We run the calc twice to get the "smallest" buttons
    // This code was inspired by QAbstractSpinBox

    QSize extra(1000, 0);
    spinOpts.rect.setSize(hint + extra);
    extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts,
                                            QStyle::SC_SpinBoxEditField, this).size();
    spinOpts.rect.setSize(hint + extra);
    extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts,
                                            QStyle::SC_SpinBoxEditField, this).size();
    hint += extra;

    spinOpts.rect.setSize(hint);

    return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint);
}

QSize DAbstractSliderSpinBox::minimumSizeHint() const
{
    return sizeHint();
}

QSize DAbstractSliderSpinBox::minimumSize() const
{
    return QWidget::minimumSize().expandedTo(minimumSizeHint());
}

QStyleOptionSpinBox DAbstractSliderSpinBox::spinBoxOptions() const
{
    const Q_D(DAbstractSliderSpinBox);

    QStyleOptionSpinBox opts;
    opts.initFrom(this);
    opts.frame         = false;
    opts.buttonSymbols = QAbstractSpinBox::UpDownArrows;
    opts.subControls   = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown;

    // Disable non-logical buttons

    if      (d->value == d->minimum)
    {
        opts.stepEnabled = QAbstractSpinBox::StepUpEnabled;
    }
    else if (d->value == d->maximum)
    {
        opts.stepEnabled = QAbstractSpinBox::StepDownEnabled;
    }
    else
    {
        opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled;
    }

    // Deal with depressed buttons

    if      (d->upButtonDown)
    {
        opts.activeSubControls = QStyle::SC_SpinBoxUp;
    }
    else if (d->downButtonDown)
    {
        opts.activeSubControls = QStyle::SC_SpinBoxDown;
    }
    else
    {
        opts.activeSubControls = QStyle::SC_None;
    }

    return opts;
}

QStyleOptionProgressBar DAbstractSliderSpinBox::progressBarOptions() const
{
    const Q_D(DAbstractSliderSpinBox);

    QStyleOptionSpinBox spinOpts = spinBoxOptions();

    // Create opts for drawing the progress portion

    QStyleOptionProgressBar progressOpts;
    progressOpts.initFrom(this);
    progressOpts.maximum         = d->maximum;
    progressOpts.minimum         = d->minimum;

    double minDbl                = d->minimum;
    double dValues               = (d->maximum - minDbl);

    progressOpts.progress        = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl;
    progressOpts.text            = d->prefix + valueString() + d->suffix;
    progressOpts.textAlignment   = Qt::AlignCenter;
    progressOpts.textVisible     = !(d->edit->isVisible());

    // Change opts rect to be only the ComboBox's text area

    progressOpts.rect            = progressRect(spinOpts);

    return progressOpts;
}

QRect DAbstractSliderSpinBox::progressRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
    const Q_D(DAbstractSliderSpinBox);

    QRect ret = style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
                                        QStyle::SC_SpinBoxEditField);

    switch (d->style)
    {
        case DAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
        {
            ret.adjust(-2, 0, 1, 0);
            break;
        }

        case DAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
        {
            ret.adjust(1, 0, 0, 0);
            break;
        }

        default:
        {
            break;
        }
    }

    return ret;
}

QRect DAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
    return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
                                   QStyle::SC_SpinBoxUp);
}

QRect DAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
    return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
                                   QStyle::SC_SpinBoxDown);
}

int DAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const
{
    const Q_D(DAbstractSliderSpinBox);

    QStyleOptionSpinBox spinOpts = spinBoxOptions();

    QRect correctedProgRect;

    if      (d->style == DAbstractSliderSpinBoxPrivate::STYLE_FUSION)
    {
        correctedProgRect = progressRect(spinOpts).adjusted(2, 0, -2, 0);
    }
    else if (d->style == DAbstractSliderSpinBoxPrivate::STYLE_BREEZE)
    {
        correctedProgRect = progressRect(spinOpts);
    }
    else
    {
        // Adjust for magic number in style code (margins)

        correctedProgRect = progressRect(spinOpts).adjusted(2, 2, -2, -2);
    }

    // Compute the distance of the progress bar, in pixel

    double leftDbl  = correctedProgRect.left();
    double xDbl     = x - leftDbl;

    // Compute the ration of the progress bar used, linearly (ignoring the exponent)

    double rightDbl = correctedProgRect.right();
    double minDbl   = d->minimum;
    double maxDbl   = d->maximum;

    double dValues  = (maxDbl - minDbl);
    double percent  = (xDbl / (rightDbl - leftDbl));

    // If SHIFT is pressed, movement should be slowed.

    if (modifiers & Qt::ShiftModifier)
    {
        percent = d->shiftPercent + (percent - d->shiftPercent) * d->slowFactor;
    }

    // Final value

    double realvalue = ((dValues * pow(percent, d->exponentRatio)) + minDbl);

    // If key CTRL is pressed, round to the closest step.

    if (modifiers & Qt::ControlModifier)
    {
        double fstep = d->fastSliderStep;

        if (modifiers & Qt::ShiftModifier)
        {
            fstep *= d->slowFactor;
        }

        realvalue = floor((realvalue + fstep / 2) / fstep) * fstep;
    }

    return int(realvalue);
}

void DAbstractSliderSpinBox::setPrefix(const QString& prefix)
{
    Q_D(DAbstractSliderSpinBox);

    d->prefix = prefix;
}

void DAbstractSliderSpinBox::setSuffix(const QString& suffix)
{
    Q_D(DAbstractSliderSpinBox);

    d->suffix = suffix;
}

void DAbstractSliderSpinBox::setExponentRatio(double dbl)
{
    Q_D(DAbstractSliderSpinBox);

    Q_ASSERT(dbl > 0);

    d->exponentRatio = dbl;
}

void DAbstractSliderSpinBox::setBlockUpdateSignalOnDrag(bool blockUpdateSignal)
{
    Q_D(DAbstractSliderSpinBox);

    d->blockUpdateSignalOnDrag = blockUpdateSignal;
}

void DAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event)
{
    event->accept();
}

void DAbstractSliderSpinBox::editLostFocus()
{
    Q_D(DAbstractSliderSpinBox);

    if (!d->edit->hasFocus())
    {
        if (d->edit->isModified())
        {
            setInternalValue(QLocale::system().toDouble(d->edit->text()) * d->factor);
        }

        hideEdit();
    }
}

void DAbstractSliderSpinBox::setInternalValue(int value)
{
    setInternalValue(value, false);
}

bool DAbstractSliderSpinBox::isDragging() const
{
    Q_D(const DAbstractSliderSpinBox);

    return d->isDragging;
}

void DAbstractSliderSpinBox::changeEvent(QEvent* e)
{
    Q_D(DAbstractSliderSpinBox);

    QWidget::changeEvent(e);

    if (e->type() == QEvent::StyleChange)
    {
        if      (style()->objectName() == QLatin1String("plastique"))
        {
            d->style = DAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE;
        }
        else if (style()->objectName() == QLatin1String("windows"))
        {
            d->style = DAbstractSliderSpinBoxPrivate::STYLE_WINDOWS;
        }
        else if (style()->objectName() == QLatin1String("breeze"))
        {
            d->style = DAbstractSliderSpinBoxPrivate::STYLE_BREEZE;
        }
        else if (style()->objectName() == QLatin1String("fusion"))
        {
            d->style = DAbstractSliderSpinBoxPrivate::STYLE_FUSION;
        }
        else if (style()->objectName() == QLatin1String("oxygen"))
        {
            d->style = DAbstractSliderSpinBoxPrivate::STYLE_OXYGEN;
        }
        else if (style()->objectName() == QLatin1String("macos"))
        {
            d->style = DAbstractSliderSpinBoxPrivate::STYLE_MACOS;
        }
        else if (style()->objectName() == QLatin1String("gtk2"))
        {
            d->style = DAbstractSliderSpinBoxPrivate::STYLE_GTK2;
        }
        else
        {
            d->style = DAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK;
        }
    }
}

// ---------------------------------------------------------------------------------------------

class Q_DECL_HIDDEN DSliderSpinBoxPrivate : public DAbstractSliderSpinBoxPrivate
{
};

DSliderSpinBox::DSliderSpinBox(QWidget* const parent)
    : DAbstractSliderSpinBox(parent, new DSliderSpinBoxPrivate)
{
    setRange(0, 99);
}

void DSliderSpinBox::setRange(int minimum, int maximum)
{
    Q_D(DSliderSpinBox);

    d->minimum        = minimum;
    d->maximum        = maximum;
    d->fastSliderStep = (maximum-minimum + 1) / 20;
    d->validator->setRange(minimum, maximum, 0);
    update();
}

int DSliderSpinBox::minimum() const
{
    const Q_D(DSliderSpinBox);

    return d->minimum;
}

void DSliderSpinBox::setMinimum(int minimum)
{
    Q_D(DSliderSpinBox);

    setRange(minimum, d->maximum);
}

int DSliderSpinBox::maximum() const
{
    const Q_D(DSliderSpinBox);

    return d->maximum;
}

void DSliderSpinBox::setMaximum(int maximum)
{
    Q_D(DSliderSpinBox);

    setRange(d->minimum, maximum);
}

int DSliderSpinBox::fastSliderStep() const
{
    const Q_D(DSliderSpinBox);

    return d->fastSliderStep;
}

void DSliderSpinBox::setFastSliderStep(int step)
{
    Q_D(DSliderSpinBox);

    d->fastSliderStep = step;
}

int DSliderSpinBox::value()
{
    Q_D(DSliderSpinBox);

    return d->value;
}

void DSliderSpinBox::setValue(int value)
{
    setInternalValue(value, false);
    update();
}

QString DSliderSpinBox::valueString() const
{
    const Q_D(DSliderSpinBox);

    QLocale locale;

    return locale.toString((double)d->value, 'f', d->validator->decimals());
}

void DSliderSpinBox::setSingleStep(int value)
{
    Q_D(DSliderSpinBox);

    d->singleStep = value;
}

void DSliderSpinBox::setPageStep(int value)
{
    Q_UNUSED(value);
}

void DSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal)
{
    Q_D(DAbstractSliderSpinBox);

    d->value = qBound(d->minimum, _value, d->maximum);

    if (!blockUpdateSignal)
    {
        Q_EMIT valueChanged(value());
    }
}

// ---------------------------------------------------------------------------------------------

class Q_DECL_HIDDEN DDoubleSliderSpinBoxPrivate : public DAbstractSliderSpinBoxPrivate
{
};

DDoubleSliderSpinBox::DDoubleSliderSpinBox(QWidget* const parent)
    : DAbstractSliderSpinBox(parent, new DDoubleSliderSpinBoxPrivate)
{
}

void DDoubleSliderSpinBox::setRange(double minimum, double maximum, int decimals)
{
    Q_D(DDoubleSliderSpinBox);

    d->factor  = pow(10.0, decimals);

    d->minimum = minimum * d->factor;
    d->maximum = maximum * d->factor;

    // This code auto-compute a new step when pressing control.
    // A flag defaulting to "do not change the fast step" should be added, but it implies changing every call

    if      (((maximum - minimum) >= 2.0) || (decimals <= 0))
    {
        // Quick step on integers

        d->fastSliderStep = int(pow(10.0, decimals));
    }
    else if (decimals == 1)
    {
        d->fastSliderStep = (maximum - minimum) * d->factor / 10;
    }
    else
    {
        d->fastSliderStep = (maximum - minimum) * d->factor / 20;
    }

    d->validator->setRange(minimum, maximum, decimals);
    update();
    setValue(value());
}

double DDoubleSliderSpinBox::minimum() const
{
    const Q_D(DAbstractSliderSpinBox);

    return d->minimum / d->factor;
}

void DDoubleSliderSpinBox::setMinimum(double minimum)
{
    Q_D(DAbstractSliderSpinBox);

    setRange(minimum, d->maximum);
}

double DDoubleSliderSpinBox::maximum() const
{
    const Q_D(DAbstractSliderSpinBox);

    return d->maximum / d->factor;
}

void DDoubleSliderSpinBox::setMaximum(double maximum)
{
    Q_D(DAbstractSliderSpinBox);

    setRange(d->minimum, maximum);
}

double DDoubleSliderSpinBox::fastSliderStep() const
{
    const Q_D(DAbstractSliderSpinBox);

    return d->fastSliderStep;
}

void DDoubleSliderSpinBox::setFastSliderStep(double step)
{
    Q_D(DAbstractSliderSpinBox);

    d->fastSliderStep = step;
}

double DDoubleSliderSpinBox::value()
{
    Q_D(DAbstractSliderSpinBox);

    return (double)d->value / d->factor;
}

void DDoubleSliderSpinBox::setValue(double value)
{
    Q_D(DAbstractSliderSpinBox);

    setInternalValue(d->value = qRound(value * d->factor), false);
    update();
}

void DDoubleSliderSpinBox::setSingleStep(double value)
{
    Q_D(DAbstractSliderSpinBox);

    d->singleStep = value * d->factor;
}

QString DDoubleSliderSpinBox::valueString() const
{
    const Q_D(DAbstractSliderSpinBox);

    QLocale locale;

    return locale.toString((double)d->value / d->factor, 'f', d->validator->decimals());
}

void DDoubleSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal)
{
    Q_D(DAbstractSliderSpinBox);

    d->value = qBound(d->minimum, _value, d->maximum);

    if (!blockUpdateSignal)
    {
        Q_EMIT valueChanged(value());
    }
}

} // namespace Digikam

#include "moc_dsliderspinbox.cpp"
