1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2020 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtWidgets module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include <private/qabstractspinbox_p.h> |
41 | #include <qspinbox.h> |
42 | |
43 | #include <qlineedit.h> |
44 | #include <qlocale.h> |
45 | #include <qvalidator.h> |
46 | #include <qdebug.h> |
47 | |
48 | #include <algorithm> |
49 | #include <cmath> |
50 | #include <float.h> |
51 | |
52 | QT_BEGIN_NAMESPACE |
53 | |
54 | //#define QSPINBOX_QSBDEBUG |
55 | #ifdef QSPINBOX_QSBDEBUG |
56 | # define QSBDEBUG qDebug |
57 | #else |
58 | # define QSBDEBUG if (false) qDebug |
59 | #endif |
60 | |
61 | class QSpinBoxPrivate : public QAbstractSpinBoxPrivate |
62 | { |
63 | Q_DECLARE_PUBLIC(QSpinBox) |
64 | public: |
65 | QSpinBoxPrivate(); |
66 | void emitSignals(EmitPolicy ep, const QVariant &) override; |
67 | |
68 | virtual QVariant valueFromText(const QString &n) const override; |
69 | virtual QString textFromValue(const QVariant &n) const override; |
70 | QVariant validateAndInterpret(QString &input, int &pos, |
71 | QValidator::State &state) const; |
72 | |
73 | inline void init() { |
74 | Q_Q(QSpinBox); |
75 | q->setInputMethodHints(Qt::ImhDigitsOnly); |
76 | setLayoutItemMargins(element: QStyle::SE_SpinBoxLayoutItem); |
77 | } |
78 | |
79 | int displayIntegerBase; |
80 | |
81 | QVariant calculateAdaptiveDecimalStep(int steps) const override; |
82 | }; |
83 | |
84 | class QDoubleSpinBoxPrivate : public QAbstractSpinBoxPrivate |
85 | { |
86 | Q_DECLARE_PUBLIC(QDoubleSpinBox) |
87 | public: |
88 | QDoubleSpinBoxPrivate(); |
89 | void emitSignals(EmitPolicy ep, const QVariant &) override; |
90 | |
91 | virtual QVariant valueFromText(const QString &n) const override; |
92 | virtual QString textFromValue(const QVariant &n) const override; |
93 | QVariant validateAndInterpret(QString &input, int &pos, |
94 | QValidator::State &state) const; |
95 | double round(double input) const; |
96 | // variables |
97 | int decimals; |
98 | |
99 | inline void init() { |
100 | Q_Q(QDoubleSpinBox); |
101 | q->setInputMethodHints(Qt::ImhFormattedNumbersOnly); |
102 | } |
103 | |
104 | // When fiddling with the decimals property, we may lose precision in these properties. |
105 | double actualMin; |
106 | double actualMax; |
107 | |
108 | QVariant calculateAdaptiveDecimalStep(int steps) const override; |
109 | }; |
110 | |
111 | |
112 | /*! |
113 | \class QSpinBox |
114 | \brief The QSpinBox class provides a spin box widget. |
115 | |
116 | \ingroup basicwidgets |
117 | \inmodule QtWidgets |
118 | |
119 | \image windows-spinbox.png |
120 | |
121 | QSpinBox is designed to handle integers and discrete sets of |
122 | values (e.g., month names); use QDoubleSpinBox for floating point |
123 | values. |
124 | |
125 | QSpinBox allows the user to choose a value by clicking the up/down |
126 | buttons or pressing up/down on the keyboard to increase/decrease |
127 | the value currently displayed. The user can also type the value in |
128 | manually. The spin box supports integer values but can be extended to |
129 | use different strings with validate(), textFromValue() and valueFromText(). |
130 | |
131 | Every time the value changes QSpinBox emits valueChanged() and |
132 | textChanged() signals, the former providing a int and the latter |
133 | a QString. The textChanged() signal provides the value with both |
134 | prefix() and suffix(). |
135 | The current value can be fetched with value() and set with setValue(). |
136 | |
137 | Clicking the up/down buttons or using the keyboard accelerator's |
138 | up and down arrows will increase or decrease the current value in |
139 | steps of size singleStep(). If you want to change this behaviour you |
140 | can reimplement the virtual function stepBy(). The minimum and |
141 | maximum value and the step size can be set using one of the |
142 | constructors, and can be changed later with setMinimum(), |
143 | setMaximum() and setSingleStep(). |
144 | |
145 | Most spin boxes are directional, but QSpinBox can also operate as |
146 | a circular spin box, i.e. if the range is 0-99 and the current |
147 | value is 99, clicking "up" will give 0 if wrapping() is set to |
148 | true. Use setWrapping() if you want circular behavior. |
149 | |
150 | The displayed value can be prepended and appended with arbitrary |
151 | strings indicating, for example, currency or the unit of |
152 | measurement. See setPrefix() and setSuffix(). The text in the spin |
153 | box is retrieved with text() (which includes any prefix() and |
154 | suffix()), or with cleanText() (which has no prefix(), no suffix() |
155 | and no leading or trailing whitespace). |
156 | |
157 | It is often desirable to give the user a special (often default) |
158 | choice in addition to the range of numeric values. See |
159 | setSpecialValueText() for how to do this with QSpinBox. |
160 | |
161 | \section1 Subclassing QSpinBox |
162 | |
163 | If using prefix(), suffix(), and specialValueText() don't provide |
164 | enough control, you subclass QSpinBox and reimplement |
165 | valueFromText() and textFromValue(). For example, here's the code |
166 | for a custom spin box that allows the user to enter icon sizes |
167 | (e.g., "32 x 32"): |
168 | |
169 | \snippet widgets/icons/iconsizespinbox.cpp 1 |
170 | \codeline |
171 | \snippet widgets/icons/iconsizespinbox.cpp 2 |
172 | |
173 | See the \l{widgets/icons}{Icons} example for the full source |
174 | code. |
175 | |
176 | \sa QDoubleSpinBox, QDateTimeEdit, QSlider, {Spin Boxes Example} |
177 | */ |
178 | |
179 | /*! |
180 | \fn void QSpinBox::valueChanged(int i) |
181 | |
182 | This signal is emitted whenever the spin box's value is changed. |
183 | The new value's integer value is passed in \a i. |
184 | */ |
185 | |
186 | /*! |
187 | \fn void QSpinBox::textChanged(const QString &text) |
188 | \since 5.14 |
189 | |
190 | This signal is emitted whenever the spin box's text is changed. |
191 | The new text is passed in \a text with prefix() and suffix(). |
192 | */ |
193 | |
194 | #if QT_DEPRECATED_SINCE(5, 14) |
195 | /*! |
196 | \fn void QSpinBox::valueChanged(const QString &text) |
197 | |
198 | \overload |
199 | \obsolete Use textChanged(QString) instead |
200 | |
201 | The new value is passed in \a text with prefix() and suffix(). |
202 | */ |
203 | #endif |
204 | |
205 | /*! |
206 | Constructs a spin box with 0 as minimum value and 99 as maximum value, a |
207 | step value of 1. The value is initially set to 0. It is parented to \a |
208 | parent. |
209 | |
210 | \sa setMinimum(), setMaximum(), setSingleStep() |
211 | */ |
212 | |
213 | QSpinBox::QSpinBox(QWidget *parent) |
214 | : QAbstractSpinBox(*new QSpinBoxPrivate, parent) |
215 | { |
216 | Q_D(QSpinBox); |
217 | d->init(); |
218 | } |
219 | |
220 | /*! |
221 | Destructor. |
222 | */ |
223 | QSpinBox::~QSpinBox() {} |
224 | |
225 | /*! |
226 | \property QSpinBox::value |
227 | \brief the value of the spin box |
228 | |
229 | setValue() will emit valueChanged() if the new value is different |
230 | from the old one. The value property has a second notifier |
231 | signal which includes the spin box's prefix and suffix. |
232 | */ |
233 | |
234 | int QSpinBox::value() const |
235 | { |
236 | Q_D(const QSpinBox); |
237 | return d->value.toInt(); |
238 | } |
239 | |
240 | void QSpinBox::setValue(int value) |
241 | { |
242 | Q_D(QSpinBox); |
243 | d->setValue(val: QVariant(value), ep: EmitIfChanged); |
244 | } |
245 | |
246 | /*! |
247 | \property QSpinBox::prefix |
248 | \brief the spin box's prefix |
249 | |
250 | The prefix is prepended to the start of the displayed value. |
251 | Typical use is to display a unit of measurement or a currency |
252 | symbol. For example: |
253 | |
254 | \snippet code/src_gui_widgets_qspinbox.cpp 0 |
255 | |
256 | To turn off the prefix display, set this property to an empty |
257 | string. The default is no prefix. The prefix is not displayed when |
258 | value() == minimum() and specialValueText() is set. |
259 | |
260 | If no prefix is set, prefix() returns an empty string. |
261 | |
262 | \sa suffix(), setSuffix(), specialValueText(), setSpecialValueText() |
263 | */ |
264 | |
265 | QString QSpinBox::prefix() const |
266 | { |
267 | Q_D(const QSpinBox); |
268 | return d->prefix; |
269 | } |
270 | |
271 | void QSpinBox::setPrefix(const QString &prefix) |
272 | { |
273 | Q_D(QSpinBox); |
274 | |
275 | d->prefix = prefix; |
276 | d->updateEdit(); |
277 | |
278 | d->cachedSizeHint = QSize(); |
279 | d->cachedMinimumSizeHint = QSize(); // minimumSizeHint cares about the prefix |
280 | updateGeometry(); |
281 | } |
282 | |
283 | /*! |
284 | \property QSpinBox::suffix |
285 | \brief the suffix of the spin box |
286 | |
287 | The suffix is appended to the end of the displayed value. Typical |
288 | use is to display a unit of measurement or a currency symbol. For |
289 | example: |
290 | |
291 | \snippet code/src_gui_widgets_qspinbox.cpp 1 |
292 | |
293 | To turn off the suffix display, set this property to an empty |
294 | string. The default is no suffix. The suffix is not displayed for |
295 | the minimum() if specialValueText() is set. |
296 | |
297 | If no suffix is set, suffix() returns an empty string. |
298 | |
299 | \sa prefix(), setPrefix(), specialValueText(), setSpecialValueText() |
300 | */ |
301 | |
302 | QString QSpinBox::suffix() const |
303 | { |
304 | Q_D(const QSpinBox); |
305 | |
306 | return d->suffix; |
307 | } |
308 | |
309 | void QSpinBox::setSuffix(const QString &suffix) |
310 | { |
311 | Q_D(QSpinBox); |
312 | |
313 | d->suffix = suffix; |
314 | d->updateEdit(); |
315 | |
316 | d->cachedSizeHint = QSize(); |
317 | updateGeometry(); |
318 | } |
319 | |
320 | /*! |
321 | \property QSpinBox::cleanText |
322 | |
323 | \brief the text of the spin box excluding any prefix, suffix, |
324 | or leading or trailing whitespace. |
325 | |
326 | \sa text, QSpinBox::prefix, QSpinBox::suffix |
327 | */ |
328 | |
329 | QString QSpinBox::cleanText() const |
330 | { |
331 | Q_D(const QSpinBox); |
332 | |
333 | return d->stripped(text: d->edit->displayText()); |
334 | } |
335 | |
336 | |
337 | /*! |
338 | \property QSpinBox::singleStep |
339 | \brief the step value |
340 | |
341 | When the user uses the arrows to change the spin box's value the |
342 | value will be incremented/decremented by the amount of the |
343 | singleStep. The default value is 1. Setting a singleStep value of |
344 | less than 0 does nothing. |
345 | */ |
346 | |
347 | int QSpinBox::singleStep() const |
348 | { |
349 | Q_D(const QSpinBox); |
350 | |
351 | return d->singleStep.toInt(); |
352 | } |
353 | |
354 | void QSpinBox::setSingleStep(int value) |
355 | { |
356 | Q_D(QSpinBox); |
357 | if (value >= 0) { |
358 | d->singleStep = QVariant(value); |
359 | d->updateEdit(); |
360 | } |
361 | } |
362 | |
363 | /*! |
364 | \property QSpinBox::minimum |
365 | |
366 | \brief the minimum value of the spin box |
367 | |
368 | When setting this property the \l maximum is adjusted |
369 | if necessary to ensure that the range remains valid. |
370 | |
371 | The default minimum value is 0. |
372 | |
373 | \sa setRange(), specialValueText |
374 | */ |
375 | |
376 | int QSpinBox::minimum() const |
377 | { |
378 | Q_D(const QSpinBox); |
379 | |
380 | return d->minimum.toInt(); |
381 | } |
382 | |
383 | void QSpinBox::setMinimum(int minimum) |
384 | { |
385 | Q_D(QSpinBox); |
386 | const QVariant m(minimum); |
387 | d->setRange(min: m, max: (QSpinBoxPrivate::variantCompare(arg1: d->maximum, arg2: m) > 0 ? d->maximum : m)); |
388 | } |
389 | |
390 | /*! |
391 | \property QSpinBox::maximum |
392 | |
393 | \brief the maximum value of the spin box |
394 | |
395 | When setting this property the minimum is adjusted |
396 | if necessary, to ensure that the range remains valid. |
397 | |
398 | The default maximum value is 99. |
399 | |
400 | \sa setRange(), specialValueText |
401 | |
402 | */ |
403 | |
404 | int QSpinBox::maximum() const |
405 | { |
406 | Q_D(const QSpinBox); |
407 | |
408 | return d->maximum.toInt(); |
409 | } |
410 | |
411 | void QSpinBox::setMaximum(int maximum) |
412 | { |
413 | Q_D(QSpinBox); |
414 | const QVariant m(maximum); |
415 | d->setRange(min: (QSpinBoxPrivate::variantCompare(arg1: d->minimum, arg2: m) < 0 ? d->minimum : m), max: m); |
416 | } |
417 | |
418 | /*! |
419 | Convenience function to set the \a minimum, and \a maximum values |
420 | with a single function call. |
421 | |
422 | \snippet code/src_gui_widgets_qspinbox.cpp 2 |
423 | is equivalent to: |
424 | \snippet code/src_gui_widgets_qspinbox.cpp 3 |
425 | |
426 | \sa minimum, maximum |
427 | */ |
428 | |
429 | void QSpinBox::setRange(int minimum, int maximum) |
430 | { |
431 | Q_D(QSpinBox); |
432 | d->setRange(min: QVariant(minimum), max: QVariant(maximum)); |
433 | } |
434 | |
435 | /*! |
436 | Sets the step type for the spin box to \a stepType, which is single |
437 | step or adaptive decimal step. |
438 | |
439 | Adaptive decimal step means that the step size will continuously be |
440 | adjusted to one power of ten below the current \l value. So when |
441 | the value is 1100, the step is set to 100, so stepping up once |
442 | increases it to 1200. For 1200 stepping up takes it to 1300. For |
443 | negative values, stepping down from -1100 goes to -1200. |
444 | |
445 | Step direction is taken into account to handle edges cases, so |
446 | that stepping down from 100 takes the value to 99 instead of 90. |
447 | Thus a step up followed by a step down -- or vice versa -- always |
448 | lands on the starting value; 99 -> 100 -> 99. |
449 | |
450 | Setting this will cause the spin box to disregard the value of |
451 | \l singleStep, although it is preserved so that \l singleStep |
452 | comes into effect if adaptive decimal step is later turned off. |
453 | |
454 | \since 5.12 |
455 | */ |
456 | |
457 | void QSpinBox::setStepType(QAbstractSpinBox::StepType stepType) |
458 | { |
459 | Q_D(QSpinBox); |
460 | d->stepType = stepType; |
461 | } |
462 | |
463 | /*! |
464 | \property QSpinBox::stepType |
465 | \brief The step type. |
466 | |
467 | The step type can be single step or adaptive decimal step. |
468 | */ |
469 | |
470 | QAbstractSpinBox::StepType QSpinBox::stepType() const |
471 | { |
472 | Q_D(const QSpinBox); |
473 | return d->stepType; |
474 | } |
475 | |
476 | /*! |
477 | \property QSpinBox::displayIntegerBase |
478 | |
479 | \brief the base used to display the value of the spin box |
480 | |
481 | The default displayIntegerBase value is 10. |
482 | |
483 | \sa textFromValue(), valueFromText() |
484 | \since 5.2 |
485 | */ |
486 | |
487 | int QSpinBox::displayIntegerBase() const |
488 | { |
489 | Q_D(const QSpinBox); |
490 | return d->displayIntegerBase; |
491 | } |
492 | |
493 | void QSpinBox::setDisplayIntegerBase(int base) |
494 | { |
495 | Q_D(QSpinBox); |
496 | // Falls back to base 10 on invalid bases (like QString) |
497 | if (Q_UNLIKELY(base < 2 || base > 36)) { |
498 | qWarning(msg: "QSpinBox::setDisplayIntegerBase: Invalid base (%d)" , base); |
499 | base = 10; |
500 | } |
501 | |
502 | if (base != d->displayIntegerBase) { |
503 | d->displayIntegerBase = base; |
504 | d->updateEdit(); |
505 | } |
506 | } |
507 | |
508 | /*! |
509 | This virtual function is used by the spin box whenever it needs to |
510 | display the given \a value. The default implementation returns a |
511 | string containing \a value printed in the standard way using |
512 | QWidget::locale().toString(), but with the thousand separator |
513 | removed unless setGroupSeparatorShown() is set. Reimplementations may |
514 | return anything. (See the example in the detailed description.) |
515 | |
516 | Note: QSpinBox does not call this function for specialValueText() |
517 | and that neither prefix() nor suffix() should be included in the |
518 | return value. |
519 | |
520 | If you reimplement this, you may also need to reimplement |
521 | valueFromText() and validate() |
522 | |
523 | \sa valueFromText(), validate(), QLocale::groupSeparator() |
524 | */ |
525 | |
526 | QString QSpinBox::textFromValue(int value) const |
527 | { |
528 | Q_D(const QSpinBox); |
529 | QString str; |
530 | |
531 | if (d->displayIntegerBase != 10) { |
532 | const QLatin1String prefix = value < 0 ? QLatin1String("-" ) : QLatin1String(); |
533 | str = prefix + QString::number(qAbs(t: value), base: d->displayIntegerBase); |
534 | } else { |
535 | str = locale().toString(i: value); |
536 | if (!d->showGroupSeparator && (qAbs(t: value) >= 1000 || value == INT_MIN)) { |
537 | str.remove(c: locale().groupSeparator()); |
538 | } |
539 | } |
540 | |
541 | return str; |
542 | } |
543 | |
544 | /*! |
545 | \fn int QSpinBox::valueFromText(const QString &text) const |
546 | |
547 | This virtual function is used by the spin box whenever it needs to |
548 | interpret \a text entered by the user as a value. |
549 | |
550 | Subclasses that need to display spin box values in a non-numeric |
551 | way need to reimplement this function. |
552 | |
553 | Note: QSpinBox handles specialValueText() separately; this |
554 | function is only concerned with the other values. |
555 | |
556 | \sa textFromValue(), validate() |
557 | */ |
558 | |
559 | int QSpinBox::valueFromText(const QString &text) const |
560 | { |
561 | Q_D(const QSpinBox); |
562 | |
563 | QString copy = text; |
564 | int pos = d->edit->cursorPosition(); |
565 | QValidator::State state = QValidator::Acceptable; |
566 | return d->validateAndInterpret(input&: copy, pos, state).toInt(); |
567 | } |
568 | |
569 | /*! |
570 | \reimp |
571 | */ |
572 | QValidator::State QSpinBox::validate(QString &text, int &pos) const |
573 | { |
574 | Q_D(const QSpinBox); |
575 | |
576 | QValidator::State state; |
577 | d->validateAndInterpret(input&: text, pos, state); |
578 | return state; |
579 | } |
580 | |
581 | |
582 | /*! |
583 | \reimp |
584 | */ |
585 | void QSpinBox::fixup(QString &input) const |
586 | { |
587 | if (!isGroupSeparatorShown()) |
588 | input.remove(c: locale().groupSeparator()); |
589 | } |
590 | |
591 | |
592 | // --- QDoubleSpinBox --- |
593 | |
594 | /*! |
595 | \class QDoubleSpinBox |
596 | \brief The QDoubleSpinBox class provides a spin box widget that |
597 | takes doubles. |
598 | |
599 | \ingroup basicwidgets |
600 | \inmodule QtWidgets |
601 | |
602 | QDoubleSpinBox allows the user to choose a value by clicking the |
603 | up and down buttons or by pressing Up or Down on the keyboard to |
604 | increase or decrease the value currently displayed. The user can |
605 | also type the value in manually. The spin box supports double |
606 | values but can be extended to use different strings with |
607 | validate(), textFromValue() and valueFromText(). |
608 | |
609 | Every time the value changes QDoubleSpinBox emits valueChanged() and |
610 | textChanged() signals, the former providing a double and the latter |
611 | a QString. The textChanged() signal provides the value with both |
612 | prefix() and suffix(). The current value can be fetched with |
613 | value() and set with setValue(). |
614 | |
615 | Note: QDoubleSpinBox will round numbers so they can be displayed |
616 | with the current precision. In a QDoubleSpinBox with decimals set |
617 | to 2, calling setValue(2.555) will cause value() to return 2.56. |
618 | |
619 | Clicking the up and down buttons or using the keyboard accelerator's |
620 | Up and Down arrows will increase or decrease the current value in |
621 | steps of size singleStep(). If you want to change this behavior you |
622 | can reimplement the virtual function stepBy(). The minimum and |
623 | maximum value and the step size can be set using one of the |
624 | constructors, and can be changed later with setMinimum(), |
625 | setMaximum() and setSingleStep(). The spinbox has a default |
626 | precision of 2 decimal places but this can be changed using |
627 | setDecimals(). |
628 | |
629 | Most spin boxes are directional, but QDoubleSpinBox can also |
630 | operate as a circular spin box, i.e. if the range is 0.0-99.9 and |
631 | the current value is 99.9, clicking "up" will give 0 if wrapping() |
632 | is set to true. Use setWrapping() if you want circular behavior. |
633 | |
634 | The displayed value can be prepended and appended with arbitrary |
635 | strings indicating, for example, currency or the unit of |
636 | measurement. See setPrefix() and setSuffix(). The text in the spin |
637 | box is retrieved with text() (which includes any prefix() and |
638 | suffix()), or with cleanText() (which has no prefix(), no suffix() |
639 | and no leading or trailing whitespace). |
640 | |
641 | It is often desirable to give the user a special (often default) |
642 | choice in addition to the range of numeric values. See |
643 | setSpecialValueText() for how to do this with QDoubleSpinBox. |
644 | |
645 | \note The displayed value of the QDoubleSpinBox is limited to 18 characters |
646 | in addition to eventual prefix and suffix content. This limitation is used |
647 | to keep the double spin box usable even with extremely large values. |
648 | \sa QSpinBox, QDateTimeEdit, QSlider, {Spin Boxes Example} |
649 | */ |
650 | |
651 | /*! |
652 | \fn void QDoubleSpinBox::valueChanged(double d); |
653 | |
654 | This signal is emitted whenever the spin box's value is changed. |
655 | The new value is passed in \a d. |
656 | */ |
657 | |
658 | /*! |
659 | \fn void QDoubleSpinBox::textChanged(const QString &text) |
660 | \since 5.14 |
661 | |
662 | This signal is emitted whenever the spin box's text is changed. |
663 | The new text is passed in \a text with prefix() and suffix(). |
664 | */ |
665 | |
666 | #if QT_DEPRECATED_SINCE(5, 14) |
667 | /*! |
668 | \fn void QDoubleSpinBox::valueChanged(const QString &text); |
669 | |
670 | \overload |
671 | \obsolete Use textChanged(QString) instead |
672 | |
673 | The new value is passed in \a text with prefix() and suffix(). |
674 | */ |
675 | #endif |
676 | |
677 | /*! |
678 | Constructs a spin box with 0.0 as minimum value and 99.99 as maximum value, |
679 | a step value of 1.0 and a precision of 2 decimal places. The value is |
680 | initially set to 0.00. The spin box has the given \a parent. |
681 | |
682 | \sa setMinimum(), setMaximum(), setSingleStep() |
683 | */ |
684 | QDoubleSpinBox::QDoubleSpinBox(QWidget *parent) |
685 | : QAbstractSpinBox(*new QDoubleSpinBoxPrivate, parent) |
686 | { |
687 | Q_D(QDoubleSpinBox); |
688 | d->init(); |
689 | } |
690 | |
691 | /*! |
692 | Destructor. |
693 | */ |
694 | QDoubleSpinBox::~QDoubleSpinBox() {} |
695 | |
696 | /*! |
697 | \property QDoubleSpinBox::value |
698 | \brief the value of the spin box |
699 | |
700 | setValue() will emit valueChanged() if the new value is different |
701 | from the old one. The value property has a second notifier |
702 | signal which includes the spin box's prefix and suffix. |
703 | |
704 | Note: The value will be rounded so it can be displayed with the |
705 | current setting of decimals. |
706 | |
707 | \sa decimals |
708 | */ |
709 | double QDoubleSpinBox::value() const |
710 | { |
711 | Q_D(const QDoubleSpinBox); |
712 | |
713 | return d->value.toDouble(); |
714 | } |
715 | |
716 | void QDoubleSpinBox::setValue(double value) |
717 | { |
718 | Q_D(QDoubleSpinBox); |
719 | QVariant v(d->round(input: value)); |
720 | d->setValue(val: v, ep: EmitIfChanged); |
721 | } |
722 | /*! |
723 | \property QDoubleSpinBox::prefix |
724 | \brief the spin box's prefix |
725 | |
726 | The prefix is prepended to the start of the displayed value. |
727 | Typical use is to display a unit of measurement or a currency |
728 | symbol. For example: |
729 | |
730 | \snippet code/src_gui_widgets_qspinbox.cpp 4 |
731 | |
732 | To turn off the prefix display, set this property to an empty |
733 | string. The default is no prefix. The prefix is not displayed when |
734 | value() == minimum() and specialValueText() is set. |
735 | |
736 | If no prefix is set, prefix() returns an empty string. |
737 | |
738 | \sa suffix(), setSuffix(), specialValueText(), setSpecialValueText() |
739 | */ |
740 | |
741 | QString QDoubleSpinBox::prefix() const |
742 | { |
743 | Q_D(const QDoubleSpinBox); |
744 | |
745 | return d->prefix; |
746 | } |
747 | |
748 | void QDoubleSpinBox::setPrefix(const QString &prefix) |
749 | { |
750 | Q_D(QDoubleSpinBox); |
751 | |
752 | d->prefix = prefix; |
753 | d->updateEdit(); |
754 | |
755 | d->cachedSizeHint = QSize(); |
756 | d->cachedMinimumSizeHint = QSize(); // minimumSizeHint cares about the prefix |
757 | updateGeometry(); |
758 | } |
759 | |
760 | /*! |
761 | \property QDoubleSpinBox::suffix |
762 | \brief the suffix of the spin box |
763 | |
764 | The suffix is appended to the end of the displayed value. Typical |
765 | use is to display a unit of measurement or a currency symbol. For |
766 | example: |
767 | |
768 | \snippet code/src_gui_widgets_qspinbox.cpp 5 |
769 | |
770 | To turn off the suffix display, set this property to an empty |
771 | string. The default is no suffix. The suffix is not displayed for |
772 | the minimum() if specialValueText() is set. |
773 | |
774 | If no suffix is set, suffix() returns an empty string. |
775 | |
776 | \sa prefix(), setPrefix(), specialValueText(), setSpecialValueText() |
777 | */ |
778 | |
779 | QString QDoubleSpinBox::suffix() const |
780 | { |
781 | Q_D(const QDoubleSpinBox); |
782 | |
783 | return d->suffix; |
784 | } |
785 | |
786 | void QDoubleSpinBox::setSuffix(const QString &suffix) |
787 | { |
788 | Q_D(QDoubleSpinBox); |
789 | |
790 | d->suffix = suffix; |
791 | d->updateEdit(); |
792 | |
793 | d->cachedSizeHint = QSize(); |
794 | updateGeometry(); |
795 | } |
796 | |
797 | /*! |
798 | \property QDoubleSpinBox::cleanText |
799 | |
800 | \brief the text of the spin box excluding any prefix, suffix, |
801 | or leading or trailing whitespace. |
802 | |
803 | \sa text, QDoubleSpinBox::prefix, QDoubleSpinBox::suffix |
804 | */ |
805 | |
806 | QString QDoubleSpinBox::cleanText() const |
807 | { |
808 | Q_D(const QDoubleSpinBox); |
809 | |
810 | return d->stripped(text: d->edit->displayText()); |
811 | } |
812 | |
813 | /*! |
814 | \property QDoubleSpinBox::singleStep |
815 | \brief the step value |
816 | |
817 | When the user uses the arrows to change the spin box's value the |
818 | value will be incremented/decremented by the amount of the |
819 | singleStep. The default value is 1.0. Setting a singleStep value |
820 | of less than 0 does nothing. |
821 | */ |
822 | double QDoubleSpinBox::singleStep() const |
823 | { |
824 | Q_D(const QDoubleSpinBox); |
825 | |
826 | return d->singleStep.toDouble(); |
827 | } |
828 | |
829 | void QDoubleSpinBox::setSingleStep(double value) |
830 | { |
831 | Q_D(QDoubleSpinBox); |
832 | |
833 | if (value >= 0) { |
834 | d->singleStep = value; |
835 | d->updateEdit(); |
836 | } |
837 | } |
838 | |
839 | /*! |
840 | \property QDoubleSpinBox::minimum |
841 | |
842 | \brief the minimum value of the spin box |
843 | |
844 | When setting this property the \l maximum is adjusted |
845 | if necessary to ensure that the range remains valid. |
846 | |
847 | The default minimum value is 0.0. |
848 | |
849 | Note: The minimum value will be rounded to match the decimals |
850 | property. |
851 | |
852 | \sa decimals, setRange(), specialValueText |
853 | */ |
854 | |
855 | double QDoubleSpinBox::minimum() const |
856 | { |
857 | Q_D(const QDoubleSpinBox); |
858 | |
859 | return d->minimum.toDouble(); |
860 | } |
861 | |
862 | void QDoubleSpinBox::setMinimum(double minimum) |
863 | { |
864 | Q_D(QDoubleSpinBox); |
865 | d->actualMin = minimum; |
866 | const QVariant m(d->round(input: minimum)); |
867 | d->setRange(min: m, max: (QDoubleSpinBoxPrivate::variantCompare(arg1: d->maximum, arg2: m) > 0 ? d->maximum : m)); |
868 | } |
869 | |
870 | /*! |
871 | \property QDoubleSpinBox::maximum |
872 | |
873 | \brief the maximum value of the spin box |
874 | |
875 | When setting this property the \l minimum is adjusted |
876 | if necessary, to ensure that the range remains valid. |
877 | |
878 | The default maximum value is 99.99. |
879 | |
880 | Note: The maximum value will be rounded to match the decimals |
881 | property. |
882 | |
883 | \sa decimals, setRange() |
884 | */ |
885 | |
886 | double QDoubleSpinBox::maximum() const |
887 | { |
888 | Q_D(const QDoubleSpinBox); |
889 | |
890 | return d->maximum.toDouble(); |
891 | } |
892 | |
893 | void QDoubleSpinBox::setMaximum(double maximum) |
894 | { |
895 | Q_D(QDoubleSpinBox); |
896 | d->actualMax = maximum; |
897 | const QVariant m(d->round(input: maximum)); |
898 | d->setRange(min: (QDoubleSpinBoxPrivate::variantCompare(arg1: d->minimum, arg2: m) < 0 ? d->minimum : m), max: m); |
899 | } |
900 | |
901 | /*! |
902 | Convenience function to set the \a minimum and \a maximum values |
903 | with a single function call. |
904 | |
905 | Note: The maximum and minimum values will be rounded to match the |
906 | decimals property. |
907 | |
908 | \snippet code/src_gui_widgets_qspinbox.cpp 6 |
909 | is equivalent to: |
910 | \snippet code/src_gui_widgets_qspinbox.cpp 7 |
911 | |
912 | \sa minimum, maximum |
913 | */ |
914 | |
915 | void QDoubleSpinBox::setRange(double minimum, double maximum) |
916 | { |
917 | Q_D(QDoubleSpinBox); |
918 | d->actualMin = minimum; |
919 | d->actualMax = maximum; |
920 | d->setRange(min: QVariant(d->round(input: minimum)), max: QVariant(d->round(input: maximum))); |
921 | } |
922 | |
923 | /*! |
924 | Sets the step type for the spin box to \a stepType, which is single |
925 | step or adaptive decimal step. |
926 | |
927 | Adaptive decimal step means that the step size will continuously be |
928 | adjusted to one power of ten below the current \l value. So when |
929 | the value is 1100, the step is set to 100, so stepping up once |
930 | increases it to 1200. For 1200 stepping up takes it to 1300. For |
931 | negative values, stepping down from -1100 goes to -1200. |
932 | |
933 | It also works for any decimal values, 0.041 is increased to 0.042 |
934 | by stepping once. |
935 | |
936 | Step direction is taken into account to handle edges cases, so |
937 | that stepping down from 100 takes the value to 99 instead of 90. |
938 | Thus a step up followed by a step down -- or vice versa -- always |
939 | lands on the starting value; 99 -> 100 -> 99. |
940 | |
941 | Setting this will cause the spin box to disregard the value of |
942 | \l singleStep, although it is preserved so that \l singleStep |
943 | comes into effect if adaptive decimal step is later turned off. |
944 | |
945 | \since 5.12 |
946 | */ |
947 | |
948 | void QDoubleSpinBox::setStepType(StepType stepType) |
949 | { |
950 | Q_D(QDoubleSpinBox); |
951 | d->stepType = stepType; |
952 | } |
953 | |
954 | /*! |
955 | \property QDoubleSpinBox::stepType |
956 | \brief The step type. |
957 | |
958 | The step type can be single step or adaptive decimal step. |
959 | */ |
960 | |
961 | QAbstractSpinBox::StepType QDoubleSpinBox::stepType() const |
962 | { |
963 | Q_D(const QDoubleSpinBox); |
964 | return d->stepType; |
965 | } |
966 | |
967 | /*! |
968 | \property QDoubleSpinBox::decimals |
969 | |
970 | \brief the precision of the spin box, in decimals |
971 | |
972 | Sets how many decimals the spinbox will use for displaying and |
973 | interpreting doubles. |
974 | |
975 | \warning The maximum value for \a decimals is DBL_MAX_10_EXP + |
976 | DBL_DIG (ie. 323) because of the limitations of the double type. |
977 | |
978 | Note: The maximum, minimum and value might change as a result of |
979 | changing this property. |
980 | */ |
981 | |
982 | int QDoubleSpinBox::decimals() const |
983 | { |
984 | Q_D(const QDoubleSpinBox); |
985 | |
986 | return d->decimals; |
987 | } |
988 | |
989 | void QDoubleSpinBox::setDecimals(int decimals) |
990 | { |
991 | Q_D(QDoubleSpinBox); |
992 | d->decimals = qBound(min: 0, val: decimals, DBL_MAX_10_EXP + DBL_DIG); |
993 | |
994 | setRange(minimum: d->actualMin, maximum: d->actualMax); // make sure values are rounded |
995 | setValue(value()); |
996 | } |
997 | |
998 | /*! |
999 | This virtual function is used by the spin box whenever it needs to |
1000 | display the given \a value. The default implementation returns a string |
1001 | containing \a value printed using QWidget::locale().toString(\a value, |
1002 | QLatin1Char('f'), decimals()) and will remove the thousand |
1003 | separator unless setGroupSeparatorShown() is set. Reimplementations may |
1004 | return anything. |
1005 | |
1006 | Note: QDoubleSpinBox does not call this function for |
1007 | specialValueText() and that neither prefix() nor suffix() should |
1008 | be included in the return value. |
1009 | |
1010 | If you reimplement this, you may also need to reimplement |
1011 | valueFromText(). |
1012 | |
1013 | \sa valueFromText(), QLocale::groupSeparator() |
1014 | */ |
1015 | |
1016 | |
1017 | QString QDoubleSpinBox::textFromValue(double value) const |
1018 | { |
1019 | Q_D(const QDoubleSpinBox); |
1020 | QString str = locale().toString(i: value, f: 'f', prec: d->decimals); |
1021 | if (!d->showGroupSeparator && qAbs(t: value) >= 1000.0) |
1022 | str.remove(c: locale().groupSeparator()); |
1023 | |
1024 | return str; |
1025 | } |
1026 | |
1027 | /*! |
1028 | This virtual function is used by the spin box whenever it needs to |
1029 | interpret \a text entered by the user as a value. |
1030 | |
1031 | Subclasses that need to display spin box values in a non-numeric |
1032 | way need to reimplement this function. |
1033 | |
1034 | Note: QDoubleSpinBox handles specialValueText() separately; this |
1035 | function is only concerned with the other values. |
1036 | |
1037 | \sa textFromValue(), validate() |
1038 | */ |
1039 | double QDoubleSpinBox::valueFromText(const QString &text) const |
1040 | { |
1041 | Q_D(const QDoubleSpinBox); |
1042 | |
1043 | QString copy = text; |
1044 | int pos = d->edit->cursorPosition(); |
1045 | QValidator::State state = QValidator::Acceptable; |
1046 | return d->validateAndInterpret(input&: copy, pos, state).toDouble(); |
1047 | } |
1048 | |
1049 | /*! |
1050 | \reimp |
1051 | */ |
1052 | QValidator::State QDoubleSpinBox::validate(QString &text, int &pos) const |
1053 | { |
1054 | Q_D(const QDoubleSpinBox); |
1055 | |
1056 | QValidator::State state; |
1057 | d->validateAndInterpret(input&: text, pos, state); |
1058 | return state; |
1059 | } |
1060 | |
1061 | |
1062 | /*! |
1063 | \reimp |
1064 | */ |
1065 | void QDoubleSpinBox::fixup(QString &input) const |
1066 | { |
1067 | input.remove(c: locale().groupSeparator()); |
1068 | } |
1069 | |
1070 | // --- QSpinBoxPrivate --- |
1071 | |
1072 | /*! |
1073 | \internal |
1074 | Constructs a QSpinBoxPrivate object |
1075 | */ |
1076 | |
1077 | QSpinBoxPrivate::QSpinBoxPrivate() |
1078 | { |
1079 | minimum = QVariant((int)0); |
1080 | maximum = QVariant((int)99); |
1081 | value = minimum; |
1082 | displayIntegerBase = 10; |
1083 | singleStep = QVariant((int)1); |
1084 | type = QMetaType::Int; |
1085 | } |
1086 | |
1087 | /*! |
1088 | \internal |
1089 | \reimp |
1090 | */ |
1091 | |
1092 | void QSpinBoxPrivate::emitSignals(EmitPolicy ep, const QVariant &old) |
1093 | { |
1094 | Q_Q(QSpinBox); |
1095 | if (ep != NeverEmit) { |
1096 | pendingEmit = false; |
1097 | if (ep == AlwaysEmit || value != old) { |
1098 | #if QT_DEPRECATED_SINCE(5, 14) |
1099 | QT_WARNING_PUSH |
1100 | QT_WARNING_DISABLE_DEPRECATED |
1101 | emit q->valueChanged(edit->displayText()); |
1102 | QT_WARNING_POP |
1103 | #endif |
1104 | emit q->textChanged(edit->displayText()); |
1105 | emit q->valueChanged(value.toInt()); |
1106 | } |
1107 | } |
1108 | } |
1109 | |
1110 | /*! |
1111 | \internal |
1112 | \reimp |
1113 | */ |
1114 | |
1115 | QString QSpinBoxPrivate::textFromValue(const QVariant &value) const |
1116 | { |
1117 | Q_Q(const QSpinBox); |
1118 | return q->textFromValue(value: value.toInt()); |
1119 | } |
1120 | /*! |
1121 | \internal |
1122 | \reimp |
1123 | */ |
1124 | |
1125 | QVariant QSpinBoxPrivate::valueFromText(const QString &text) const |
1126 | { |
1127 | Q_Q(const QSpinBox); |
1128 | |
1129 | return QVariant(q->valueFromText(text)); |
1130 | } |
1131 | |
1132 | |
1133 | /*! |
1134 | \internal Multi purpose function that parses input, sets state to |
1135 | the appropriate state and returns the value it will be interpreted |
1136 | as. |
1137 | */ |
1138 | |
1139 | QVariant QSpinBoxPrivate::validateAndInterpret(QString &input, int &pos, |
1140 | QValidator::State &state) const |
1141 | { |
1142 | if (cachedText == input && !input.isEmpty()) { |
1143 | state = cachedState; |
1144 | QSBDEBUG() << "cachedText was '" << cachedText << "' state was " |
1145 | << state << " and value was " << cachedValue; |
1146 | |
1147 | return cachedValue; |
1148 | } |
1149 | const int max = maximum.toInt(); |
1150 | const int min = minimum.toInt(); |
1151 | |
1152 | QString copy = stripped(text: input, pos: &pos); |
1153 | QSBDEBUG() << "input" << input << "copy" << copy; |
1154 | state = QValidator::Acceptable; |
1155 | int num = min; |
1156 | |
1157 | if (max != min && (copy.isEmpty() |
1158 | || (min < 0 && copy == QLatin1String("-" )) |
1159 | || (max >= 0 && copy == QLatin1String("+" )))) { |
1160 | state = QValidator::Intermediate; |
1161 | QSBDEBUG() << __FILE__ << __LINE__<< "num is set to" << num; |
1162 | } else if (copy.startsWith(c: QLatin1Char('-')) && min >= 0) { |
1163 | state = QValidator::Invalid; // special-case -0 will be interpreted as 0 and thus not be invalid with a range from 0-100 |
1164 | } else { |
1165 | bool ok = false; |
1166 | if (displayIntegerBase != 10) { |
1167 | num = copy.toInt(ok: &ok, base: displayIntegerBase); |
1168 | } else { |
1169 | num = locale.toInt(s: copy, ok: &ok); |
1170 | if (!ok && (max >= 1000 || min <= -1000)) { |
1171 | const QString sep(locale.groupSeparator()); |
1172 | const QString doubleSep = sep + sep; |
1173 | if (copy.contains(s: sep) && !copy.contains(s: doubleSep)) { |
1174 | QString copy2 = copy; |
1175 | copy2.remove(s: sep); |
1176 | num = locale.toInt(s: copy2, ok: &ok); |
1177 | } |
1178 | } |
1179 | } |
1180 | QSBDEBUG() << __FILE__ << __LINE__<< "num is set to" << num; |
1181 | if (!ok) { |
1182 | state = QValidator::Invalid; |
1183 | } else if (num >= min && num <= max) { |
1184 | state = QValidator::Acceptable; |
1185 | } else if (max == min) { |
1186 | state = QValidator::Invalid; |
1187 | } else { |
1188 | if ((num >= 0 && num > max) || (num < 0 && num < min)) { |
1189 | state = QValidator::Invalid; |
1190 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1191 | } else { |
1192 | state = QValidator::Intermediate; |
1193 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Intermediate" ; |
1194 | } |
1195 | } |
1196 | } |
1197 | if (state != QValidator::Acceptable) |
1198 | num = max > 0 ? min : max; |
1199 | input = prefix + copy + suffix; |
1200 | cachedText = input; |
1201 | cachedState = state; |
1202 | cachedValue = QVariant((int)num); |
1203 | |
1204 | QSBDEBUG() << "cachedText is set to '" << cachedText << "' state is set to " |
1205 | << state << " and value is set to " << cachedValue; |
1206 | return cachedValue; |
1207 | } |
1208 | |
1209 | QVariant QSpinBoxPrivate::calculateAdaptiveDecimalStep(int steps) const |
1210 | { |
1211 | const int intValue = value.toInt(); |
1212 | const int absValue = qAbs(t: intValue); |
1213 | |
1214 | if (absValue < 100) |
1215 | return 1; |
1216 | |
1217 | const bool valueNegative = intValue < 0; |
1218 | const bool stepsNegative = steps < 0; |
1219 | const int signCompensation = (valueNegative == stepsNegative) ? 0 : 1; |
1220 | |
1221 | const int log = static_cast<int>(std::log10(x: absValue - signCompensation)) - 1; |
1222 | return static_cast<int>(std::pow(x: 10, y: log)); |
1223 | } |
1224 | |
1225 | // --- QDoubleSpinBoxPrivate --- |
1226 | |
1227 | /*! |
1228 | \internal |
1229 | Constructs a QSpinBoxPrivate object |
1230 | */ |
1231 | |
1232 | QDoubleSpinBoxPrivate::QDoubleSpinBoxPrivate() |
1233 | { |
1234 | actualMin = 0.0; |
1235 | actualMax = 99.99; |
1236 | minimum = QVariant(actualMin); |
1237 | maximum = QVariant(actualMax); |
1238 | value = minimum; |
1239 | singleStep = QVariant(1.0); |
1240 | decimals = 2; |
1241 | type = QMetaType::Double; |
1242 | } |
1243 | |
1244 | /*! |
1245 | \internal |
1246 | \reimp |
1247 | */ |
1248 | |
1249 | void QDoubleSpinBoxPrivate::emitSignals(EmitPolicy ep, const QVariant &old) |
1250 | { |
1251 | Q_Q(QDoubleSpinBox); |
1252 | if (ep != NeverEmit) { |
1253 | pendingEmit = false; |
1254 | if (ep == AlwaysEmit || value != old) { |
1255 | #if QT_DEPRECATED_SINCE(5, 14) |
1256 | QT_WARNING_PUSH |
1257 | QT_WARNING_DISABLE_DEPRECATED |
1258 | emit q->valueChanged(edit->displayText()); |
1259 | QT_WARNING_POP |
1260 | #endif |
1261 | emit q->textChanged(edit->displayText()); |
1262 | emit q->valueChanged(value.toDouble()); |
1263 | } |
1264 | } |
1265 | } |
1266 | |
1267 | |
1268 | /*! |
1269 | \internal |
1270 | \reimp |
1271 | */ |
1272 | QVariant QDoubleSpinBoxPrivate::valueFromText(const QString &f) const |
1273 | { |
1274 | Q_Q(const QDoubleSpinBox); |
1275 | return QVariant(q->valueFromText(text: f)); |
1276 | } |
1277 | |
1278 | /*! |
1279 | \internal |
1280 | Rounds to a double value that is restricted to decimals. |
1281 | E.g. // decimals = 2 |
1282 | |
1283 | round(5.555) => 5.56 |
1284 | */ |
1285 | |
1286 | double QDoubleSpinBoxPrivate::round(double value) const |
1287 | { |
1288 | return QString::number(value, f: 'f', prec: decimals).toDouble(); |
1289 | } |
1290 | |
1291 | |
1292 | /*! |
1293 | \internal Multi purpose function that parses input, sets state to |
1294 | the appropriate state and returns the value it will be interpreted |
1295 | as. |
1296 | */ |
1297 | |
1298 | QVariant QDoubleSpinBoxPrivate::validateAndInterpret(QString &input, int &pos, |
1299 | QValidator::State &state) const |
1300 | { |
1301 | if (cachedText == input && !input.isEmpty()) { |
1302 | state = cachedState; |
1303 | QSBDEBUG() << "cachedText was '" << cachedText << "' state was " |
1304 | << state << " and value was " << cachedValue; |
1305 | return cachedValue; |
1306 | } |
1307 | const double max = maximum.toDouble(); |
1308 | const double min = minimum.toDouble(); |
1309 | |
1310 | QString copy = stripped(text: input, pos: &pos); |
1311 | QSBDEBUG() << "input" << input << "copy" << copy; |
1312 | int len = copy.size(); |
1313 | double num = min; |
1314 | const bool plus = max >= 0; |
1315 | const bool minus = min <= 0; |
1316 | |
1317 | const QString group(locale.groupSeparator()); |
1318 | const uint groupUcs = (group.size() > 1 && group.at(i: 0).isHighSurrogate() |
1319 | ? QChar::surrogateToUcs4(high: group.at(i: 0), low: group.at(i: 1)) |
1320 | : group.at(i: 0).unicode()); |
1321 | switch (len) { |
1322 | case 0: |
1323 | state = max != min ? QValidator::Intermediate : QValidator::Invalid; |
1324 | goto end; |
1325 | case 1: |
1326 | if (copy.at(i: 0) == locale.decimalPoint() |
1327 | || (plus && copy.at(i: 0) == QLatin1Char('+')) |
1328 | || (minus && copy.at(i: 0) == QLatin1Char('-'))) { |
1329 | state = QValidator::Intermediate; |
1330 | goto end; |
1331 | } |
1332 | break; |
1333 | case 2: |
1334 | if (copy.at(i: 1) == locale.decimalPoint() |
1335 | && ((plus && copy.at(i: 0) == QLatin1Char('+')) || (minus && copy.at(i: 0) == QLatin1Char('-')))) { |
1336 | state = QValidator::Intermediate; |
1337 | goto end; |
1338 | } |
1339 | break; |
1340 | default: break; |
1341 | } |
1342 | |
1343 | if (copy.at(i: 0) == locale.groupSeparator()) { |
1344 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1345 | state = QValidator::Invalid; |
1346 | goto end; |
1347 | } else if (len > 1) { |
1348 | const int dec = copy.indexOf(c: locale.decimalPoint()); |
1349 | if (dec != -1) { |
1350 | if (dec + 1 < copy.size() && copy.at(i: dec + 1) == locale.decimalPoint() && pos == dec + 1) { |
1351 | copy.remove(i: dec + 1, len: 1); // typing a delimiter when you are on the delimiter |
1352 | } // should be treated as typing right arrow |
1353 | |
1354 | if (copy.size() - dec > decimals + 1) { |
1355 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1356 | state = QValidator::Invalid; |
1357 | goto end; |
1358 | } |
1359 | for (int i=dec + 1; i<copy.size(); ++i) { |
1360 | if (copy.at(i).isSpace() || copy.at(i) == locale.groupSeparator()) { |
1361 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1362 | state = QValidator::Invalid; |
1363 | goto end; |
1364 | } |
1365 | } |
1366 | } else { |
1367 | const QChar last = copy.back(); |
1368 | const bool groupEnd = copy.endsWith(s: group); |
1369 | const QStringView head(copy.constData(), groupEnd ? len - group.size() : len - 1); |
1370 | const QChar secondLast = head.back(); |
1371 | if ((groupEnd || last.isSpace()) && (head.endsWith(s: group) || secondLast.isSpace())) { |
1372 | state = QValidator::Invalid; |
1373 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1374 | goto end; |
1375 | } else if (last.isSpace() && (!QChar::isSpace(ucs4: groupUcs) || secondLast.isSpace())) { |
1376 | state = QValidator::Invalid; |
1377 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1378 | goto end; |
1379 | } |
1380 | } |
1381 | } |
1382 | |
1383 | { |
1384 | bool ok = false; |
1385 | num = locale.toDouble(s: copy, ok: &ok); |
1386 | QSBDEBUG() << __FILE__ << __LINE__ << locale << copy << num << ok; |
1387 | |
1388 | if (!ok) { |
1389 | if (QChar::isPrint(ucs4: groupUcs)) { |
1390 | if (max < 1000 && min > -1000 && copy.contains(s: group)) { |
1391 | state = QValidator::Invalid; |
1392 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1393 | goto end; |
1394 | } |
1395 | |
1396 | const int len = copy.size(); |
1397 | for (int i = 0; i < len - 1;) { |
1398 | if (QStringView(copy).mid(pos: i).startsWith(s: group)) { |
1399 | if (QStringView(copy).mid(pos: i + group.size()).startsWith(s: group)) { |
1400 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1401 | state = QValidator::Invalid; |
1402 | goto end; |
1403 | } |
1404 | i += group.size(); |
1405 | } else { |
1406 | i++; |
1407 | } |
1408 | } |
1409 | |
1410 | QString copy2 = copy; |
1411 | copy2.remove(s: group); |
1412 | num = locale.toDouble(s: copy2, ok: &ok); |
1413 | QSBDEBUG() << group << num << copy2 << ok; |
1414 | |
1415 | if (!ok) { |
1416 | state = QValidator::Invalid; |
1417 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1418 | goto end; |
1419 | } |
1420 | } |
1421 | } |
1422 | |
1423 | if (!ok) { |
1424 | state = QValidator::Invalid; |
1425 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1426 | } else if (num >= min && num <= max) { |
1427 | state = QValidator::Acceptable; |
1428 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Acceptable" ; |
1429 | } else if (max == min) { // when max and min is the same the only non-Invalid input is max (or min) |
1430 | state = QValidator::Invalid; |
1431 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1432 | } else { |
1433 | if ((num >= 0 && num > max) || (num < 0 && num < min)) { |
1434 | state = QValidator::Invalid; |
1435 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Invalid" ; |
1436 | } else { |
1437 | state = QValidator::Intermediate; |
1438 | QSBDEBUG() << __FILE__ << __LINE__<< "state is set to Intermediate" ; |
1439 | } |
1440 | } |
1441 | } |
1442 | |
1443 | end: |
1444 | if (state != QValidator::Acceptable) { |
1445 | num = max > 0 ? min : max; |
1446 | } |
1447 | |
1448 | input = prefix + copy + suffix; |
1449 | cachedText = input; |
1450 | cachedState = state; |
1451 | cachedValue = QVariant(num); |
1452 | return QVariant(num); |
1453 | } |
1454 | |
1455 | /* |
1456 | \internal |
1457 | \reimp |
1458 | */ |
1459 | |
1460 | QString QDoubleSpinBoxPrivate::textFromValue(const QVariant &f) const |
1461 | { |
1462 | Q_Q(const QDoubleSpinBox); |
1463 | return q->textFromValue(value: f.toDouble()); |
1464 | } |
1465 | |
1466 | QVariant QDoubleSpinBoxPrivate::calculateAdaptiveDecimalStep(int steps) const |
1467 | { |
1468 | const double doubleValue = value.toDouble(); |
1469 | const double minStep = std::pow(x: 10, y: -decimals); |
1470 | double absValue = qAbs(t: doubleValue); |
1471 | |
1472 | if (absValue < minStep) |
1473 | return minStep; |
1474 | |
1475 | const bool valueNegative = doubleValue < 0; |
1476 | const bool stepsNegative = steps < 0; |
1477 | if (valueNegative != stepsNegative) |
1478 | absValue /= 1.01; |
1479 | |
1480 | const double shift = std::pow(x: 10, y: 1 - std::floor(x: std::log10(x: absValue))); |
1481 | const double absRounded = round(value: absValue * shift) / shift; |
1482 | const double log = floorf(x: std::log10(x: absRounded)) - 1; |
1483 | |
1484 | return std::max(a: minStep, b: std::pow(x: 10, y: log)); |
1485 | } |
1486 | |
1487 | /*! \reimp */ |
1488 | bool QSpinBox::event(QEvent *event) |
1489 | { |
1490 | Q_D(QSpinBox); |
1491 | if (event->type() == QEvent::StyleChange |
1492 | #ifdef Q_OS_MAC |
1493 | || event->type() == QEvent::MacSizeChange |
1494 | #endif |
1495 | ) |
1496 | d->setLayoutItemMargins(element: QStyle::SE_SpinBoxLayoutItem); |
1497 | return QAbstractSpinBox::event(event); |
1498 | } |
1499 | |
1500 | QT_END_NAMESPACE |
1501 | |
1502 | #include "moc_qspinbox.cpp" |
1503 | |