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

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