File indexing completed on 2024-04-21 14:55:56

0001 /* This file is part of the KDE libraries
0002  * Initial implementation:
0003  *     Copyright (c) 1997 Patrick Dowler <dowler@morgul.fsh.uvic.ca>
0004  * Rewritten and maintained by:
0005  *     Copyright (c) 2000 Dirk Mueller <mueller@kde.org>
0006  *
0007  *  This library is free software; you can redistribute it and/or
0008  *  modify it under the terms of the GNU Library General Public
0009  *  License as published by the Free Software Foundation; either
0010  *  version 2 of the License, or (at your option) any later version.
0011  *
0012  *  This library is distributed in the hope that it will be useful,
0013  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  *  Library General Public License for more details.
0016  *
0017  *  You should have received a copy of the GNU Library General Public License
0018  *  along with this library; see the file COPYING.LIB.  If not, write to
0019  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020  *  Boston, MA 02110-1301, USA.
0021  */
0022 
0023 #include "knuminput.h"
0024 
0025 #include <config-kdelibs4support.h>
0026 #if HAVE_LIMITS_H
0027 #include <limits.h>
0028 #endif
0029 
0030 #include <cmath>
0031 
0032 #include <QApplication>
0033 #include <QLabel>
0034 #include <QLineEdit>
0035 #include <QResizeEvent>
0036 #include <QSlider>
0037 #include <QStyle>
0038 
0039 #include <kconfigdialogmanager.h>
0040 #include <kdebug.h>
0041 #include <klocalizedstring.h>
0042 
0043 static inline int calcDiffByTen(int x, int y)
0044 {
0045     // calculate ( x - y ) / 10 without overflowing ints:
0046     return (x / 10) - (y / 10)  + (x % 10 - y % 10) / 10;
0047 }
0048 
0049 // ----------------------------------------------------------------------------
0050 
0051 class KNumInputPrivate
0052 {
0053 public:
0054     KNumInputPrivate(KNumInput *q, KNumInput *below = nullptr) :
0055         q(q),
0056         previousNumInput(nullptr),
0057         nextNumInput(nullptr),
0058         column1Width(0),
0059         column2Width(0),
0060         label(nullptr),
0061         slider(nullptr),
0062         labelAlignment()
0063     {
0064         if (below) {
0065             nextNumInput = below->d->nextNumInput;
0066             previousNumInput = below;
0067             below->d->nextNumInput = q;
0068             if (nextNumInput) {
0069                 nextNumInput->d->previousNumInput = q;
0070             }
0071         }
0072     }
0073 
0074     static KNumInputPrivate *get(const KNumInput *i)
0075     {
0076         return i->d;
0077     }
0078 
0079     KNumInput *q;
0080     KNumInput *previousNumInput, *nextNumInput;
0081     int column1Width, column2Width;
0082 
0083     QLabel  *label;
0084     QSlider *slider;
0085     QSize    sliderSize, labelSize;
0086 
0087     Qt::Alignment labelAlignment;
0088 };
0089 
0090 #define K_USING_KNUMINPUT_P(_d) KNumInputPrivate *_d = KNumInputPrivate::get(this)
0091 
0092 KNumInput::KNumInput(QWidget *parent)
0093     : QWidget(parent), d(new KNumInputPrivate(this))
0094 {
0095     setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
0096     setFocusPolicy(Qt::StrongFocus);
0097     KConfigDialogManager::changedMap()->insert("KIntNumInput", SIGNAL(valueChanged(int)));
0098     KConfigDialogManager::changedMap()->insert("KIntSpinBox", SIGNAL(valueChanged(int)));
0099     KConfigDialogManager::changedMap()->insert("KDoubleSpinBox", SIGNAL(valueChanged(double)));
0100 }
0101 
0102 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
0103 KNumInput::KNumInput(QWidget *parent, KNumInput *below)
0104     : QWidget(parent), d(new KNumInputPrivate(this, below))
0105 {
0106     setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
0107     setFocusPolicy(Qt::StrongFocus);
0108 }
0109 #endif
0110 
0111 KNumInput::~KNumInput()
0112 {
0113     if (d->previousNumInput) {
0114         d->previousNumInput->d->nextNumInput = d->nextNumInput;
0115     }
0116 
0117     if (d->nextNumInput) {
0118         d->nextNumInput->d->previousNumInput = d->previousNumInput;
0119     }
0120 
0121     delete d;
0122 }
0123 
0124 QSlider *KNumInput::slider() const
0125 {
0126     return d->slider;
0127 }
0128 
0129 bool KNumInput::showSlider() const
0130 {
0131     return d->slider;
0132 }
0133 
0134 void KNumInput::setLabel(const QString &label, Qt::Alignment a)
0135 {
0136     if (label.isEmpty()) {
0137         delete d->label;
0138         d->label = nullptr;
0139         d->labelAlignment = Qt::Alignment();
0140     } else {
0141         if (!d->label) {
0142             d->label = new QLabel(this);
0143         }
0144         d->label->setText(label);
0145         d->label->setObjectName("KNumInput::QLabel");
0146         d->label->setAlignment(a);
0147         // if no vertical alignment set, use Top alignment
0148         if (!(a & (Qt::AlignTop | Qt::AlignBottom | Qt::AlignVCenter))) {
0149             a |= Qt::AlignTop;
0150         }
0151         d->labelAlignment = a;
0152     }
0153 
0154     layout(true);
0155 }
0156 
0157 QString KNumInput::label() const
0158 {
0159     return d->label ? d->label->text() : QString();
0160 }
0161 
0162 void KNumInput::layout(bool deep)
0163 {
0164     int w1 = d->column1Width;
0165     int w2 = d->column2Width;
0166 
0167     // label sizeHint
0168     d->labelSize = (d->label ? d->label->sizeHint() : QSize(0, 0));
0169 
0170     if (d->label && (d->labelAlignment & Qt::AlignVCenter)) {
0171         d->column1Width = d->labelSize.width() + 4;
0172     } else {
0173         d->column1Width = 0;
0174     }
0175 
0176     // slider sizeHint
0177     d->sliderSize = (d->slider ? d->slider->sizeHint() : QSize(0, 0));
0178 
0179     doLayout();
0180 
0181     if (!deep) {
0182         d->column1Width = w1;
0183         d->column2Width = w2;
0184         return;
0185     }
0186 
0187     w2 = d->column2Width;
0188 
0189     KNumInput *p = d->previousNumInput;
0190     while (p) {
0191         p->doLayout();
0192         w1 = qMax(w1, p->d->column1Width);
0193         w2 = qMax(w2, p->d->column2Width);
0194         p = p->d->previousNumInput;
0195     }
0196 
0197     p = d->nextNumInput;
0198     while (p) {
0199         p->doLayout();
0200         w1 = qMax(w1, p->d->column1Width);
0201         w2 = qMax(w2, p->d->column2Width);
0202         p = p->d->nextNumInput;
0203     }
0204 
0205     p = this;
0206     while (p) {
0207         p->d->column1Width = w1;
0208         p->d->column2Width = w2;
0209         p = p->d->previousNumInput;
0210     }
0211 
0212     p = d->nextNumInput;
0213     while (p) {
0214         p->d->column1Width = w1;
0215         p->d->column2Width = w2;
0216         p = p->d->nextNumInput;
0217     }
0218 
0219 //    kDebug() << "w1 " << w1 << " w2 " << w2;
0220 }
0221 
0222 QSize KNumInput::sizeHint() const
0223 {
0224     return minimumSizeHint();
0225 }
0226 
0227 void KNumInput::setSteps(int minor, int major)
0228 {
0229     if (d->slider) {
0230         d->slider->setSingleStep(minor);
0231         d->slider->setPageStep(major);
0232     }
0233 }
0234 
0235 // ----------------------------------------------------------------------------
0236 
0237 class Q_DECL_HIDDEN KIntSpinBox::KIntSpinBoxPrivate
0238 {
0239 public:
0240     KIntSpinBoxPrivate(KIntSpinBox *q, int val_base = 10): q(q), val_base(val_base)
0241     {
0242         connect(q, SIGNAL(valueChanged(int)), q, SLOT(updateSuffix(int)));
0243     }
0244 
0245     void updateSuffix(int value)
0246     {
0247         if (!pluralSuffix.isEmpty()) {
0248             KLocalizedString s = pluralSuffix;
0249             q->setSuffix(s.subs(value).toString());
0250         }
0251     }
0252 
0253     KIntSpinBox *q;
0254     int val_base;
0255     KLocalizedString pluralSuffix;
0256 };
0257 
0258 KIntSpinBox::KIntSpinBox(QWidget *parent)
0259     : QSpinBox(parent), d(new KIntSpinBoxPrivate(this))
0260 {
0261     setValue(0);
0262 }
0263 
0264 KIntSpinBox::~KIntSpinBox()
0265 {
0266     delete d;
0267 }
0268 
0269 KIntSpinBox::KIntSpinBox(int lower, int upper, int singleStep, int value, QWidget *parent, int base)
0270     : QSpinBox(parent), d(new KIntSpinBoxPrivate(this, base))
0271 {
0272     setRange(lower, upper);
0273     setSingleStep(singleStep);
0274     setValue(value);
0275 }
0276 
0277 void KIntSpinBox::setBase(int base)
0278 {
0279     d->val_base = base;
0280 }
0281 
0282 int KIntSpinBox::base() const
0283 {
0284     return d->val_base;
0285 }
0286 
0287 QString KIntSpinBox::textFromValue(int v) const
0288 {
0289     return QString::number(v, d->val_base);
0290 }
0291 
0292 int KIntSpinBox::valueFromText(const QString &text) const
0293 {
0294     bool ok;
0295     QString theText = text;
0296     if (theText.startsWith(prefix())) {
0297         theText.remove(0, prefix().length());
0298     }
0299     if (theText.endsWith(suffix())) {
0300         theText.chop(suffix().length());
0301     }
0302     return theText.trimmed().toInt(&ok, d->val_base);
0303 }
0304 
0305 void KIntSpinBox::setEditFocus(bool mark)
0306 {
0307     lineEdit()->setFocus();
0308     if (mark) {
0309         lineEdit()->selectAll();
0310     }
0311 }
0312 
0313 void KIntSpinBox::setSuffix(const KLocalizedString &suffix)
0314 {
0315     d->pluralSuffix = suffix;
0316     if (suffix.isEmpty()) {
0317         setSuffix(QString());
0318     } else {
0319         d->updateSuffix(value());
0320     }
0321 }
0322 
0323 // ----------------------------------------------------------------------------
0324 
0325 class Q_DECL_HIDDEN KIntNumInput::KIntNumInputPrivate
0326 {
0327 public:
0328     KIntNumInput *q;
0329     int referencePoint;
0330     short blockRelative;
0331     KIntSpinBox *intSpinBox;
0332     QSize        intSpinBoxSize;
0333 
0334     KIntNumInputPrivate(KIntNumInput *q, int r)
0335         : q(q),
0336           referencePoint(r),
0337           blockRelative(0) {}
0338 };
0339 
0340 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
0341 KIntNumInput::KIntNumInput(KNumInput *below, int val, QWidget *parent, int _base)
0342     : KNumInput(parent, below)
0343     , d(new KIntNumInputPrivate(this, val))
0344 {
0345     initWidget(val, _base);
0346 }
0347 #endif
0348 
0349 KIntNumInput::KIntNumInput(QWidget *parent)
0350     : KNumInput(parent)
0351     , d(new KIntNumInputPrivate(this, 0))
0352 {
0353     initWidget(0, 10);
0354 }
0355 
0356 KIntNumInput::KIntNumInput(int val, QWidget *parent, int _base)
0357     : KNumInput(parent)
0358     , d(new KIntNumInputPrivate(this, val))
0359 {
0360     initWidget(val, _base);
0361 }
0362 
0363 QSpinBox *KIntNumInput::spinBox() const
0364 {
0365     return d->intSpinBox;
0366 }
0367 
0368 void KIntNumInput::initWidget(int val, int _base)
0369 {
0370     d->intSpinBox = new KIntSpinBox(INT_MIN, INT_MAX, 1, val, this, _base);
0371     d->intSpinBox->setObjectName("KIntNumInput::KIntSpinBox");
0372     // the KIntValidator is broken beyond believe for
0373     // spinboxes which have suffix or prefix texts, so
0374     // better don't use it unless absolutely necessary
0375 
0376     if (_base != 10) {
0377         kWarning() << "WARNING: Validation is broken in KIntNumInput! Needs to be fixed.";
0378 //         d->intSpinBox->setValidator(new KIntValidator(this, _base, "KNumInput::KIntValidator"));
0379     }
0380 
0381     connect(d->intSpinBox, SIGNAL(valueChanged(int)), SLOT(spinValueChanged(int)));
0382     connect(this, SIGNAL(valueChanged(int)),
0383             SLOT(slotEmitRelativeValueChanged(int)));
0384 
0385     setFocusProxy(d->intSpinBox);
0386     layout(true);
0387 }
0388 
0389 void KIntNumInput::setReferencePoint(int ref)
0390 {
0391     // clip to valid range:
0392     ref = qMin(maximum(), qMax(minimum(),  ref));
0393     d->referencePoint = ref;
0394 }
0395 
0396 int KIntNumInput::referencePoint() const
0397 {
0398     return d->referencePoint;
0399 }
0400 
0401 void KIntNumInput::spinValueChanged(int val)
0402 {
0403     K_USING_KNUMINPUT_P(priv);
0404 
0405     if (priv->slider) {
0406         priv->slider->setValue(val);
0407     }
0408 
0409     emit valueChanged(val);
0410 }
0411 
0412 void KIntNumInput::slotEmitRelativeValueChanged(int value)
0413 {
0414     if (d->blockRelative || !d->referencePoint) {
0415         return;
0416     }
0417     emit relativeValueChanged(double(value) / double(d->referencePoint));
0418 }
0419 
0420 void KIntNumInput::setSliderEnabled(bool slider)
0421 {
0422     K_USING_KNUMINPUT_P(priv);
0423     if (slider) {
0424         if (!priv->slider) {
0425             priv->slider = new QSlider(Qt::Horizontal, this);
0426             connect(priv->slider, SIGNAL(valueChanged(int)),
0427                     d->intSpinBox, SLOT(setValue(int)));
0428             priv->slider->setTickPosition(QSlider::TicksBelow);
0429             layout(true);
0430         }
0431 
0432         const int value = d->intSpinBox->value();
0433         priv->slider->setRange(d->intSpinBox->minimum(), d->intSpinBox->maximum());
0434         priv->slider->setPageStep(d->intSpinBox->singleStep());
0435         priv->slider->setValue(value);
0436 
0437         // calculate (upper-lower)/10 without overflowing int's:
0438         const int major = calcDiffByTen(d->intSpinBox->maximum(), d->intSpinBox->minimum());
0439 
0440         priv->slider->setSingleStep(d->intSpinBox->singleStep());
0441         priv->slider->setPageStep(qMax(1, major));
0442         priv->slider->setTickInterval(major);
0443     } else {
0444         if (priv->slider) {
0445             layout(true);
0446         }
0447         delete priv->slider;
0448         priv->slider = nullptr;
0449     }
0450 }
0451 
0452 void KIntNumInput::setRange(int lower, int upper, int singleStep)
0453 {
0454     if (upper < lower || singleStep <= 0) {
0455         kWarning() << "WARNING: KIntNumInput::setRange() called with bad arguments. Ignoring call...";
0456         return;
0457     }
0458 
0459     d->intSpinBox->setMinimum(lower);
0460     d->intSpinBox->setMaximum(upper);
0461     d->intSpinBox->setSingleStep(singleStep);
0462 
0463     singleStep = d->intSpinBox->singleStep(); // maybe QRangeControl didn't like our lineStep?
0464 
0465     // check that reference point is still inside valid range:
0466     setReferencePoint(referencePoint());
0467 
0468     layout(true);
0469 
0470     // update slider information if it's shown
0471     K_USING_KNUMINPUT_P(priv);
0472     setSliderEnabled(priv->slider);
0473 }
0474 
0475 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
0476 void KIntNumInput::setRange(int lower, int upper, int singleStep, bool slider)
0477 {
0478     setRange(lower, upper, singleStep);
0479     setSliderEnabled(slider);
0480 }
0481 #endif
0482 
0483 void KIntNumInput::setMinimum(int min)
0484 {
0485     setRange(min, d->intSpinBox->maximum(), d->intSpinBox->singleStep());
0486 }
0487 
0488 int KIntNumInput::minimum() const
0489 {
0490     return d->intSpinBox->minimum();
0491 }
0492 
0493 void KIntNumInput::setMaximum(int max)
0494 {
0495     setRange(d->intSpinBox->minimum(), max, d->intSpinBox->singleStep());
0496 }
0497 
0498 int KIntNumInput::maximum() const
0499 {
0500     return d->intSpinBox->maximum();
0501 }
0502 
0503 int KIntNumInput::singleStep() const
0504 {
0505     return d->intSpinBox->singleStep();
0506 }
0507 
0508 void KIntNumInput::setSingleStep(int singleStep)
0509 {
0510     d->intSpinBox->setSingleStep(singleStep);
0511 }
0512 
0513 void KIntNumInput::setSuffix(const QString &suffix)
0514 {
0515     d->intSpinBox->setSuffix(suffix);
0516 
0517     layout(true);
0518 }
0519 
0520 void KIntNumInput::setSuffix(const KLocalizedString &suffix)
0521 {
0522     d->intSpinBox->setSuffix(suffix);
0523     layout(true);
0524 }
0525 
0526 QString KIntNumInput::suffix() const
0527 {
0528     return d->intSpinBox->suffix();
0529 }
0530 
0531 void KIntNumInput::setPrefix(const QString &prefix)
0532 {
0533     d->intSpinBox->setPrefix(prefix);
0534 
0535     layout(true);
0536 }
0537 
0538 QString KIntNumInput::prefix() const
0539 {
0540     return d->intSpinBox->prefix();
0541 }
0542 
0543 void KIntNumInput::setEditFocus(bool mark)
0544 {
0545     d->intSpinBox->setEditFocus(mark);
0546 }
0547 
0548 QSize KIntNumInput::minimumSizeHint() const
0549 {
0550     K_USING_KNUMINPUT_P(priv);
0551     ensurePolished();
0552 
0553     int w;
0554     int h;
0555 
0556     h = qMax(d->intSpinBoxSize.height(), priv->sliderSize.height());
0557 
0558     // if in extra row, then count it here
0559     if (priv->label && (priv->labelAlignment & (Qt::AlignBottom | Qt::AlignTop))) {
0560         h += 4 + priv->labelSize.height();
0561     } else {
0562         // label is in the same row as the other widgets
0563         h = qMax(h, priv->labelSize.height() + 2);
0564     }
0565 
0566     const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
0567     w = priv->slider ? priv->slider->sizeHint().width() + spacingHint : 0;
0568     w += priv->column1Width + priv->column2Width;
0569 
0570     if (priv->labelAlignment & (Qt::AlignTop | Qt::AlignBottom)) {
0571         w = qMax(w, priv->labelSize.width() + 4);
0572     }
0573 
0574     return QSize(w, h);
0575 }
0576 
0577 void KIntNumInput::doLayout()
0578 {
0579     K_USING_KNUMINPUT_P(priv);
0580 
0581     d->intSpinBoxSize = d->intSpinBox->sizeHint();
0582     priv->column2Width = d->intSpinBoxSize.width();
0583 
0584     if (priv->label) {
0585         priv->label->setBuddy(d->intSpinBox);
0586     }
0587 }
0588 
0589 void KIntNumInput::resizeEvent(QResizeEvent *e)
0590 {
0591     K_USING_KNUMINPUT_P(priv);
0592 
0593     int w = priv->column1Width;
0594     int h = 0;
0595     const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
0596 
0597     if (priv->label && (priv->labelAlignment & Qt::AlignTop)) {
0598         priv->label->setGeometry(0, 0, e->size().width(), priv->labelSize.height());
0599         h += priv->labelSize.height() + spacingHint;
0600     }
0601 
0602     if (priv->label && (priv->labelAlignment & Qt::AlignVCenter)) {
0603         priv->label->setGeometry(0, 0, w, d->intSpinBoxSize.height());
0604     }
0605 
0606     if (qApp->layoutDirection() == Qt::RightToLeft) {
0607         d->intSpinBox->setGeometry(w, h, priv->slider ? priv->column2Width : qMax(priv->column2Width, e->size().width() - w), d->intSpinBoxSize.height());
0608         w += priv->column2Width + spacingHint;
0609 
0610         if (priv->slider) {
0611             priv->slider->setGeometry(w, h, e->size().width() - w, d->intSpinBoxSize.height() + spacingHint);
0612         }
0613     } else if (priv->slider) {
0614         priv->slider->setGeometry(w, h, e->size().width() - (w + priv->column2Width + spacingHint), d->intSpinBoxSize.height() + spacingHint);
0615         d->intSpinBox->setGeometry(w + priv->slider->size().width() + spacingHint, h, priv->column2Width, d->intSpinBoxSize.height());
0616     } else {
0617         d->intSpinBox->setGeometry(w, h, qMax(priv->column2Width, e->size().width() - w), d->intSpinBoxSize.height());
0618     }
0619 
0620     h += d->intSpinBoxSize.height() + 2;
0621 
0622     if (priv->label && (priv->labelAlignment & Qt::AlignBottom)) {
0623         priv->label->setGeometry(0, h, priv->labelSize.width(), priv->labelSize.height());
0624     }
0625 }
0626 
0627 KIntNumInput::~KIntNumInput()
0628 {
0629     delete d;
0630 }
0631 
0632 void KIntNumInput::setValue(int val)
0633 {
0634     d->intSpinBox->setValue(val);
0635     // slider value is changed by spinValueChanged
0636 }
0637 
0638 void KIntNumInput::setRelativeValue(double r)
0639 {
0640     if (!d->referencePoint) {
0641         return;
0642     }
0643     ++d->blockRelative;
0644     setValue(qRound(d->referencePoint * r + 0.5));
0645     --d->blockRelative;
0646 }
0647 
0648 double KIntNumInput::relativeValue() const
0649 {
0650     if (!d->referencePoint) {
0651         return 0;
0652     }
0653     return double(value()) / double(d->referencePoint);
0654 }
0655 
0656 int KIntNumInput::value() const
0657 {
0658     return d->intSpinBox->value();
0659 }
0660 
0661 void KIntNumInput::setSpecialValueText(const QString &text)
0662 {
0663     d->intSpinBox->setSpecialValueText(text);
0664     layout(true);
0665 }
0666 
0667 QString KIntNumInput::specialValueText() const
0668 {
0669     return d->intSpinBox->specialValueText();
0670 }
0671 
0672 void KIntNumInput::setLabel(const QString &label, Qt::Alignment a)
0673 {
0674     K_USING_KNUMINPUT_P(priv);
0675 
0676     KNumInput::setLabel(label, a);
0677 
0678     if (priv->label) {
0679         priv->label->setBuddy(d->intSpinBox);
0680     }
0681 }
0682 
0683 // ----------------------------------------------------------------------------
0684 
0685 class Q_DECL_HIDDEN KDoubleNumInput::KDoubleNumInputPrivate
0686 {
0687 public:
0688     KDoubleNumInputPrivate(double r)
0689         : spin(nullptr),
0690           referencePoint(r),
0691           blockRelative(0),
0692           exponentRatio(1.0) {}
0693     QDoubleSpinBox *spin;
0694     double referencePoint;
0695     short blockRelative;
0696     QSize editSize;
0697     QString specialValue;
0698     double exponentRatio;
0699 };
0700 
0701 KDoubleNumInput::KDoubleNumInput(QWidget *parent)
0702     : KNumInput(parent)
0703     , d(new KDoubleNumInputPrivate(0.0))
0704 
0705 {
0706     initWidget(0.0, 0.0, 9999.0, 0.01, 2);
0707 }
0708 
0709 KDoubleNumInput::KDoubleNumInput(double lower, double upper, double value, QWidget *parent,
0710                                  double singleStep, int precision)
0711     : KNumInput(parent)
0712     , d(new KDoubleNumInputPrivate(value))
0713 {
0714     initWidget(value, lower, upper, singleStep, precision);
0715 }
0716 
0717 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
0718 KDoubleNumInput::KDoubleNumInput(KNumInput *below,
0719                                  double lower, double upper, double value, QWidget *parent,
0720                                  double singleStep, int precision)
0721     : KNumInput(parent, below)
0722     , d(new KDoubleNumInputPrivate(value))
0723 {
0724     initWidget(value, lower, upper, singleStep, precision);
0725 }
0726 #endif
0727 
0728 KDoubleNumInput::~KDoubleNumInput()
0729 {
0730     delete d;
0731 }
0732 
0733 QString KDoubleNumInput::specialValueText() const
0734 {
0735     return d->specialValue;
0736 }
0737 
0738 void KDoubleNumInput::initWidget(double value, double lower, double upper,
0739                                  double singleStep, int precision)
0740 {
0741     d->spin = new QDoubleSpinBox(this);
0742     d->spin->setRange(lower, upper);
0743     d->spin->setSingleStep(singleStep);
0744     d->spin->setValue(value);
0745     d->spin->setDecimals(precision);
0746 
0747     d->spin->setObjectName("KDoubleNumInput::QDoubleSpinBox");
0748     setFocusProxy(d->spin);
0749     connect(d->spin, SIGNAL(valueChanged(double)),
0750             this, SIGNAL(valueChanged(double)));
0751     connect(this, SIGNAL(valueChanged(double)),
0752             this, SLOT(slotEmitRelativeValueChanged(double)));
0753 
0754     updateLegacyMembers();
0755 
0756     layout(true);
0757 }
0758 
0759 void KDoubleNumInput::updateLegacyMembers()
0760 {
0761     d->specialValue = specialValueText();
0762 }
0763 
0764 double KDoubleNumInput::mapSliderToSpin(int val) const
0765 {
0766     K_USING_KNUMINPUT_P(priv);
0767 
0768     // map [slidemin,slidemax] to [spinmin,spinmax]
0769     const double spinmin = d->spin->minimum();
0770     const double spinmax = d->spin->maximum();
0771     const double slidemin = priv->slider->minimum(); // cast int to double to avoid
0772     const double slidemax = priv->slider->maximum(); // overflow in rel denominator
0773     const double rel = (double(val) - slidemin) / (slidemax - slidemin);
0774     Q_ASSERT(d->exponentRatio > 0.0);
0775     return spinmin + pow(rel, d->exponentRatio) * (spinmax - spinmin);
0776 }
0777 
0778 void KDoubleNumInput::sliderMoved(int val)
0779 {
0780     d->spin->setValue(mapSliderToSpin(val));
0781 }
0782 
0783 void KDoubleNumInput::spinBoxChanged(double val)
0784 {
0785     K_USING_KNUMINPUT_P(priv);
0786 
0787     const double spinmin = d->spin->minimum();
0788     const double spinmax = d->spin->maximum();
0789     const double slidemin = priv->slider->minimum(); // cast int to double to avoid
0790     const double slidemax = priv->slider->maximum(); // overflow in rel denominator
0791 
0792     Q_ASSERT(d->exponentRatio > 0.0);
0793     const double rel = pow((val - spinmin) / (spinmax - spinmin), 1.0 / d->exponentRatio);
0794 
0795     if (priv->slider) {
0796         priv->slider->blockSignals(true);
0797         priv->slider->setValue(qRound(slidemin + rel * (slidemax - slidemin)));
0798         priv->slider->blockSignals(false);
0799     }
0800 }
0801 
0802 void KDoubleNumInput::slotEmitRelativeValueChanged(double value)
0803 {
0804     if (!d->referencePoint) {
0805         return;
0806     }
0807     emit relativeValueChanged(value / d->referencePoint);
0808 }
0809 
0810 QSize KDoubleNumInput::minimumSizeHint() const
0811 {
0812     K_USING_KNUMINPUT_P(priv);
0813 
0814     ensurePolished();
0815 
0816     int w;
0817     int h;
0818 
0819     h = qMax(d->editSize.height(), priv->sliderSize.height());
0820 
0821     // if in extra row, then count it here
0822     if (priv->label && (priv->labelAlignment & (Qt::AlignBottom | Qt::AlignTop))) {
0823         h += 4 + priv->labelSize.height();
0824     } else {
0825         // label is in the same row as the other widgets
0826         h = qMax(h, priv->labelSize.height() + 2);
0827     }
0828 
0829     const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
0830     w = priv->slider ? priv->slider->sizeHint().width() + spacingHint : 0;
0831     w += priv->column1Width + priv->column2Width;
0832 
0833     if (priv->labelAlignment & (Qt::AlignTop | Qt::AlignBottom)) {
0834         w = qMax(w, priv->labelSize.width() + 4);
0835     }
0836 
0837     return QSize(w, h);
0838 }
0839 
0840 void KDoubleNumInput::resizeEvent(QResizeEvent *e)
0841 {
0842     K_USING_KNUMINPUT_P(priv);
0843 
0844     int w = priv->column1Width;
0845     int h = 0;
0846     const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
0847 
0848     if (priv->label && (priv->labelAlignment & Qt::AlignTop)) {
0849         priv->label->setGeometry(0, 0, e->size().width(), priv->labelSize.height());
0850         h += priv->labelSize.height() + 4;
0851     }
0852 
0853     if (priv->label && (priv->labelAlignment & Qt::AlignVCenter)) {
0854         priv->label->setGeometry(0, 0, w, d->editSize.height());
0855     }
0856 
0857     if (qApp->layoutDirection() == Qt::RightToLeft) {
0858         d->spin->setGeometry(w, h, priv->slider ? priv->column2Width
0859                              : e->size().width() - w, d->editSize.height());
0860         w += priv->column2Width + spacingHint;
0861 
0862         if (priv->slider) {
0863             priv->slider->setGeometry(w, h, e->size().width() - w, d->editSize.height() + spacingHint);
0864         }
0865     } else if (priv->slider) {
0866         priv->slider->setGeometry(w, h, e->size().width() -
0867                                   (priv->column1Width + priv->column2Width + spacingHint),
0868                                   d->editSize.height() + spacingHint);
0869         d->spin->setGeometry(w + priv->slider->width() + spacingHint, h,
0870                              priv->column2Width, d->editSize.height());
0871     } else {
0872         d->spin->setGeometry(w, h, e->size().width() - w, d->editSize.height());
0873     }
0874 
0875     h += d->editSize.height() + 2;
0876 
0877     if (priv->label && (priv->labelAlignment & Qt::AlignBottom)) {
0878         priv->label->setGeometry(0, h, priv->labelSize.width(), priv->labelSize.height());
0879     }
0880 }
0881 
0882 void KDoubleNumInput::doLayout()
0883 {
0884     K_USING_KNUMINPUT_P(priv);
0885 
0886     d->editSize = d->spin->sizeHint();
0887     priv->column2Width = d->editSize.width();
0888 }
0889 
0890 void KDoubleNumInput::setValue(double val)
0891 {
0892     d->spin->setValue(val);
0893 }
0894 
0895 void KDoubleNumInput::setRelativeValue(double r)
0896 {
0897     if (!d->referencePoint) {
0898         return;
0899     }
0900     ++d->blockRelative;
0901     setValue(r * d->referencePoint);
0902     --d->blockRelative;
0903 }
0904 
0905 void KDoubleNumInput::setReferencePoint(double ref)
0906 {
0907     // clip to valid range:
0908     ref = qMin(maximum(), qMax(minimum(), ref));
0909     d->referencePoint = ref;
0910 }
0911 
0912 void KDoubleNumInput::setRange(double lower, double upper, double singleStep,
0913                                bool slider)
0914 {
0915     K_USING_KNUMINPUT_P(priv);
0916 
0917     if (priv->slider) {
0918         // don't update the slider to avoid an endless recursion
0919         QDoubleSpinBox *spin = d->spin;
0920         disconnect(spin, SIGNAL(valueChanged(double)),
0921                    priv->slider, SLOT(setValue(int)));
0922     }
0923     d->spin->setRange(lower, upper);
0924     d->spin->setSingleStep(singleStep);
0925 
0926     setSliderEnabled(slider);
0927 
0928     setReferencePoint(referencePoint());
0929 
0930     layout(true);
0931     updateLegacyMembers();
0932 }
0933 
0934 void KDoubleNumInput::setSliderEnabled(bool enabled)
0935 {
0936     K_USING_KNUMINPUT_P(priv);
0937     if (enabled) {
0938         QDoubleSpinBox *spin = d->spin;
0939         const double range = spin->maximum() - spin->minimum();
0940         const double steps = range * pow(10.0, spin->decimals());
0941         if (!priv->slider) {
0942             priv->slider = new QSlider(Qt::Horizontal, this);
0943             priv->slider->setTickPosition(QSlider::TicksBelow);
0944             // feedback line: when one moves, the other moves, too:
0945             connect(priv->slider, SIGNAL(valueChanged(int)),
0946                     SLOT(sliderMoved(int)));
0947             layout(true);
0948         }
0949         if (steps > 1000 || d->exponentRatio != 1.0) {
0950             priv->slider->setRange(0, 1000);
0951             priv->slider->setSingleStep(1);
0952             priv->slider->setPageStep(50);
0953         } else {
0954             const int singleSteps = qRound(steps);
0955             priv->slider->setRange(0, singleSteps);
0956             priv->slider->setSingleStep(1);
0957             const int pageSteps = qBound(1, singleSteps / 20, 10);
0958             priv->slider->setPageStep(pageSteps);
0959         }
0960         spinBoxChanged(spin->value());
0961         connect(spin, SIGNAL(valueChanged(double)), SLOT(spinBoxChanged(double)));
0962     } else {
0963         if (priv->slider) {
0964             layout(true);
0965         }
0966         delete priv->slider;
0967         priv->slider = nullptr;
0968     }
0969 }
0970 
0971 void KDoubleNumInput::setMinimum(double min)
0972 {
0973     K_USING_KNUMINPUT_P(priv);
0974     setRange(min, maximum(), d->spin->singleStep(), priv->slider);
0975 }
0976 
0977 double KDoubleNumInput::minimum() const
0978 {
0979     return d->spin->minimum();
0980 }
0981 
0982 void KDoubleNumInput::setMaximum(double max)
0983 {
0984     K_USING_KNUMINPUT_P(priv);
0985     setRange(minimum(), max, d->spin->singleStep(), priv->slider);
0986 }
0987 
0988 double KDoubleNumInput::maximum() const
0989 {
0990     return d->spin->maximum();
0991 }
0992 
0993 double KDoubleNumInput::singleStep() const
0994 {
0995     return d->spin->singleStep();
0996 }
0997 
0998 void KDoubleNumInput::setSingleStep(double singleStep)
0999 {
1000     d->spin->setSingleStep(singleStep);
1001 }
1002 
1003 double KDoubleNumInput::value() const
1004 {
1005     return d->spin->value();
1006 }
1007 
1008 double KDoubleNumInput::relativeValue() const
1009 {
1010     if (!d->referencePoint) {
1011         return 0;
1012     }
1013     return value() / d->referencePoint;
1014 }
1015 
1016 double KDoubleNumInput::referencePoint() const
1017 {
1018     return d->referencePoint;
1019 }
1020 
1021 QString KDoubleNumInput::suffix() const
1022 {
1023     return d->spin->suffix();
1024 }
1025 
1026 QString KDoubleNumInput::prefix() const
1027 {
1028     return d->spin->prefix();
1029 }
1030 
1031 void KDoubleNumInput::setSuffix(const QString &suffix)
1032 {
1033     d->spin->setSuffix(suffix);
1034 
1035     layout(true);
1036 }
1037 
1038 void KDoubleNumInput::setPrefix(const QString &prefix)
1039 {
1040     d->spin->setPrefix(prefix);
1041 
1042     layout(true);
1043 }
1044 
1045 void KDoubleNumInput::setDecimals(int decimals)
1046 {
1047     d->spin->setDecimals(decimals);
1048 
1049     layout(true);
1050 }
1051 
1052 int KDoubleNumInput::decimals() const
1053 {
1054     return d->spin->decimals();
1055 }
1056 
1057 void KDoubleNumInput::setSpecialValueText(const QString &text)
1058 {
1059     d->spin->setSpecialValueText(text);
1060 
1061     layout(true);
1062     updateLegacyMembers();
1063 }
1064 
1065 void KDoubleNumInput::setLabel(const QString &label, Qt::Alignment a)
1066 {
1067     K_USING_KNUMINPUT_P(priv);
1068 
1069     KNumInput::setLabel(label, a);
1070 
1071     if (priv->label) {
1072         priv->label->setBuddy(d->spin);
1073     }
1074 }
1075 
1076 double KDoubleNumInput::exponentRatio() const
1077 {
1078     return d->exponentRatio;
1079 }
1080 
1081 void KDoubleNumInput::setExponentRatio(double dbl)
1082 {
1083     Q_ASSERT(dbl > 0.0);
1084     if (dbl > 0.0) {
1085         d->exponentRatio = dbl;
1086         spinBoxChanged(d->spin->value());   // used to reset the value of the slider
1087     } else {
1088         kError() << "ExponentRatio need to be strictly positive.";
1089     }
1090 }
1091 
1092 #include "moc_knuminput.cpp"