1// Copyright (C) 2017 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
4#include "qquickspinbox_p.h"
5#include "qquickcontrol_p_p.h"
6#include "qquickindicatorbutton_p.h"
7#include "qquickdeferredexecute_p_p.h"
8
9#include <QtGui/qguiapplication.h>
10#include <QtGui/qstylehints.h>
11
12#include <QtQml/qqmlinfo.h>
13#if QT_CONFIG(qml_locale)
14#include <QtQml/private/qqmllocale_p.h>
15#endif
16#include <QtQml/private/qqmlengine_p.h>
17#include <QtQuick/private/qquicktextinput_p.h>
18
19QT_BEGIN_NAMESPACE
20
21// copied from qabstractbutton.cpp
22static const int AUTO_REPEAT_DELAY = 300;
23static const int AUTO_REPEAT_INTERVAL = 100;
24
25/*!
26 \qmltype SpinBox
27 \inherits Control
28//! \instantiates QQuickSpinBox
29 \inqmlmodule QtQuick.Controls
30 \since 5.7
31 \ingroup input
32 \ingroup qtquickcontrols-focusscopes
33 \brief Allows the user to select from a set of preset values.
34
35 \image qtquickcontrols-spinbox.png
36
37 SpinBox allows the user to choose an integer value by clicking the up
38 or down indicator buttons, or by pressing up or down on the keyboard.
39 Optionally, SpinBox can be also made \l editable, so the user can enter
40 a text value in the input field.
41
42 By default, SpinBox provides discrete values in the range of \c [0-99]
43 with a \l stepSize of \c 1.
44
45 \snippet qtquickcontrols-spinbox.qml 1
46
47 \section2 Custom Values
48
49 \image qtquickcontrols-spinbox-textual.png
50
51 Even though SpinBox works on integer values, it can be customized to
52 accept arbitrary input values. The following snippet demonstrates how
53 \l validator, \l textFromValue and \l valueFromText can be used to
54 customize the default behavior.
55
56 \snippet qtquickcontrols-spinbox-textual.qml 1
57
58 In the same manner, SpinBox can be customized to accept floating point
59 numbers:
60
61 \image qtquickcontrols-spinbox-double.png
62
63 \snippet qtquickcontrols-spinbox-double.qml 1
64
65 \sa Tumbler, {Customizing SpinBox}, {Focus Management in Qt Quick Controls}
66*/
67
68/*!
69 \since QtQuick.Controls 2.2 (Qt 5.9)
70 \qmlsignal QtQuick.Controls::SpinBox::valueModified()
71
72 This signal is emitted when the spin box value has been interactively
73 modified by the user by either touch, mouse, wheel, or keys.
74 In the case of interaction via keyboard, the signal is only emitted
75 when the text has been accepted; meaning when the enter or return keys
76 are pressed, or the input field loses focus.
77*/
78
79class QQuickSpinBoxPrivate : public QQuickControlPrivate
80{
81 Q_DECLARE_PUBLIC(QQuickSpinBox)
82
83public:
84 int boundValue(int value, bool wrap) const;
85 void updateValue();
86 bool setValue(int value, bool wrap, bool modified);
87 bool stepBy(int steps, bool modified);
88 void increase(bool modified);
89 void decrease(bool modified);
90
91 int effectiveStepSize() const;
92
93 void updateDisplayText();
94 void setDisplayText(const QString &displayText);
95 void contentItemTextChanged();
96
97 bool upEnabled() const;
98 void updateUpEnabled();
99 bool downEnabled() const;
100 void updateDownEnabled();
101 void updateHover(const QPointF &pos);
102
103 void startRepeatDelay();
104 void startPressRepeat();
105 void stopPressRepeat();
106
107 bool handlePress(const QPointF &point, ulong timestamp) override;
108 bool handleMove(const QPointF &point, ulong timestamp) override;
109 bool handleRelease(const QPointF &point, ulong timestamp) override;
110 void handleUngrab() override;
111
112 void itemImplicitWidthChanged(QQuickItem *item) override;
113 void itemImplicitHeightChanged(QQuickItem *item) override;
114
115 QString evaluateTextFromValue(int val) const;
116 int evaluateValueFromText(const QString &text) const;
117
118 QPalette defaultPalette() const override { return QQuickTheme::palette(scope: QQuickTheme::SpinBox); }
119
120 bool editable = false;
121 bool live = false;
122 bool wrap = false;
123 int from = 0;
124 int to = 99;
125 int value = 0;
126 int stepSize = 1;
127 int delayTimer = 0;
128 int repeatTimer = 0;
129 QString displayText;
130 QQuickIndicatorButton *up = nullptr;
131 QQuickIndicatorButton *down = nullptr;
132#if QT_CONFIG(validator)
133 QValidator *validator = nullptr;
134#endif
135 mutable QJSValue textFromValue;
136 mutable QJSValue valueFromText;
137 Qt::InputMethodHints inputMethodHints = Qt::ImhDigitsOnly;
138};
139
140int QQuickSpinBoxPrivate::boundValue(int value, bool wrap) const
141{
142 bool inverted = from > to;
143 if (!wrap)
144 return inverted ? qBound(min: to, val: value, max: from) : qBound(min: from, val: value, max: to);
145
146 int f = inverted ? to : from;
147 int t = inverted ? from : to;
148 if (value < f)
149 value = t;
150 else if (value > t)
151 value = f;
152
153 return value;
154}
155
156void QQuickSpinBoxPrivate::updateValue()
157{
158 if (contentItem) {
159 QVariant text = contentItem->property(name: "text");
160 if (text.isValid()) {
161 setValue(value: evaluateValueFromText(text: text.toString()), /* allowWrap = */ wrap: false, /* modified = */ true);
162 }
163 }
164}
165
166// modified indicates if the value was modified by the user and not programatically
167// this is then passed on to updateDisplayText to indicate that the user has modified
168// the value so it may need to trigger an update of the contentItem's text too
169
170bool QQuickSpinBoxPrivate::setValue(int newValue, bool allowWrap, bool modified)
171{
172 Q_Q(QQuickSpinBox);
173 int correctedValue = newValue;
174 if (q->isComponentComplete())
175 correctedValue = boundValue(value: newValue, wrap: allowWrap);
176
177 if (!modified && newValue == correctedValue && newValue == value)
178 return false;
179
180 const bool emitSignals = (value != correctedValue);
181 value = correctedValue;
182
183 updateDisplayText();
184 updateUpEnabled();
185 updateDownEnabled();
186
187 // Only emit the signals if the corrected value is not the same as the
188 // original value to avoid unnecessary updates
189 if (emitSignals) {
190 emit q->valueChanged();
191 if (modified)
192 emit q->valueModified();
193 }
194 return true;
195}
196
197bool QQuickSpinBoxPrivate::stepBy(int steps, bool modified)
198{
199 return setValue(newValue: value + steps, allowWrap: wrap, modified);
200}
201
202void QQuickSpinBoxPrivate::increase(bool modified)
203{
204 setValue(newValue: value + effectiveStepSize(), allowWrap: wrap, modified);
205}
206
207void QQuickSpinBoxPrivate::decrease(bool modified)
208{
209 setValue(newValue: value - effectiveStepSize(), allowWrap: wrap, modified);
210}
211
212int QQuickSpinBoxPrivate::effectiveStepSize() const
213{
214 return from > to ? -1 * stepSize : stepSize;
215}
216
217void QQuickSpinBoxPrivate::updateDisplayText()
218{
219 setDisplayText(evaluateTextFromValue(val: value));
220}
221
222void QQuickSpinBoxPrivate::setDisplayText(const QString &text)
223{
224 Q_Q(QQuickSpinBox);
225
226 if (displayText == text)
227 return;
228
229 displayText = text;
230 emit q->displayTextChanged();
231}
232
233void QQuickSpinBoxPrivate::contentItemTextChanged()
234{
235 Q_Q(QQuickSpinBox);
236
237 QQuickTextInput *inputTextItem = qobject_cast<QQuickTextInput *>(object: q->contentItem());
238 if (!inputTextItem)
239 return;
240 QString text = inputTextItem->text();
241 validator->fixup(text);
242
243 if (live) {
244 const int enteredVal = evaluateValueFromText(text);
245 const int correctedValue = boundValue(value: enteredVal, wrap: false);
246 if (correctedValue == enteredVal && correctedValue != value) {
247 // If live is true and the text is valid change the value
248 // setValue will set the displayText for us.
249 q->setValue(correctedValue);
250 return;
251 }
252 }
253 // If live is false or the value is not valid, just set the displayText
254 setDisplayText(text);
255}
256
257bool QQuickSpinBoxPrivate::upEnabled() const
258{
259 const QQuickItem *upIndicator = up->indicator();
260 return upIndicator && upIndicator->isEnabled();
261}
262
263void QQuickSpinBoxPrivate::updateUpEnabled()
264{
265 QQuickItem *upIndicator = up->indicator();
266 if (!upIndicator)
267 return;
268
269 upIndicator->setEnabled(wrap || (from < to ? value < to : value > to));
270}
271
272bool QQuickSpinBoxPrivate::downEnabled() const
273{
274 const QQuickItem *downIndicator = down->indicator();
275 return downIndicator && downIndicator->isEnabled();
276}
277
278void QQuickSpinBoxPrivate::updateDownEnabled()
279{
280 QQuickItem *downIndicator = down->indicator();
281 if (!downIndicator)
282 return;
283
284 downIndicator->setEnabled(wrap || (from < to ? value > from : value < from));
285}
286
287void QQuickSpinBoxPrivate::updateHover(const QPointF &pos)
288{
289 Q_Q(QQuickSpinBox);
290 QQuickItem *ui = up->indicator();
291 QQuickItem *di = down->indicator();
292 up->setHovered(ui && ui->isEnabled() && ui->contains(point: q->mapToItem(item: ui, point: pos)));
293 down->setHovered(di && di->isEnabled() && di->contains(point: q->mapToItem(item: di, point: pos)));
294}
295
296void QQuickSpinBoxPrivate::startRepeatDelay()
297{
298 Q_Q(QQuickSpinBox);
299 stopPressRepeat();
300 delayTimer = q->startTimer(interval: AUTO_REPEAT_DELAY);
301}
302
303void QQuickSpinBoxPrivate::startPressRepeat()
304{
305 Q_Q(QQuickSpinBox);
306 stopPressRepeat();
307 repeatTimer = q->startTimer(interval: AUTO_REPEAT_INTERVAL);
308}
309
310void QQuickSpinBoxPrivate::stopPressRepeat()
311{
312 Q_Q(QQuickSpinBox);
313 if (delayTimer > 0) {
314 q->killTimer(id: delayTimer);
315 delayTimer = 0;
316 }
317 if (repeatTimer > 0) {
318 q->killTimer(id: repeatTimer);
319 repeatTimer = 0;
320 }
321}
322
323bool QQuickSpinBoxPrivate::handlePress(const QPointF &point, ulong timestamp)
324{
325 Q_Q(QQuickSpinBox);
326 QQuickControlPrivate::handlePress(point, timestamp);
327 QQuickItem *ui = up->indicator();
328 QQuickItem *di = down->indicator();
329 up->setPressed(ui && ui->isEnabled() && ui->contains(point: ui->mapFromItem(item: q, point)));
330 down->setPressed(di && di->isEnabled() && di->contains(point: di->mapFromItem(item: q, point)));
331
332 bool pressed = up->isPressed() || down->isPressed();
333 q->setAccessibleProperty(propertyName: "pressed", value: pressed);
334 if (pressed)
335 startRepeatDelay();
336 return true;
337}
338
339bool QQuickSpinBoxPrivate::handleMove(const QPointF &point, ulong timestamp)
340{
341 Q_Q(QQuickSpinBox);
342 QQuickControlPrivate::handleMove(point, timestamp);
343 QQuickItem *ui = up->indicator();
344 QQuickItem *di = down->indicator();
345 up->setHovered(ui && ui->isEnabled() && ui->contains(point: ui->mapFromItem(item: q, point)));
346 up->setPressed(up->isHovered());
347 down->setHovered(di && di->isEnabled() && di->contains(point: di->mapFromItem(item: q, point)));
348 down->setPressed(down->isHovered());
349
350 bool pressed = up->isPressed() || down->isPressed();
351 q->setAccessibleProperty(propertyName: "pressed", value: pressed);
352 if (!pressed)
353 stopPressRepeat();
354 return true;
355}
356
357bool QQuickSpinBoxPrivate::handleRelease(const QPointF &point, ulong timestamp)
358{
359 Q_Q(QQuickSpinBox);
360 QQuickControlPrivate::handleRelease(point, timestamp);
361 QQuickItem *ui = up->indicator();
362 QQuickItem *di = down->indicator();
363
364 int oldValue = value;
365 if (up->isPressed()) {
366 if (repeatTimer <= 0 && ui && ui->contains(point: ui->mapFromItem(item: q, point)))
367 q->increase();
368 // Retain pressed state until after increasing is done in case user code binds stepSize
369 // to up/down.pressed.
370 up->setPressed(false);
371 } else if (down->isPressed()) {
372 if (repeatTimer <= 0 && di && di->contains(point: di->mapFromItem(item: q, point)))
373 q->decrease();
374 down->setPressed(false);
375 }
376 if (value != oldValue)
377 emit q->valueModified();
378
379 q->setAccessibleProperty(propertyName: "pressed", value: false);
380 stopPressRepeat();
381 return true;
382}
383
384void QQuickSpinBoxPrivate::handleUngrab()
385{
386 Q_Q(QQuickSpinBox);
387 QQuickControlPrivate::handleUngrab();
388 up->setPressed(false);
389 down->setPressed(false);
390
391 q->setAccessibleProperty(propertyName: "pressed", value: false);
392 stopPressRepeat();
393}
394
395void QQuickSpinBoxPrivate::itemImplicitWidthChanged(QQuickItem *item)
396{
397 QQuickControlPrivate::itemImplicitWidthChanged(item);
398 if (item == up->indicator())
399 emit up->implicitIndicatorWidthChanged();
400 else if (item == down->indicator())
401 emit down->implicitIndicatorWidthChanged();
402}
403
404void QQuickSpinBoxPrivate::itemImplicitHeightChanged(QQuickItem *item)
405{
406 QQuickControlPrivate::itemImplicitHeightChanged(item);
407 if (item == up->indicator())
408 emit up->implicitIndicatorHeightChanged();
409 else if (item == down->indicator())
410 emit down->implicitIndicatorHeightChanged();
411}
412
413
414QString QQuickSpinBoxPrivate::evaluateTextFromValue(int val) const
415{
416 Q_Q(const QQuickSpinBox);
417
418 QString text;
419 QQmlEngine *engine = qmlEngine(q);
420 if (engine && textFromValue.isCallable()) {
421 QJSValue loc;
422#if QT_CONFIG(qml_locale)
423 QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(e: engine);
424 loc = QJSValuePrivate::fromReturnedValue(d: QQmlLocale::wrap(engine: v4, locale));
425#endif
426 text = textFromValue.call(args: QJSValueList() << val << loc).toString();
427 } else {
428 text = locale.toString(i: val);
429 }
430 return text;
431}
432
433int QQuickSpinBoxPrivate::evaluateValueFromText(const QString &text) const
434{
435 Q_Q(const QQuickSpinBox);
436 int value;
437 QQmlEngine *engine = qmlEngine(q);
438 if (engine && valueFromText.isCallable()) {
439 QJSValue loc;
440#if QT_CONFIG(qml_locale)
441 QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(e: engine);
442 loc = QJSValuePrivate::fromReturnedValue(d: QQmlLocale::wrap(engine: v4, locale));
443#endif
444 value = valueFromText.call(args: QJSValueList() << text << loc).toInt();
445 } else {
446 value = locale.toInt(s: text);
447 }
448 return value;
449}
450
451QQuickSpinBox::QQuickSpinBox(QQuickItem *parent)
452 : QQuickControl(*(new QQuickSpinBoxPrivate), parent)
453{
454 Q_D(QQuickSpinBox);
455 d->up = new QQuickIndicatorButton(this);
456 d->down = new QQuickIndicatorButton(this);
457
458 setFlag(flag: ItemIsFocusScope);
459 setFiltersChildMouseEvents(true);
460 setAcceptedMouseButtons(Qt::LeftButton);
461#if QT_CONFIG(cursor)
462 setCursor(Qt::ArrowCursor);
463#endif
464}
465
466QQuickSpinBox::~QQuickSpinBox()
467{
468 Q_D(QQuickSpinBox);
469 d->removeImplicitSizeListener(item: d->up->indicator());
470 d->removeImplicitSizeListener(item: d->down->indicator());
471}
472
473/*!
474 \qmlproperty int QtQuick.Controls::SpinBox::from
475
476 This property holds the starting value for the range. The default value is \c 0.
477
478 \sa to, value
479*/
480int QQuickSpinBox::from() const
481{
482 Q_D(const QQuickSpinBox);
483 return d->from;
484}
485
486void QQuickSpinBox::setFrom(int from)
487{
488 Q_D(QQuickSpinBox);
489 if (d->from == from)
490 return;
491
492 d->from = from;
493 emit fromChanged();
494 if (isComponentComplete()) {
495 if (!d->setValue(newValue: d->value, /* allowWrap = */ false, /* modified = */ false)) {
496 d->updateUpEnabled();
497 d->updateDownEnabled();
498 }
499 }
500}
501
502/*!
503 \qmlproperty int QtQuick.Controls::SpinBox::to
504
505 This property holds the end value for the range. The default value is \c 99.
506
507 \sa from, value
508*/
509int QQuickSpinBox::to() const
510{
511 Q_D(const QQuickSpinBox);
512 return d->to;
513}
514
515void QQuickSpinBox::setTo(int to)
516{
517 Q_D(QQuickSpinBox);
518 if (d->to == to)
519 return;
520
521 d->to = to;
522 emit toChanged();
523 if (isComponentComplete()) {
524 if (!d->setValue(newValue: d->value, /* allowWrap = */false, /* modified = */ false)) {
525 d->updateUpEnabled();
526 d->updateDownEnabled();
527 }
528 }
529}
530
531/*!
532 \qmlproperty int QtQuick.Controls::SpinBox::value
533
534 This property holds the value in the range \c from - \c to. The default value is \c 0.
535*/
536int QQuickSpinBox::value() const
537{
538 Q_D(const QQuickSpinBox);
539 return d->value;
540}
541
542void QQuickSpinBox::setValue(int value)
543{
544 Q_D(QQuickSpinBox);
545 d->setValue(newValue: value, /* allowWrap = */ false, /* modified = */ false);
546}
547
548/*!
549 \qmlproperty int QtQuick.Controls::SpinBox::stepSize
550
551 This property holds the step size. The default value is \c 1.
552
553 \sa increase(), decrease()
554*/
555int QQuickSpinBox::stepSize() const
556{
557 Q_D(const QQuickSpinBox);
558 return d->stepSize;
559}
560
561void QQuickSpinBox::setStepSize(int step)
562{
563 Q_D(QQuickSpinBox);
564 if (d->stepSize == step)
565 return;
566
567 d->stepSize = step;
568 emit stepSizeChanged();
569}
570
571/*!
572 \qmlproperty bool QtQuick.Controls::SpinBox::editable
573
574 This property holds whether the spinbox is editable. The default value is \c false.
575
576 \sa validator
577*/
578bool QQuickSpinBox::isEditable() const
579{
580 Q_D(const QQuickSpinBox);
581 return d->editable;
582}
583
584void QQuickSpinBox::setEditable(bool editable)
585{
586 Q_D(QQuickSpinBox);
587 if (d->editable == editable)
588 return;
589
590#if QT_CONFIG(cursor)
591 if (d->contentItem) {
592 if (editable)
593 d->contentItem->setCursor(Qt::IBeamCursor);
594 else
595 d->contentItem->unsetCursor();
596 }
597#endif
598
599 d->editable = editable;
600 setAccessibleProperty(propertyName: "editable", value: editable);
601 emit editableChanged();
602}
603
604/*!
605 \qmlproperty bool QtQuick.Controls::SpinBox::live
606 \since 6.6
607
608 This property holds whether the \l value is updated when the user edits the
609 \l displayText. The default value is \c false. If this property is \c true and
610 the value entered by the user is valid and within the bounds of the spinbox
611 [\l from, \l to], the value of the SpinBox will be set. If this property is
612 \c false or the value entered by the user is outside the boundaries, the
613 value will not be updated until the enter or return keys are pressed, or the
614 input field loses focus.
615
616 \sa editable, displayText
617*/
618bool QQuickSpinBox::isLive() const
619{
620 Q_D(const QQuickSpinBox);
621 return d->live;
622}
623
624void QQuickSpinBox::setLive(bool live)
625{
626 Q_D(QQuickSpinBox);
627 if (d->live == live)
628 return;
629
630 d->live = live;
631
632 //make sure to update the value when changing to live
633 if (live)
634 d->contentItemTextChanged();
635
636 emit liveChanged();
637}
638
639#if QT_CONFIG(validator)
640/*!
641 \qmlproperty Validator QtQuick.Controls::SpinBox::validator
642
643 This property holds the input text validator for editable spinboxes. By
644 default, SpinBox uses \l IntValidator to accept input of integer numbers.
645
646 \code
647 SpinBox {
648 id: control
649 validator: IntValidator {
650 locale: control.locale.name
651 bottom: Math.min(control.from, control.to)
652 top: Math.max(control.from, control.to)
653 }
654 }
655 \endcode
656
657 \sa editable, textFromValue, valueFromText, {Control::locale}{locale},
658 {Validating Input Text}
659*/
660QValidator *QQuickSpinBox::validator() const
661{
662 Q_D(const QQuickSpinBox);
663 return d->validator;
664}
665
666void QQuickSpinBox::setValidator(QValidator *validator)
667{
668 Q_D(QQuickSpinBox);
669 if (d->validator == validator)
670 return;
671
672 d->validator = validator;
673 emit validatorChanged();
674}
675#endif
676
677/*!
678 \qmlproperty function QtQuick.Controls::SpinBox::textFromValue
679
680 This property holds a callback function that is called whenever
681 an integer value needs to be converted to display text.
682
683 The default function can be overridden to display custom text for a given
684 value. This applies to both editable and non-editable spinboxes;
685 for example, when using the up and down buttons or a mouse wheel to
686 increment and decrement the value, the new value is converted to display
687 text using this function.
688
689 The callback function signature is \c {string function(value, locale)}.
690 The function can have one or two arguments, where the first argument
691 is the value to be converted, and the optional second argument is the
692 locale that should be used for the conversion, if applicable.
693
694 The default implementation does the conversion using
695 \l {QtQml::Number::toLocaleString()}{Number.toLocaleString}():
696
697 \code
698 textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 0); }
699 \endcode
700
701 \note When applying a custom \c textFromValue implementation for editable
702 spinboxes, a matching \l valueFromText implementation must be provided
703 to be able to convert the custom text back to an integer value.
704
705 \sa valueFromText, validator, {Control::locale}{locale}
706*/
707QJSValue QQuickSpinBox::textFromValue() const
708{
709 Q_D(const QQuickSpinBox);
710 if (!d->textFromValue.isCallable()) {
711 QQmlEngine *engine = qmlEngine(this);
712 if (engine)
713 d->textFromValue = engine->evaluate(QStringLiteral("(function(value, locale) { return Number(value).toLocaleString(locale, 'f', 0); })"));
714 }
715 return d->textFromValue;
716}
717
718void QQuickSpinBox::setTextFromValue(const QJSValue &callback)
719{
720 Q_D(QQuickSpinBox);
721 if (!callback.isCallable()) {
722 qmlWarning(me: this) << "textFromValue must be a callable function";
723 return;
724 }
725 d->textFromValue = callback;
726 emit textFromValueChanged();
727}
728
729/*!
730 \qmlproperty function QtQuick.Controls::SpinBox::valueFromText
731
732 This property holds a callback function that is called whenever
733 input text needs to be converted to an integer value.
734
735 This function only needs to be overridden when \l textFromValue
736 is overridden for an editable spinbox.
737
738 The callback function signature is \c {int function(text, locale)}.
739 The function can have one or two arguments, where the first argument
740 is the text to be converted, and the optional second argument is the
741 locale that should be used for the conversion, if applicable.
742
743 The default implementation does the conversion using \l {QtQml::Locale}{Number.fromLocaleString()}:
744
745 \code
746 valueFromText: function(text, locale) { return Number.fromLocaleString(locale, text); }
747 \endcode
748
749 \note When applying a custom \l textFromValue implementation for editable
750 spinboxes, a matching \c valueFromText implementation must be provided
751 to be able to convert the custom text back to an integer value.
752
753 \sa textFromValue, validator, {Control::locale}{locale}
754*/
755QJSValue QQuickSpinBox::valueFromText() const
756{
757 Q_D(const QQuickSpinBox);
758 if (!d->valueFromText.isCallable()) {
759 QQmlEngine *engine = qmlEngine(this);
760 if (engine)
761 d->valueFromText = engine->evaluate(QStringLiteral("(function(text, locale) { return Number.fromLocaleString(locale, text); })"));
762 }
763 return d->valueFromText;
764}
765
766void QQuickSpinBox::setValueFromText(const QJSValue &callback)
767{
768 Q_D(QQuickSpinBox);
769 if (!callback.isCallable()) {
770 qmlWarning(me: this) << "valueFromText must be a callable function";
771 return;
772 }
773 d->valueFromText = callback;
774 emit valueFromTextChanged();
775}
776
777/*!
778 \qmlproperty bool QtQuick.Controls::SpinBox::up.pressed
779 \qmlproperty Item QtQuick.Controls::SpinBox::up.indicator
780 \qmlproperty bool QtQuick.Controls::SpinBox::up.hovered
781 \qmlproperty real QtQuick.Controls::SpinBox::up.implicitIndicatorWidth
782 \qmlproperty real QtQuick.Controls::SpinBox::up.implicitIndicatorHeight
783
784 These properties hold the up indicator item and whether it is pressed or
785 hovered. The \c up.hovered property was introduced in QtQuick.Controls 2.1,
786 and the \c up.implicitIndicatorWidth and \c up.implicitIndicatorHeight
787 properties were introduced in QtQuick.Controls 2.5.
788
789 \sa increase()
790*/
791QQuickIndicatorButton *QQuickSpinBox::up() const
792{
793 Q_D(const QQuickSpinBox);
794 return d->up;
795}
796
797/*!
798 \qmlproperty bool QtQuick.Controls::SpinBox::down.pressed
799 \qmlproperty Item QtQuick.Controls::SpinBox::down.indicator
800 \qmlproperty bool QtQuick.Controls::SpinBox::down.hovered
801 \qmlproperty real QtQuick.Controls::SpinBox::down.implicitIndicatorWidth
802 \qmlproperty real QtQuick.Controls::SpinBox::down.implicitIndicatorHeight
803
804 These properties hold the down indicator item and whether it is pressed or
805 hovered. The \c down.hovered property was introduced in QtQuick.Controls 2.1,
806 and the \c down.implicitIndicatorWidth and \c down.implicitIndicatorHeight
807 properties were introduced in QtQuick.Controls 2.5.
808
809 \sa decrease()
810*/
811QQuickIndicatorButton *QQuickSpinBox::down() const
812{
813 Q_D(const QQuickSpinBox);
814 return d->down;
815}
816
817/*!
818 \since QtQuick.Controls 2.2 (Qt 5.9)
819 \qmlproperty flags QtQuick.Controls::SpinBox::inputMethodHints
820
821 This property provides hints to the input method about the expected content
822 of the spin box and how it should operate.
823
824 The default value is \c Qt.ImhDigitsOnly.
825
826 \include inputmethodhints.qdocinc
827*/
828Qt::InputMethodHints QQuickSpinBox::inputMethodHints() const
829{
830 Q_D(const QQuickSpinBox);
831 return d->inputMethodHints;
832}
833
834void QQuickSpinBox::setInputMethodHints(Qt::InputMethodHints hints)
835{
836 Q_D(QQuickSpinBox);
837 if (d->inputMethodHints == hints)
838 return;
839
840 d->inputMethodHints = hints;
841 emit inputMethodHintsChanged();
842}
843
844/*!
845 \since QtQuick.Controls 2.2 (Qt 5.9)
846 \qmlproperty bool QtQuick.Controls::SpinBox::inputMethodComposing
847 \readonly
848
849 This property holds whether an editable spin box has partial text input from an input method.
850
851 While it is composing, an input method may rely on mouse or key events from the spin box to
852 edit or commit the partial text. This property can be used to determine when to disable event
853 handlers that may interfere with the correct operation of an input method.
854*/
855bool QQuickSpinBox::isInputMethodComposing() const
856{
857 Q_D(const QQuickSpinBox);
858 return d->contentItem && d->contentItem->property(name: "inputMethodComposing").toBool();
859}
860
861/*!
862 \since QtQuick.Controls 2.3 (Qt 5.10)
863 \qmlproperty bool QtQuick.Controls::SpinBox::wrap
864
865 This property holds whether the spinbox wraps. The default value is \c false.
866
867 If wrap is \c true, stepping past \l to changes the value to \l from and vice versa.
868*/
869bool QQuickSpinBox::wrap() const
870{
871 Q_D(const QQuickSpinBox);
872 return d->wrap;
873}
874
875void QQuickSpinBox::setWrap(bool wrap)
876{
877 Q_D(QQuickSpinBox);
878 if (d->wrap == wrap)
879 return;
880
881 d->wrap = wrap;
882 if (d->value == d->from || d->value == d->to) {
883 d->updateUpEnabled();
884 d->updateDownEnabled();
885 }
886 emit wrapChanged();
887}
888
889/*!
890 \since QtQuick.Controls 2.4 (Qt 5.11)
891 \qmlproperty string QtQuick.Controls::SpinBox::displayText
892 \readonly
893
894 This property holds the textual value of the spinbox.
895
896 The value of the property is based on \l textFromValue and \l {Control::}
897 {locale}, and equal to:
898 \badcode
899 var text = spinBox.textFromValue(spinBox.value, spinBox.locale)
900 \endcode
901
902 \sa textFromValue
903*/
904QString QQuickSpinBox::displayText() const
905{
906 Q_D(const QQuickSpinBox);
907 return d->displayText;
908}
909
910/*!
911 \qmlmethod void QtQuick.Controls::SpinBox::increase()
912
913 Increases the value by \l stepSize, or \c 1 if stepSize is not defined.
914
915 \sa stepSize
916*/
917void QQuickSpinBox::increase()
918{
919 Q_D(QQuickSpinBox);
920 d->increase(modified: false);
921}
922
923/*!
924 \qmlmethod void QtQuick.Controls::SpinBox::decrease()
925
926 Decreases the value by \l stepSize, or \c 1 if stepSize is not defined.
927
928 \sa stepSize
929*/
930void QQuickSpinBox::decrease()
931{
932 Q_D(QQuickSpinBox);
933 d->decrease(modified: false);
934}
935
936void QQuickSpinBox::focusInEvent(QFocusEvent *event)
937{
938 Q_D(QQuickSpinBox);
939 QQuickControl::focusInEvent(event);
940
941 // When an editable SpinBox gets focus, it must pass on the focus to its editor.
942 if (d->editable && d->contentItem && !d->contentItem->hasActiveFocus())
943 d->contentItem->forceActiveFocus(reason: event->reason());
944}
945
946void QQuickSpinBox::hoverEnterEvent(QHoverEvent *event)
947{
948 Q_D(QQuickSpinBox);
949 QQuickControl::hoverEnterEvent(event);
950 d->updateHover(pos: event->position());
951 event->ignore();
952}
953
954void QQuickSpinBox::hoverMoveEvent(QHoverEvent *event)
955{
956 Q_D(QQuickSpinBox);
957 QQuickControl::hoverMoveEvent(event);
958 d->updateHover(pos: event->position());
959 event->ignore();
960}
961
962void QQuickSpinBox::hoverLeaveEvent(QHoverEvent *event)
963{
964 Q_D(QQuickSpinBox);
965 QQuickControl::hoverLeaveEvent(event);
966 d->down->setHovered(false);
967 d->up->setHovered(false);
968 event->ignore();
969}
970
971void QQuickSpinBox::keyPressEvent(QKeyEvent *event)
972{
973 Q_D(QQuickSpinBox);
974 QQuickControl::keyPressEvent(event);
975
976 switch (event->key()) {
977 case Qt::Key_Up:
978 if (d->upEnabled()) {
979 // Update the pressed state before increasing/decreasing in case user code binds
980 // stepSize to up/down.pressed.
981 d->up->setPressed(true);
982 d->increase(modified: true);
983 event->accept();
984 }
985 break;
986
987 case Qt::Key_Down:
988 if (d->downEnabled()) {
989 d->down->setPressed(true);
990 d->decrease(modified: true);
991 event->accept();
992 }
993 break;
994
995 default:
996 break;
997 }
998
999 setAccessibleProperty(propertyName: "pressed", value: d->up->isPressed() || d->down->isPressed());
1000}
1001
1002void QQuickSpinBox::keyReleaseEvent(QKeyEvent *event)
1003{
1004 Q_D(QQuickSpinBox);
1005 QQuickControl::keyReleaseEvent(event);
1006
1007 if (d->editable && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return))
1008 d->updateValue();
1009
1010 d->up->setPressed(false);
1011 d->down->setPressed(false);
1012 setAccessibleProperty(propertyName: "pressed", value: false);
1013}
1014
1015void QQuickSpinBox::timerEvent(QTimerEvent *event)
1016{
1017 Q_D(QQuickSpinBox);
1018 QQuickControl::timerEvent(event);
1019 if (event->timerId() == d->delayTimer) {
1020 d->startPressRepeat();
1021 } else if (event->timerId() == d->repeatTimer) {
1022 if (d->up->isPressed())
1023 d->increase(modified: true);
1024 else if (d->down->isPressed())
1025 d->decrease(modified: true);
1026 }
1027}
1028
1029#if QT_CONFIG(wheelevent)
1030void QQuickSpinBox::wheelEvent(QWheelEvent *event)
1031{
1032 Q_D(QQuickSpinBox);
1033 QQuickControl::wheelEvent(event);
1034 if (d->wheelEnabled) {
1035 const QPointF angle = event->angleDelta();
1036 const qreal delta = (qFuzzyIsNull(d: angle.y()) ? angle.x() : angle.y()) / int(QWheelEvent::DefaultDeltasPerStep);
1037 d->stepBy(steps: qRound(d: d->effectiveStepSize() * delta), modified: true);
1038 }
1039}
1040#endif
1041
1042void QQuickSpinBox::classBegin()
1043{
1044 Q_D(QQuickSpinBox);
1045 QQuickControl::classBegin();
1046
1047 QQmlContext *context = qmlContext(this);
1048 if (context) {
1049 QQmlEngine::setContextForObject(d->up, context);
1050 QQmlEngine::setContextForObject(d->down, context);
1051 }
1052}
1053
1054void QQuickSpinBox::componentComplete()
1055{
1056 Q_D(QQuickSpinBox);
1057 QQuickIndicatorButtonPrivate::get(button: d->up)->executeIndicator(complete: true);
1058 QQuickIndicatorButtonPrivate::get(button: d->down)->executeIndicator(complete: true);
1059
1060 QQuickControl::componentComplete();
1061 if (!d->setValue(newValue: d->value, /* allowWrap = */ false, /* modified = */ false)) {
1062 d->updateDisplayText();
1063 d->updateUpEnabled();
1064 d->updateDownEnabled();
1065 }
1066}
1067
1068void QQuickSpinBox::itemChange(ItemChange change, const ItemChangeData &value)
1069{
1070 Q_D(QQuickSpinBox);
1071 QQuickControl::itemChange(change, value);
1072 if (d->editable && change == ItemActiveFocusHasChanged && !value.boolValue)
1073 d->updateValue();
1074}
1075
1076void QQuickSpinBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
1077{
1078 Q_D(QQuickSpinBox);
1079 if (QQuickTextInput *oldInput = qobject_cast<QQuickTextInput *>(object: oldItem)) {
1080 disconnect(sender: oldInput, signal: &QQuickTextInput::inputMethodComposingChanged, receiver: this, slot: &QQuickSpinBox::inputMethodComposingChanged);
1081 QObjectPrivate::disconnect(sender: oldInput, signal: &QQuickTextInput::textChanged, receiverPrivate: d, slot: &QQuickSpinBoxPrivate::contentItemTextChanged);
1082 }
1083
1084 if (newItem) {
1085 newItem->setActiveFocusOnTab(true);
1086 if (d->activeFocus)
1087 newItem->forceActiveFocus(reason: d->focusReason);
1088#if QT_CONFIG(cursor)
1089 if (d->editable)
1090 newItem->setCursor(Qt::IBeamCursor);
1091#endif
1092
1093 if (QQuickTextInput *newInput = qobject_cast<QQuickTextInput *>(object: newItem)) {
1094 connect(sender: newInput, signal: &QQuickTextInput::inputMethodComposingChanged, context: this, slot: &QQuickSpinBox::inputMethodComposingChanged);
1095 QObjectPrivate::connect(sender: newInput, signal: &QQuickTextInput::textChanged, receiverPrivate: d, slot: &QQuickSpinBoxPrivate::contentItemTextChanged);
1096 }
1097 }
1098}
1099
1100void QQuickSpinBox::localeChange(const QLocale &newLocale, const QLocale &oldLocale)
1101{
1102 Q_D(QQuickSpinBox);
1103 QQuickControl::localeChange(newLocale, oldLocale);
1104 d->updateDisplayText();
1105}
1106
1107QFont QQuickSpinBox::defaultFont() const
1108{
1109 return QQuickTheme::font(scope: QQuickTheme::SpinBox);
1110}
1111
1112#if QT_CONFIG(accessibility)
1113QAccessible::Role QQuickSpinBox::accessibleRole() const
1114{
1115 return QAccessible::SpinBox;
1116}
1117
1118void QQuickSpinBox::accessibilityActiveChanged(bool active)
1119{
1120 Q_D(QQuickSpinBox);
1121 QQuickControl::accessibilityActiveChanged(active);
1122
1123 if (active)
1124 setAccessibleProperty(propertyName: "editable", value: d->editable);
1125}
1126#endif
1127
1128QT_END_NAMESPACE
1129
1130#include "moc_qquickspinbox_p.cpp"
1131

source code of qtdeclarative/src/quicktemplates/qquickspinbox.cpp