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 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | // copied from qabstractbutton.cpp |
22 | static const int AUTO_REPEAT_DELAY = 300; |
23 | static 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 | |
79 | class QQuickSpinBoxPrivate : public QQuickControlPrivate |
80 | { |
81 | Q_DECLARE_PUBLIC(QQuickSpinBox) |
82 | |
83 | public: |
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 | |
140 | int 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 | |
156 | void 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 | |
170 | bool 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 | |
197 | bool QQuickSpinBoxPrivate::stepBy(int steps, bool modified) |
198 | { |
199 | return setValue(newValue: value + steps, allowWrap: wrap, modified); |
200 | } |
201 | |
202 | void QQuickSpinBoxPrivate::increase(bool modified) |
203 | { |
204 | setValue(newValue: value + effectiveStepSize(), allowWrap: wrap, modified); |
205 | } |
206 | |
207 | void QQuickSpinBoxPrivate::decrease(bool modified) |
208 | { |
209 | setValue(newValue: value - effectiveStepSize(), allowWrap: wrap, modified); |
210 | } |
211 | |
212 | int QQuickSpinBoxPrivate::effectiveStepSize() const |
213 | { |
214 | return from > to ? -1 * stepSize : stepSize; |
215 | } |
216 | |
217 | void QQuickSpinBoxPrivate::updateDisplayText() |
218 | { |
219 | setDisplayText(evaluateTextFromValue(val: value)); |
220 | } |
221 | |
222 | void 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 | |
233 | void 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 | |
257 | bool QQuickSpinBoxPrivate::upEnabled() const |
258 | { |
259 | const QQuickItem *upIndicator = up->indicator(); |
260 | return upIndicator && upIndicator->isEnabled(); |
261 | } |
262 | |
263 | void 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 | |
272 | bool QQuickSpinBoxPrivate::downEnabled() const |
273 | { |
274 | const QQuickItem *downIndicator = down->indicator(); |
275 | return downIndicator && downIndicator->isEnabled(); |
276 | } |
277 | |
278 | void 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 | |
287 | void 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 | |
296 | void QQuickSpinBoxPrivate::startRepeatDelay() |
297 | { |
298 | Q_Q(QQuickSpinBox); |
299 | stopPressRepeat(); |
300 | delayTimer = q->startTimer(interval: AUTO_REPEAT_DELAY); |
301 | } |
302 | |
303 | void QQuickSpinBoxPrivate::startPressRepeat() |
304 | { |
305 | Q_Q(QQuickSpinBox); |
306 | stopPressRepeat(); |
307 | repeatTimer = q->startTimer(interval: AUTO_REPEAT_INTERVAL); |
308 | } |
309 | |
310 | void 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 | |
323 | bool 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 | |
339 | bool 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 | |
357 | bool 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 | |
384 | void 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 | |
395 | void 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 | |
404 | void 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 | |
414 | QString 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 | |
433 | int 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 | |
451 | QQuickSpinBox::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 | |
466 | QQuickSpinBox::~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 | */ |
480 | int QQuickSpinBox::from() const |
481 | { |
482 | Q_D(const QQuickSpinBox); |
483 | return d->from; |
484 | } |
485 | |
486 | void 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 | */ |
509 | int QQuickSpinBox::to() const |
510 | { |
511 | Q_D(const QQuickSpinBox); |
512 | return d->to; |
513 | } |
514 | |
515 | void 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 | */ |
536 | int QQuickSpinBox::value() const |
537 | { |
538 | Q_D(const QQuickSpinBox); |
539 | return d->value; |
540 | } |
541 | |
542 | void 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 | */ |
555 | int QQuickSpinBox::stepSize() const |
556 | { |
557 | Q_D(const QQuickSpinBox); |
558 | return d->stepSize; |
559 | } |
560 | |
561 | void 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 | */ |
578 | bool QQuickSpinBox::isEditable() const |
579 | { |
580 | Q_D(const QQuickSpinBox); |
581 | return d->editable; |
582 | } |
583 | |
584 | void 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 | */ |
618 | bool QQuickSpinBox::isLive() const |
619 | { |
620 | Q_D(const QQuickSpinBox); |
621 | return d->live; |
622 | } |
623 | |
624 | void 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 | */ |
660 | QValidator *QQuickSpinBox::validator() const |
661 | { |
662 | Q_D(const QQuickSpinBox); |
663 | return d->validator; |
664 | } |
665 | |
666 | void 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 | */ |
707 | QJSValue 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 | |
718 | void 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 | */ |
755 | QJSValue 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 | |
766 | void 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 | */ |
791 | QQuickIndicatorButton *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 | */ |
811 | QQuickIndicatorButton *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 | */ |
828 | Qt::InputMethodHints QQuickSpinBox::inputMethodHints() const |
829 | { |
830 | Q_D(const QQuickSpinBox); |
831 | return d->inputMethodHints; |
832 | } |
833 | |
834 | void 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 | */ |
855 | bool 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 | */ |
869 | bool QQuickSpinBox::wrap() const |
870 | { |
871 | Q_D(const QQuickSpinBox); |
872 | return d->wrap; |
873 | } |
874 | |
875 | void 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 | */ |
904 | QString 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 | */ |
917 | void 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 | */ |
930 | void QQuickSpinBox::decrease() |
931 | { |
932 | Q_D(QQuickSpinBox); |
933 | d->decrease(modified: false); |
934 | } |
935 | |
936 | void 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 | |
946 | void QQuickSpinBox::hoverEnterEvent(QHoverEvent *event) |
947 | { |
948 | Q_D(QQuickSpinBox); |
949 | QQuickControl::hoverEnterEvent(event); |
950 | d->updateHover(pos: event->position()); |
951 | event->ignore(); |
952 | } |
953 | |
954 | void QQuickSpinBox::hoverMoveEvent(QHoverEvent *event) |
955 | { |
956 | Q_D(QQuickSpinBox); |
957 | QQuickControl::hoverMoveEvent(event); |
958 | d->updateHover(pos: event->position()); |
959 | event->ignore(); |
960 | } |
961 | |
962 | void 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 | |
971 | void 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 | |
1002 | void 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 | |
1015 | void 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) |
1030 | void 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 | |
1042 | void 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 | |
1054 | void 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 | |
1068 | void 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 | |
1076 | void 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 | |
1100 | void QQuickSpinBox::localeChange(const QLocale &newLocale, const QLocale &oldLocale) |
1101 | { |
1102 | Q_D(QQuickSpinBox); |
1103 | QQuickControl::localeChange(newLocale, oldLocale); |
1104 | d->updateDisplayText(); |
1105 | } |
1106 | |
1107 | QFont QQuickSpinBox::defaultFont() const |
1108 | { |
1109 | return QQuickTheme::font(scope: QQuickTheme::SpinBox); |
1110 | } |
1111 | |
1112 | #if QT_CONFIG(accessibility) |
1113 | QAccessible::Role QQuickSpinBox::accessibleRole() const |
1114 | { |
1115 | return QAccessible::SpinBox; |
1116 | } |
1117 | |
1118 | void 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 | |
1128 | QT_END_NAMESPACE |
1129 | |
1130 | #include "moc_qquickspinbox_p.cpp" |
1131 | |