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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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