kis_slider_spin_box.cpp 25.8 KB
Newer Older
1
/* This file is part of the KDE project
2 3
 * Copyright (c) 2010 Justin Noel <justin@ics.com>
 * Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
Moritz Molch's avatar
Moritz Molch committed
4
 * Copyright (c) 2015 Moritz Molch <kde@moritzmolch.de>
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
#include "kis_slider_spin_box.h"

23 24
#include <math.h>

25 26 27 28 29 30 31 32 33
#include <QPainter>
#include <QStyle>
#include <QLineEdit>
#include <QApplication>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QIntValidator>
#include <QTimer>
#include <QtDebug>
34
#include <QDoubleSpinBox>
35

36 37 38
#include "KisPart.h"
#include "input/kis_input_manager.h"

Halla Rempt's avatar
Halla Rempt committed
39 40
class KisAbstractSliderSpinBoxPrivate {
public:
Moritz Molch's avatar
Moritz Molch committed
41 42
    enum Style {
        STYLE_NOQUIRK,
43 44
        STYLE_PLASTIQUE,
        STYLE_BREEZE
Moritz Molch's avatar
Moritz Molch committed
45 46
    };

Cyrille Berger's avatar
Cyrille Berger committed
47 48 49 50 51
    QLineEdit* edit;
    QDoubleValidator* validator;
    bool upButtonDown;
    bool downButtonDown;
    int factor;
52
    int fastSliderStep;
53 54 55
    qreal slowFactor;
    qreal shiftPercent;
    bool shiftMode;
56
    QString prefix;
Cyrille Berger's avatar
Cyrille Berger committed
57 58
    QString suffix;
    qreal exponentRatio;
59 60 61 62
    int value;
    int maximum;
    int minimum;
    int singleStep;
63
    QSpinBox* dummySpinBox;
Moritz Molch's avatar
Moritz Molch committed
64
    Style style;
65
    bool blockUpdateSignalOnDrag;
Cyrille Berger's avatar
Cyrille Berger committed
66 67
};

68 69 70
KisAbstractSliderSpinBox::KisAbstractSliderSpinBox(QWidget* parent, KisAbstractSliderSpinBoxPrivate* _d)
    : QWidget(parent)
    , d_ptr(_d)
71
{
72
    Q_D(KisAbstractSliderSpinBox);
Halla Rempt's avatar
Halla Rempt committed
73 74
    QEvent e(QEvent::StyleChange);
    changeEvent(&e);
Moritz Molch's avatar
Moritz Molch committed
75

76 77 78 79 80 81
    d->upButtonDown = false;
    d->downButtonDown = false;
    d->edit = new QLineEdit(this);
    d->edit->setFrame(false);
    d->edit->setAlignment(Qt::AlignCenter);
    d->edit->hide();
Moritz Molch's avatar
Moritz Molch committed
82
    d->edit->setContentsMargins(0,0,0,0);
83 84 85 86 87 88 89
    d->edit->installEventFilter(this);

    //Make edit transparent
    d->edit->setAutoFillBackground(false);
    QPalette pal = d->edit->palette();
    pal.setColor(QPalette::Base, Qt::transparent);
    d->edit->setPalette(pal);
Halla Rempt's avatar
Halla Rempt committed
90

Halla Rempt's avatar
Halla Rempt committed
91
    connect(d->edit, SIGNAL(editingFinished()), this, SLOT(editLostFocus()));
92 93 94

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

Halla Rempt's avatar
Halla Rempt committed
96
    d->value = 0;
97 98 99 100
    d->minimum = 0;
    d->maximum = 100;
    d->factor = 1.0;
    d->singleStep = 1;
101
    d->fastSliderStep = 5;
102 103
    d->slowFactor = 0.1;
    d->shiftMode = false;
104
    d->blockUpdateSignalOnDrag = false;
105

106 107 108 109 110
    setExponentRatio(1.0);

    //Set sane defaults
    setFocusPolicy(Qt::StrongFocus);
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
111

112
    //dummy needed to fix a bug in the polyester theme
Dmitry Kazakov's avatar
Dmitry Kazakov committed
113
    d->dummySpinBox = new QSpinBox(this);
114
    d->dummySpinBox->hide();
115 116
}

117
KisAbstractSliderSpinBox::~KisAbstractSliderSpinBox()
118
{
119
    Q_D(KisAbstractSliderSpinBox);
Cyrille Berger's avatar
Cyrille Berger committed
120
    delete d;
121 122
}

123
void KisAbstractSliderSpinBox::showEdit()
124
{
125
    Q_D(KisAbstractSliderSpinBox);
Cyrille Berger's avatar
Cyrille Berger committed
126
    if (d->edit->isVisible()) return;
Moritz Molch's avatar
Moritz Molch committed
127 128 129 130 131 132
    if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE) {
        d->edit->setGeometry(progressRect(spinBoxOptions()).adjusted(0,0,-2,0));
    }
    else {
        d->edit->setGeometry(progressRect(spinBoxOptions()));
    }
Cyrille Berger's avatar
Cyrille Berger committed
133 134 135 136 137
    d->edit->setText(valueString());
    d->edit->selectAll();
    d->edit->show();
    d->edit->setFocus(Qt::OtherFocusReason);
    update();
138
    KisPart::currentInputManager()->slotFocusOnEnter(false);
139 140
}

141
void KisAbstractSliderSpinBox::hideEdit()
142
{
143
    Q_D(KisAbstractSliderSpinBox);
144 145
    d->edit->hide();
    update();
146
    KisPart::currentInputManager()->slotFocusOnEnter(true);
147 148
}

149
void KisAbstractSliderSpinBox::paintEvent(QPaintEvent* e)
150
{
151
    Q_D(KisAbstractSliderSpinBox);
152 153 154 155
    Q_UNUSED(e)

    QPainter painter(this);

Moritz Molch's avatar
Moritz Molch committed
156 157 158 159
    switch (d->style) {
    case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
        paintPlastique(painter);
        break;
160 161 162
    case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
        paintBreeze(painter);
        break;
Moritz Molch's avatar
Moritz Molch committed
163 164 165 166 167 168 169 170 171 172 173 174
    default:
        paint(painter);
        break;
    }

    painter.end();
}

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

175 176
    //Create options to draw spin box parts
    QStyleOptionSpinBox spinOpts = spinBoxOptions();
Moritz Molch's avatar
Moritz Molch committed
177
    spinOpts.rect.adjust(0, 2, 0, -2);
178

179
    //Draw "SpinBox".Clip off the area of the lineEdit to avoid double
180
    //borders being drawn
181
    painter.save();
182
    painter.setClipping(true);
Moritz Molch's avatar
Moritz Molch committed
183

184 185
    QRect eraseRect(QPoint(rect().x(), rect().y()),
                    QPoint(progressRect(spinOpts).right(), rect().bottom()));
Moritz Molch's avatar
Moritz Molch committed
186

187
    painter.setClipRegion(QRegion(rect()).subtracted(eraseRect));
188
    style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
189
    painter.setClipping(false);
190
    painter.restore();
191 192 193


    QStyleOptionProgressBar progressOpts = progressBarOptions();
Moritz Molch's avatar
Moritz Molch committed
194
    progressOpts.rect.adjust(0, 2, 0, -2);
195 196 197
    style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0);

    //Draw focus if necessary
198 199
    if (hasFocus() &&
            d->edit->hasFocus()) {
200 201 202
        QStyleOptionFocusRect focusOpts;
        focusOpts.initFrom(this);
        focusOpts.rect = progressOpts.rect;
203
        focusOpts.backgroundColor = palette().color(QPalette::Window);
204 205
        style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this);
    }
Moritz Molch's avatar
Moritz Molch committed
206 207 208 209 210 211 212 213 214 215
}


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

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

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

    painter.save();
219

220 221
    QRect rect = progressOpts.rect.adjusted(2,0,-2,0);
    QRect leftRect;
Moritz Molch's avatar
Moritz Molch committed
222

223 224
    int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0),
                               qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width();
Moritz Molch's avatar
Moritz Molch committed
225

226 227 228 229 230 231 232
    if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width() && (progressOpts.progress != 0)) {
        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());
    }
Moritz Molch's avatar
Moritz Molch committed
233

234 235
    QRegion rightRect = rect;
    rightRect = rightRect.subtracted(leftRect);
Moritz Molch's avatar
Moritz Molch committed
236

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

240
    if (!(d->edit && d->edit->isVisible())) {
Moritz Molch's avatar
Moritz Molch committed
241
        painter.setClipRegion(rightRect);
242
        painter.setClipping(true);
243
        painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption);
244 245 246 247 248 249 250 251
        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())) {
Moritz Molch's avatar
Moritz Molch committed
252
            painter.setPen(palette().highlightedText().color());
253 254
            painter.setClipRect(leftRect.adjusted(0,0,1,0));
            painter.setClipping(true);
255
            painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption);
256
            painter.setClipping(false);
Moritz Molch's avatar
Moritz Molch committed
257 258
        }
    }
259 260

    painter.restore();
261 262
}

263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
void KisAbstractSliderSpinBox::paintBreeze(QPainter &painter)
{
    Q_D(KisAbstractSliderSpinBox);

    QStyleOptionSpinBox spinOpts = spinBoxOptions();
    QStyleOptionProgressBar progressOpts = progressBarOptions();
    QString valueText = progressOpts.text;
    progressOpts.text = "";
    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 - qreal(progressOpts.minimum)) / qMax(qreal(1.0),
                               qreal(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);

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

298
    if (!(d->edit && d->edit->isVisible())) {
299
        painter.drawText(progressOpts.rect, valueText, textOption);
300 301 302 303 304 305 306
    }

    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())) {
307
            painter.drawText(progressOpts.rect, valueText, textOption);
308 309 310 311 312 313 314
        }
    }

    painter.restore();

}

315
void KisAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e)
316
{
317
    Q_D(KisAbstractSliderSpinBox);
318 319 320 321
    QStyleOptionSpinBox spinOpts = spinBoxOptions();

    //Depress buttons or highlight slider
    //Also used to emulate mouse grab...
322 323
    if (e->buttons() & Qt::LeftButton) {
        if (upButtonRect(spinOpts).contains(e->pos())) {
Cyrille Berger's avatar
Cyrille Berger committed
324
            d->upButtonDown = true;
325
        } else if (downButtonRect(spinOpts).contains(e->pos())) {
Cyrille Berger's avatar
Cyrille Berger committed
326
            d->downButtonDown = true;
327
        }
328
    } else if (e->buttons() & Qt::RightButton) {
329
        showEdit();
330 331
    }

332

333
    update();
334 335
}

336
void KisAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e)
337
{
338
    Q_D(KisAbstractSliderSpinBox);
339 340 341 342
    QStyleOptionSpinBox spinOpts = spinBoxOptions();

    //Step up/down for buttons
    //Emualting mouse grab too
343 344 345 346 347 348 349
    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)) {
350
        //Snap to percentage for progress area
351
        setInternalValue(valueForX(e->pos().x(),e->modifiers()));
352 353
    } else { // Confirm the last known value, since we might be ignoring move events
        setInternalValue(d->value);
354 355 356 357 358
    }

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

361
void KisAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e)
362
{
363
    Q_D(KisAbstractSliderSpinBox);
364 365 366 367 368 369 370 371 372 373

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

374
    //Respect emulated mouse grab.
375
    if (e->buttons() & Qt::LeftButton &&
376
            !(d->downButtonDown || d->upButtonDown)) {
377
        setInternalValue(valueForX(e->pos().x(),e->modifiers()), d->blockUpdateSignalOnDrag);
Sven Langkamp's avatar
Sven Langkamp committed
378
        update();
379
    }
380 381
}

382
void KisAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e)
383
{
384 385
    Q_D(KisAbstractSliderSpinBox);
    switch (e->key()) {
386 387
    case Qt::Key_Up:
    case Qt::Key_Right:
388
        setInternalValue(d->value + d->singleStep);
389 390 391
        break;
    case Qt::Key_Down:
    case Qt::Key_Left:
392
        setInternalValue(d->value - d->singleStep);
393
        break;
394 395 396 397 398
    case Qt::Key_Shift:
        d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) );
        d->shiftMode = true;
        break;
    case Qt::Key_Enter: //Line edit isn't "accepting" key strokes...
399 400
    case Qt::Key_Return:
    case Qt::Key_Escape:
401 402 403 404 405
    case Qt::Key_Control:
    case Qt::Key_Alt:
    case Qt::Key_AltGr:
    case Qt::Key_Super_L:
    case Qt::Key_Super_R:
406 407 408 409 410 411
        break;
    default:
        showEdit();
        d->edit->event(e);
        break;
    }
412 413
}

414 415 416 417
void KisAbstractSliderSpinBox::wheelEvent(QWheelEvent *e)
{

    Q_D(KisAbstractSliderSpinBox);
418
    if ( e->delta() > 0) {
419 420 421 422
        setInternalValue(d->value + d->singleStep);
    } else {
        setInternalValue(d->value - d->singleStep);
    }
423
    update();
424 425 426
    e->accept();
}

427
bool KisAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e)
428
{
429 430 431
    Q_D(KisAbstractSliderSpinBox);
    if (recv == static_cast<QObject*>(d->edit) &&
            e->type() == QEvent::KeyRelease) {
432 433
        QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);

434
        switch (keyEvent->key()) {
435 436
        case Qt::Key_Enter:
        case Qt::Key_Return:
437
            setInternalValue(d->edit->text().toDouble()*d->factor);
438 439
            hideEdit();
            return true;
440
        case Qt::Key_Escape:
441 442
            hideEdit();
            return true;
443
        default:
444
            break;
445 446
        }
    }
447

448
    return false;
449 450
}

451
QSize KisAbstractSliderSpinBox::sizeHint() const
452
{
453
    const Q_D(KisAbstractSliderSpinBox);
454 455
    QStyleOptionSpinBox spinOpts = spinBoxOptions();

456
    QFont ft(font());
457 458 459 460 461 462
    if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK) {
        // Some styles use bold font in progressbars
        // unfortunately there is no reliable way to check for that
        ft.setBold(true);
    }

463
    QFontMetrics fm(ft);
Moritz Molch's avatar
Moritz Molch committed
464
    QSize hint(fm.boundingRect(d->prefix + QString::number(d->maximum) + d->suffix).size());
465
    hint += QSize(0, 2);
Moritz Molch's avatar
Moritz Molch committed
466

467 468 469 470 471 472 473 474 475 476 477 478 479
    switch (d->style) {
    case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
        hint += QSize(8, 0);
        break;
    case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
        hint += QSize(2, 0);
        break;
    case KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK:
        // almost all "modern" styles have a margin around controls
        hint += QSize(6, 6);
        break;
    default:
        break;
Moritz Molch's avatar
Moritz Molch committed
480 481
    }

482 483 484 485 486 487 488 489 490 491 492 493
    //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;

494

Moritz Molch's avatar
Moritz Molch committed
495 496
    spinOpts.rect.setSize(hint);
    return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint)
497
            .expandedTo(QApplication::globalStrut());
498 499 500

}

501
QSize KisAbstractSliderSpinBox::minimumSizeHint() const
502
{
503
    return sizeHint();
504 505
}

Moritz Molch's avatar
Moritz Molch committed
506 507 508 509 510
QSize KisAbstractSliderSpinBox::minimumSize() const
{
    return QWidget::minimumSize().expandedTo(minimumSizeHint());
}

511
QStyleOptionSpinBox KisAbstractSliderSpinBox::spinBoxOptions() const
512
{
513
    const Q_D(KisAbstractSliderSpinBox);
514 515 516 517 518 519 520
    QStyleOptionSpinBox opts;
    opts.initFrom(this);
    opts.frame = false;
    opts.buttonSymbols = QAbstractSpinBox::UpDownArrows;
    opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown;

    //Disable non-logical buttons
521
    if (d->value == d->minimum) {
522
        opts.stepEnabled = QAbstractSpinBox::StepUpEnabled;
523
    } else if (d->value == d->maximum) {
524
        opts.stepEnabled = QAbstractSpinBox::StepDownEnabled;
525
    } else {
526 527 528 529
        opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled;
    }

    //Deal with depressed buttons
530
    if (d->upButtonDown) {
531
        opts.activeSubControls = QStyle::SC_SpinBoxUp;
532
    } else if (d->downButtonDown) {
533
        opts.activeSubControls = QStyle::SC_SpinBoxDown;
534
    } else {
535 536 537 538
        opts.activeSubControls = 0;
    }

    return opts;
539 540
}

541
QStyleOptionProgressBar KisAbstractSliderSpinBox::progressBarOptions() const
542
{
543
    const Q_D(KisAbstractSliderSpinBox);
544 545 546 547 548
    QStyleOptionSpinBox spinOpts = spinBoxOptions();

    //Create opts for drawing the progress portion
    QStyleOptionProgressBar progressOpts;
    progressOpts.initFrom(this);
549 550
    progressOpts.maximum = d->maximum;
    progressOpts.minimum = d->minimum;
551

552
    qreal minDbl = d->minimum;
553

554
    qreal dValues = (d->maximum - minDbl);
555

Halla Rempt's avatar
Halla Rempt committed
556
    progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl;
557
    progressOpts.text = d->prefix + valueString() + d->suffix;
558 559
    progressOpts.textAlignment = Qt::AlignCenter;
    progressOpts.textVisible = !(d->edit->isVisible());
560

561 562
    //Change opts rect to be only the ComboBox's text area
    progressOpts.rect = progressRect(spinOpts);
563

564
    return progressOpts;
565 566
}

567
QRect KisAbstractSliderSpinBox::progressRect(const QStyleOptionSpinBox& spinBoxOptions) const
568
{
Moritz Molch's avatar
Moritz Molch committed
569 570 571 572
    const Q_D(KisAbstractSliderSpinBox);
    QRect ret = style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
                                        QStyle::SC_SpinBoxEditField);

573 574
    switch (d->style) {
    case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
575
        ret.adjust(-2, 0, 1, 0);
576 577 578 579 580 581
        break;
    case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
        ret.adjust(1, 0, 0, 0);
        break;
    default:
        break;
Moritz Molch's avatar
Moritz Molch committed
582 583 584
    }

    return ret;
585 586
}

587
QRect KisAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
588
{
589 590
    return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
                                   QStyle::SC_SpinBoxUp);
591 592
}

593
QRect KisAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
594
{
595 596
    return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
                                   QStyle::SC_SpinBoxDown);
597 598
}

599
int KisAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const
600
{
601
    const Q_D(KisAbstractSliderSpinBox);
602 603
    QStyleOptionSpinBox spinOpts = spinBoxOptions();

604 605 606 607 608 609 610 611
    QRect correctedProgRect;
    if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE) {
        correctedProgRect = progressRect(spinOpts);
    }
    else {
        //Adjust for magic number in style code (margins)
        correctedProgRect = progressRect(spinOpts).adjusted(2, 2, -2, -2);
    }
612

613
    //Compute the distance of the progress bar, in pixel
614 615
    qreal leftDbl = correctedProgRect.left();
    qreal xDbl = x - leftDbl;
616 617

    //Compute the ration of the progress bar used, linearly (ignoring the exponent)
618
    qreal rightDbl = correctedProgRect.right();
619 620
    qreal minDbl = d->minimum;
    qreal maxDbl = d->maximum;
621 622

    qreal dValues = (maxDbl - minDbl);
623
    qreal percent = (xDbl / (rightDbl - leftDbl));
624

625 626 627 628
    //If SHIFT is pressed, movement should be slowed.
    if( modifiers & Qt::ShiftModifier ) {
        percent = d->shiftPercent + ( percent - d->shiftPercent ) * d->slowFactor;
    }
629

630 631 632
    //Final value
    qreal realvalue = ((dValues * pow(percent, d->exponentRatio)) + minDbl);
    //If key CTRL is pressed, round to the closest step.
633
    if( modifiers & Qt::ControlModifier ) {
634 635 636 637 638
        qreal fstep = d->fastSliderStep;
        if( modifiers & Qt::ShiftModifier ) {
            fstep*=d->slowFactor;
        }
        realvalue = floor( (realvalue+fstep/2) / fstep ) * fstep;
639
    }
640
    //Return the value
641
    return int(realvalue);
642 643
}

644 645 646 647 648 649
void KisAbstractSliderSpinBox::setPrefix(const QString& prefix)
{
    Q_D(KisAbstractSliderSpinBox);
    d->prefix = prefix;
}

650
void KisAbstractSliderSpinBox::setSuffix(const QString& suffix)
651
{
652 653
    Q_D(KisAbstractSliderSpinBox);
    d->suffix = suffix;
654 655
}

656
void KisAbstractSliderSpinBox::setExponentRatio(qreal dbl)
657
{
658 659 660 661 662
    Q_D(KisAbstractSliderSpinBox);
    Q_ASSERT(dbl > 0);
    d->exponentRatio = dbl;
}

663 664 665 666 667 668
void KisAbstractSliderSpinBox::setBlockUpdateSignalOnDrag(bool blockUpdateSignal)
{
    Q_D(KisAbstractSliderSpinBox);
    d->blockUpdateSignalOnDrag = blockUpdateSignal;
}

669 670 671
void KisAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event)
{
    event->accept();
672 673
}

Sven Langkamp's avatar
Sven Langkamp committed
674 675
void KisAbstractSliderSpinBox::editLostFocus()
{
676 677 678 679 680
    // only hide on focus lost, if editing is finished that will be handled in eventFilter
    Q_D(KisAbstractSliderSpinBox);
    if (!d->edit->hasFocus()) {
        hideEdit();
    }
Sven Langkamp's avatar
Sven Langkamp committed
681 682
}

683 684 685 686 687
void KisAbstractSliderSpinBox::setInternalValue(int value)
{
    setInternalValue(value, false);
}

Halla Rempt's avatar
Halla Rempt committed
688
class KisSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate {
689 690 691
};

KisSliderSpinBox::KisSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisSliderSpinBoxPrivate)
692
{
693
    setRange(0,99);
694 695
}

696
KisSliderSpinBox::~KisSliderSpinBox()
697
{
698 699 700 701 702 703 704
}

void KisSliderSpinBox::setRange(int minimum, int maximum)
{
    Q_D(KisSliderSpinBox);
    d->minimum = minimum;
    d->maximum = maximum;
705
    d->fastSliderStep = (maximum-minimum+1)/20;
706
    d->validator->setRange(minimum, maximum, 0);
707
    update();
708 709
}

710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
int KisSliderSpinBox::minimum() const
{
    const Q_D(KisSliderSpinBox);
    return d->minimum;
}

void KisSliderSpinBox::setMinimum(int minimum)
{
    Q_D(KisSliderSpinBox);
    setRange(minimum, d->maximum);
}

int KisSliderSpinBox::maximum() const
{
    const Q_D(KisSliderSpinBox);
    return d->maximum;
}

void KisSliderSpinBox::setMaximum(int maximum)
{
    Q_D(KisSliderSpinBox);
    setRange(d->minimum, maximum);
}

734 735 736 737 738 739 740 741 742 743 744 745
int KisSliderSpinBox::fastSliderStep() const
{
    const Q_D(KisSliderSpinBox);
    return d->fastSliderStep;
}

void KisSliderSpinBox::setFastSliderStep(int step)
{
    Q_D(KisSliderSpinBox);
    d->fastSliderStep = step;
}

746 747 748 749 750 751 752 753
int KisSliderSpinBox::value()
{
    Q_D(KisSliderSpinBox);
    return d->value;
}

void KisSliderSpinBox::setValue(int value)
{
754
    setInternalValue(value, false);
755
    update();
756 757
}

758
QString KisSliderSpinBox::valueString() const
759
{
760 761
    const Q_D(KisSliderSpinBox);
    return QString::number(d->value, 'f', d->validator->decimals());
762 763
}

764
void KisSliderSpinBox::setSingleStep(int value)
765
{
766 767
    Q_D(KisSliderSpinBox);
    d->singleStep = value;
768
}
769

770
void KisSliderSpinBox::setPageStep(int value)
771
{
772 773 774
    Q_UNUSED(value);
}

775
void KisSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal)
776 777
{
    Q_D(KisAbstractSliderSpinBox);
778
    d->value = qBound(d->minimum, _value, d->maximum);
779 780 781 782

    if(!blockUpdateSignal) {
        emit(valueChanged(value()));
    }
783 784
}

Halla Rempt's avatar
Halla Rempt committed
785
class KisDoubleSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate {
786 787 788 789 790 791 792 793 794 795 796 797 798
};

KisDoubleSliderSpinBox::KisDoubleSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisDoubleSliderSpinBoxPrivate)
{
}

KisDoubleSliderSpinBox::~KisDoubleSliderSpinBox()
{
}

void KisDoubleSliderSpinBox::setRange(qreal minimum, qreal maximum, int decimals)
{
    Q_D(KisDoubleSliderSpinBox);
Halla Rempt's avatar
Halla Rempt committed
799
    d->factor = pow(10.0, decimals);
800 801 802

    d->minimum = minimum * d->factor;
    d->maximum = maximum * d->factor;
803 804 805
    //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
Halla Rempt's avatar
Halla Rempt committed
806
        d->fastSliderStep = int(pow(10.0, decimals));
807 808 809 810 811
    } else if(decimals == 1) {
        d->fastSliderStep = (maximum-minimum)*d->factor/10;
    } else {
        d->fastSliderStep = (maximum-minimum)*d->factor/20;
    }
812
    d->validator->setRange(minimum, maximum, decimals);
813
    update();
814
    setValue(value());
815 816
}

817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851
qreal KisDoubleSliderSpinBox::minimum() const
{
    const Q_D(KisAbstractSliderSpinBox);
    return d->minimum / d->factor;
}

void KisDoubleSliderSpinBox::setMinimum(qreal minimum)
{
    Q_D(KisAbstractSliderSpinBox);
    setRange(minimum, d->maximum);
}

qreal KisDoubleSliderSpinBox::maximum() const
{
    const Q_D(KisAbstractSliderSpinBox);
    return d->maximum / d->factor;
}

void KisDoubleSliderSpinBox::setMaximum(qreal maximum)
{
    Q_D(KisAbstractSliderSpinBox);
    setRange(d->minimum, maximum);
}

qreal KisDoubleSliderSpinBox::fastSliderStep() const
{
    const Q_D(KisAbstractSliderSpinBox);
    return d->fastSliderStep;
}
void KisDoubleSliderSpinBox::setFastSliderStep(qreal step)
{
    Q_D(KisAbstractSliderSpinBox);
    d->fastSliderStep = step;
}

852 853 854 855 856 857 858 859 860
qreal KisDoubleSliderSpinBox::value()
{
    Q_D(KisAbstractSliderSpinBox);
    return (qreal)d->value / d->factor;
}

void KisDoubleSliderSpinBox::setValue(qreal value)
{
    Q_D(KisAbstractSliderSpinBox);
861
    setInternalValue(d->value = qRound(value * d->factor), false);
862 863 864 865 866 867 868 869 870 871 872 873 874
    update();
}

void KisDoubleSliderSpinBox::setSingleStep(qreal value)
{
    Q_D(KisAbstractSliderSpinBox);
    d->singleStep = value * d->factor;
}

QString KisDoubleSliderSpinBox::valueString() const
{
    const Q_D(KisAbstractSliderSpinBox);
    return QString::number((qreal)d->value / d->factor, 'f', d->validator->decimals());
875
}
876

877
void KisDoubleSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal)
878
{
879
    Q_D(KisAbstractSliderSpinBox);
880
    d->value = qBound(d->minimum, _value, d->maximum);
881 882 883 884

    if(!blockUpdateSignal) {
        emit(valueChanged(value()));
    }
885
}
Moritz Molch's avatar
Moritz Molch committed
886 887 888 889 890 891 892 893 894 895 896 897 898


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

    QWidget::changeEvent(e);

    switch (e->type()) {
    case QEvent::StyleChange:
        if (style()->objectName() == "plastique") {
            d->style = KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE;
        }
899 900 901
        else if (style()->objectName() == "breeze") {
            d->style = KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE;
        }
Moritz Molch's avatar
Moritz Molch committed
902 903 904 905 906 907
        else {
            d->style = KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK;
        }
        break;
    }
}