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;