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