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;