File indexing completed on 2024-05-19 04:36:40

0001 /* This file is part of the TikZKit project.
0002  *
0003  * Copyright (C) 2014 Dominik Haumann <dhaumann@kde.org>
0004  *
0005  * This code is based on the KisSliderSpinBox used in Krita:
0006  * Copyright (c) 2010 Justin Noel <justin@ics.com>
0007  * Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
0008  *
0009  * This library is free software; you can redistribute it and/or modify
0010  * it under the terms of the GNU Library General Public License as published
0011  * by the Free Software Foundation, either version 2 of the License, or
0012  * (at your option) any later version.
0013  *
0014  * This library is distributed in the hope that it will be useful,
0015  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017  * GNU Library General Public License for more details.
0018  *
0019  * You should have received a copy of the GNU Library General Public License
0020  * along with this library; see the file COPYING.LIB.  If not, see
0021  * <http://www.gnu.org/licenses/>.
0022  */
0023 
0024 #include "SliderSpinBox.h"
0025 
0026 #include <cmath>
0027 
0028 #include <QStyle>
0029 #include <QLineEdit>
0030 #include <QKeyEvent>
0031 #include <QMouseEvent>
0032 #include <QStylePainter>
0033 #include <QStyleOptionSpinBox>
0034 #include <QStyleOptionProgressBar>
0035 #include <QContextMenuEvent>
0036 #include <QCursor>
0037 #include <QDebug>
0038 
0039 class SliderSpinBoxPrivate
0040 {
0041 public:
0042     SliderSpinBoxPrivate(SliderSpinBox * spinBox)
0043         : q(spinBox)
0044     {}
0045 
0046     QStyleOptionSpinBox spinBoxOptions() const;
0047     QStyleOptionProgressBar progressBarOptions() const;
0048     QRect progressRect(const QStyleOptionSpinBox& spinBoxOptions) const;
0049     QRect upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const;
0050     QRect downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const;
0051     int valueForX(int x) const;
0052     void showLineEdit();
0053 
0054 public:
0055     SliderSpinBox * const q;
0056     bool pressedInButton;
0057 };
0058 
0059 QStyleOptionSpinBox SliderSpinBoxPrivate::spinBoxOptions() const
0060 {
0061     QStyleOptionSpinBox opts;
0062     opts.initFrom(q);
0063     opts.frame = false;
0064     opts.subControls &= ~0x4; // SC_SpinBoxFrame
0065     opts.subControls &= ~0x8; // SC_SpinBoxEditField
0066 
0067     if (q->value() == q->minimum()) {
0068         opts.stepEnabled = QAbstractSpinBox::StepUpEnabled;
0069     } else if (q->value() == q->maximum()) {
0070         opts.stepEnabled = QAbstractSpinBox::StepDownEnabled;
0071     } else {
0072         opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled;
0073     }
0074 
0075     //Deal with depressed buttons
0076     QPoint mousePos = q->mapFromGlobal(QCursor::pos());
0077     if (upButtonRect(opts).contains(mousePos)) {
0078         opts.activeSubControls = QStyle::SC_SpinBoxUp;
0079     } else if (downButtonRect(opts).contains(mousePos)) {
0080         opts.activeSubControls = QStyle::SC_SpinBoxDown;
0081     } else {
0082         opts.activeSubControls = QStyle::SC_None;
0083     }
0084 
0085     return opts;
0086 }
0087 
0088 QStyleOptionProgressBar SliderSpinBoxPrivate::progressBarOptions() const
0089 {
0090     QStyleOptionSpinBox spinOpts = spinBoxOptions();
0091 
0092     //Create opts for drawing the progress portion
0093     QStyleOptionProgressBar progressOpts;
0094     progressOpts.initFrom(q);
0095     progressOpts.maximum = q->maximum();
0096     progressOpts.minimum = q->minimum();
0097     progressOpts.progress = q->value();
0098     progressOpts.text = QString::number(q->value()) + q->suffix();
0099     progressOpts.textAlignment = Qt::AlignCenter;
0100     progressOpts.textVisible = ! q->lineEdit()->isVisible();
0101 
0102     //Change opts rect to be only the ComboBox's text area
0103     progressOpts.rect = progressRect(spinOpts);
0104 
0105     return progressOpts;
0106 }
0107 
0108 QRect SliderSpinBoxPrivate::progressRect(const QStyleOptionSpinBox& spinBoxOptions) const
0109 {
0110     return q->style()->subControlRect(QStyle::CC_SpinBox,
0111                                       &spinBoxOptions,
0112                                       QStyle::SC_SpinBoxEditField);
0113 }
0114 
0115 int SliderSpinBoxPrivate::valueForX(int x) const
0116 {
0117     QStyleOptionSpinBox spinOpts = spinBoxOptions();
0118 
0119     // adjust for border used in the QStyles (FIXME: probably not exact)
0120     QRect correctedProgRect = progressRect(spinOpts).adjusted(2, 2, -2, -2);
0121 
0122     const int left = correctedProgRect.left();
0123     const int right = correctedProgRect.right();
0124     const int val = qMax(0, x - left);
0125 
0126     const int range = q->maximum() - q->minimum();
0127     qreal percent = static_cast<qreal>(val) / (right - left);
0128 
0129     if (q->layoutDirection() == Qt::RightToLeft) {
0130         percent = 1 - percent;
0131     }
0132 
0133     return q->minimum() + qRound(percent * (range));
0134 }
0135 
0136 QRect SliderSpinBoxPrivate::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
0137 {
0138     return q->style()->subControlRect(QStyle::CC_SpinBox,
0139                                       &spinBoxOptions,
0140                                       QStyle::SC_SpinBoxUp);
0141 }
0142 
0143 QRect SliderSpinBoxPrivate::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
0144 {
0145     return q->style()->subControlRect(QStyle::CC_SpinBox,
0146                                       &spinBoxOptions,
0147                                       QStyle::SC_SpinBoxDown);
0148 }
0149 
0150 void SliderSpinBoxPrivate::showLineEdit()
0151 {
0152     if (!q->lineEdit()->isVisible()) {
0153         q->lineEdit()->setVisible(true);
0154         q->lineEdit()->setText(QString::number(q->value()));
0155         q->lineEdit()->selectAll();
0156     }
0157 }
0158 
0159 
0160 
0161 
0162 
0163 
0164 
0165 SliderSpinBox::SliderSpinBox(QWidget * parent)
0166     : QSpinBox(parent)
0167     , d(new SliderSpinBoxPrivate(this))
0168 {
0169     d->pressedInButton = false;
0170 
0171     // mouse tracking is required for the mouse-over effect on the up/down buttons
0172     setMouseTracking(true);
0173 
0174     // seup line edit
0175     lineEdit()->setFrame(false);
0176     lineEdit()->setAlignment(Qt::AlignCenter);
0177 
0178     // since the line edit is an overlay, do not autofill the background
0179     lineEdit()->setAutoFillBackground(false);
0180 
0181     // change the background color of the line edit to transparent
0182     QPalette pal = lineEdit()->palette();
0183     pal.setColor(QPalette::Base, Qt::transparent);
0184     lineEdit()->setPalette(pal);
0185 
0186     // hide line edit by default
0187     lineEdit()->hide();
0188 
0189     // make sure text is only accepted on editingFinished()
0190     lineEdit()->disconnect(this);
0191     connect(this, SIGNAL(editingFinished()), lineEdit(), SLOT(hide()));
0192 }
0193 
0194 SliderSpinBox::~SliderSpinBox()
0195 {
0196     delete d;
0197 }
0198 
0199 void SliderSpinBox::paintEvent(QPaintEvent* event)
0200 {
0201     Q_UNUSED(event)
0202 
0203     //
0204     // draw the QSpinBox without frame (see spinBoxOptions())
0205     //
0206     QStyleOptionSpinBox opt = d->spinBoxOptions();
0207     QStylePainter p(this);
0208     p.drawComplexControl(QStyle::CC_SpinBox, opt);
0209 
0210     //
0211     // draw progress bar background on top of the spin box
0212     //
0213     QStyleOptionProgressBar progressOpts = d->progressBarOptions();
0214     p.drawControl(QStyle::CE_ProgressBar, progressOpts);
0215 
0216     //
0217     // draw the progress bar label, if applicable
0218     //
0219     if (progressOpts.textVisible) {
0220         p.drawControl(QStyle::CE_ProgressBarLabel, progressOpts);
0221     }
0222 }
0223 
0224 void SliderSpinBox::mousePressEvent(QMouseEvent * event)
0225 {
0226     if (event->buttons() & Qt::LeftButton) {
0227         QStyleOptionSpinBox spinOpts = d->spinBoxOptions();
0228         // mouse in progress area?
0229         if (d->progressRect(spinOpts).contains(event->pos())
0230             && ! lineEdit()->isVisible())
0231         {
0232             setValue(d->valueForX(event->pos().x()));
0233         }
0234         // mouse over up-button?
0235         else if (d->upButtonRect(spinOpts).contains(event->pos())) {
0236             setValue(value() + singleStep());
0237             d->pressedInButton = true;
0238         }
0239         // mouse over down-button?
0240         else if (d->downButtonRect(spinOpts).contains(event->pos())) {
0241             setValue(value() - singleStep());
0242             d->pressedInButton = true;
0243         } else {
0244             // whatever this mouse press is, just pass it on
0245             QSpinBox::mousePressEvent(event);
0246         }
0247     } else if (event->buttons() & Qt::RightButton) {
0248         // right now, RMB switches into line edit mode
0249         d->showLineEdit();
0250     }
0251 }
0252 
0253 void SliderSpinBox::mouseReleaseEvent(QMouseEvent * event)
0254 {
0255     if (d->pressedInButton && event->button() == Qt::LeftButton) {
0256         d->pressedInButton = false;
0257     }
0258 
0259     QSpinBox::mouseReleaseEvent(event);
0260 }
0261 
0262 void SliderSpinBox::mouseMoveEvent(QMouseEvent * event)
0263 {
0264     if (event->buttons() & Qt::LeftButton) {
0265         QStyleOptionSpinBox spinOpts = d->spinBoxOptions();
0266         if (d->progressRect(spinOpts).contains(event->pos())
0267             && ! lineEdit()->isVisible()
0268             && ! d->pressedInButton)
0269         {
0270             // on LMB: mouse move sets new value
0271             setValue(d->valueForX(event->pos().x()));
0272         }
0273     }
0274 
0275     // always trigger update, since the cursor might hover over the up/down buttons
0276     update();
0277 }
0278 
0279 void SliderSpinBox::contextMenuEvent(QContextMenuEvent * event)
0280 {
0281     // we don't want any context menu.
0282     // Instad, we show the line edit and, thus, change to edit mode.
0283     event->accept();
0284 }
0285 
0286 void SliderSpinBox::keyPressEvent(QKeyEvent * event)
0287 {
0288     if (lineEdit()->isVisible()) {
0289         // on Escape, just hide the line edit and do nothing
0290         if (event->key() == Qt::Key_Escape) {
0291             lineEdit()->hide();
0292         }
0293         QSpinBox::keyPressEvent(event);
0294         return;
0295     }
0296 
0297     switch (event->key()) {
0298         case Qt::Key_Up:
0299         case Qt::Key_Right:
0300             setValue(value() + singleStep());
0301             break;
0302         case Qt::Key_Down:
0303         case Qt::Key_Left:
0304             setValue(value() - singleStep());
0305             break;
0306         case Qt::Key_Escape:
0307             // do nothing
0308             break;
0309         default:
0310             // for all other keys, show the line edit and pass the event to
0311             // the line edit so that the typed digit already counts as input
0312             d->showLineEdit();
0313             lineEdit()->event(event);
0314             break;
0315     }
0316 }
0317 
0318 // kate: indent-width 4; replace-tabs on;