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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | // copied from qabstractbutton.cpp |
17 | static const int AUTO_REPEAT_DELAY = 300; |
18 | static 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 | |
78 | class QQuickSpinBoxPrivate : public QQuickControlPrivate |
79 | { |
80 | Q_DECLARE_PUBLIC(QQuickSpinBox) |
81 | |
82 | public: |
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 | |
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 | #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 | |
260 | bool QQuickSpinBoxPrivate::upEnabled() const |
261 | { |
262 | const QQuickItem *upIndicator = up->indicator(); |
263 | return upIndicator && upIndicator->isEnabled(); |
264 | } |
265 | |
266 | void 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 | |
275 | bool QQuickSpinBoxPrivate::downEnabled() const |
276 | { |
277 | const QQuickItem *downIndicator = down->indicator(); |
278 | return downIndicator && downIndicator->isEnabled(); |
279 | } |
280 | |
281 | void 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 | |
290 | void 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 | |
299 | void QQuickSpinBoxPrivate::startRepeatDelay() |
300 | { |
301 | Q_Q(QQuickSpinBox); |
302 | stopPressRepeat(); |
303 | delayTimer = q->startTimer(interval: AUTO_REPEAT_DELAY); |
304 | } |
305 | |
306 | void QQuickSpinBoxPrivate::startPressRepeat() |
307 | { |
308 | Q_Q(QQuickSpinBox); |
309 | stopPressRepeat(); |
310 | repeatTimer = q->startTimer(interval: AUTO_REPEAT_INTERVAL); |
311 | } |
312 | |
313 | void 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 | |
326 | bool 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 | |
342 | bool 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 | |
360 | bool 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 | |
387 | void 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 | |
398 | void 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 | |
407 | void 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 | |
416 | void 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 | |
426 | QString 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 | |
446 | int 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 | |
465 | QQuickSpinBox::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 | |
481 | QQuickSpinBox::~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 | */ |
495 | int QQuickSpinBox::from() const |
496 | { |
497 | Q_D(const QQuickSpinBox); |
498 | return d->from; |
499 | } |
500 | |
501 | void 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 | */ |
524 | int QQuickSpinBox::to() const |
525 | { |
526 | Q_D(const QQuickSpinBox); |
527 | return d->to; |
528 | } |
529 | |
530 | void 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 | */ |
551 | int QQuickSpinBox::value() const |
552 | { |
553 | Q_D(const QQuickSpinBox); |
554 | return d->value; |
555 | } |
556 | |
557 | void 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 | */ |
570 | int QQuickSpinBox::stepSize() const |
571 | { |
572 | Q_D(const QQuickSpinBox); |
573 | return d->stepSize; |
574 | } |
575 | |
576 | void 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 | */ |
593 | bool QQuickSpinBox::isEditable() const |
594 | { |
595 | Q_D(const QQuickSpinBox); |
596 | return d->editable; |
597 | } |
598 | |
599 | void 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 | */ |
633 | bool QQuickSpinBox::isLive() const |
634 | { |
635 | Q_D(const QQuickSpinBox); |
636 | return d->live; |
637 | } |
638 | |
639 | void 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 | */ |
675 | QValidator *QQuickSpinBox::validator() const |
676 | { |
677 | Q_D(const QQuickSpinBox); |
678 | return d->validator; |
679 | } |
680 | |
681 | void 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 | */ |
722 | QJSValue 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 | |
733 | void 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 | */ |
770 | QJSValue 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 | |
781 | void 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 | */ |
806 | QQuickIndicatorButton *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 | */ |
826 | QQuickIndicatorButton *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 | */ |
843 | Qt::InputMethodHints QQuickSpinBox::inputMethodHints() const |
844 | { |
845 | Q_D(const QQuickSpinBox); |
846 | return d->inputMethodHints; |
847 | } |
848 | |
849 | void 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 | */ |
870 | bool 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 | */ |
884 | bool QQuickSpinBox::wrap() const |
885 | { |
886 | Q_D(const QQuickSpinBox); |
887 | return d->wrap; |
888 | } |
889 | |
890 | void 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 | */ |
919 | QString 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 | */ |
932 | void 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 | */ |
945 | void QQuickSpinBox::decrease() |
946 | { |
947 | Q_D(QQuickSpinBox); |
948 | d->decrease(modified: false); |
949 | } |
950 | |
951 | void 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 | |
961 | void QQuickSpinBox::hoverEnterEvent(QHoverEvent *event) |
962 | { |
963 | Q_D(QQuickSpinBox); |
964 | QQuickControl::hoverEnterEvent(event); |
965 | d->updateHover(pos: event->position()); |
966 | event->ignore(); |
967 | } |
968 | |
969 | void QQuickSpinBox::hoverMoveEvent(QHoverEvent *event) |
970 | { |
971 | Q_D(QQuickSpinBox); |
972 | QQuickControl::hoverMoveEvent(event); |
973 | d->updateHover(pos: event->position()); |
974 | event->ignore(); |
975 | } |
976 | |
977 | void 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 | |
986 | void 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 | |
1017 | void 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 | |
1030 | void 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) |
1045 | void 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 | |
1057 | void 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 | |
1069 | void 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 | |
1083 | void 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 | |
1091 | void 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 | |
1115 | void QQuickSpinBox::localeChange(const QLocale &newLocale, const QLocale &oldLocale) |
1116 | { |
1117 | Q_D(QQuickSpinBox); |
1118 | QQuickControl::localeChange(newLocale, oldLocale); |
1119 | d->updateDisplayText(); |
1120 | } |
1121 | |
1122 | QFont QQuickSpinBox::defaultFont() const |
1123 | { |
1124 | return QQuickTheme::font(scope: QQuickTheme::SpinBox); |
1125 | } |
1126 | |
1127 | #if QT_CONFIG(accessibility) |
1128 | QAccessible::Role QQuickSpinBox::accessibleRole() const |
1129 | { |
1130 | return QAccessible::SpinBox; |
1131 | } |
1132 | |
1133 | void 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 | |
1143 | QT_END_NAMESPACE |
1144 | |
1145 | #include "moc_qquickspinbox_p.cpp" |
1146 |
Definitions
- AUTO_REPEAT_DELAY
- AUTO_REPEAT_INTERVAL
- QQuickSpinBoxPrivate
- defaultPalette
- boundValue
- updateValue
- setValue
- stepBy
- increase
- decrease
- effectiveStepSize
- updateDisplayText
- setDisplayText
- contentItemTextChanged
- upEnabled
- updateUpEnabled
- downEnabled
- updateDownEnabled
- updateHover
- startRepeatDelay
- startPressRepeat
- stopPressRepeat
- handlePress
- handleMove
- handleRelease
- handleUngrab
- itemImplicitWidthChanged
- itemImplicitHeightChanged
- itemDestroyed
- evaluateTextFromValue
- evaluateValueFromText
- QQuickSpinBox
- ~QQuickSpinBox
- from
- setFrom
- to
- setTo
- value
- setValue
- stepSize
- setStepSize
- isEditable
- setEditable
- isLive
- setLive
- validator
- setValidator
- textFromValue
- setTextFromValue
- valueFromText
- setValueFromText
- up
- down
- inputMethodHints
- setInputMethodHints
- isInputMethodComposing
- wrap
- setWrap
- displayText
- increase
- decrease
- focusInEvent
- hoverEnterEvent
- hoverMoveEvent
- hoverLeaveEvent
- keyPressEvent
- keyReleaseEvent
- timerEvent
- wheelEvent
- classBegin
- componentComplete
- itemChange
- contentItemChange
- localeChange
- defaultFont
- accessibleRole
Learn Advanced QML with KDAB
Find out more