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