1 /* This file is part of the KDE libraries
2  * Initial implementation:
3  *     Copyright (c) 1997 Patrick Dowler <dowler@morgul.fsh.uvic.ca>
4  * Rewritten and maintained by:
5  *     Copyright (c) 2000 Dirk Mueller <mueller@kde.org>
6  *
7  *  This library is free software; you can redistribute it and/or
8  *  modify it under the terms of the GNU Library General Public
9  *  License as published by the Free Software Foundation; either
10  *  version 2 of the License, or (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Library General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Library General Public License
18  *  along with this library; see the file COPYING.LIB.  If not, write to
19  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  *  Boston, MA 02110-1301, USA.
21  */
22 
23 #include "knuminput.h"
24 
25 #include <config-kdelibs4support.h>
26 #if HAVE_LIMITS_H
27 #include <limits.h>
28 #endif
29 
30 #include <cmath>
31 
32 #include <QApplication>
33 #include <QLabel>
34 #include <QLineEdit>
35 #include <QResizeEvent>
36 #include <QSlider>
37 #include <QStyle>
38 
39 #include <kconfigdialogmanager.h>
40 #include <kdebug.h>
41 #include <klocalizedstring.h>
42 
calcDiffByTen(int x,int y)43 static inline int calcDiffByTen(int x, int y)
44 {
45     // calculate ( x - y ) / 10 without overflowing ints:
46     return (x / 10) - (y / 10)  + (x % 10 - y % 10) / 10;
47 }
48 
49 // ----------------------------------------------------------------------------
50 
51 class KNumInputPrivate
52 {
53 public:
KNumInputPrivate(KNumInput * q,KNumInput * below=nullptr)54     KNumInputPrivate(KNumInput *q, KNumInput *below = nullptr) :
55         q(q),
56         previousNumInput(nullptr),
57         nextNumInput(nullptr),
58         column1Width(0),
59         column2Width(0),
60         label(nullptr),
61         slider(nullptr),
62         labelAlignment()
63     {
64         if (below) {
65             nextNumInput = below->d->nextNumInput;
66             previousNumInput = below;
67             below->d->nextNumInput = q;
68             if (nextNumInput) {
69                 nextNumInput->d->previousNumInput = q;
70             }
71         }
72     }
73 
get(const KNumInput * i)74     static KNumInputPrivate *get(const KNumInput *i)
75     {
76         return i->d;
77     }
78 
79     KNumInput *q;
80     KNumInput *previousNumInput, *nextNumInput;
81     int column1Width, column2Width;
82 
83     QLabel  *label;
84     QSlider *slider;
85     QSize    sliderSize, labelSize;
86 
87     Qt::Alignment labelAlignment;
88 };
89 
90 #define K_USING_KNUMINPUT_P(_d) KNumInputPrivate *_d = KNumInputPrivate::get(this)
91 
KNumInput(QWidget * parent)92 KNumInput::KNumInput(QWidget *parent)
93     : QWidget(parent), d(new KNumInputPrivate(this))
94 {
95     setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
96     setFocusPolicy(Qt::StrongFocus);
97     KConfigDialogManager::changedMap()->insert("KIntNumInput", SIGNAL(valueChanged(int)));
98     KConfigDialogManager::changedMap()->insert("KIntSpinBox", SIGNAL(valueChanged(int)));
99     KConfigDialogManager::changedMap()->insert("KDoubleSpinBox", SIGNAL(valueChanged(double)));
100 }
101 
102 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
KNumInput(QWidget * parent,KNumInput * below)103 KNumInput::KNumInput(QWidget *parent, KNumInput *below)
104     : QWidget(parent), d(new KNumInputPrivate(this, below))
105 {
106     setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
107     setFocusPolicy(Qt::StrongFocus);
108 }
109 #endif
110 
~KNumInput()111 KNumInput::~KNumInput()
112 {
113     if (d->previousNumInput) {
114         d->previousNumInput->d->nextNumInput = d->nextNumInput;
115     }
116 
117     if (d->nextNumInput) {
118         d->nextNumInput->d->previousNumInput = d->previousNumInput;
119     }
120 
121     delete d;
122 }
123 
slider() const124 QSlider *KNumInput::slider() const
125 {
126     return d->slider;
127 }
128 
showSlider() const129 bool KNumInput::showSlider() const
130 {
131     return d->slider;
132 }
133 
setLabel(const QString & label,Qt::Alignment a)134 void KNumInput::setLabel(const QString &label, Qt::Alignment a)
135 {
136     if (label.isEmpty()) {
137         delete d->label;
138         d->label = nullptr;
139         d->labelAlignment = Qt::Alignment();
140     } else {
141         if (!d->label) {
142             d->label = new QLabel(this);
143         }
144         d->label->setText(label);
145         d->label->setObjectName("KNumInput::QLabel");
146         d->label->setAlignment(a);
147         // if no vertical alignment set, use Top alignment
148         if (!(a & (Qt::AlignTop | Qt::AlignBottom | Qt::AlignVCenter))) {
149             a |= Qt::AlignTop;
150         }
151         d->labelAlignment = a;
152     }
153 
154     layout(true);
155 }
156 
label() const157 QString KNumInput::label() const
158 {
159     return d->label ? d->label->text() : QString();
160 }
161 
layout(bool deep)162 void KNumInput::layout(bool deep)
163 {
164     int w1 = d->column1Width;
165     int w2 = d->column2Width;
166 
167     // label sizeHint
168     d->labelSize = (d->label ? d->label->sizeHint() : QSize(0, 0));
169 
170     if (d->label && (d->labelAlignment & Qt::AlignVCenter)) {
171         d->column1Width = d->labelSize.width() + 4;
172     } else {
173         d->column1Width = 0;
174     }
175 
176     // slider sizeHint
177     d->sliderSize = (d->slider ? d->slider->sizeHint() : QSize(0, 0));
178 
179     doLayout();
180 
181     if (!deep) {
182         d->column1Width = w1;
183         d->column2Width = w2;
184         return;
185     }
186 
187     w2 = d->column2Width;
188 
189     KNumInput *p = d->previousNumInput;
190     while (p) {
191         p->doLayout();
192         w1 = qMax(w1, p->d->column1Width);
193         w2 = qMax(w2, p->d->column2Width);
194         p = p->d->previousNumInput;
195     }
196 
197     p = d->nextNumInput;
198     while (p) {
199         p->doLayout();
200         w1 = qMax(w1, p->d->column1Width);
201         w2 = qMax(w2, p->d->column2Width);
202         p = p->d->nextNumInput;
203     }
204 
205     p = this;
206     while (p) {
207         p->d->column1Width = w1;
208         p->d->column2Width = w2;
209         p = p->d->previousNumInput;
210     }
211 
212     p = d->nextNumInput;
213     while (p) {
214         p->d->column1Width = w1;
215         p->d->column2Width = w2;
216         p = p->d->nextNumInput;
217     }
218 
219 //    kDebug() << "w1 " << w1 << " w2 " << w2;
220 }
221 
sizeHint() const222 QSize KNumInput::sizeHint() const
223 {
224     return minimumSizeHint();
225 }
226 
setSteps(int minor,int major)227 void KNumInput::setSteps(int minor, int major)
228 {
229     if (d->slider) {
230         d->slider->setSingleStep(minor);
231         d->slider->setPageStep(major);
232     }
233 }
234 
235 // ----------------------------------------------------------------------------
236 
237 class Q_DECL_HIDDEN KIntSpinBox::KIntSpinBoxPrivate
238 {
239 public:
KIntSpinBoxPrivate(KIntSpinBox * q,int val_base=10)240     KIntSpinBoxPrivate(KIntSpinBox *q, int val_base = 10): q(q), val_base(val_base)
241     {
242         connect(q, SIGNAL(valueChanged(int)), q, SLOT(updateSuffix(int)));
243     }
244 
updateSuffix(int value)245     void updateSuffix(int value)
246     {
247         if (!pluralSuffix.isEmpty()) {
248             KLocalizedString s = pluralSuffix;
249             q->setSuffix(s.subs(value).toString());
250         }
251     }
252 
253     KIntSpinBox *q;
254     int val_base;
255     KLocalizedString pluralSuffix;
256 };
257 
KIntSpinBox(QWidget * parent)258 KIntSpinBox::KIntSpinBox(QWidget *parent)
259     : QSpinBox(parent), d(new KIntSpinBoxPrivate(this))
260 {
261     setValue(0);
262 }
263 
~KIntSpinBox()264 KIntSpinBox::~KIntSpinBox()
265 {
266     delete d;
267 }
268 
KIntSpinBox(int lower,int upper,int singleStep,int value,QWidget * parent,int base)269 KIntSpinBox::KIntSpinBox(int lower, int upper, int singleStep, int value, QWidget *parent, int base)
270     : QSpinBox(parent), d(new KIntSpinBoxPrivate(this, base))
271 {
272     setRange(lower, upper);
273     setSingleStep(singleStep);
274     setValue(value);
275 }
276 
setBase(int base)277 void KIntSpinBox::setBase(int base)
278 {
279     d->val_base = base;
280 }
281 
base() const282 int KIntSpinBox::base() const
283 {
284     return d->val_base;
285 }
286 
textFromValue(int v) const287 QString KIntSpinBox::textFromValue(int v) const
288 {
289     return QString::number(v, d->val_base);
290 }
291 
valueFromText(const QString & text) const292 int KIntSpinBox::valueFromText(const QString &text) const
293 {
294     bool ok;
295     QString theText = text;
296     if (theText.startsWith(prefix())) {
297         theText.remove(0, prefix().length());
298     }
299     if (theText.endsWith(suffix())) {
300         theText.chop(suffix().length());
301     }
302     return theText.trimmed().toInt(&ok, d->val_base);
303 }
304 
setEditFocus(bool mark)305 void KIntSpinBox::setEditFocus(bool mark)
306 {
307     lineEdit()->setFocus();
308     if (mark) {
309         lineEdit()->selectAll();
310     }
311 }
312 
setSuffix(const KLocalizedString & suffix)313 void KIntSpinBox::setSuffix(const KLocalizedString &suffix)
314 {
315     d->pluralSuffix = suffix;
316     if (suffix.isEmpty()) {
317         setSuffix(QString());
318     } else {
319         d->updateSuffix(value());
320     }
321 }
322 
323 // ----------------------------------------------------------------------------
324 
325 class Q_DECL_HIDDEN KIntNumInput::KIntNumInputPrivate
326 {
327 public:
328     KIntNumInput *q;
329     int referencePoint;
330     short blockRelative;
331     KIntSpinBox *intSpinBox;
332     QSize        intSpinBoxSize;
333 
KIntNumInputPrivate(KIntNumInput * q,int r)334     KIntNumInputPrivate(KIntNumInput *q, int r)
335         : q(q),
336           referencePoint(r),
337           blockRelative(0) {}
338 };
339 
340 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
KIntNumInput(KNumInput * below,int val,QWidget * parent,int _base)341 KIntNumInput::KIntNumInput(KNumInput *below, int val, QWidget *parent, int _base)
342     : KNumInput(parent, below)
343     , d(new KIntNumInputPrivate(this, val))
344 {
345     initWidget(val, _base);
346 }
347 #endif
348 
KIntNumInput(QWidget * parent)349 KIntNumInput::KIntNumInput(QWidget *parent)
350     : KNumInput(parent)
351     , d(new KIntNumInputPrivate(this, 0))
352 {
353     initWidget(0, 10);
354 }
355 
KIntNumInput(int val,QWidget * parent,int _base)356 KIntNumInput::KIntNumInput(int val, QWidget *parent, int _base)
357     : KNumInput(parent)
358     , d(new KIntNumInputPrivate(this, val))
359 {
360     initWidget(val, _base);
361 }
362 
spinBox() const363 QSpinBox *KIntNumInput::spinBox() const
364 {
365     return d->intSpinBox;
366 }
367 
initWidget(int val,int _base)368 void KIntNumInput::initWidget(int val, int _base)
369 {
370     d->intSpinBox = new KIntSpinBox(INT_MIN, INT_MAX, 1, val, this, _base);
371     d->intSpinBox->setObjectName("KIntNumInput::KIntSpinBox");
372     // the KIntValidator is broken beyond believe for
373     // spinboxes which have suffix or prefix texts, so
374     // better don't use it unless absolutely necessary
375 
376     if (_base != 10) {
377         kWarning() << "WARNING: Validation is broken in KIntNumInput! Needs to be fixed.";
378 //         d->intSpinBox->setValidator(new KIntValidator(this, _base, "KNumInput::KIntValidator"));
379     }
380 
381     connect(d->intSpinBox, SIGNAL(valueChanged(int)), SLOT(spinValueChanged(int)));
382     connect(this, SIGNAL(valueChanged(int)),
383             SLOT(slotEmitRelativeValueChanged(int)));
384 
385     setFocusProxy(d->intSpinBox);
386     layout(true);
387 }
388 
setReferencePoint(int ref)389 void KIntNumInput::setReferencePoint(int ref)
390 {
391     // clip to valid range:
392     ref = qMin(maximum(), qMax(minimum(),  ref));
393     d->referencePoint = ref;
394 }
395 
referencePoint() const396 int KIntNumInput::referencePoint() const
397 {
398     return d->referencePoint;
399 }
400 
spinValueChanged(int val)401 void KIntNumInput::spinValueChanged(int val)
402 {
403     K_USING_KNUMINPUT_P(priv);
404 
405     if (priv->slider) {
406         priv->slider->setValue(val);
407     }
408 
409     emit valueChanged(val);
410 }
411 
slotEmitRelativeValueChanged(int value)412 void KIntNumInput::slotEmitRelativeValueChanged(int value)
413 {
414     if (d->blockRelative || !d->referencePoint) {
415         return;
416     }
417     emit relativeValueChanged(double(value) / double(d->referencePoint));
418 }
419 
setSliderEnabled(bool slider)420 void KIntNumInput::setSliderEnabled(bool slider)
421 {
422     K_USING_KNUMINPUT_P(priv);
423     if (slider) {
424         if (!priv->slider) {
425             priv->slider = new QSlider(Qt::Horizontal, this);
426             connect(priv->slider, SIGNAL(valueChanged(int)),
427                     d->intSpinBox, SLOT(setValue(int)));
428             priv->slider->setTickPosition(QSlider::TicksBelow);
429             layout(true);
430         }
431 
432         const int value = d->intSpinBox->value();
433         priv->slider->setRange(d->intSpinBox->minimum(), d->intSpinBox->maximum());
434         priv->slider->setPageStep(d->intSpinBox->singleStep());
435         priv->slider->setValue(value);
436 
437         // calculate (upper-lower)/10 without overflowing int's:
438         const int major = calcDiffByTen(d->intSpinBox->maximum(), d->intSpinBox->minimum());
439 
440         priv->slider->setSingleStep(d->intSpinBox->singleStep());
441         priv->slider->setPageStep(qMax(1, major));
442         priv->slider->setTickInterval(major);
443     } else {
444         if (priv->slider) {
445             layout(true);
446         }
447         delete priv->slider;
448         priv->slider = nullptr;
449     }
450 }
451 
setRange(int lower,int upper,int singleStep)452 void KIntNumInput::setRange(int lower, int upper, int singleStep)
453 {
454     if (upper < lower || singleStep <= 0) {
455         kWarning() << "WARNING: KIntNumInput::setRange() called with bad arguments. Ignoring call...";
456         return;
457     }
458 
459     d->intSpinBox->setMinimum(lower);
460     d->intSpinBox->setMaximum(upper);
461     d->intSpinBox->setSingleStep(singleStep);
462 
463     singleStep = d->intSpinBox->singleStep(); // maybe QRangeControl didn't like our lineStep?
464 
465     // check that reference point is still inside valid range:
466     setReferencePoint(referencePoint());
467 
468     layout(true);
469 
470     // update slider information if it's shown
471     K_USING_KNUMINPUT_P(priv);
472     setSliderEnabled(priv->slider);
473 }
474 
475 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
setRange(int lower,int upper,int singleStep,bool slider)476 void KIntNumInput::setRange(int lower, int upper, int singleStep, bool slider)
477 {
478     setRange(lower, upper, singleStep);
479     setSliderEnabled(slider);
480 }
481 #endif
482 
setMinimum(int min)483 void KIntNumInput::setMinimum(int min)
484 {
485     setRange(min, d->intSpinBox->maximum(), d->intSpinBox->singleStep());
486 }
487 
minimum() const488 int KIntNumInput::minimum() const
489 {
490     return d->intSpinBox->minimum();
491 }
492 
setMaximum(int max)493 void KIntNumInput::setMaximum(int max)
494 {
495     setRange(d->intSpinBox->minimum(), max, d->intSpinBox->singleStep());
496 }
497 
maximum() const498 int KIntNumInput::maximum() const
499 {
500     return d->intSpinBox->maximum();
501 }
502 
singleStep() const503 int KIntNumInput::singleStep() const
504 {
505     return d->intSpinBox->singleStep();
506 }
507 
setSingleStep(int singleStep)508 void KIntNumInput::setSingleStep(int singleStep)
509 {
510     d->intSpinBox->setSingleStep(singleStep);
511 }
512 
setSuffix(const QString & suffix)513 void KIntNumInput::setSuffix(const QString &suffix)
514 {
515     d->intSpinBox->setSuffix(suffix);
516 
517     layout(true);
518 }
519 
setSuffix(const KLocalizedString & suffix)520 void KIntNumInput::setSuffix(const KLocalizedString &suffix)
521 {
522     d->intSpinBox->setSuffix(suffix);
523     layout(true);
524 }
525 
suffix() const526 QString KIntNumInput::suffix() const
527 {
528     return d->intSpinBox->suffix();
529 }
530 
setPrefix(const QString & prefix)531 void KIntNumInput::setPrefix(const QString &prefix)
532 {
533     d->intSpinBox->setPrefix(prefix);
534 
535     layout(true);
536 }
537 
prefix() const538 QString KIntNumInput::prefix() const
539 {
540     return d->intSpinBox->prefix();
541 }
542 
setEditFocus(bool mark)543 void KIntNumInput::setEditFocus(bool mark)
544 {
545     d->intSpinBox->setEditFocus(mark);
546 }
547 
minimumSizeHint() const548 QSize KIntNumInput::minimumSizeHint() const
549 {
550     K_USING_KNUMINPUT_P(priv);
551     ensurePolished();
552 
553     int w;
554     int h;
555 
556     h = qMax(d->intSpinBoxSize.height(), priv->sliderSize.height());
557 
558     // if in extra row, then count it here
559     if (priv->label && (priv->labelAlignment & (Qt::AlignBottom | Qt::AlignTop))) {
560         h += 4 + priv->labelSize.height();
561     } else {
562         // label is in the same row as the other widgets
563         h = qMax(h, priv->labelSize.height() + 2);
564     }
565 
566     const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
567     w = priv->slider ? priv->slider->sizeHint().width() + spacingHint : 0;
568     w += priv->column1Width + priv->column2Width;
569 
570     if (priv->labelAlignment & (Qt::AlignTop | Qt::AlignBottom)) {
571         w = qMax(w, priv->labelSize.width() + 4);
572     }
573 
574     return QSize(w, h);
575 }
576 
doLayout()577 void KIntNumInput::doLayout()
578 {
579     K_USING_KNUMINPUT_P(priv);
580 
581     d->intSpinBoxSize = d->intSpinBox->sizeHint();
582     priv->column2Width = d->intSpinBoxSize.width();
583 
584     if (priv->label) {
585         priv->label->setBuddy(d->intSpinBox);
586     }
587 }
588 
resizeEvent(QResizeEvent * e)589 void KIntNumInput::resizeEvent(QResizeEvent *e)
590 {
591     K_USING_KNUMINPUT_P(priv);
592 
593     int w = priv->column1Width;
594     int h = 0;
595     const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
596 
597     if (priv->label && (priv->labelAlignment & Qt::AlignTop)) {
598         priv->label->setGeometry(0, 0, e->size().width(), priv->labelSize.height());
599         h += priv->labelSize.height() + spacingHint;
600     }
601 
602     if (priv->label && (priv->labelAlignment & Qt::AlignVCenter)) {
603         priv->label->setGeometry(0, 0, w, d->intSpinBoxSize.height());
604     }
605 
606     if (qApp->layoutDirection() == Qt::RightToLeft) {
607         d->intSpinBox->setGeometry(w, h, priv->slider ? priv->column2Width : qMax(priv->column2Width, e->size().width() - w), d->intSpinBoxSize.height());
608         w += priv->column2Width + spacingHint;
609 
610         if (priv->slider) {
611             priv->slider->setGeometry(w, h, e->size().width() - w, d->intSpinBoxSize.height() + spacingHint);
612         }
613     } else if (priv->slider) {
614         priv->slider->setGeometry(w, h, e->size().width() - (w + priv->column2Width + spacingHint), d->intSpinBoxSize.height() + spacingHint);
615         d->intSpinBox->setGeometry(w + priv->slider->size().width() + spacingHint, h, priv->column2Width, d->intSpinBoxSize.height());
616     } else {
617         d->intSpinBox->setGeometry(w, h, qMax(priv->column2Width, e->size().width() - w), d->intSpinBoxSize.height());
618     }
619 
620     h += d->intSpinBoxSize.height() + 2;
621 
622     if (priv->label && (priv->labelAlignment & Qt::AlignBottom)) {
623         priv->label->setGeometry(0, h, priv->labelSize.width(), priv->labelSize.height());
624     }
625 }
626 
~KIntNumInput()627 KIntNumInput::~KIntNumInput()
628 {
629     delete d;
630 }
631 
setValue(int val)632 void KIntNumInput::setValue(int val)
633 {
634     d->intSpinBox->setValue(val);
635     // slider value is changed by spinValueChanged
636 }
637 
setRelativeValue(double r)638 void KIntNumInput::setRelativeValue(double r)
639 {
640     if (!d->referencePoint) {
641         return;
642     }
643     ++d->blockRelative;
644     setValue(qRound(d->referencePoint * r + 0.5));
645     --d->blockRelative;
646 }
647 
relativeValue() const648 double KIntNumInput::relativeValue() const
649 {
650     if (!d->referencePoint) {
651         return 0;
652     }
653     return double(value()) / double(d->referencePoint);
654 }
655 
value() const656 int KIntNumInput::value() const
657 {
658     return d->intSpinBox->value();
659 }
660 
setSpecialValueText(const QString & text)661 void KIntNumInput::setSpecialValueText(const QString &text)
662 {
663     d->intSpinBox->setSpecialValueText(text);
664     layout(true);
665 }
666 
specialValueText() const667 QString KIntNumInput::specialValueText() const
668 {
669     return d->intSpinBox->specialValueText();
670 }
671 
setLabel(const QString & label,Qt::Alignment a)672 void KIntNumInput::setLabel(const QString &label, Qt::Alignment a)
673 {
674     K_USING_KNUMINPUT_P(priv);
675 
676     KNumInput::setLabel(label, a);
677 
678     if (priv->label) {
679         priv->label->setBuddy(d->intSpinBox);
680     }
681 }
682 
683 // ----------------------------------------------------------------------------
684 
685 class Q_DECL_HIDDEN KDoubleNumInput::KDoubleNumInputPrivate
686 {
687 public:
KDoubleNumInputPrivate(double r)688     KDoubleNumInputPrivate(double r)
689         : spin(nullptr),
690           referencePoint(r),
691           blockRelative(0),
692           exponentRatio(1.0) {}
693     QDoubleSpinBox *spin;
694     double referencePoint;
695     short blockRelative;
696     QSize editSize;
697     QString specialValue;
698     double exponentRatio;
699 };
700 
KDoubleNumInput(QWidget * parent)701 KDoubleNumInput::KDoubleNumInput(QWidget *parent)
702     : KNumInput(parent)
703     , d(new KDoubleNumInputPrivate(0.0))
704 
705 {
706     initWidget(0.0, 0.0, 9999.0, 0.01, 2);
707 }
708 
KDoubleNumInput(double lower,double upper,double value,QWidget * parent,double singleStep,int precision)709 KDoubleNumInput::KDoubleNumInput(double lower, double upper, double value, QWidget *parent,
710                                  double singleStep, int precision)
711     : KNumInput(parent)
712     , d(new KDoubleNumInputPrivate(value))
713 {
714     initWidget(value, lower, upper, singleStep, precision);
715 }
716 
717 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
KDoubleNumInput(KNumInput * below,double lower,double upper,double value,QWidget * parent,double singleStep,int precision)718 KDoubleNumInput::KDoubleNumInput(KNumInput *below,
719                                  double lower, double upper, double value, QWidget *parent,
720                                  double singleStep, int precision)
721     : KNumInput(parent, below)
722     , d(new KDoubleNumInputPrivate(value))
723 {
724     initWidget(value, lower, upper, singleStep, precision);
725 }
726 #endif
727 
~KDoubleNumInput()728 KDoubleNumInput::~KDoubleNumInput()
729 {
730     delete d;
731 }
732 
specialValueText() const733 QString KDoubleNumInput::specialValueText() const
734 {
735     return d->specialValue;
736 }
737 
initWidget(double value,double lower,double upper,double singleStep,int precision)738 void KDoubleNumInput::initWidget(double value, double lower, double upper,
739                                  double singleStep, int precision)
740 {
741     d->spin = new QDoubleSpinBox(this);
742     d->spin->setRange(lower, upper);
743     d->spin->setSingleStep(singleStep);
744     d->spin->setValue(value);
745     d->spin->setDecimals(precision);
746 
747     d->spin->setObjectName("KDoubleNumInput::QDoubleSpinBox");
748     setFocusProxy(d->spin);
749     connect(d->spin, SIGNAL(valueChanged(double)),
750             this, SIGNAL(valueChanged(double)));
751     connect(this, SIGNAL(valueChanged(double)),
752             this, SLOT(slotEmitRelativeValueChanged(double)));
753 
754     updateLegacyMembers();
755 
756     layout(true);
757 }
758 
updateLegacyMembers()759 void KDoubleNumInput::updateLegacyMembers()
760 {
761     d->specialValue = specialValueText();
762 }
763 
mapSliderToSpin(int val) const764 double KDoubleNumInput::mapSliderToSpin(int val) const
765 {
766     K_USING_KNUMINPUT_P(priv);
767 
768     // map [slidemin,slidemax] to [spinmin,spinmax]
769     const double spinmin = d->spin->minimum();
770     const double spinmax = d->spin->maximum();
771     const double slidemin = priv->slider->minimum(); // cast int to double to avoid
772     const double slidemax = priv->slider->maximum(); // overflow in rel denominator
773     const double rel = (double(val) - slidemin) / (slidemax - slidemin);
774     Q_ASSERT(d->exponentRatio > 0.0);
775     return spinmin + pow(rel, d->exponentRatio) * (spinmax - spinmin);
776 }
777 
sliderMoved(int val)778 void KDoubleNumInput::sliderMoved(int val)
779 {
780     d->spin->setValue(mapSliderToSpin(val));
781 }
782 
spinBoxChanged(double val)783 void KDoubleNumInput::spinBoxChanged(double val)
784 {
785     K_USING_KNUMINPUT_P(priv);
786 
787     const double spinmin = d->spin->minimum();
788     const double spinmax = d->spin->maximum();
789     const double slidemin = priv->slider->minimum(); // cast int to double to avoid
790     const double slidemax = priv->slider->maximum(); // overflow in rel denominator
791 
792     Q_ASSERT(d->exponentRatio > 0.0);
793     const double rel = pow((val - spinmin) / (spinmax - spinmin), 1.0 / d->exponentRatio);
794 
795     if (priv->slider) {
796         priv->slider->blockSignals(true);
797         priv->slider->setValue(qRound(slidemin + rel * (slidemax - slidemin)));
798         priv->slider->blockSignals(false);
799     }
800 }
801 
slotEmitRelativeValueChanged(double value)802 void KDoubleNumInput::slotEmitRelativeValueChanged(double value)
803 {
804     if (!d->referencePoint) {
805         return;
806     }
807     emit relativeValueChanged(value / d->referencePoint);
808 }
809 
minimumSizeHint() const810 QSize KDoubleNumInput::minimumSizeHint() const
811 {
812     K_USING_KNUMINPUT_P(priv);
813 
814     ensurePolished();
815 
816     int w;
817     int h;
818 
819     h = qMax(d->editSize.height(), priv->sliderSize.height());
820 
821     // if in extra row, then count it here
822     if (priv->label && (priv->labelAlignment & (Qt::AlignBottom | Qt::AlignTop))) {
823         h += 4 + priv->labelSize.height();
824     } else {
825         // label is in the same row as the other widgets
826         h = qMax(h, priv->labelSize.height() + 2);
827     }
828 
829     const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
830     w = priv->slider ? priv->slider->sizeHint().width() + spacingHint : 0;
831     w += priv->column1Width + priv->column2Width;
832 
833     if (priv->labelAlignment & (Qt::AlignTop | Qt::AlignBottom)) {
834         w = qMax(w, priv->labelSize.width() + 4);
835     }
836 
837     return QSize(w, h);
838 }
839 
resizeEvent(QResizeEvent * e)840 void KDoubleNumInput::resizeEvent(QResizeEvent *e)
841 {
842     K_USING_KNUMINPUT_P(priv);
843 
844     int w = priv->column1Width;
845     int h = 0;
846     const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
847 
848     if (priv->label && (priv->labelAlignment & Qt::AlignTop)) {
849         priv->label->setGeometry(0, 0, e->size().width(), priv->labelSize.height());
850         h += priv->labelSize.height() + 4;
851     }
852 
853     if (priv->label && (priv->labelAlignment & Qt::AlignVCenter)) {
854         priv->label->setGeometry(0, 0, w, d->editSize.height());
855     }
856 
857     if (qApp->layoutDirection() == Qt::RightToLeft) {
858         d->spin->setGeometry(w, h, priv->slider ? priv->column2Width
859                              : e->size().width() - w, d->editSize.height());
860         w += priv->column2Width + spacingHint;
861 
862         if (priv->slider) {
863             priv->slider->setGeometry(w, h, e->size().width() - w, d->editSize.height() + spacingHint);
864         }
865     } else if (priv->slider) {
866         priv->slider->setGeometry(w, h, e->size().width() -
867                                   (priv->column1Width + priv->column2Width + spacingHint),
868                                   d->editSize.height() + spacingHint);
869         d->spin->setGeometry(w + priv->slider->width() + spacingHint, h,
870                              priv->column2Width, d->editSize.height());
871     } else {
872         d->spin->setGeometry(w, h, e->size().width() - w, d->editSize.height());
873     }
874 
875     h += d->editSize.height() + 2;
876 
877     if (priv->label && (priv->labelAlignment & Qt::AlignBottom)) {
878         priv->label->setGeometry(0, h, priv->labelSize.width(), priv->labelSize.height());
879     }
880 }
881 
doLayout()882 void KDoubleNumInput::doLayout()
883 {
884     K_USING_KNUMINPUT_P(priv);
885 
886     d->editSize = d->spin->sizeHint();
887     priv->column2Width = d->editSize.width();
888 }
889 
setValue(double val)890 void KDoubleNumInput::setValue(double val)
891 {
892     d->spin->setValue(val);
893 }
894 
setRelativeValue(double r)895 void KDoubleNumInput::setRelativeValue(double r)
896 {
897     if (!d->referencePoint) {
898         return;
899     }
900     ++d->blockRelative;
901     setValue(r * d->referencePoint);
902     --d->blockRelative;
903 }
904 
setReferencePoint(double ref)905 void KDoubleNumInput::setReferencePoint(double ref)
906 {
907     // clip to valid range:
908     ref = qMin(maximum(), qMax(minimum(), ref));
909     d->referencePoint = ref;
910 }
911 
setRange(double lower,double upper,double singleStep,bool slider)912 void KDoubleNumInput::setRange(double lower, double upper, double singleStep,
913                                bool slider)
914 {
915     K_USING_KNUMINPUT_P(priv);
916 
917     if (priv->slider) {
918         // don't update the slider to avoid an endless recursion
919         QDoubleSpinBox *spin = d->spin;
920         disconnect(spin, SIGNAL(valueChanged(double)),
921                    priv->slider, SLOT(setValue(int)));
922     }
923     d->spin->setRange(lower, upper);
924     d->spin->setSingleStep(singleStep);
925 
926     setSliderEnabled(slider);
927 
928     setReferencePoint(referencePoint());
929 
930     layout(true);
931     updateLegacyMembers();
932 }
933 
setSliderEnabled(bool enabled)934 void KDoubleNumInput::setSliderEnabled(bool enabled)
935 {
936     K_USING_KNUMINPUT_P(priv);
937     if (enabled) {
938         QDoubleSpinBox *spin = d->spin;
939         const double range = spin->maximum() - spin->minimum();
940         const double steps = range * pow(10.0, spin->decimals());
941         if (!priv->slider) {
942             priv->slider = new QSlider(Qt::Horizontal, this);
943             priv->slider->setTickPosition(QSlider::TicksBelow);
944             // feedback line: when one moves, the other moves, too:
945             connect(priv->slider, SIGNAL(valueChanged(int)),
946                     SLOT(sliderMoved(int)));
947             layout(true);
948         }
949         if (steps > 1000 || d->exponentRatio != 1.0) {
950             priv->slider->setRange(0, 1000);
951             priv->slider->setSingleStep(1);
952             priv->slider->setPageStep(50);
953         } else {
954             const int singleSteps = qRound(steps);
955             priv->slider->setRange(0, singleSteps);
956             priv->slider->setSingleStep(1);
957             const int pageSteps = qBound(1, singleSteps / 20, 10);
958             priv->slider->setPageStep(pageSteps);
959         }
960         spinBoxChanged(spin->value());
961         connect(spin, SIGNAL(valueChanged(double)), SLOT(spinBoxChanged(double)));
962     } else {
963         if (priv->slider) {
964             layout(true);
965         }
966         delete priv->slider;
967         priv->slider = nullptr;
968     }
969 }
970 
setMinimum(double min)971 void KDoubleNumInput::setMinimum(double min)
972 {
973     K_USING_KNUMINPUT_P(priv);
974     setRange(min, maximum(), d->spin->singleStep(), priv->slider);
975 }
976 
minimum() const977 double KDoubleNumInput::minimum() const
978 {
979     return d->spin->minimum();
980 }
981 
setMaximum(double max)982 void KDoubleNumInput::setMaximum(double max)
983 {
984     K_USING_KNUMINPUT_P(priv);
985     setRange(minimum(), max, d->spin->singleStep(), priv->slider);
986 }
987 
maximum() const988 double KDoubleNumInput::maximum() const
989 {
990     return d->spin->maximum();
991 }
992 
singleStep() const993 double KDoubleNumInput::singleStep() const
994 {
995     return d->spin->singleStep();
996 }
997 
setSingleStep(double singleStep)998 void KDoubleNumInput::setSingleStep(double singleStep)
999 {
1000     d->spin->setSingleStep(singleStep);
1001 }
1002 
value() const1003 double KDoubleNumInput::value() const
1004 {
1005     return d->spin->value();
1006 }
1007 
relativeValue() const1008 double KDoubleNumInput::relativeValue() const
1009 {
1010     if (!d->referencePoint) {
1011         return 0;
1012     }
1013     return value() / d->referencePoint;
1014 }
1015 
referencePoint() const1016 double KDoubleNumInput::referencePoint() const
1017 {
1018     return d->referencePoint;
1019 }
1020 
suffix() const1021 QString KDoubleNumInput::suffix() const
1022 {
1023     return d->spin->suffix();
1024 }
1025 
prefix() const1026 QString KDoubleNumInput::prefix() const
1027 {
1028     return d->spin->prefix();
1029 }
1030 
setSuffix(const QString & suffix)1031 void KDoubleNumInput::setSuffix(const QString &suffix)
1032 {
1033     d->spin->setSuffix(suffix);
1034 
1035     layout(true);
1036 }
1037 
setPrefix(const QString & prefix)1038 void KDoubleNumInput::setPrefix(const QString &prefix)
1039 {
1040     d->spin->setPrefix(prefix);
1041 
1042     layout(true);
1043 }
1044 
setDecimals(int decimals)1045 void KDoubleNumInput::setDecimals(int decimals)
1046 {
1047     d->spin->setDecimals(decimals);
1048 
1049     layout(true);
1050 }
1051 
decimals() const1052 int KDoubleNumInput::decimals() const
1053 {
1054     return d->spin->decimals();
1055 }
1056 
setSpecialValueText(const QString & text)1057 void KDoubleNumInput::setSpecialValueText(const QString &text)
1058 {
1059     d->spin->setSpecialValueText(text);
1060 
1061     layout(true);
1062     updateLegacyMembers();
1063 }
1064 
setLabel(const QString & label,Qt::Alignment a)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 
exponentRatio() const1076 double KDoubleNumInput::exponentRatio() const
1077 {
1078     return d->exponentRatio;
1079 }
1080 
setExponentRatio(double dbl)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"
1093