Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qabstractspinbox.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <qplatformdefs.h>
6#ifdef Q_OS_WASM
7# include <private/qstdweb_p.h>
8#endif
9#include <private/qabstractspinbox_p.h>
10#include <private/qapplication_p.h>
11#if QT_CONFIG(datetimeparser)
12#include <private/qdatetimeparser_p.h>
13#endif
14#include <private/qlineedit_p.h>
15#include <qabstractspinbox.h>
16
17#include <qapplication.h>
18#include <qstylehints.h>
19#include <qclipboard.h>
20#include <qdatetime.h>
21#include <qevent.h>
22#include <qloggingcategory.h>
23#if QT_CONFIG(menu)
24#include <qmenu.h>
25#endif
26#include <qstylepainter.h>
27#if QT_CONFIG(accessibility)
28# include <qaccessible.h>
29#endif
30
31#include <QtCore/qpointer.h>
32
34
35using namespace Qt::StringLiterals;
36using namespace std::chrono_literals;
37
38Q_STATIC_LOGGING_CATEGORY(lcWidgetAbstractSpinBox, "qt.widgets.qabstractspinbox")
39
40/*!
41 \class QAbstractSpinBox
42 \brief The QAbstractSpinBox class provides a spinbox and a line edit to
43 display values.
44
45 \ingroup abstractwidgets
46 \inmodule QtWidgets
47
48 The class is designed as a common super class for widgets like
49 QSpinBox, QDoubleSpinBox and QDateTimeEdit
50
51 Here are the main properties of the class:
52
53 \list 1
54
55 \li \l text: The text that is displayed in the QAbstractSpinBox.
56
57 \li \l alignment: The alignment of the text in the QAbstractSpinBox.
58
59 \li \l wrapping: Whether the QAbstractSpinBox wraps from the
60 minimum value to the maximum value and vice versa.
61
62 \endlist
63
64 QAbstractSpinBox provides a virtual stepBy() function that is
65 called whenever the user triggers a step. This function takes an
66 integer value to signify how many steps were taken. E.g. Pressing
67 Qt::Key_Down will trigger a call to stepBy(-1).
68
69 When the user triggers a step whilst holding the Qt::ControlModifier,
70 QAbstractSpinBox steps by 10 instead of making a single step. This
71 step modifier affects wheel events, key events and interaction with
72 the spinbox buttons. Note that on macOS, Control corresponds to the
73 Command key.
74
75 Since Qt 5.12, QStyle::SH_SpinBox_StepModifier can be used to select
76 which Qt::KeyboardModifier increases the step rate. Qt::NoModifier
77 disables this feature.
78
79 QAbstractSpinBox also provide a virtual function stepEnabled() to
80 determine whether stepping up/down is allowed at any point. This
81 function returns a bitset of StepEnabled.
82
83 \sa QAbstractSlider, QSpinBox, QDoubleSpinBox, QDateTimeEdit,
84 {Spin Boxes Example}
85*/
86
87/*!
88 \enum QAbstractSpinBox::StepEnabledFlag
89
90 \value StepNone
91 \value StepUpEnabled
92 \value StepDownEnabled
93*/
94
95/*!
96 \enum QAbstractSpinBox::StepType
97
98 \value DefaultStepType
99 \value AdaptiveDecimalStepType
100*/
101
102/*!
103 \fn void QAbstractSpinBox::editingFinished()
104
105 This signal is emitted editing is finished. This happens when the
106 spinbox loses focus and when enter is pressed.
107*/
108
109/*!
110 \fn void QAbstractSpinBox::returnPressed()
111 \since 6.10
112
113 This signal is emitted when the Return or Enter key is used.
114*/
115
116/*!
117 Constructs an abstract spinbox with the given \a parent with default
118 \l wrapping, and \l alignment properties.
119*/
120
121QAbstractSpinBox::QAbstractSpinBox(QWidget *parent)
122 : QWidget(*new QAbstractSpinBoxPrivate, parent, { })
123{
124 Q_D(QAbstractSpinBox);
125 d->init();
126}
127
128/*!
129 \internal
130*/
131QAbstractSpinBox::QAbstractSpinBox(QAbstractSpinBoxPrivate &dd, QWidget *parent)
132 : QWidget(dd, parent, { })
133{
134 Q_D(QAbstractSpinBox);
135 d->init();
136}
137
138/*!
139 Called when the QAbstractSpinBox is destroyed.
140*/
141
142QAbstractSpinBox::~QAbstractSpinBox()
143{
144}
145
146/*!
147 \enum QAbstractSpinBox::ButtonSymbols
148
149 This enum type describes the symbols that can be displayed on the buttons
150 in a spin box.
151
152 \inlineimage qspinbox-updown.png
153 {Spinbox with the up and down arrow symbols}
154 \inlineimage qspinbox-plusminus.png
155 {Spinbox with the plus and minus symbols}
156
157 \value UpDownArrows Little arrows in the classic style.
158 \value PlusMinus \b{+} and \b{-} symbols.
159 \value NoButtons Don't display buttons.
160
161 \sa QAbstractSpinBox::buttonSymbols
162*/
163
164/*!
165 \property QAbstractSpinBox::buttonSymbols
166
167 \brief the current button symbol mode
168
169 The possible values can be either \c UpDownArrows or \c PlusMinus.
170 The default is \c UpDownArrows.
171
172 Note that some styles might render PlusMinus and UpDownArrows
173 identically.
174
175 \sa ButtonSymbols
176*/
177
178QAbstractSpinBox::ButtonSymbols QAbstractSpinBox::buttonSymbols() const
179{
180 Q_D(const QAbstractSpinBox);
181 return d->buttonSymbols;
182}
183
184void QAbstractSpinBox::setButtonSymbols(ButtonSymbols buttonSymbols)
185{
186 Q_D(QAbstractSpinBox);
187 if (d->buttonSymbols != buttonSymbols) {
188 d->buttonSymbols = buttonSymbols;
189 d->updateEditFieldGeometry();
190 updateGeometry();
191 update();
192 }
193}
194
195/*!
196 \property QAbstractSpinBox::text
197
198 \brief the spin box's text, including any prefix and suffix
199
200 There is no default text.
201*/
202
203QString QAbstractSpinBox::text() const
204{
205 return lineEdit()->displayText();
206}
207
208
209/*!
210 \property QAbstractSpinBox::specialValueText
211 \brief the special-value text
212
213 If set, the spin box will display this text instead of a numeric
214 value whenever the current value is equal to minimum(). Typical use
215 is to indicate that this choice has a special (default) meaning.
216
217 For example, if your spin box allows the user to choose a scale factor
218 (or zoom level) for displaying an image, and your application is able
219 to automatically choose one that will enable the image to fit completely
220 within the display window, you can set up the spin box like this:
221
222 \snippet widgets/spinboxes/window.cpp 3
223
224 The user will then be able to choose a scale from 1% to 1000%
225 or select "Auto" to leave it up to the application to choose. Your code
226 must then interpret the spin box value of 0 as a request from the user
227 to scale the image to fit inside the window.
228
229 All values are displayed with the prefix and suffix (if set), \e
230 except for the special value, which only shows the special value
231 text. This special text is passed in the QSpinBox::textChanged()
232 signal that passes a QString.
233
234 To turn off the special-value text display, call this function
235 with an empty string. The default is no special-value text, i.e.
236 the numeric value is shown as usual.
237
238 If no special-value text is set, specialValueText() returns an
239 empty string.
240*/
241
242QString QAbstractSpinBox::specialValueText() const
243{
244 Q_D(const QAbstractSpinBox);
245 return d->specialValueText;
246}
247
248void QAbstractSpinBox::setSpecialValueText(const QString &specialValueText)
249{
250 Q_D(QAbstractSpinBox);
251
252 d->specialValueText = specialValueText;
253 d->cachedSizeHint = QSize(); // minimumSizeHint doesn't care about specialValueText
254 d->clearCache();
255 d->updateEdit();
256}
257
258/*!
259 \property QAbstractSpinBox::wrapping
260
261 \brief whether the spin box is circular.
262
263 If wrapping is true stepping up from maximum() value will take you
264 to the minimum() value and vice versa. Wrapping only make sense if
265 you have minimum() and maximum() values set.
266
267 \snippet code/src_gui_widgets_qabstractspinbox.cpp 0
268
269 \sa QSpinBox::minimum(), QSpinBox::maximum()
270*/
271
272bool QAbstractSpinBox::wrapping() const
273{
274 Q_D(const QAbstractSpinBox);
275 return d->wrapping;
276}
277
278void QAbstractSpinBox::setWrapping(bool wrapping)
279{
280 Q_D(QAbstractSpinBox);
281 d->wrapping = wrapping;
282}
283
284
285/*!
286 \property QAbstractSpinBox::readOnly
287 \brief whether the spin box is read only.
288
289 In read-only mode, the user can still copy the text to the
290 clipboard, or drag and drop the text;
291 but cannot edit it.
292
293 The QLineEdit in the QAbstractSpinBox does not show a cursor in
294 read-only mode.
295
296 \sa QLineEdit::readOnly
297*/
298
299bool QAbstractSpinBox::isReadOnly() const
300{
301 Q_D(const QAbstractSpinBox);
302 return d->readOnly;
303}
304
305void QAbstractSpinBox::setReadOnly(bool enable)
306{
307 Q_D(QAbstractSpinBox);
308 d->readOnly = enable;
309 d->edit->setReadOnly(enable);
310 QEvent event(QEvent::ReadOnlyChange);
311 QCoreApplication::sendEvent(this, &event);
312 update();
313}
314
315/*!
316 \property QAbstractSpinBox::keyboardTracking
317 \brief whether keyboard tracking is enabled for the spinbox.
318 \since 4.3
319
320 If keyboard tracking is enabled (the default), the spinbox
321 emits the valueChanged() and textChanged() signals while the
322 new value is being entered from the keyboard.
323
324 E.g. when the user enters the value 600 by typing 6, 0, and 0,
325 the spinbox emits 3 signals with the values 6, 60, and 600
326 respectively.
327
328 If keyboard tracking is disabled, the spinbox doesn't emit the
329 valueChanged() and textChanged() signals while typing. It emits
330 the signals later, when the return key is pressed, when keyboard
331 focus is lost, or when other spinbox functionality is used, e.g.
332 pressing an arrow key.
333*/
334
335bool QAbstractSpinBox::keyboardTracking() const
336{
337 Q_D(const QAbstractSpinBox);
338 return d->keyboardTracking;
339}
340
341void QAbstractSpinBox::setKeyboardTracking(bool enable)
342{
343 Q_D(QAbstractSpinBox);
344 d->keyboardTracking = enable;
345}
346
347/*!
348 \property QAbstractSpinBox::frame
349 \brief whether the spin box draws itself with a frame
350
351 If enabled (the default) the spin box draws itself inside a frame,
352 otherwise the spin box draws itself without any frame.
353*/
354
355bool QAbstractSpinBox::hasFrame() const
356{
357 Q_D(const QAbstractSpinBox);
358 return d->frame;
359}
360
361
362void QAbstractSpinBox::setFrame(bool enable)
363{
364 Q_D(QAbstractSpinBox);
365 d->frame = enable;
366 update();
367 d->updateEditFieldGeometry();
368}
369
370/*!
371 \property QAbstractSpinBox::accelerated
372 \brief whether the spin box will accelerate the frequency of the steps when
373 pressing the step Up/Down buttons.
374 \since 4.2
375
376 If enabled the spin box will increase/decrease the value faster
377 the longer you hold the button down.
378*/
379
380void QAbstractSpinBox::setAccelerated(bool accelerate)
381{
382 Q_D(QAbstractSpinBox);
383 d->accelerate = accelerate;
384
385}
386bool QAbstractSpinBox::isAccelerated() const
387{
388 Q_D(const QAbstractSpinBox);
389 return d->accelerate;
390}
391
392/*!
393 \property QAbstractSpinBox::showGroupSeparator
394 \since 5.3
395
396
397 \brief whether a thousands separator is enabled. By default this
398 property is false.
399*/
400bool QAbstractSpinBox::isGroupSeparatorShown() const
401{
402 Q_D(const QAbstractSpinBox);
403 return d->showGroupSeparator;
404}
405
406void QAbstractSpinBox::setGroupSeparatorShown(bool shown)
407{
408 Q_D(QAbstractSpinBox);
409 if (d->showGroupSeparator == shown)
410 return;
411 d->showGroupSeparator = shown;
412 d->setValue(d->value, EmitIfChanged);
413 updateGeometry();
414}
415
416/*!
417 \enum QAbstractSpinBox::CorrectionMode
418
419 This enum type describes the mode the spinbox will use to correct
420 an \l{QValidator::}{Intermediate} value if editing finishes.
421
422 \value CorrectToPreviousValue The spinbox will revert to the last
423 valid value.
424
425 \value CorrectToNearestValue The spinbox will revert to the nearest
426 valid value.
427
428 \sa correctionMode
429*/
430
431/*!
432 \property QAbstractSpinBox::correctionMode
433 \brief the mode to correct an \l{QValidator::}{Intermediate}
434 value if editing finishes
435 \since 4.2
436
437 The default mode is QAbstractSpinBox::CorrectToPreviousValue.
438
439 \sa acceptableInput, validate(), fixup()
440*/
441void QAbstractSpinBox::setCorrectionMode(CorrectionMode correctionMode)
442{
443 Q_D(QAbstractSpinBox);
444 d->correctionMode = correctionMode;
445
446}
447QAbstractSpinBox::CorrectionMode QAbstractSpinBox::correctionMode() const
448{
449 Q_D(const QAbstractSpinBox);
450 return d->correctionMode;
451}
452
453
454/*!
455 \property QAbstractSpinBox::acceptableInput
456 \brief whether the input satisfies the current validation
457 \since 4.2
458
459 \sa validate(), fixup(), correctionMode
460*/
461
462bool QAbstractSpinBox::hasAcceptableInput() const
463{
464 Q_D(const QAbstractSpinBox);
465 return d->edit->hasAcceptableInput();
466}
467
468/*!
469 \property QAbstractSpinBox::alignment
470 \brief the alignment of the spin box
471
472 Possible Values are Qt::AlignLeft, Qt::AlignRight, and Qt::AlignHCenter.
473
474 By default, the alignment is Qt::AlignLeft
475
476 Attempting to set the alignment to an illegal flag combination
477 does nothing.
478
479 \sa Qt::Alignment
480*/
481
482Qt::Alignment QAbstractSpinBox::alignment() const
483{
484 Q_D(const QAbstractSpinBox);
485
486 return (Qt::Alignment)d->edit->alignment();
487}
488
489void QAbstractSpinBox::setAlignment(Qt::Alignment flag)
490{
491 Q_D(QAbstractSpinBox);
492
493 d->edit->setAlignment(flag);
494}
495
496/*!
497 Selects all the text in the spinbox except the prefix and suffix.
498*/
499
500void QAbstractSpinBox::selectAll()
501{
502 Q_D(QAbstractSpinBox);
503
504
505 if (!d->specialValue()) {
506 const int tmp = d->edit->displayText().size() - d->suffix.size();
507 d->edit->setSelection(tmp, -(tmp - d->prefix.size()));
508 } else {
509 d->edit->selectAll();
510 }
511}
512
513/*!
514 Clears the lineedit of all text but prefix and suffix.
515*/
516
517void QAbstractSpinBox::clear()
518{
519 Q_D(QAbstractSpinBox);
520
521 d->edit->setText(d->prefix + d->suffix);
522 d->edit->setCursorPosition(d->prefix.size());
523 d->cleared = true;
524}
525
526/*!
527 Virtual function that determines whether stepping up and down is
528 legal at any given time.
529
530 The up arrow will be painted as disabled unless (stepEnabled() &
531 StepUpEnabled) != 0.
532
533 The default implementation will return (StepUpEnabled|
534 StepDownEnabled) if wrapping is turned on. Else it will return
535 StepDownEnabled if value is > minimum() or'ed with StepUpEnabled if
536 value < maximum().
537
538 If you subclass QAbstractSpinBox you will need to reimplement this function.
539
540 \sa QSpinBox::minimum(), QSpinBox::maximum(), wrapping()
541*/
542
543
544QAbstractSpinBox::StepEnabled QAbstractSpinBox::stepEnabled() const
545{
546 Q_D(const QAbstractSpinBox);
547 if (d->readOnly || d->type == QMetaType::UnknownType)
548 return StepNone;
549 if (d->wrapping)
550 return StepEnabled(StepUpEnabled | StepDownEnabled);
551 StepEnabled ret = StepNone;
552 if (QAbstractSpinBoxPrivate::variantCompare(d->value, d->maximum) < 0) {
553 ret |= StepUpEnabled;
554 }
555 if (QAbstractSpinBoxPrivate::variantCompare(d->value, d->minimum) > 0) {
556 ret |= StepDownEnabled;
557 }
558 return ret;
559}
560
561/*!
562 This virtual function is called by the QAbstractSpinBox to
563 determine whether \a input is valid. The \a pos parameter indicates
564 the position in the string. Reimplemented in the various
565 subclasses.
566*/
567
568QValidator::State QAbstractSpinBox::validate(QString & /* input */, int & /* pos */) const
569{
570 return QValidator::Acceptable;
571}
572
573/*!
574 This virtual function is called by the QAbstractSpinBox if the
575 \a input is not validated to QValidator::Acceptable when Return is
576 pressed or interpretText() is called. It will try to change the
577 text so it is valid. Reimplemented in the various subclasses.
578*/
579
580void QAbstractSpinBox::fixup(QString & /* input */) const
581{
582}
583
584/*!
585 Steps up by one linestep
586 Calling this slot is analogous to calling stepBy(1);
587 \sa stepBy(), stepDown()
588*/
589
590void QAbstractSpinBox::stepUp()
591{
592 stepBy(1);
593}
594
595/*!
596 Steps down by one linestep
597 Calling this slot is analogous to calling stepBy(-1);
598 \sa stepBy(), stepUp()
599*/
600
601void QAbstractSpinBox::stepDown()
602{
603 stepBy(-1);
604}
605/*!
606 Virtual function that is called whenever the user triggers a step.
607 The \a steps parameter indicates how many steps were taken.
608 For example, pressing \c Qt::Key_Down will trigger a call to \c stepBy(-1),
609 whereas pressing \c Qt::Key_PageUp will trigger a call to \c stepBy(10).
610
611 If you subclass \c QAbstractSpinBox you must reimplement this
612 function. Note that this function is called even if the resulting
613 value will be outside the bounds of minimum and maximum. It's this
614 function's job to handle these situations.
615
616 \sa stepUp(), stepDown(), keyPressEvent()
617*/
618
619void QAbstractSpinBox::stepBy(int steps)
620{
621 Q_D(QAbstractSpinBox);
622
623 const QVariant old = d->value;
624 QString tmp = d->edit->displayText();
625 int cursorPos = d->edit->cursorPosition();
626 bool dontstep = false;
627 EmitPolicy e = EmitIfChanged;
628 if (d->pendingEmit) {
629 dontstep = validate(tmp, cursorPos) != QValidator::Acceptable;
630 d->cleared = false;
631 d->interpret(NeverEmit);
632 if (d->value != old)
633 e = AlwaysEmit;
634 }
635 if (!dontstep) {
636 QVariant singleStep;
637 switch (d->stepType) {
638 case QAbstractSpinBox::StepType::AdaptiveDecimalStepType:
639 singleStep = d->calculateAdaptiveDecimalStep(steps);
640 break;
641 default:
642 singleStep = d->singleStep;
643 }
644 const auto newVal = d->bound(d->value + (singleStep * steps), old, steps);
645 d->setValue(d->roundValue(newVal), e);
646 } else if (e == AlwaysEmit) {
647 d->emitSignals(e, old);
648 }
649 if (style()->styleHint(QStyle::SH_SpinBox_SelectOnStep, nullptr, this, nullptr))
650 selectAll();
651}
652
653/*!
654 This function returns a pointer to the line edit of the spin box.
655*/
656
657QLineEdit *QAbstractSpinBox::lineEdit() const
658{
659 Q_D(const QAbstractSpinBox);
660
661 return d->edit;
662}
663
664
665/*!
666 \fn void QAbstractSpinBox::setLineEdit(QLineEdit *lineEdit)
667
668 Sets the line edit of the spinbox to be \a lineEdit instead of the
669 current line edit widget. \a lineEdit cannot be \nullptr.
670
671 QAbstractSpinBox takes ownership of the new lineEdit
672
673 If QLineEdit::validator() for the \a lineEdit returns \nullptr, the internal
674 validator of the spinbox will be set on the line edit.
675*/
676
677void QAbstractSpinBox::setLineEdit(QLineEdit *lineEdit)
678{
679 Q_D(QAbstractSpinBox);
680
681 if (!lineEdit) {
682 Q_ASSERT(lineEdit);
683 return;
684 }
685
686 if (lineEdit == d->edit)
687 return;
688
689 delete d->edit;
690 d->edit = lineEdit;
691 setProperty("_q_spinbox_lineedit", QVariant::fromValue<QWidget *>(d->edit));
692 if (!d->edit->validator())
693 d->edit->setValidator(d->validator);
694
695 if (d->edit->parent() != this)
696 d->edit->setParent(this);
697
698 d->edit->setFrame(!style()->styleHint(QStyle::SH_SpinBox_ButtonsInsideFrame, nullptr, this));
699 d->edit->setFocusProxy(this);
700 d->edit->setAcceptDrops(false);
701
702 if (d->type != QMetaType::UnknownType) {
703 QObjectPrivate::connect(d->edit, &QLineEdit::textChanged,
704 d, &QAbstractSpinBoxPrivate::editorTextChanged);
705 QObjectPrivate::connect(d->edit, &QLineEdit::cursorPositionChanged,
706 d, &QAbstractSpinBoxPrivate::editorCursorPositionChanged);
707 connect(d->edit, &QLineEdit::cursorPositionChanged,
708 this, [this]() { updateMicroFocus(); });
709 connect(d->edit->d_func()->control, &QWidgetLineControl::updateMicroFocus,
710 this, [this]() { updateMicroFocus(); });
711 }
712 d->updateEditFieldGeometry();
713 d->edit->setContextMenuPolicy(Qt::NoContextMenu);
714 d->edit->d_func()->control->setAccessibleObject(this);
715
716 if (isVisible())
717 d->edit->show();
718 if (isVisible())
719 d->updateEdit();
720}
721
722
723/*!
724 This function interprets the text of the spin box. If the value
725 has changed since last interpretation it will emit signals.
726*/
727
728void QAbstractSpinBox::interpretText()
729{
730 Q_D(QAbstractSpinBox);
731 d->interpret(EmitIfChanged);
732}
733
734/*
735 Reimplemented in 4.6, so be careful.
736 */
737/*!
738 \reimp
739*/
740QVariant QAbstractSpinBox::inputMethodQuery(Qt::InputMethodQuery query) const
741{
742 Q_D(const QAbstractSpinBox);
743 const QVariant lineEditValue = d->edit->inputMethodQuery(query);
744 switch (query) {
745 case Qt::ImHints:
746 if (const int hints = inputMethodHints())
747 return QVariant(hints | lineEditValue.toInt());
748 break;
749 default:
750 break;
751 }
752 return lineEditValue;
753}
754
755/*!
756 \reimp
757*/
758
759bool QAbstractSpinBox::event(QEvent *event)
760{
761 Q_D(QAbstractSpinBox);
762 switch (event->type()) {
763 case QEvent::FontChange:
764 case QEvent::StyleChange:
765 d->cachedSizeHint = d->cachedMinimumSizeHint = QSize();
766 break;
767 case QEvent::ApplicationLayoutDirectionChange:
768 case QEvent::LayoutDirectionChange:
769 d->updateEditFieldGeometry();
770 break;
771 case QEvent::HoverEnter:
772 case QEvent::HoverLeave:
773 case QEvent::HoverMove:
774 d->updateHoverControl(static_cast<const QHoverEvent *>(event)->position().toPoint());
775 break;
776 case QEvent::ShortcutOverride:
777 if (d->edit->event(event))
778 return true;
779 break;
780 case QEvent::InputMethod:
781 return d->edit->event(event);
782 default:
783 break;
784 }
785 return QWidget::event(event);
786}
787
788/*!
789 \reimp
790*/
791
792void QAbstractSpinBox::showEvent(QShowEvent *)
793{
794 Q_D(QAbstractSpinBox);
795 d->reset();
796
797 if (d->ignoreUpdateEdit) {
798 d->ignoreUpdateEdit = false;
799 } else {
800 d->updateEdit();
801 }
802}
803
804/*!
805 \reimp
806*/
807
808void QAbstractSpinBox::changeEvent(QEvent *event)
809{
810 Q_D(QAbstractSpinBox);
811
812 switch (event->type()) {
813 case QEvent::StyleChange:
814 d->spinClickTimerInterval = style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatRate, nullptr, this);
815 d->spinClickThresholdTimerInterval =
816 style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatThreshold, nullptr, this);
817 if (d->edit)
818 d->edit->setFrame(!style()->styleHint(QStyle::SH_SpinBox_ButtonsInsideFrame, nullptr, this));
819 d->stepModifier = static_cast<Qt::KeyboardModifier>(style()->styleHint(QStyle::SH_SpinBox_StepModifier, nullptr, this));
820 d->reset();
821 d->updateEditFieldGeometry();
822 break;
823 case QEvent::LocaleChange:
824 d->updateEdit();
825 break;
826 case QEvent::EnabledChange:
827 if (!isEnabled()) {
828 d->reset();
829 }
830 break;
831 case QEvent::ActivationChange:
832 if (!isActiveWindow()){
833 d->reset();
834 if (d->pendingEmit) // pendingEmit can be true even if it hasn't changed.
835 d->interpret(EmitIfChanged); // E.g. 10 to 10.0
836 }
837 break;
838 default:
839 break;
840 }
841 QWidget::changeEvent(event);
842}
843
844/*!
845 \reimp
846*/
847
848void QAbstractSpinBox::resizeEvent(QResizeEvent *event)
849{
850 Q_D(QAbstractSpinBox);
851 QWidget::resizeEvent(event);
852
853 d->updateEditFieldGeometry();
854 update();
855}
856
857/*!
858 \reimp
859*/
860
861QSize QAbstractSpinBox::sizeHint() const
862{
863 Q_D(const QAbstractSpinBox);
864 if (d->cachedSizeHint.isEmpty()) {
865 ensurePolished();
866
867 const QFontMetrics fm(fontMetrics());
868 int h = d->edit->sizeHint().height();
869 int w = 0;
870 QString s;
871 QString fixedContent = d->prefix + d->suffix + u' ';
872 s = d->textFromValue(d->minimum);
873 s.truncate(18);
874 s += fixedContent;
875 w = qMax(w, fm.horizontalAdvance(s));
876 s = d->textFromValue(d->maximum);
877 s.truncate(18);
878 s += fixedContent;
879 w = qMax(w, fm.horizontalAdvance(s));
880
881 if (d->specialValueText.size()) {
882 s = d->specialValueText;
883 w = qMax(w, fm.horizontalAdvance(s));
884 }
885 w += 2; // cursor blinking space
886
887 QStyleOptionSpinBox opt;
888 initStyleOption(&opt);
889 QSize hint(w, h);
890 d->cachedSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this);
891 }
892 return d->cachedSizeHint;
893}
894
895/*!
896 \reimp
897*/
898
899QSize QAbstractSpinBox::minimumSizeHint() const
900{
901 Q_D(const QAbstractSpinBox);
902 if (d->cachedMinimumSizeHint.isEmpty()) {
903 //Use the prefix and range to calculate the minimumSizeHint
904 ensurePolished();
905
906 const QFontMetrics fm(fontMetrics());
907 int h = d->edit->minimumSizeHint().height();
908 int w = 0;
909
910 QString s;
911 QString fixedContent = d->prefix + u' ';
912 s = d->textFromValue(d->minimum);
913 s.truncate(18);
914 s += fixedContent;
915 w = qMax(w, fm.horizontalAdvance(s));
916 s = d->textFromValue(d->maximum);
917 s.truncate(18);
918 s += fixedContent;
919 w = qMax(w, fm.horizontalAdvance(s));
920
921 if (d->specialValueText.size()) {
922 s = d->specialValueText;
923 w = qMax(w, fm.horizontalAdvance(s));
924 }
925 w += 2; // cursor blinking space
926
927 QStyleOptionSpinBox opt;
928 initStyleOption(&opt);
929 QSize hint(w, h);
930
931 d->cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this);
932 }
933 return d->cachedMinimumSizeHint;
934}
935
936/*!
937 \reimp
938*/
939
940void QAbstractSpinBox::paintEvent(QPaintEvent *)
941{
942 QStyleOptionSpinBox opt;
943 initStyleOption(&opt);
944 QStylePainter p(this);
945 p.drawComplexControl(QStyle::CC_SpinBox, opt);
946}
947
948/*!
949 \reimp
950
951 This function handles keyboard input.
952
953 The following keys are handled specifically:
954 \table
955 \row \li Enter/Return
956 \li This will reinterpret the text and emit a signal even if the value has not changed
957 since last time a signal was emitted.
958 \row \li Up
959 \li This will invoke stepBy(1)
960 \row \li Down
961 \li This will invoke stepBy(-1)
962 \row \li Page up
963 \li This will invoke stepBy(10)
964 \row \li Page down
965 \li This will invoke stepBy(-10)
966 \endtable
967
968 \sa stepBy()
969*/
970
971
972void QAbstractSpinBox::keyPressEvent(QKeyEvent *event)
973{
974 Q_D(QAbstractSpinBox);
975
976 d->keyboardModifiers = event->modifiers();
977
978 if (!event->text().isEmpty() && d->edit->cursorPosition() < d->prefix.size())
979 d->edit->setCursorPosition(d->prefix.size());
980
981 int steps = 1;
982 bool isPgUpOrDown = false;
983 switch (event->key()) {
984 case Qt::Key_PageUp:
985 case Qt::Key_PageDown:
986 steps *= 10;
987 isPgUpOrDown = true;
988 Q_FALLTHROUGH();
989 case Qt::Key_Up:
990 case Qt::Key_Down: {
991 event->accept();
992 const bool up = (event->key() == Qt::Key_PageUp || event->key() == Qt::Key_Up);
993 if (!(stepEnabled() & (up ? StepUpEnabled : StepDownEnabled)))
994 return;
995 if (!isPgUpOrDown && (event->modifiers() & d->stepModifier))
996 steps *= 10;
997 if (!up)
998 steps *= -1;
999 if (style()->styleHint(QStyle::SH_SpinBox_AnimateButton, nullptr, this)) {
1000 d->buttonState = (Keyboard | (up ? Up : Down));
1001 }
1002 if (!d->spinClickTimer.isActive())
1003 stepBy(steps);
1004 if (event->isAutoRepeat() && !isPgUpOrDown) {
1005 if (!d->spinClickThresholdTimer.isActive() && !d->spinClickTimer.isActive()) {
1006 d->updateState(up, true);
1007 }
1008 }
1009#if QT_CONFIG(accessibility)
1010 QAccessibleValueChangeEvent event(this, d->value);
1011 QAccessible::updateAccessibility(&event);
1012#endif
1013 return;
1014 }
1015 case Qt::Key_Enter:
1016 case Qt::Key_Return:
1017 d->edit->d_func()->control->clearUndo();
1018 d->interpret(d->keyboardTracking ? AlwaysEmit : EmitIfChanged);
1019 selectAll();
1020 event->ignore();
1021 emit editingFinished();
1022 emit returnPressed();
1023 emit d->edit->returnPressed();
1024 return;
1025
1026 case Qt::Key_U:
1027 if (event->modifiers() & Qt::ControlModifier
1028 && QGuiApplication::platformName() == "xcb"_L1) { // only X11
1029 event->accept();
1030 if (!isReadOnly())
1031 clear();
1032 return;
1033 }
1034 break;
1035
1036 case Qt::Key_End:
1037 case Qt::Key_Home:
1038 if (event->modifiers() & Qt::ShiftModifier) {
1039 int currentPos = d->edit->cursorPosition();
1040 const QString text = d->edit->displayText();
1041 if (event->key() == Qt::Key_End) {
1042 if ((currentPos == 0 && !d->prefix.isEmpty()) || text.size() - d->suffix.size() <= currentPos) {
1043 break; // let lineedit handle this
1044 } else {
1045 d->edit->setSelection(currentPos, text.size() - d->suffix.size() - currentPos);
1046 }
1047 } else {
1048 if ((currentPos == text.size() && !d->suffix.isEmpty()) || currentPos <= d->prefix.size()) {
1049 break; // let lineedit handle this
1050 } else {
1051 d->edit->setSelection(currentPos, d->prefix.size() - currentPos);
1052 }
1053 }
1054 event->accept();
1055 return;
1056 }
1057 break;
1058
1059 default:
1060#ifndef QT_NO_SHORTCUT
1061 if (event == QKeySequence::SelectAll) {
1062 selectAll();
1063 event->accept();
1064 return;
1065 }
1066#endif
1067 break;
1068 }
1069
1070 d->edit->event(event);
1071 if (!d->edit->text().isEmpty())
1072 d->cleared = false;
1073 if (!isVisible())
1074 d->ignoreUpdateEdit = true;
1075}
1076
1077/*!
1078 \reimp
1079*/
1080
1081void QAbstractSpinBox::keyReleaseEvent(QKeyEvent *event)
1082{
1083 Q_D(QAbstractSpinBox);
1084
1085 d->keyboardModifiers = event->modifiers();
1086 if (d->buttonState & Keyboard && !event->isAutoRepeat()) {
1087 d->reset();
1088 } else {
1089 d->edit->event(event);
1090 }
1091}
1092
1093/*!
1094 \reimp
1095*/
1096
1097#if QT_CONFIG(wheelevent)
1098void QAbstractSpinBox::wheelEvent(QWheelEvent *event)
1099{
1100 Q_D(QAbstractSpinBox);
1101#ifdef Q_OS_MACOS
1102 // If the event comes from a real mouse wheel, rather than a track pad
1103 // (Qt::MouseEventSynthesizedBySystem), the shift modifier changes the
1104 // scroll orientation to horizontal.
1105 // Convert horizontal events back to vertical whilst shift is held.
1106 if ((event->modifiers() & Qt::ShiftModifier)
1107 && event->source() == Qt::MouseEventNotSynthesized) {
1108 d->wheelDeltaRemainder += event->angleDelta().x();
1109 } else {
1110 d->wheelDeltaRemainder += event->angleDelta().y();
1111 }
1112#else
1113 d->wheelDeltaRemainder += event->angleDelta().y();
1114#endif
1115 const int steps = d->wheelDeltaRemainder / 120;
1116 d->wheelDeltaRemainder -= steps * 120;
1117 if (stepEnabled() & (steps > 0 ? StepUpEnabled : StepDownEnabled))
1118 stepBy(event->modifiers() & d->stepModifier ? steps * 10 : steps);
1119 event->accept();
1120}
1121#endif
1122
1123
1124/*!
1125 \reimp
1126*/
1127void QAbstractSpinBox::focusInEvent(QFocusEvent *event)
1128{
1129 Q_D(QAbstractSpinBox);
1130
1131 d->edit->event(event);
1132 if (event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason) {
1133 selectAll();
1134 }
1135 QWidget::focusInEvent(event);
1136}
1137
1138/*!
1139 \reimp
1140*/
1141
1142void QAbstractSpinBox::focusOutEvent(QFocusEvent *event)
1143{
1144 Q_D(QAbstractSpinBox);
1145
1146 if (d->pendingEmit)
1147 d->interpret(EmitIfChanged);
1148
1149 d->reset();
1150 d->edit->event(event);
1151 d->updateEdit();
1152 QWidget::focusOutEvent(event);
1153
1154 emit editingFinished();
1155}
1156
1157/*!
1158 \reimp
1159*/
1160
1161void QAbstractSpinBox::closeEvent(QCloseEvent *event)
1162{
1163 Q_D(QAbstractSpinBox);
1164
1165 d->reset();
1166 if (d->pendingEmit)
1167 d->interpret(EmitIfChanged);
1168 QWidget::closeEvent(event);
1169}
1170
1171/*!
1172 \reimp
1173*/
1174
1175void QAbstractSpinBox::hideEvent(QHideEvent *event)
1176{
1177 Q_D(QAbstractSpinBox);
1178 d->reset();
1179 if (d->pendingEmit)
1180 d->interpret(EmitIfChanged);
1181 QWidget::hideEvent(event);
1182}
1183
1184
1185/*!
1186 \reimp
1187*/
1188
1189void QAbstractSpinBox::timerEvent(QTimerEvent *event)
1190{
1191 Q_D(QAbstractSpinBox);
1192
1193 bool doStep = false;
1194 if (event->id() == d->spinClickThresholdTimer.id()) {
1195 d->spinClickThresholdTimer.stop();
1196 d->effectiveSpinRepeatRate = d->buttonState & Keyboard
1197 ? QGuiApplication::styleHints()->keyboardAutoRepeatRateF()
1198 : d->spinClickTimerInterval;
1199 d->spinClickTimer.start(d->effectiveSpinRepeatRate, this);
1200 doStep = true;
1201 } else if (event->id() == d->spinClickTimer.id()) {
1202 if (d->accelerate) {
1203 d->acceleration = d->acceleration + (int)(d->effectiveSpinRepeatRate * 0.05);
1204 auto interval = int(d->effectiveSpinRepeatRate - d->acceleration) * 1ms;
1205 if (interval >= 10ms)
1206 d->spinClickTimer.start(interval, this);
1207 }
1208 doStep = true;
1209 }
1210
1211 if (doStep) {
1212 const bool increaseStepRate = d->keyboardModifiers & d->stepModifier;
1213 const StepEnabled st = stepEnabled();
1214 if (d->buttonState & Up) {
1215 if (!(st & StepUpEnabled)) {
1216 d->reset();
1217 } else {
1218 stepBy(increaseStepRate ? 10 : 1);
1219 }
1220 } else if (d->buttonState & Down) {
1221 if (!(st & StepDownEnabled)) {
1222 d->reset();
1223 } else {
1224 stepBy(increaseStepRate ? -10 : -1);
1225 }
1226 }
1227 return;
1228 }
1229 QWidget::timerEvent(event);
1230 return;
1231}
1232
1233/*!
1234 \reimp
1235*/
1236
1237#if QT_CONFIG(contextmenu)
1238void QAbstractSpinBox::contextMenuEvent(QContextMenuEvent *event)
1239{
1240#ifdef Q_OS_WASM
1241 if (!qstdweb::haveAsyncify()) {
1242 qDebug() << " Skipping context menu for spinbox since asyncify is off";
1243 return;
1244 }
1245#endif
1246 Q_D(QAbstractSpinBox);
1247
1248 QPointer<QMenu> menu = d->edit->createStandardContextMenu();
1249 if (!menu)
1250 return;
1251
1252 d->reset();
1253
1254 QAction *selAll = new QAction(tr("&Select All"), menu);
1255#if QT_CONFIG(shortcut)
1256 selAll->setShortcut(QKeySequence::SelectAll);
1257#endif
1258 menu->insertAction(d->edit->d_func()->selectAllAction,
1259 selAll);
1260 menu->removeAction(d->edit->d_func()->selectAllAction);
1261 menu->addSeparator();
1262 const uint se = stepEnabled();
1263 QAction *up = menu->addAction(tr("&Step up"));
1264 up->setEnabled(se & StepUpEnabled);
1265 QAction *down = menu->addAction(tr("Step &down"));
1266 down->setEnabled(se & StepDownEnabled);
1267 menu->addSeparator();
1268
1269 const QPointer<QAbstractSpinBox> that = this;
1270 const QPoint pos = (event->reason() == QContextMenuEvent::Mouse)
1271 ? event->globalPos() : mapToGlobal(QPoint(event->pos().x(), 0)) + QPoint(width() / 2, height() / 2);
1272 const QAction *action = menu->exec(pos);
1273 delete static_cast<QMenu *>(menu);
1274 if (that && action) {
1275 if (action == up) {
1276 stepBy(1);
1277 } else if (action == down) {
1278 stepBy(-1);
1279 } else if (action == selAll) {
1280 selectAll();
1281 }
1282 }
1283 event->accept();
1284}
1285#endif // QT_CONFIG(contextmenu)
1286
1287/*!
1288 \reimp
1289*/
1290
1291void QAbstractSpinBox::mouseMoveEvent(QMouseEvent *event)
1292{
1293 Q_D(QAbstractSpinBox);
1294
1295 d->keyboardModifiers = event->modifiers();
1296 d->updateHoverControl(event->position().toPoint());
1297
1298 // If we have a timer ID, update the state
1299 if (d->spinClickTimer.isActive() && d->buttonSymbols != NoButtons) {
1300 const StepEnabled se = stepEnabled();
1301 if ((se & StepUpEnabled) && d->hoverControl == QStyle::SC_SpinBoxUp)
1302 d->updateState(true);
1303 else if ((se & StepDownEnabled) && d->hoverControl == QStyle::SC_SpinBoxDown)
1304 d->updateState(false);
1305 else
1306 d->reset();
1307 event->accept();
1308 }
1309}
1310
1311/*!
1312 \reimp
1313*/
1314
1315void QAbstractSpinBox::mousePressEvent(QMouseEvent *event)
1316{
1317 Q_D(QAbstractSpinBox);
1318
1319 d->keyboardModifiers = event->modifiers();
1320 if (event->button() != Qt::LeftButton || d->buttonState != None) {
1321 event->ignore();
1322 return;
1323 }
1324
1325 d->updateHoverControl(event->position().toPoint());
1326 event->accept();
1327
1328 const StepEnabled se = (d->buttonSymbols == NoButtons) ? StepEnabled(StepNone) : stepEnabled();
1329 if ((se & StepUpEnabled) && d->hoverControl == QStyle::SC_SpinBoxUp) {
1330 d->updateState(true);
1331 } else if ((se & StepDownEnabled) && d->hoverControl == QStyle::SC_SpinBoxDown) {
1332 d->updateState(false);
1333 } else {
1334 event->ignore();
1335 }
1336}
1337
1338/*!
1339 \reimp
1340*/
1341void QAbstractSpinBox::mouseReleaseEvent(QMouseEvent *event)
1342{
1343 Q_D(QAbstractSpinBox);
1344
1345 d->keyboardModifiers = event->modifiers();
1346 if ((d->buttonState & Mouse) != 0)
1347 d->reset();
1348 event->accept();
1349}
1350
1351// --- QAbstractSpinBoxPrivate ---
1352
1353/*!
1354 \internal
1355 Constructs a QAbstractSpinBoxPrivate object
1356*/
1357
1358QAbstractSpinBoxPrivate::QAbstractSpinBoxPrivate()
1359 : pendingEmit(false), readOnly(false), wrapping(false),
1360 ignoreCursorPositionChanged(false), frame(true), accelerate(false), keyboardTracking(true),
1361 cleared(false), ignoreUpdateEdit(false), showGroupSeparator(false)
1362{
1363}
1364
1365/*
1366 \internal
1367 Called when the QAbstractSpinBoxPrivate is destroyed
1368*/
1369QAbstractSpinBoxPrivate::~QAbstractSpinBoxPrivate()
1370{
1371}
1372
1373/*!
1374 \internal
1375 Updates the old and new hover control. Does nothing if the hover
1376 control has not changed.
1377*/
1378bool QAbstractSpinBoxPrivate::updateHoverControl(const QPoint &pos)
1379{
1380 Q_Q(QAbstractSpinBox);
1381 QRect lastHoverRect = hoverRect;
1382 QStyle::SubControl lastHoverControl = hoverControl;
1383 bool doesHover = q->testAttribute(Qt::WA_Hover);
1384 if (lastHoverControl != newHoverControl(pos) && doesHover) {
1385 q->update(lastHoverRect);
1386 q->update(hoverRect);
1387 return true;
1388 }
1389 return !doesHover;
1390}
1391
1392/*!
1393 \internal
1394 Returns the hover control at \a pos.
1395 This will update the hoverRect and hoverControl.
1396*/
1397QStyle::SubControl QAbstractSpinBoxPrivate::newHoverControl(const QPoint &pos)
1398{
1399 Q_Q(QAbstractSpinBox);
1400
1401 QStyleOptionSpinBox opt;
1402 q->initStyleOption(&opt);
1403 opt.subControls = QStyle::SC_All;
1404 hoverControl = q->style()->hitTestComplexControl(QStyle::CC_SpinBox, &opt, pos, q);
1405 hoverRect = q->style()->subControlRect(QStyle::CC_SpinBox, &opt, hoverControl, q);
1406 return hoverControl;
1407}
1408
1409/*!
1410 \internal
1411 Strips any prefix/suffix from \a text.
1412*/
1413
1414QString QAbstractSpinBoxPrivate::stripped(const QString &t, int *pos) const
1415{
1416 QStringView text(t);
1417 if (specialValueText.size() == 0 || text != specialValueText) {
1418 int from = 0;
1419 int size = text.size();
1420 bool changed = false;
1421 if (prefix.size() && text.startsWith(prefix)) {
1422 from += prefix.size();
1423 size -= from;
1424 changed = true;
1425 }
1426 if (suffix.size() && text.endsWith(suffix)) {
1427 size -= suffix.size();
1428 changed = true;
1429 }
1430 if (changed)
1431 text = text.mid(from, size);
1432 }
1433
1434 const int s = text.size();
1435 text = text.trimmed();
1436 if (pos)
1437 (*pos) -= (s - text.size());
1438 return text.toString();
1439
1440}
1441
1442void QAbstractSpinBoxPrivate::updateEditFieldGeometry()
1443{
1444 Q_Q(QAbstractSpinBox);
1445 QStyleOptionSpinBox opt;
1446 q->initStyleOption(&opt);
1447 opt.subControls = QStyle::SC_SpinBoxEditField;
1448 edit->setGeometry(q->style()->subControlRect(QStyle::CC_SpinBox, &opt,
1449 QStyle::SC_SpinBoxEditField, q));
1450}
1451/*!
1452 \internal
1453 Returns \c true if a specialValueText has been set and the current value is minimum.
1454*/
1455
1456bool QAbstractSpinBoxPrivate::specialValue() const
1457{
1458 return (value == minimum && !specialValueText.isEmpty());
1459}
1460
1461/*!
1462 \internal Virtual function that emits signals when the value
1463 changes. Reimplemented in the different subclasses.
1464*/
1465
1466void QAbstractSpinBoxPrivate::emitSignals(EmitPolicy, const QVariant &)
1467{
1468}
1469
1470/*!
1471 \internal
1472
1473 Slot connected to the line edit's textChanged(const QString &)
1474 signal.
1475*/
1476
1477void QAbstractSpinBoxPrivate::editorTextChanged(const QString &t)
1478{
1479 Q_Q(QAbstractSpinBox);
1480
1481 if (keyboardTracking) {
1482 QString tmp = t;
1483 int pos = edit->cursorPosition();
1484 QValidator::State state = q->validate(tmp, pos);
1485 if (state == QValidator::Acceptable) {
1486 const QVariant v = valueFromText(tmp);
1487 setValue(v, EmitIfChanged, tmp != t);
1488 pendingEmit = false;
1489 } else {
1490 pendingEmit = true;
1491 }
1492 } else {
1493 pendingEmit = true;
1494 }
1495}
1496
1497/*!
1498 \internal
1499
1500 Virtual slot connected to the line edit's
1501 cursorPositionChanged(int, int) signal. Will move the cursor to a
1502 valid position if the new one is invalid. E.g. inside the prefix.
1503 Reimplemented in Q[Date|Time|DateTime]EditPrivate to account for
1504 the different sections etc.
1505*/
1506
1507void QAbstractSpinBoxPrivate::editorCursorPositionChanged(int oldpos, int newpos)
1508{
1509 if (!edit->hasSelectedText() && !ignoreCursorPositionChanged && !specialValue()) {
1510 ignoreCursorPositionChanged = true;
1511
1512 bool allowSelection = true;
1513 int pos = -1;
1514 if (newpos < prefix.size() && newpos != 0) {
1515 if (oldpos == 0) {
1516 allowSelection = false;
1517 pos = prefix.size();
1518 } else {
1519 pos = oldpos;
1520 }
1521 } else if (newpos > edit->text().size() - suffix.size()
1522 && newpos != edit->text().size()) {
1523 if (oldpos == edit->text().size()) {
1524 pos = edit->text().size() - suffix.size();
1525 allowSelection = false;
1526 } else {
1527 pos = edit->text().size();
1528 }
1529 }
1530 if (pos != -1) {
1531 const int selSize = edit->selectionStart() >= 0 && allowSelection
1532 ? (edit->selectedText().size()
1533 * (newpos < pos ? -1 : 1)) - newpos + pos
1534 : 0;
1535
1536 const QSignalBlocker blocker(edit);
1537 if (selSize != 0) {
1538 edit->setSelection(pos - selSize, selSize);
1539 } else {
1540 edit->setCursorPosition(pos);
1541 }
1542 }
1543 ignoreCursorPositionChanged = false;
1544 }
1545}
1546
1547/*!
1548 \internal
1549
1550 Initialises the QAbstractSpinBoxPrivate object.
1551*/
1552
1553void QAbstractSpinBoxPrivate::init()
1554{
1555 Q_Q(QAbstractSpinBox);
1556
1557 q->setLineEdit(new QLineEdit(q));
1558 edit->setObjectName("qt_spinbox_lineedit"_L1);
1559 validator = new QSpinBoxValidator(q, this);
1560 edit->setValidator(validator);
1561
1562 QStyleOptionSpinBox opt;
1563 // ### This is called from the ctor and thus we shouldn't call initStyleOption yet
1564 // ### as we only call the base class implementation of initStyleOption called.
1565 q->initStyleOption(&opt);
1566 spinClickTimerInterval = q->style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatRate, &opt, q);
1567 spinClickThresholdTimerInterval = q->style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatThreshold, &opt, q);
1568 q->setFocusPolicy(Qt::WheelFocus);
1569 q->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::SpinBox));
1570 q->setAttribute(Qt::WA_InputMethodEnabled);
1571
1572 q->setAttribute(Qt::WA_MacShowFocusRect);
1573}
1574
1575/*!
1576 \internal
1577
1578 Resets the state of the spinbox. E.g. the state is set to
1579 (Keyboard|Up) if Key up is currently pressed.
1580*/
1581
1582void QAbstractSpinBoxPrivate::reset()
1583{
1584 Q_Q(QAbstractSpinBox);
1585
1586 buttonState = None;
1587 if (q) {
1588 spinClickTimer.stop();
1589 spinClickThresholdTimer.stop();
1590 acceleration = 0;
1591 q->update();
1592 }
1593}
1594
1595/*!
1596 \internal
1597
1598 Updates the state of the spinbox.
1599*/
1600
1601void QAbstractSpinBoxPrivate::updateState(bool up, bool fromKeyboard /* = false */)
1602{
1603 Q_Q(QAbstractSpinBox);
1604 if ((up && (buttonState & Up)) || (!up && (buttonState & Down)))
1605 return;
1606 reset();
1607 if (q && (q->stepEnabled() & (up ? QAbstractSpinBox::StepUpEnabled
1608 : QAbstractSpinBox::StepDownEnabled))) {
1609 buttonState = (up ? Up : Down) | (fromKeyboard ? Keyboard : Mouse);
1610 int steps = up ? 1 : -1;
1611 if (keyboardModifiers & stepModifier)
1612 steps *= 10;
1613 q->stepBy(steps);
1614 spinClickThresholdTimer.start(spinClickThresholdTimerInterval * 1ms, q);
1615#if QT_CONFIG(accessibility)
1616 QAccessibleValueChangeEvent event(q, value);
1617 QAccessible::updateAccessibility(&event);
1618#endif
1619 }
1620}
1621
1622
1623/*!
1624 Initialize \a option with the values from this QSpinBox. This method
1625 is useful for subclasses when they need a QStyleOptionSpinBox, but don't want
1626 to fill in all the information themselves.
1627
1628 \sa QStyleOption::initFrom()
1629*/
1630void QAbstractSpinBox::initStyleOption(QStyleOptionSpinBox *option) const
1631{
1632 if (!option)
1633 return;
1634
1635 Q_D(const QAbstractSpinBox);
1636 option->initFrom(this);
1637 option->activeSubControls = QStyle::SC_None;
1638 option->buttonSymbols = d->buttonSymbols;
1639 option->subControls = QStyle::SC_SpinBoxEditField;
1640 if (style()->styleHint(QStyle::SH_SpinBox_ButtonsInsideFrame, nullptr, this))
1641 option->subControls |= QStyle::SC_SpinBoxFrame;
1642 if (d->buttonSymbols != QAbstractSpinBox::NoButtons) {
1643 option->subControls |= QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown;
1644 if (d->buttonState & Up) {
1645 option->activeSubControls = QStyle::SC_SpinBoxUp;
1646 } else if (d->buttonState & Down) {
1647 option->activeSubControls = QStyle::SC_SpinBoxDown;
1648 }
1649 }
1650
1651 if (d->buttonState) {
1652 option->state |= QStyle::State_Sunken;
1653 } else {
1654 option->activeSubControls = d->hoverControl;
1655 }
1656
1657 option->stepEnabled = style()->styleHint(QStyle::SH_SpinControls_DisableOnBounds, nullptr, this)
1658 ? stepEnabled()
1659 : (QAbstractSpinBox::StepDownEnabled|QAbstractSpinBox::StepUpEnabled);
1660
1661 option->frame = d->frame;
1662}
1663
1664/*!
1665 \internal
1666
1667 Bounds \a val to be within minimum and maximum. Also tries to be
1668 clever about setting it at min and max depending on what it was
1669 and what direction it was changed etc.
1670*/
1671
1672QVariant QAbstractSpinBoxPrivate::bound(const QVariant &val, const QVariant &old, int steps) const
1673{
1674 QVariant v = val;
1675 if (!wrapping || steps == 0 || old.isNull()) {
1676 if (variantCompare(v, minimum) < 0) {
1677 v = wrapping ? maximum : minimum;
1678 }
1679 if (variantCompare(v, maximum) > 0) {
1680 v = wrapping ? minimum : maximum;
1681 }
1682 } else {
1683 const bool wasMin = old == minimum;
1684 const bool wasMax = old == maximum;
1685 const int oldcmp = variantCompare(v, old);
1686 const int maxcmp = variantCompare(v, maximum);
1687 const int mincmp = variantCompare(v, minimum);
1688 const bool wrapped = (oldcmp > 0 && steps < 0) || (oldcmp < 0 && steps > 0);
1689 if (maxcmp > 0) {
1690 v = ((wasMax && !wrapped && steps > 0) || (steps < 0 && !wasMin && wrapped))
1691 ? minimum : maximum;
1692 } else if (wrapped && (maxcmp > 0 || mincmp < 0)) {
1693 v = ((wasMax && steps > 0) || (!wasMin && steps < 0)) ? minimum : maximum;
1694 } else if (mincmp < 0) {
1695 v = (!wasMax && !wasMin ? minimum : maximum);
1696 }
1697 }
1698
1699 return v;
1700}
1701
1702/*!
1703 \internal
1704
1705 Sets the value of the spin box to \a val. Depending on the value
1706 of \a ep it will also emit signals.
1707*/
1708
1709void QAbstractSpinBoxPrivate::setValue(const QVariant &val, EmitPolicy ep,
1710 bool doUpdate)
1711{
1712 Q_Q(QAbstractSpinBox);
1713 const QVariant old = value;
1714 value = bound(val);
1715 pendingEmit = false;
1716 cleared = false;
1717 if (doUpdate) {
1718 updateEdit();
1719 }
1720 q->update();
1721
1722 if (ep == AlwaysEmit || (ep == EmitIfChanged && old != value)) {
1723 emitSignals(ep, old);
1724 }
1725}
1726
1727QVariant QAbstractSpinBoxPrivate::roundValue(const QVariant &val) const
1728{
1729 return val;
1730}
1731
1732/*!
1733 \internal
1734
1735 Updates the line edit to reflect the current value of the spin box.
1736*/
1737
1738void QAbstractSpinBoxPrivate::updateEdit()
1739{
1740 Q_Q(QAbstractSpinBox);
1741 if (type == QMetaType::UnknownType)
1742 return;
1743 const QString newText = specialValue() ? specialValueText : prefix + textFromValue(value) + suffix;
1744 if (newText == edit->displayText() || cleared)
1745 return;
1746
1747 const bool empty = edit->text().isEmpty();
1748 int cursor = edit->cursorPosition();
1749 int selsize = edit->selectedText().size();
1750 const QSignalBlocker blocker(edit);
1751 edit->setText(newText);
1752
1753 if (!specialValue()) {
1754 cursor = qBound(prefix.size(), cursor, edit->displayText().size() - suffix.size());
1755
1756 if (selsize > 0) {
1757 edit->setSelection(cursor, selsize);
1758 } else {
1759 edit->setCursorPosition(empty ? prefix.size() : cursor);
1760 }
1761 }
1762 q->update();
1763}
1764
1765/*!
1766 \internal
1767
1768 Convenience function to set min/max values.
1769*/
1770
1771void QAbstractSpinBoxPrivate::setRange(const QVariant &min, const QVariant &max)
1772{
1773 Q_Q(QAbstractSpinBox);
1774
1775 clearCache();
1776 minimum = min;
1777 maximum = (variantCompare(min, max) < 0 ? max : min);
1778 cachedSizeHint = QSize();
1779 cachedMinimumSizeHint = QSize(); // minimumSizeHint cares about min/max
1780
1781 reset();
1782 if (!(bound(value) == value)) {
1783 setValue(bound(value), EmitIfChanged);
1784 } else if (value == minimum && !specialValueText.isEmpty()) {
1785 updateEdit();
1786 }
1787
1788 q->updateGeometry();
1789}
1790
1791/*!
1792 \internal
1793
1794 Convenience function to get a variant of the right type.
1795*/
1796
1797QVariant QAbstractSpinBoxPrivate::getZeroVariant() const
1798{
1799 QVariant ret;
1800 switch (type) {
1801 case QMetaType::Int: ret = QVariant(0); break;
1802 case QMetaType::Double: ret = QVariant(0.0); break;
1803 default: break;
1804 }
1805 return ret;
1806}
1807
1808/*!
1809 \internal
1810
1811 Virtual method called that calls the public textFromValue()
1812 functions in the subclasses. Needed to change signature from
1813 QVariant to int/double/QDateTime etc. Used when needing to display
1814 a value textually.
1815
1816 This method is reimeplemented in the various subclasses.
1817*/
1818
1819QString QAbstractSpinBoxPrivate::textFromValue(const QVariant &) const
1820{
1821 return QString();
1822}
1823
1824/*!
1825 \internal
1826
1827 Virtual method called that calls the public valueFromText()
1828 functions in the subclasses. Needed to change signature from
1829 QVariant to int/double/QDateTime etc. Used when needing to
1830 interpret a string as another type.
1831
1832 This method is reimeplemented in the various subclasses.
1833*/
1834
1835QVariant QAbstractSpinBoxPrivate::valueFromText(const QString &) const
1836{
1837 return QVariant();
1838}
1839/*!
1840 \internal
1841
1842 Interprets text and emits signals. Called when the spinbox needs
1843 to interpret the text on the lineedit.
1844*/
1845
1846void QAbstractSpinBoxPrivate::interpret(EmitPolicy ep)
1847{
1848 Q_Q(QAbstractSpinBox);
1849 if (type == QMetaType::UnknownType || cleared)
1850 return;
1851
1852 QVariant v = getZeroVariant();
1853 bool doInterpret = true;
1854 QString tmp = edit->displayText();
1855 int pos = edit->cursorPosition();
1856 const int oldpos = pos;
1857
1858 if (q->validate(tmp, pos) != QValidator::Acceptable) {
1859 const QString copy = tmp;
1860 q->fixup(tmp);
1861 qDebug(lcWidgetAbstractSpinBox)
1862 << "QAbstractSpinBoxPrivate::interpret() text '"
1863 << edit->displayText()
1864 << "' >> '" << copy << '\''
1865 << "' >> '" << tmp << '\'';
1866
1867 doInterpret = tmp != copy && (q->validate(tmp, pos) == QValidator::Acceptable);
1868 if (!doInterpret) {
1869 v = (correctionMode == QAbstractSpinBox::CorrectToNearestValue
1870 ? variantBound(minimum, v, maximum) : value);
1871 }
1872 }
1873 if (doInterpret) {
1874 v = valueFromText(tmp);
1875 }
1876 clearCache();
1877 setValue(v, ep, true);
1878 if (oldpos != pos)
1879 edit->setCursorPosition(pos);
1880}
1881
1882void QAbstractSpinBoxPrivate::clearCache() const
1883{
1884 cachedText.clear();
1885 cachedValue.clear();
1886 cachedState = QValidator::Acceptable;
1887}
1888
1889QVariant QAbstractSpinBoxPrivate::calculateAdaptiveDecimalStep(int steps) const
1890{
1891 Q_UNUSED(steps);
1892 return singleStep;
1893}
1894
1895// --- QSpinBoxValidator ---
1896
1897/*!
1898 \internal
1899 Constructs a QSpinBoxValidator object
1900*/
1901
1902QSpinBoxValidator::QSpinBoxValidator(QAbstractSpinBox *qp, QAbstractSpinBoxPrivate *dp)
1903 : QValidator(qp), qptr(qp), dptr(dp)
1904{
1905 setObjectName("qt_spinboxvalidator"_L1);
1906}
1907
1908/*!
1909 \internal
1910
1911 Checks for specialValueText, prefix, suffix and calls
1912 the virtual QAbstractSpinBox::validate function.
1913*/
1914
1915QValidator::State QSpinBoxValidator::validate(QString &input, int &pos) const
1916{
1917 if (dptr->specialValueText.size() > 0 && input == dptr->specialValueText)
1918 return QValidator::Acceptable;
1919
1920 if (!dptr->prefix.isEmpty() && !input.startsWith(dptr->prefix)) {
1921 input.prepend(dptr->prefix);
1922 pos += dptr->prefix.size();
1923 }
1924
1925 if (!dptr->suffix.isEmpty() && !input.endsWith(dptr->suffix))
1926 input.append(dptr->suffix);
1927
1928 return qptr->validate(input, pos);
1929}
1930/*!
1931 \internal
1932 Calls the virtual QAbstractSpinBox::fixup function.
1933*/
1934
1935void QSpinBoxValidator::fixup(QString &input) const
1936{
1937 qptr->fixup(input);
1938}
1939
1940// --- global ---
1941
1942/*!
1943 \internal
1944 Adds two variants together and returns the result.
1945*/
1946
1947QVariant operator+(const QVariant &arg1, const QVariant &arg2)
1948{
1949 QVariant ret;
1950 if (Q_UNLIKELY(arg1.userType() != arg2.userType()))
1951 qWarning("QAbstractSpinBox: Internal error: Different types (%s vs %s) (%s:%d)",
1952 arg1.typeName(), arg2.typeName(), __FILE__, __LINE__);
1953 switch (arg1.userType()) {
1954 case QMetaType::Int: {
1955 const int int1 = arg1.toInt();
1956 const int int2 = arg2.toInt();
1957 if (int1 > 0 && (int2 >= INT_MAX - int1)) {
1958 // The increment overflows
1959 ret = QVariant(INT_MAX);
1960 } else if (int1 < 0 && (int2 <= INT_MIN - int1)) {
1961 // The increment underflows
1962 ret = QVariant(INT_MIN);
1963 } else {
1964 ret = QVariant(int1 + int2);
1965 }
1966 break;
1967 }
1968 case QMetaType::Double: ret = QVariant(arg1.toDouble() + arg2.toDouble()); break;
1969#if QT_CONFIG(datetimeparser)
1970 case QMetaType::QDateTime: {
1971 QDateTime a2 = arg2.toDateTime();
1972 QDateTime a1 = arg1.toDateTime().addDays(QDATETIMEEDIT_DATE_MIN.daysTo(a2.date()));
1973 a1.setTime(a1.time().addMSecs(a2.time().msecsSinceStartOfDay()));
1974 ret = QVariant(std::move(a1));
1975 break;
1976 }
1977#endif // datetimeparser
1978 default: break;
1979 }
1980 return ret;
1981}
1982
1983
1984/*!
1985 \internal
1986 Subtracts two variants and returns the result.
1987*/
1988
1989QVariant operator-(const QVariant &arg1, const QVariant &arg2)
1990{
1991 QVariant ret;
1992 if (Q_UNLIKELY(arg1.userType() != arg2.userType()))
1993 qWarning("QAbstractSpinBox: Internal error: Different types (%s vs %s) (%s:%d)",
1994 arg1.typeName(), arg2.typeName(), __FILE__, __LINE__);
1995 switch (arg1.userType()) {
1996 case QMetaType::Int: ret = QVariant(arg1.toInt() - arg2.toInt()); break;
1997 case QMetaType::Double: ret = QVariant(arg1.toDouble() - arg2.toDouble()); break;
1998 case QMetaType::QDateTime: {
1999 QDateTime a1 = arg1.toDateTime();
2000 QDateTime a2 = arg2.toDateTime();
2001 int days = a2.daysTo(a1);
2002 int secs = a2.secsTo(a1);
2003 int msecs = qMax(0, a1.time().msec() - a2.time().msec());
2004 if (days < 0 || secs < 0 || msecs < 0) {
2005 ret = arg1;
2006 } else {
2007 QDateTime dt = a2.addDays(days).addSecs(secs);
2008 if (msecs > 0)
2009 dt.setTime(dt.time().addMSecs(msecs));
2010 ret = QVariant(std::move(dt));
2011 }
2012 break;
2013 }
2014 default: break;
2015 }
2016 return ret;
2017}
2018
2019/*!
2020 \internal
2021 Multiplies \a arg1 by \a multiplier and returns the result.
2022*/
2023
2024QVariant operator*(const QVariant &arg1, double multiplier)
2025{
2026 QVariant ret;
2027
2028 switch (arg1.userType()) {
2029 case QMetaType::Int:
2030 ret = static_cast<int>(qBound<double>(INT_MIN, arg1.toInt() * multiplier, INT_MAX));
2031 break;
2032 case QMetaType::Double: ret = QVariant(arg1.toDouble() * multiplier); break;
2033#if QT_CONFIG(datetimeparser)
2034 case QMetaType::QDateTime: {
2035 double days = QDATETIMEEDIT_DATE_MIN.daysTo(arg1.toDateTime().date()) * multiplier;
2036 const qint64 daysInt = qint64(days);
2037 days -= daysInt;
2038 qint64 msecs = qint64(arg1.toDateTime().time().msecsSinceStartOfDay() * multiplier
2039 + days * (24 * 3600 * 1000));
2040 ret = QDATETIMEEDIT_DATE_MIN.addDays(daysInt).startOfDay().addMSecs(msecs);
2041 break;
2042 }
2043#endif // datetimeparser
2044 default: ret = arg1; break;
2045 }
2046
2047 return ret;
2048}
2049
2050
2051
2052double operator/(const QVariant &arg1, const QVariant &arg2)
2053{
2054 double a1 = 0;
2055 double a2 = 0;
2056
2057 switch (arg1.userType()) {
2058 case QMetaType::Int:
2059 a1 = (double)arg1.toInt();
2060 a2 = (double)arg2.toInt();
2061 break;
2062 case QMetaType::Double:
2063 a1 = arg1.toDouble();
2064 a2 = arg2.toDouble();
2065 break;
2066#if QT_CONFIG(datetimeparser)
2067 case QMetaType::QDateTime:
2068 a1 = QDATETIMEEDIT_DATE_MIN.daysTo(arg1.toDate());
2069 a2 = QDATETIMEEDIT_DATE_MIN.daysTo(arg2.toDate());
2070 a1 += arg1.toDateTime().time().msecsSinceStartOfDay() / (36e5 * 24);
2071 a2 += arg2.toDateTime().time().msecsSinceStartOfDay() / (36e5 * 24);
2072 break;
2073#endif // datetimeparser
2074 default: break;
2075 }
2076
2077 return (a1 != 0 && a2 != 0) ? (a1 / a2) : 0.0;
2078}
2079
2080int QAbstractSpinBoxPrivate::variantCompare(const QVariant &arg1, const QVariant &arg2)
2081{
2082 switch (arg2.userType()) {
2083 case QMetaType::QDate:
2084 Q_ASSERT_X(arg1.userType() == QMetaType::QDate, "QAbstractSpinBoxPrivate::variantCompare",
2085 qPrintable(QString::fromLatin1("Internal error 1 (%1)").
2086 arg(QString::fromLatin1(arg1.typeName()))));
2087 if (arg1.toDate() == arg2.toDate()) {
2088 return 0;
2089 } else if (arg1.toDate() < arg2.toDate()) {
2090 return -1;
2091 } else {
2092 return 1;
2093 }
2094 case QMetaType::QTime:
2095 Q_ASSERT_X(arg1.userType() == QMetaType::QTime, "QAbstractSpinBoxPrivate::variantCompare",
2096 qPrintable(QString::fromLatin1("Internal error 2 (%1)").
2097 arg(QString::fromLatin1(arg1.typeName()))));
2098 if (arg1.toTime() == arg2.toTime()) {
2099 return 0;
2100 } else if (arg1.toTime() < arg2.toTime()) {
2101 return -1;
2102 } else {
2103 return 1;
2104 }
2105
2106
2107 case QMetaType::QDateTime:
2108 if (arg1.toDateTime() == arg2.toDateTime()) {
2109 return 0;
2110 } else if (arg1.toDateTime() < arg2.toDateTime()) {
2111 return -1;
2112 } else {
2113 return 1;
2114 }
2115 case QMetaType::Int:
2116 if (arg1.toInt() == arg2.toInt()) {
2117 return 0;
2118 } else if (arg1.toInt() < arg2.toInt()) {
2119 return -1;
2120 } else {
2121 return 1;
2122 }
2123 case QMetaType::Double:
2124 if (arg1.toDouble() == arg2.toDouble()) {
2125 return 0;
2126 } else if (arg1.toDouble() < arg2.toDouble()) {
2127 return -1;
2128 } else {
2129 return 1;
2130 }
2131 case QMetaType::UnknownType:
2132 if (arg2.userType() == QMetaType::UnknownType)
2133 return 0;
2134 Q_FALLTHROUGH();
2135 default:
2136 Q_ASSERT_X(0, "QAbstractSpinBoxPrivate::variantCompare",
2137 qPrintable(QString::fromLatin1("Internal error 3 (%1 %2)").
2138 arg(QString::fromLatin1(arg1.typeName()),
2139 QString::fromLatin1(arg2.typeName()))));
2140 }
2141 return -2;
2142}
2143
2144QVariant QAbstractSpinBoxPrivate::variantBound(const QVariant &min,
2145 const QVariant &value,
2146 const QVariant &max)
2147{
2148 Q_ASSERT(variantCompare(min, max) <= 0);
2149 if (variantCompare(min, value) < 0) {
2150 const int compMax = variantCompare(value, max);
2151 return (compMax < 0 ? value : max);
2152 } else {
2153 return min;
2154 }
2155}
2156
2157
2158QT_END_NAMESPACE
2159
2160#include "moc_qabstractspinbox.cpp"
Combined button and popup list for selecting options.
QVariant operator*(const QVariant &arg1, double multiplier)
QByteArray operator+(const QByteArray &a1, const QByteArray &a2)
Definition qbytearray.h:705
#define Q_STATIC_LOGGING_CATEGORY(name,...)
QMatrix4x4 operator-(const QMatrix4x4 &m1, const QMatrix4x4 &m2)
Definition qmatrix4x4.h:581
QPointF operator/(const QPointF &p1, const QPointF &p2)
Definition qscroller.cpp:91