1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtWidgets module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
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 https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include <qplatformdefs.h> |
41 | #include <private/qabstractspinbox_p.h> |
42 | #include <private/qapplication_p.h> |
43 | #if QT_CONFIG(datetimeparser) |
44 | #include <private/qdatetimeparser_p.h> |
45 | #endif |
46 | #include <private/qlineedit_p.h> |
47 | #include <qabstractspinbox.h> |
48 | |
49 | #include <qapplication.h> |
50 | #include <qstylehints.h> |
51 | #include <qclipboard.h> |
52 | #include <qdatetime.h> |
53 | #include <qevent.h> |
54 | #if QT_CONFIG(menu) |
55 | #include <qmenu.h> |
56 | #endif |
57 | #include <qpainter.h> |
58 | #include <qpalette.h> |
59 | #include <qstylepainter.h> |
60 | #include <qdebug.h> |
61 | #ifndef QT_NO_ACCESSIBILITY |
62 | # include <qaccessible.h> |
63 | #endif |
64 | |
65 | |
66 | //#define QABSTRACTSPINBOX_QSBDEBUG |
67 | #ifdef QABSTRACTSPINBOX_QSBDEBUG |
68 | # define QASBDEBUG qDebug |
69 | #else |
70 | # define QASBDEBUG if (false) qDebug |
71 | #endif |
72 | |
73 | QT_BEGIN_NAMESPACE |
74 | |
75 | /*! |
76 | \class QAbstractSpinBox |
77 | \brief The QAbstractSpinBox class provides a spinbox and a line edit to |
78 | display values. |
79 | |
80 | \ingroup abstractwidgets |
81 | \inmodule QtWidgets |
82 | |
83 | The class is designed as a common super class for widgets like |
84 | QSpinBox, QDoubleSpinBox and QDateTimeEdit |
85 | |
86 | Here are the main properties of the class: |
87 | |
88 | \list 1 |
89 | |
90 | \li \l text: The text that is displayed in the QAbstractSpinBox. |
91 | |
92 | \li \l alignment: The alignment of the text in the QAbstractSpinBox. |
93 | |
94 | \li \l wrapping: Whether the QAbstractSpinBox wraps from the |
95 | minimum value to the maximum value and vice versa. |
96 | |
97 | \endlist |
98 | |
99 | QAbstractSpinBox provides a virtual stepBy() function that is |
100 | called whenever the user triggers a step. This function takes an |
101 | integer value to signify how many steps were taken. E.g. Pressing |
102 | Qt::Key_Down will trigger a call to stepBy(-1). |
103 | |
104 | When the user triggers a step whilst holding the Qt::ControlModifier, |
105 | QAbstractSpinBox steps by 10 instead of making a single step. This |
106 | step modifier affects wheel events, key events and interaction with |
107 | the spinbox buttons. Note that on macOS, Control corresponds to the |
108 | Command key. |
109 | |
110 | Since Qt 5.12, QStyle::SH_SpinBox_StepModifier can be used to select |
111 | which Qt::KeyboardModifier increases the step rate. Qt::NoModifier |
112 | disables this feature. |
113 | |
114 | QAbstractSpinBox also provide a virtual function stepEnabled() to |
115 | determine whether stepping up/down is allowed at any point. This |
116 | function returns a bitset of StepEnabled. |
117 | |
118 | \sa QAbstractSlider, QSpinBox, QDoubleSpinBox, QDateTimeEdit, |
119 | {Spin Boxes Example} |
120 | */ |
121 | |
122 | /*! |
123 | \enum QAbstractSpinBox::StepEnabledFlag |
124 | |
125 | \value StepNone |
126 | \value StepUpEnabled |
127 | \value StepDownEnabled |
128 | */ |
129 | |
130 | /*! |
131 | \enum QAbstractSpinBox::StepType |
132 | |
133 | \value DefaultStepType |
134 | \value AdaptiveDecimalStepType |
135 | */ |
136 | |
137 | /*! |
138 | \fn void QAbstractSpinBox::editingFinished() |
139 | |
140 | This signal is emitted editing is finished. This happens when the |
141 | spinbox loses focus and when enter is pressed. |
142 | */ |
143 | |
144 | /*! |
145 | Constructs an abstract spinbox with the given \a parent with default |
146 | \l wrapping, and \l alignment properties. |
147 | */ |
148 | |
149 | QAbstractSpinBox::QAbstractSpinBox(QWidget *parent) |
150 | : QWidget(*new QAbstractSpinBoxPrivate, parent, { }) |
151 | { |
152 | Q_D(QAbstractSpinBox); |
153 | d->init(); |
154 | } |
155 | |
156 | /*! |
157 | \internal |
158 | */ |
159 | QAbstractSpinBox::QAbstractSpinBox(QAbstractSpinBoxPrivate &dd, QWidget *parent) |
160 | : QWidget(dd, parent, { }) |
161 | { |
162 | Q_D(QAbstractSpinBox); |
163 | d->init(); |
164 | } |
165 | |
166 | /*! |
167 | Called when the QAbstractSpinBox is destroyed. |
168 | */ |
169 | |
170 | QAbstractSpinBox::~QAbstractSpinBox() |
171 | { |
172 | } |
173 | |
174 | /*! |
175 | \enum QAbstractSpinBox::ButtonSymbols |
176 | |
177 | This enum type describes the symbols that can be displayed on the buttons |
178 | in a spin box. |
179 | |
180 | \inlineimage qspinbox-updown.png |
181 | \inlineimage qspinbox-plusminus.png |
182 | |
183 | \value UpDownArrows Little arrows in the classic style. |
184 | \value PlusMinus \b{+} and \b{-} symbols. |
185 | \value NoButtons Don't display buttons. |
186 | |
187 | \sa QAbstractSpinBox::buttonSymbols |
188 | */ |
189 | |
190 | /*! |
191 | \property QAbstractSpinBox::buttonSymbols |
192 | |
193 | \brief the current button symbol mode |
194 | |
195 | The possible values can be either \c UpDownArrows or \c PlusMinus. |
196 | The default is \c UpDownArrows. |
197 | |
198 | Note that some styles might render PlusMinus and UpDownArrows |
199 | identically. |
200 | |
201 | \sa ButtonSymbols |
202 | */ |
203 | |
204 | QAbstractSpinBox::ButtonSymbols QAbstractSpinBox::buttonSymbols() const |
205 | { |
206 | Q_D(const QAbstractSpinBox); |
207 | return d->buttonSymbols; |
208 | } |
209 | |
210 | void QAbstractSpinBox::setButtonSymbols(ButtonSymbols buttonSymbols) |
211 | { |
212 | Q_D(QAbstractSpinBox); |
213 | if (d->buttonSymbols != buttonSymbols) { |
214 | d->buttonSymbols = buttonSymbols; |
215 | d->updateEditFieldGeometry(); |
216 | updateGeometry(); |
217 | update(); |
218 | } |
219 | } |
220 | |
221 | /*! |
222 | \property QAbstractSpinBox::text |
223 | |
224 | \brief the spin box's text, including any prefix and suffix |
225 | |
226 | There is no default text. |
227 | */ |
228 | |
229 | QString QAbstractSpinBox::text() const |
230 | { |
231 | return lineEdit()->displayText(); |
232 | } |
233 | |
234 | |
235 | /*! |
236 | \property QAbstractSpinBox::specialValueText |
237 | \brief the special-value text |
238 | |
239 | If set, the spin box will display this text instead of a numeric |
240 | value whenever the current value is equal to minimum(). Typical use |
241 | is to indicate that this choice has a special (default) meaning. |
242 | |
243 | For example, if your spin box allows the user to choose a scale factor |
244 | (or zoom level) for displaying an image, and your application is able |
245 | to automatically choose one that will enable the image to fit completely |
246 | within the display window, you can set up the spin box like this: |
247 | |
248 | \snippet widgets/spinboxes/window.cpp 3 |
249 | |
250 | The user will then be able to choose a scale from 1% to 1000% |
251 | or select "Auto" to leave it up to the application to choose. Your code |
252 | must then interpret the spin box value of 0 as a request from the user |
253 | to scale the image to fit inside the window. |
254 | |
255 | All values are displayed with the prefix and suffix (if set), \e |
256 | except for the special value, which only shows the special value |
257 | text. This special text is passed in the QSpinBox::textChanged() |
258 | signal that passes a QString. |
259 | |
260 | To turn off the special-value text display, call this function |
261 | with an empty string. The default is no special-value text, i.e. |
262 | the numeric value is shown as usual. |
263 | |
264 | If no special-value text is set, specialValueText() returns an |
265 | empty string. |
266 | */ |
267 | |
268 | QString QAbstractSpinBox::specialValueText() const |
269 | { |
270 | Q_D(const QAbstractSpinBox); |
271 | return d->specialValueText; |
272 | } |
273 | |
274 | void QAbstractSpinBox::setSpecialValueText(const QString &specialValueText) |
275 | { |
276 | Q_D(QAbstractSpinBox); |
277 | |
278 | d->specialValueText = specialValueText; |
279 | d->cachedSizeHint = QSize(); // minimumSizeHint doesn't care about specialValueText |
280 | d->clearCache(); |
281 | d->updateEdit(); |
282 | } |
283 | |
284 | /*! |
285 | \property QAbstractSpinBox::wrapping |
286 | |
287 | \brief whether the spin box is circular. |
288 | |
289 | If wrapping is true stepping up from maximum() value will take you |
290 | to the minimum() value and vice versa. Wrapping only make sense if |
291 | you have minimum() and maximum() values set. |
292 | |
293 | \snippet code/src_gui_widgets_qabstractspinbox.cpp 0 |
294 | |
295 | \sa QSpinBox::minimum(), QSpinBox::maximum() |
296 | */ |
297 | |
298 | bool QAbstractSpinBox::wrapping() const |
299 | { |
300 | Q_D(const QAbstractSpinBox); |
301 | return d->wrapping; |
302 | } |
303 | |
304 | void QAbstractSpinBox::setWrapping(bool wrapping) |
305 | { |
306 | Q_D(QAbstractSpinBox); |
307 | d->wrapping = wrapping; |
308 | } |
309 | |
310 | |
311 | /*! |
312 | \property QAbstractSpinBox::readOnly |
313 | \brief whether the spin box is read only. |
314 | |
315 | In read-only mode, the user can still copy the text to the |
316 | clipboard, or drag and drop the text; |
317 | but cannot edit it. |
318 | |
319 | The QLineEdit in the QAbstractSpinBox does not show a cursor in |
320 | read-only mode. |
321 | |
322 | \sa QLineEdit::readOnly |
323 | */ |
324 | |
325 | bool QAbstractSpinBox::isReadOnly() const |
326 | { |
327 | Q_D(const QAbstractSpinBox); |
328 | return d->readOnly; |
329 | } |
330 | |
331 | void QAbstractSpinBox::setReadOnly(bool enable) |
332 | { |
333 | Q_D(QAbstractSpinBox); |
334 | d->readOnly = enable; |
335 | d->edit->setReadOnly(enable); |
336 | QEvent event(QEvent::ReadOnlyChange); |
337 | QCoreApplication::sendEvent(this, &event); |
338 | update(); |
339 | } |
340 | |
341 | /*! |
342 | \property QAbstractSpinBox::keyboardTracking |
343 | \brief whether keyboard tracking is enabled for the spinbox. |
344 | \since 4.3 |
345 | |
346 | If keyboard tracking is enabled (the default), the spinbox |
347 | emits the valueChanged() and textChanged() signals while the |
348 | new value is being entered from the keyboard. |
349 | |
350 | E.g. when the user enters the value 600 by typing 6, 0, and 0, |
351 | the spinbox emits 3 signals with the values 6, 60, and 600 |
352 | respectively. |
353 | |
354 | If keyboard tracking is disabled, the spinbox doesn't emit the |
355 | valueChanged() and textChanged() signals while typing. It emits |
356 | the signals later, when the return key is pressed, when keyboard |
357 | focus is lost, or when other spinbox functionality is used, e.g. |
358 | pressing an arrow key. |
359 | */ |
360 | |
361 | bool QAbstractSpinBox::keyboardTracking() const |
362 | { |
363 | Q_D(const QAbstractSpinBox); |
364 | return d->keyboardTracking; |
365 | } |
366 | |
367 | void QAbstractSpinBox::setKeyboardTracking(bool enable) |
368 | { |
369 | Q_D(QAbstractSpinBox); |
370 | d->keyboardTracking = enable; |
371 | } |
372 | |
373 | /*! |
374 | \property QAbstractSpinBox::frame |
375 | \brief whether the spin box draws itself with a frame |
376 | |
377 | If enabled (the default) the spin box draws itself inside a frame, |
378 | otherwise the spin box draws itself without any frame. |
379 | */ |
380 | |
381 | bool QAbstractSpinBox::hasFrame() const |
382 | { |
383 | Q_D(const QAbstractSpinBox); |
384 | return d->frame; |
385 | } |
386 | |
387 | |
388 | void QAbstractSpinBox::setFrame(bool enable) |
389 | { |
390 | Q_D(QAbstractSpinBox); |
391 | d->frame = enable; |
392 | update(); |
393 | d->updateEditFieldGeometry(); |
394 | } |
395 | |
396 | /*! |
397 | \property QAbstractSpinBox::accelerated |
398 | \brief whether the spin box will accelerate the frequency of the steps when |
399 | pressing the step Up/Down buttons. |
400 | \since 4.2 |
401 | |
402 | If enabled the spin box will increase/decrease the value faster |
403 | the longer you hold the button down. |
404 | */ |
405 | |
406 | void QAbstractSpinBox::setAccelerated(bool accelerate) |
407 | { |
408 | Q_D(QAbstractSpinBox); |
409 | d->accelerate = accelerate; |
410 | |
411 | } |
412 | bool QAbstractSpinBox::isAccelerated() const |
413 | { |
414 | Q_D(const QAbstractSpinBox); |
415 | return d->accelerate; |
416 | } |
417 | |
418 | /*! |
419 | \property QAbstractSpinBox::showGroupSeparator |
420 | \since 5.3 |
421 | |
422 | |
423 | This property holds whether a thousands separator is enabled. By default this |
424 | property is false. |
425 | */ |
426 | bool QAbstractSpinBox::isGroupSeparatorShown() const |
427 | { |
428 | Q_D(const QAbstractSpinBox); |
429 | return d->showGroupSeparator; |
430 | } |
431 | |
432 | void QAbstractSpinBox::setGroupSeparatorShown(bool shown) |
433 | { |
434 | Q_D(QAbstractSpinBox); |
435 | if (d->showGroupSeparator == shown) |
436 | return; |
437 | d->showGroupSeparator = shown; |
438 | d->setValue(d->value, EmitIfChanged); |
439 | updateGeometry(); |
440 | } |
441 | |
442 | /*! |
443 | \enum QAbstractSpinBox::CorrectionMode |
444 | |
445 | This enum type describes the mode the spinbox will use to correct |
446 | an \l{QValidator::}{Intermediate} value if editing finishes. |
447 | |
448 | \value CorrectToPreviousValue The spinbox will revert to the last |
449 | valid value. |
450 | |
451 | \value CorrectToNearestValue The spinbox will revert to the nearest |
452 | valid value. |
453 | |
454 | \sa correctionMode |
455 | */ |
456 | |
457 | /*! |
458 | \property QAbstractSpinBox::correctionMode |
459 | \brief the mode to correct an \l{QValidator::}{Intermediate} |
460 | value if editing finishes |
461 | \since 4.2 |
462 | |
463 | The default mode is QAbstractSpinBox::CorrectToPreviousValue. |
464 | |
465 | \sa acceptableInput, validate(), fixup() |
466 | */ |
467 | void QAbstractSpinBox::setCorrectionMode(CorrectionMode correctionMode) |
468 | { |
469 | Q_D(QAbstractSpinBox); |
470 | d->correctionMode = correctionMode; |
471 | |
472 | } |
473 | QAbstractSpinBox::CorrectionMode QAbstractSpinBox::correctionMode() const |
474 | { |
475 | Q_D(const QAbstractSpinBox); |
476 | return d->correctionMode; |
477 | } |
478 | |
479 | |
480 | /*! |
481 | \property QAbstractSpinBox::acceptableInput |
482 | \brief whether the input satisfies the current validation |
483 | \since 4.2 |
484 | |
485 | \sa validate(), fixup(), correctionMode |
486 | */ |
487 | |
488 | bool QAbstractSpinBox::hasAcceptableInput() const |
489 | { |
490 | Q_D(const QAbstractSpinBox); |
491 | return d->edit->hasAcceptableInput(); |
492 | } |
493 | |
494 | /*! |
495 | \property QAbstractSpinBox::alignment |
496 | \brief the alignment of the spin box |
497 | |
498 | Possible Values are Qt::AlignLeft, Qt::AlignRight, and Qt::AlignHCenter. |
499 | |
500 | By default, the alignment is Qt::AlignLeft |
501 | |
502 | Attempting to set the alignment to an illegal flag combination |
503 | does nothing. |
504 | |
505 | \sa Qt::Alignment |
506 | */ |
507 | |
508 | Qt::Alignment QAbstractSpinBox::alignment() const |
509 | { |
510 | Q_D(const QAbstractSpinBox); |
511 | |
512 | return (Qt::Alignment)d->edit->alignment(); |
513 | } |
514 | |
515 | void QAbstractSpinBox::setAlignment(Qt::Alignment flag) |
516 | { |
517 | Q_D(QAbstractSpinBox); |
518 | |
519 | d->edit->setAlignment(flag); |
520 | } |
521 | |
522 | /*! |
523 | Selects all the text in the spinbox except the prefix and suffix. |
524 | */ |
525 | |
526 | void QAbstractSpinBox::selectAll() |
527 | { |
528 | Q_D(QAbstractSpinBox); |
529 | |
530 | |
531 | if (!d->specialValue()) { |
532 | const int tmp = d->edit->displayText().size() - d->suffix.size(); |
533 | d->edit->setSelection(tmp, -(tmp - d->prefix.size())); |
534 | } else { |
535 | d->edit->selectAll(); |
536 | } |
537 | } |
538 | |
539 | /*! |
540 | Clears the lineedit of all text but prefix and suffix. |
541 | */ |
542 | |
543 | void QAbstractSpinBox::clear() |
544 | { |
545 | Q_D(QAbstractSpinBox); |
546 | |
547 | d->edit->setText(d->prefix + d->suffix); |
548 | d->edit->setCursorPosition(d->prefix.size()); |
549 | d->cleared = true; |
550 | } |
551 | |
552 | /*! |
553 | Virtual function that determines whether stepping up and down is |
554 | legal at any given time. |
555 | |
556 | The up arrow will be painted as disabled unless (stepEnabled() & |
557 | StepUpEnabled) != 0. |
558 | |
559 | The default implementation will return (StepUpEnabled| |
560 | StepDownEnabled) if wrapping is turned on. Else it will return |
561 | StepDownEnabled if value is > minimum() or'ed with StepUpEnabled if |
562 | value < maximum(). |
563 | |
564 | If you subclass QAbstractSpinBox you will need to reimplement this function. |
565 | |
566 | \sa QSpinBox::minimum(), QSpinBox::maximum(), wrapping() |
567 | */ |
568 | |
569 | |
570 | QAbstractSpinBox::StepEnabled QAbstractSpinBox::stepEnabled() const |
571 | { |
572 | Q_D(const QAbstractSpinBox); |
573 | if (d->readOnly || d->type == QMetaType::UnknownType) |
574 | return StepNone; |
575 | if (d->wrapping) |
576 | return StepEnabled(StepUpEnabled | StepDownEnabled); |
577 | StepEnabled ret = StepNone; |
578 | if (QAbstractSpinBoxPrivate::variantCompare(d->value, d->maximum) < 0) { |
579 | ret |= StepUpEnabled; |
580 | } |
581 | if (QAbstractSpinBoxPrivate::variantCompare(d->value, d->minimum) > 0) { |
582 | ret |= StepDownEnabled; |
583 | } |
584 | return ret; |
585 | } |
586 | |
587 | /*! |
588 | This virtual function is called by the QAbstractSpinBox to |
589 | determine whether \a input is valid. The \a pos parameter indicates |
590 | the position in the string. Reimplemented in the various |
591 | subclasses. |
592 | */ |
593 | |
594 | QValidator::State QAbstractSpinBox::validate(QString & /* input */, int & /* pos */) const |
595 | { |
596 | return QValidator::Acceptable; |
597 | } |
598 | |
599 | /*! |
600 | This virtual function is called by the QAbstractSpinBox if the |
601 | \a input is not validated to QValidator::Acceptable when Return is |
602 | pressed or interpretText() is called. It will try to change the |
603 | text so it is valid. Reimplemented in the various subclasses. |
604 | */ |
605 | |
606 | void QAbstractSpinBox::fixup(QString & /* input */) const |
607 | { |
608 | } |
609 | |
610 | /*! |
611 | Steps up by one linestep |
612 | Calling this slot is analogous to calling stepBy(1); |
613 | \sa stepBy(), stepDown() |
614 | */ |
615 | |
616 | void QAbstractSpinBox::stepUp() |
617 | { |
618 | stepBy(1); |
619 | } |
620 | |
621 | /*! |
622 | Steps down by one linestep |
623 | Calling this slot is analogous to calling stepBy(-1); |
624 | \sa stepBy(), stepUp() |
625 | */ |
626 | |
627 | void QAbstractSpinBox::stepDown() |
628 | { |
629 | stepBy(-1); |
630 | } |
631 | /*! |
632 | Virtual function that is called whenever the user triggers a step. |
633 | The \a steps parameter indicates how many steps were taken. |
634 | For example, pressing \c Qt::Key_Down will trigger a call to \c stepBy(-1), |
635 | whereas pressing \c Qt::Key_PageUp will trigger a call to \c stepBy(10). |
636 | |
637 | If you subclass \c QAbstractSpinBox you must reimplement this |
638 | function. Note that this function is called even if the resulting |
639 | value will be outside the bounds of minimum and maximum. It's this |
640 | function's job to handle these situations. |
641 | |
642 | \sa stepUp(), stepDown(), keyPressEvent() |
643 | */ |
644 | |
645 | void QAbstractSpinBox::stepBy(int steps) |
646 | { |
647 | Q_D(QAbstractSpinBox); |
648 | |
649 | const QVariant old = d->value; |
650 | QString tmp = d->edit->displayText(); |
651 | int cursorPos = d->edit->cursorPosition(); |
652 | bool dontstep = false; |
653 | EmitPolicy e = EmitIfChanged; |
654 | if (d->pendingEmit) { |
655 | dontstep = validate(tmp, cursorPos) != QValidator::Acceptable; |
656 | d->cleared = false; |
657 | d->interpret(NeverEmit); |
658 | if (d->value != old) |
659 | e = AlwaysEmit; |
660 | } |
661 | if (!dontstep) { |
662 | QVariant singleStep; |
663 | switch (d->stepType) { |
664 | case QAbstractSpinBox::StepType::AdaptiveDecimalStepType: |
665 | singleStep = d->calculateAdaptiveDecimalStep(steps); |
666 | break; |
667 | default: |
668 | singleStep = d->singleStep; |
669 | } |
670 | d->setValue(d->bound(d->value + (singleStep * steps), old, steps), e); |
671 | } else if (e == AlwaysEmit) { |
672 | d->emitSignals(e, old); |
673 | } |
674 | selectAll(); |
675 | } |
676 | |
677 | /*! |
678 | This function returns a pointer to the line edit of the spin box. |
679 | */ |
680 | |
681 | QLineEdit *QAbstractSpinBox::lineEdit() const |
682 | { |
683 | Q_D(const QAbstractSpinBox); |
684 | |
685 | return d->edit; |
686 | } |
687 | |
688 | |
689 | /*! |
690 | \fn void QAbstractSpinBox::setLineEdit(QLineEdit *lineEdit) |
691 | |
692 | Sets the line edit of the spinbox to be \a lineEdit instead of the |
693 | current line edit widget. \a lineEdit cannot be \nullptr. |
694 | |
695 | QAbstractSpinBox takes ownership of the new lineEdit |
696 | |
697 | If QLineEdit::validator() for the \a lineEdit returns \nullptr, the internal |
698 | validator of the spinbox will be set on the line edit. |
699 | */ |
700 | |
701 | void QAbstractSpinBox::setLineEdit(QLineEdit *lineEdit) |
702 | { |
703 | Q_D(QAbstractSpinBox); |
704 | |
705 | if (!lineEdit) { |
706 | Q_ASSERT(lineEdit); |
707 | return; |
708 | } |
709 | |
710 | if (lineEdit == d->edit) |
711 | return; |
712 | |
713 | delete d->edit; |
714 | d->edit = lineEdit; |
715 | setProperty("_q_spinbox_lineedit" , QVariant::fromValue<QWidget *>(d->edit)); |
716 | if (!d->edit->validator()) |
717 | d->edit->setValidator(d->validator); |
718 | |
719 | if (d->edit->parent() != this) |
720 | d->edit->setParent(this); |
721 | |
722 | d->edit->setFrame(!style()->styleHint(QStyle::SH_SpinBox_ButtonsInsideFrame, nullptr, this)); |
723 | d->edit->setFocusProxy(this); |
724 | d->edit->setAcceptDrops(false); |
725 | |
726 | if (d->type != QMetaType::UnknownType) { |
727 | connect(d->edit, SIGNAL(textChanged(QString)), |
728 | this, SLOT(_q_editorTextChanged(QString))); |
729 | connect(d->edit, SIGNAL(cursorPositionChanged(int,int)), |
730 | this, SLOT(_q_editorCursorPositionChanged(int,int))); |
731 | connect(d->edit, SIGNAL(cursorPositionChanged(int,int)), |
732 | this, SLOT(updateMicroFocus())); |
733 | connect(d->edit->d_func()->control, SIGNAL(updateMicroFocus()), |
734 | this, SLOT(updateMicroFocus())); |
735 | } |
736 | d->updateEditFieldGeometry(); |
737 | d->edit->setContextMenuPolicy(Qt::NoContextMenu); |
738 | d->edit->d_func()->control->setAccessibleObject(this); |
739 | |
740 | if (isVisible()) |
741 | d->edit->show(); |
742 | if (isVisible()) |
743 | d->updateEdit(); |
744 | } |
745 | |
746 | |
747 | /*! |
748 | This function interprets the text of the spin box. If the value |
749 | has changed since last interpretation it will emit signals. |
750 | */ |
751 | |
752 | void QAbstractSpinBox::interpretText() |
753 | { |
754 | Q_D(QAbstractSpinBox); |
755 | d->interpret(EmitIfChanged); |
756 | } |
757 | |
758 | /* |
759 | Reimplemented in 4.6, so be careful. |
760 | */ |
761 | /*! |
762 | \reimp |
763 | */ |
764 | QVariant QAbstractSpinBox::inputMethodQuery(Qt::InputMethodQuery query) const |
765 | { |
766 | Q_D(const QAbstractSpinBox); |
767 | const QVariant lineEditValue = d->edit->inputMethodQuery(query); |
768 | switch (query) { |
769 | case Qt::ImHints: |
770 | if (const int hints = inputMethodHints()) |
771 | return QVariant(hints | lineEditValue.toInt()); |
772 | break; |
773 | default: |
774 | break; |
775 | } |
776 | return lineEditValue; |
777 | } |
778 | |
779 | /*! |
780 | \reimp |
781 | */ |
782 | |
783 | bool QAbstractSpinBox::event(QEvent *event) |
784 | { |
785 | Q_D(QAbstractSpinBox); |
786 | switch (event->type()) { |
787 | case QEvent::FontChange: |
788 | case QEvent::StyleChange: |
789 | d->cachedSizeHint = d->cachedMinimumSizeHint = QSize(); |
790 | break; |
791 | case QEvent::ApplicationLayoutDirectionChange: |
792 | case QEvent::LayoutDirectionChange: |
793 | d->updateEditFieldGeometry(); |
794 | break; |
795 | case QEvent::HoverEnter: |
796 | case QEvent::HoverLeave: |
797 | case QEvent::HoverMove: |
798 | d->updateHoverControl(static_cast<const QHoverEvent *>(event)->position().toPoint()); |
799 | break; |
800 | case QEvent::ShortcutOverride: |
801 | if (d->edit->event(event)) |
802 | return true; |
803 | break; |
804 | #ifdef QT_KEYPAD_NAVIGATION |
805 | case QEvent::EnterEditFocus: |
806 | case QEvent::LeaveEditFocus: |
807 | if (QApplicationPrivate::keypadNavigationEnabled()) { |
808 | const bool b = d->edit->event(event); |
809 | d->edit->setSelection(d->edit->displayText().size() - d->suffix.size(),0); |
810 | if (event->type() == QEvent::LeaveEditFocus) |
811 | emit editingFinished(); |
812 | if (b) |
813 | return true; |
814 | } |
815 | break; |
816 | #endif |
817 | case QEvent::InputMethod: |
818 | return d->edit->event(event); |
819 | default: |
820 | break; |
821 | } |
822 | return QWidget::event(event); |
823 | } |
824 | |
825 | /*! |
826 | \reimp |
827 | */ |
828 | |
829 | void QAbstractSpinBox::showEvent(QShowEvent *) |
830 | { |
831 | Q_D(QAbstractSpinBox); |
832 | d->reset(); |
833 | |
834 | if (d->ignoreUpdateEdit) { |
835 | d->ignoreUpdateEdit = false; |
836 | } else { |
837 | d->updateEdit(); |
838 | } |
839 | } |
840 | |
841 | /*! |
842 | \reimp |
843 | */ |
844 | |
845 | void QAbstractSpinBox::changeEvent(QEvent *event) |
846 | { |
847 | Q_D(QAbstractSpinBox); |
848 | |
849 | switch (event->type()) { |
850 | case QEvent::StyleChange: |
851 | d->spinClickTimerInterval = style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatRate, nullptr, this); |
852 | d->spinClickThresholdTimerInterval = |
853 | style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatThreshold, nullptr, this); |
854 | if (d->edit) |
855 | d->edit->setFrame(!style()->styleHint(QStyle::SH_SpinBox_ButtonsInsideFrame, nullptr, this)); |
856 | d->stepModifier = static_cast<Qt::KeyboardModifier>(style()->styleHint(QStyle::SH_SpinBox_StepModifier, nullptr, this)); |
857 | d->reset(); |
858 | d->updateEditFieldGeometry(); |
859 | break; |
860 | case QEvent::LocaleChange: |
861 | d->updateEdit(); |
862 | break; |
863 | case QEvent::EnabledChange: |
864 | if (!isEnabled()) { |
865 | d->reset(); |
866 | } |
867 | break; |
868 | case QEvent::ActivationChange: |
869 | if (!isActiveWindow()){ |
870 | d->reset(); |
871 | if (d->pendingEmit) // pendingEmit can be true even if it hasn't changed. |
872 | d->interpret(EmitIfChanged); // E.g. 10 to 10.0 |
873 | } |
874 | break; |
875 | default: |
876 | break; |
877 | } |
878 | QWidget::changeEvent(event); |
879 | } |
880 | |
881 | /*! |
882 | \reimp |
883 | */ |
884 | |
885 | void QAbstractSpinBox::resizeEvent(QResizeEvent *event) |
886 | { |
887 | Q_D(QAbstractSpinBox); |
888 | QWidget::resizeEvent(event); |
889 | |
890 | d->updateEditFieldGeometry(); |
891 | update(); |
892 | } |
893 | |
894 | /*! |
895 | \reimp |
896 | */ |
897 | |
898 | QSize QAbstractSpinBox::sizeHint() const |
899 | { |
900 | Q_D(const QAbstractSpinBox); |
901 | if (d->cachedSizeHint.isEmpty()) { |
902 | ensurePolished(); |
903 | |
904 | const QFontMetrics fm(fontMetrics()); |
905 | int h = d->edit->sizeHint().height(); |
906 | int w = 0; |
907 | QString s; |
908 | QString fixedContent = d->prefix + d->suffix + QLatin1Char(' '); |
909 | s = d->textFromValue(d->minimum); |
910 | s.truncate(18); |
911 | s += fixedContent; |
912 | w = qMax(w, fm.horizontalAdvance(s)); |
913 | s = d->textFromValue(d->maximum); |
914 | s.truncate(18); |
915 | s += fixedContent; |
916 | w = qMax(w, fm.horizontalAdvance(s)); |
917 | |
918 | if (d->specialValueText.size()) { |
919 | s = d->specialValueText; |
920 | w = qMax(w, fm.horizontalAdvance(s)); |
921 | } |
922 | w += 2; // cursor blinking space |
923 | |
924 | QStyleOptionSpinBox opt; |
925 | initStyleOption(&opt); |
926 | QSize hint(w, h); |
927 | d->cachedSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this); |
928 | } |
929 | return d->cachedSizeHint; |
930 | } |
931 | |
932 | /*! |
933 | \reimp |
934 | */ |
935 | |
936 | QSize QAbstractSpinBox::minimumSizeHint() const |
937 | { |
938 | Q_D(const QAbstractSpinBox); |
939 | if (d->cachedMinimumSizeHint.isEmpty()) { |
940 | //Use the prefix and range to calculate the minimumSizeHint |
941 | ensurePolished(); |
942 | |
943 | const QFontMetrics fm(fontMetrics()); |
944 | int h = d->edit->minimumSizeHint().height(); |
945 | int w = 0; |
946 | |
947 | QString s; |
948 | QString fixedContent = d->prefix + QLatin1Char(' '); |
949 | s = d->textFromValue(d->minimum); |
950 | s.truncate(18); |
951 | s += fixedContent; |
952 | w = qMax(w, fm.horizontalAdvance(s)); |
953 | s = d->textFromValue(d->maximum); |
954 | s.truncate(18); |
955 | s += fixedContent; |
956 | w = qMax(w, fm.horizontalAdvance(s)); |
957 | |
958 | if (d->specialValueText.size()) { |
959 | s = d->specialValueText; |
960 | w = qMax(w, fm.horizontalAdvance(s)); |
961 | } |
962 | w += 2; // cursor blinking space |
963 | |
964 | QStyleOptionSpinBox opt; |
965 | initStyleOption(&opt); |
966 | QSize hint(w, h); |
967 | |
968 | d->cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this); |
969 | } |
970 | return d->cachedMinimumSizeHint; |
971 | } |
972 | |
973 | /*! |
974 | \reimp |
975 | */ |
976 | |
977 | void QAbstractSpinBox::paintEvent(QPaintEvent *) |
978 | { |
979 | QStyleOptionSpinBox opt; |
980 | initStyleOption(&opt); |
981 | QStylePainter p(this); |
982 | p.drawComplexControl(QStyle::CC_SpinBox, opt); |
983 | } |
984 | |
985 | /*! |
986 | \reimp |
987 | |
988 | This function handles keyboard input. |
989 | |
990 | The following keys are handled specifically: |
991 | \table |
992 | \row \li Enter/Return |
993 | \li This will reinterpret the text and emit a signal even if the value has not changed |
994 | since last time a signal was emitted. |
995 | \row \li Up |
996 | \li This will invoke stepBy(1) |
997 | \row \li Down |
998 | \li This will invoke stepBy(-1) |
999 | \row \li Page up |
1000 | \li This will invoke stepBy(10) |
1001 | \row \li Page down |
1002 | \li This will invoke stepBy(-10) |
1003 | \endtable |
1004 | |
1005 | \sa stepBy() |
1006 | */ |
1007 | |
1008 | |
1009 | void QAbstractSpinBox::keyPressEvent(QKeyEvent *event) |
1010 | { |
1011 | Q_D(QAbstractSpinBox); |
1012 | |
1013 | d->keyboardModifiers = event->modifiers(); |
1014 | |
1015 | if (!event->text().isEmpty() && d->edit->cursorPosition() < d->prefix.size()) |
1016 | d->edit->setCursorPosition(d->prefix.size()); |
1017 | |
1018 | int steps = 1; |
1019 | bool isPgUpOrDown = false; |
1020 | switch (event->key()) { |
1021 | case Qt::Key_PageUp: |
1022 | case Qt::Key_PageDown: |
1023 | steps *= 10; |
1024 | isPgUpOrDown = true; |
1025 | Q_FALLTHROUGH(); |
1026 | case Qt::Key_Up: |
1027 | case Qt::Key_Down: { |
1028 | #ifdef QT_KEYPAD_NAVIGATION |
1029 | if (QApplicationPrivate::keypadNavigationEnabled()) { |
1030 | // Reserve up/down for nav - use left/right for edit. |
1031 | if (!hasEditFocus() && (event->key() == Qt::Key_Up |
1032 | || event->key() == Qt::Key_Down)) { |
1033 | event->ignore(); |
1034 | return; |
1035 | } |
1036 | } |
1037 | #endif |
1038 | event->accept(); |
1039 | const bool up = (event->key() == Qt::Key_PageUp || event->key() == Qt::Key_Up); |
1040 | if (!(stepEnabled() & (up ? StepUpEnabled : StepDownEnabled))) |
1041 | return; |
1042 | if (!isPgUpOrDown && (event->modifiers() & d->stepModifier)) |
1043 | steps *= 10; |
1044 | if (!up) |
1045 | steps *= -1; |
1046 | if (style()->styleHint(QStyle::SH_SpinBox_AnimateButton, nullptr, this)) { |
1047 | d->buttonState = (Keyboard | (up ? Up : Down)); |
1048 | } |
1049 | if (d->spinClickTimerId == -1) |
1050 | stepBy(steps); |
1051 | if(event->isAutoRepeat() && !isPgUpOrDown) { |
1052 | if(d->spinClickThresholdTimerId == -1 && d->spinClickTimerId == -1) { |
1053 | d->updateState(up, true); |
1054 | } |
1055 | } |
1056 | #ifndef QT_NO_ACCESSIBILITY |
1057 | QAccessibleValueChangeEvent event(this, d->value); |
1058 | QAccessible::updateAccessibility(&event); |
1059 | #endif |
1060 | return; |
1061 | } |
1062 | #ifdef QT_KEYPAD_NAVIGATION |
1063 | case Qt::Key_Left: |
1064 | case Qt::Key_Right: |
1065 | if (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus()) { |
1066 | event->ignore(); |
1067 | return; |
1068 | } |
1069 | break; |
1070 | case Qt::Key_Back: |
1071 | if (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus()) { |
1072 | event->ignore(); |
1073 | return; |
1074 | } |
1075 | break; |
1076 | #endif |
1077 | case Qt::Key_Enter: |
1078 | case Qt::Key_Return: |
1079 | d->edit->d_func()->control->clearUndo(); |
1080 | d->interpret(d->keyboardTracking ? AlwaysEmit : EmitIfChanged); |
1081 | selectAll(); |
1082 | event->ignore(); |
1083 | emit editingFinished(); |
1084 | emit d->edit->returnPressed(); |
1085 | return; |
1086 | |
1087 | #ifdef QT_KEYPAD_NAVIGATION |
1088 | case Qt::Key_Select: |
1089 | if (QApplicationPrivate::keypadNavigationEnabled()) { |
1090 | // Toggles between left/right moving cursor and inc/dec. |
1091 | setEditFocus(!hasEditFocus()); |
1092 | } |
1093 | return; |
1094 | #endif |
1095 | |
1096 | case Qt::Key_U: |
1097 | if (event->modifiers() & Qt::ControlModifier |
1098 | && QGuiApplication::platformName() == QLatin1String("xcb" )) { // only X11 |
1099 | event->accept(); |
1100 | if (!isReadOnly()) |
1101 | clear(); |
1102 | return; |
1103 | } |
1104 | break; |
1105 | |
1106 | case Qt::Key_End: |
1107 | case Qt::Key_Home: |
1108 | if (event->modifiers() & Qt::ShiftModifier) { |
1109 | int currentPos = d->edit->cursorPosition(); |
1110 | const QString text = d->edit->displayText(); |
1111 | if (event->key() == Qt::Key_End) { |
1112 | if ((currentPos == 0 && !d->prefix.isEmpty()) || text.size() - d->suffix.size() <= currentPos) { |
1113 | break; // let lineedit handle this |
1114 | } else { |
1115 | d->edit->setSelection(currentPos, text.size() - d->suffix.size() - currentPos); |
1116 | } |
1117 | } else { |
1118 | if ((currentPos == text.size() && !d->suffix.isEmpty()) || currentPos <= d->prefix.size()) { |
1119 | break; // let lineedit handle this |
1120 | } else { |
1121 | d->edit->setSelection(currentPos, d->prefix.size() - currentPos); |
1122 | } |
1123 | } |
1124 | event->accept(); |
1125 | return; |
1126 | } |
1127 | break; |
1128 | |
1129 | default: |
1130 | #ifndef QT_NO_SHORTCUT |
1131 | if (event == QKeySequence::SelectAll) { |
1132 | selectAll(); |
1133 | event->accept(); |
1134 | return; |
1135 | } |
1136 | #endif |
1137 | break; |
1138 | } |
1139 | |
1140 | d->edit->event(event); |
1141 | if (!d->edit->text().isEmpty()) |
1142 | d->cleared = false; |
1143 | if (!isVisible()) |
1144 | d->ignoreUpdateEdit = true; |
1145 | } |
1146 | |
1147 | /*! |
1148 | \reimp |
1149 | */ |
1150 | |
1151 | void QAbstractSpinBox::keyReleaseEvent(QKeyEvent *event) |
1152 | { |
1153 | Q_D(QAbstractSpinBox); |
1154 | |
1155 | d->keyboardModifiers = event->modifiers(); |
1156 | if (d->buttonState & Keyboard && !event->isAutoRepeat()) { |
1157 | d->reset(); |
1158 | } else { |
1159 | d->edit->event(event); |
1160 | } |
1161 | } |
1162 | |
1163 | /*! |
1164 | \reimp |
1165 | */ |
1166 | |
1167 | #if QT_CONFIG(wheelevent) |
1168 | void QAbstractSpinBox::wheelEvent(QWheelEvent *event) |
1169 | { |
1170 | Q_D(QAbstractSpinBox); |
1171 | #ifdef Q_OS_MACOS |
1172 | // If the event comes from a real mouse wheel, rather than a track pad |
1173 | // (Qt::MouseEventSynthesizedBySystem), the shift modifier changes the |
1174 | // scroll orientation to horizontal. |
1175 | // Convert horizontal events back to vertical whilst shift is held. |
1176 | if ((event->modifiers() & Qt::ShiftModifier) |
1177 | && event->source() == Qt::MouseEventNotSynthesized) { |
1178 | d->wheelDeltaRemainder += event->angleDelta().x(); |
1179 | } else { |
1180 | d->wheelDeltaRemainder += event->angleDelta().y(); |
1181 | } |
1182 | #else |
1183 | d->wheelDeltaRemainder += event->angleDelta().y(); |
1184 | #endif |
1185 | const int steps = d->wheelDeltaRemainder / 120; |
1186 | d->wheelDeltaRemainder -= steps * 120; |
1187 | if (stepEnabled() & (steps > 0 ? StepUpEnabled : StepDownEnabled)) |
1188 | stepBy(event->modifiers() & d->stepModifier ? steps * 10 : steps); |
1189 | event->accept(); |
1190 | } |
1191 | #endif |
1192 | |
1193 | |
1194 | /*! |
1195 | \reimp |
1196 | */ |
1197 | void QAbstractSpinBox::focusInEvent(QFocusEvent *event) |
1198 | { |
1199 | Q_D(QAbstractSpinBox); |
1200 | |
1201 | d->edit->event(event); |
1202 | if (event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason) { |
1203 | selectAll(); |
1204 | } |
1205 | QWidget::focusInEvent(event); |
1206 | } |
1207 | |
1208 | /*! |
1209 | \reimp |
1210 | */ |
1211 | |
1212 | void QAbstractSpinBox::focusOutEvent(QFocusEvent *event) |
1213 | { |
1214 | Q_D(QAbstractSpinBox); |
1215 | |
1216 | if (d->pendingEmit) |
1217 | d->interpret(EmitIfChanged); |
1218 | |
1219 | d->reset(); |
1220 | d->edit->event(event); |
1221 | d->updateEdit(); |
1222 | QWidget::focusOutEvent(event); |
1223 | |
1224 | #ifdef QT_KEYPAD_NAVIGATION |
1225 | // editingFinished() is already emitted on LeaveEditFocus |
1226 | if (!QApplicationPrivate::keypadNavigationEnabled()) |
1227 | #endif |
1228 | emit editingFinished(); |
1229 | } |
1230 | |
1231 | /*! |
1232 | \reimp |
1233 | */ |
1234 | |
1235 | void QAbstractSpinBox::closeEvent(QCloseEvent *event) |
1236 | { |
1237 | Q_D(QAbstractSpinBox); |
1238 | |
1239 | d->reset(); |
1240 | if (d->pendingEmit) |
1241 | d->interpret(EmitIfChanged); |
1242 | QWidget::closeEvent(event); |
1243 | } |
1244 | |
1245 | /*! |
1246 | \reimp |
1247 | */ |
1248 | |
1249 | void QAbstractSpinBox::hideEvent(QHideEvent *event) |
1250 | { |
1251 | Q_D(QAbstractSpinBox); |
1252 | d->reset(); |
1253 | if (d->pendingEmit) |
1254 | d->interpret(EmitIfChanged); |
1255 | QWidget::hideEvent(event); |
1256 | } |
1257 | |
1258 | |
1259 | /*! |
1260 | \reimp |
1261 | */ |
1262 | |
1263 | void QAbstractSpinBox::timerEvent(QTimerEvent *event) |
1264 | { |
1265 | Q_D(QAbstractSpinBox); |
1266 | |
1267 | bool doStep = false; |
1268 | if (event->timerId() == d->spinClickThresholdTimerId) { |
1269 | killTimer(d->spinClickThresholdTimerId); |
1270 | d->spinClickThresholdTimerId = -1; |
1271 | d->effectiveSpinRepeatRate = d->buttonState & Keyboard |
1272 | ? QGuiApplication::styleHints()->keyboardAutoRepeatRate() |
1273 | : d->spinClickTimerInterval; |
1274 | d->spinClickTimerId = startTimer(d->effectiveSpinRepeatRate); |
1275 | doStep = true; |
1276 | } else if (event->timerId() == d->spinClickTimerId) { |
1277 | if (d->accelerate) { |
1278 | d->acceleration = d->acceleration + (int)(d->effectiveSpinRepeatRate * 0.05); |
1279 | if (d->effectiveSpinRepeatRate - d->acceleration >= 10) { |
1280 | killTimer(d->spinClickTimerId); |
1281 | d->spinClickTimerId = startTimer(d->effectiveSpinRepeatRate - d->acceleration); |
1282 | } |
1283 | } |
1284 | doStep = true; |
1285 | } |
1286 | |
1287 | if (doStep) { |
1288 | const bool increaseStepRate = d->keyboardModifiers & d->stepModifier; |
1289 | const StepEnabled st = stepEnabled(); |
1290 | if (d->buttonState & Up) { |
1291 | if (!(st & StepUpEnabled)) { |
1292 | d->reset(); |
1293 | } else { |
1294 | stepBy(increaseStepRate ? 10 : 1); |
1295 | } |
1296 | } else if (d->buttonState & Down) { |
1297 | if (!(st & StepDownEnabled)) { |
1298 | d->reset(); |
1299 | } else { |
1300 | stepBy(increaseStepRate ? -10 : -1); |
1301 | } |
1302 | } |
1303 | return; |
1304 | } |
1305 | QWidget::timerEvent(event); |
1306 | return; |
1307 | } |
1308 | |
1309 | /*! |
1310 | \reimp |
1311 | */ |
1312 | |
1313 | #if QT_CONFIG(contextmenu) |
1314 | void QAbstractSpinBox::(QContextMenuEvent *event) |
1315 | { |
1316 | Q_D(QAbstractSpinBox); |
1317 | |
1318 | QPointer<QMenu> = d->edit->createStandardContextMenu(); |
1319 | if (!menu) |
1320 | return; |
1321 | |
1322 | d->reset(); |
1323 | |
1324 | QAction *selAll = new QAction(tr("&Select All" ), menu); |
1325 | #if QT_CONFIG(shortcut) |
1326 | selAll->setShortcut(QKeySequence::SelectAll); |
1327 | #endif |
1328 | menu->insertAction(d->edit->d_func()->selectAllAction, |
1329 | selAll); |
1330 | menu->removeAction(d->edit->d_func()->selectAllAction); |
1331 | menu->addSeparator(); |
1332 | const uint se = stepEnabled(); |
1333 | QAction *up = menu->addAction(tr("&Step up" )); |
1334 | up->setEnabled(se & StepUpEnabled); |
1335 | QAction *down = menu->addAction(tr("Step &down" )); |
1336 | down->setEnabled(se & StepDownEnabled); |
1337 | menu->addSeparator(); |
1338 | |
1339 | const QPointer<QAbstractSpinBox> that = this; |
1340 | const QPoint pos = (event->reason() == QContextMenuEvent::Mouse) |
1341 | ? event->globalPos() : mapToGlobal(QPoint(event->pos().x(), 0)) + QPoint(width() / 2, height() / 2); |
1342 | const QAction *action = menu->exec(pos); |
1343 | delete static_cast<QMenu *>(menu); |
1344 | if (that && action) { |
1345 | if (action == up) { |
1346 | stepBy(1); |
1347 | } else if (action == down) { |
1348 | stepBy(-1); |
1349 | } else if (action == selAll) { |
1350 | selectAll(); |
1351 | } |
1352 | } |
1353 | event->accept(); |
1354 | } |
1355 | #endif // QT_CONFIG(contextmenu) |
1356 | |
1357 | /*! |
1358 | \reimp |
1359 | */ |
1360 | |
1361 | void QAbstractSpinBox::mouseMoveEvent(QMouseEvent *event) |
1362 | { |
1363 | Q_D(QAbstractSpinBox); |
1364 | |
1365 | d->keyboardModifiers = event->modifiers(); |
1366 | d->updateHoverControl(event->position().toPoint()); |
1367 | |
1368 | // If we have a timer ID, update the state |
1369 | if (d->spinClickTimerId != -1 && d->buttonSymbols != NoButtons) { |
1370 | const StepEnabled se = stepEnabled(); |
1371 | if ((se & StepUpEnabled) && d->hoverControl == QStyle::SC_SpinBoxUp) |
1372 | d->updateState(true); |
1373 | else if ((se & StepDownEnabled) && d->hoverControl == QStyle::SC_SpinBoxDown) |
1374 | d->updateState(false); |
1375 | else |
1376 | d->reset(); |
1377 | event->accept(); |
1378 | } |
1379 | } |
1380 | |
1381 | /*! |
1382 | \reimp |
1383 | */ |
1384 | |
1385 | void QAbstractSpinBox::mousePressEvent(QMouseEvent *event) |
1386 | { |
1387 | Q_D(QAbstractSpinBox); |
1388 | |
1389 | d->keyboardModifiers = event->modifiers(); |
1390 | if (event->button() != Qt::LeftButton || d->buttonState != None) { |
1391 | return; |
1392 | } |
1393 | |
1394 | d->updateHoverControl(event->position().toPoint()); |
1395 | event->accept(); |
1396 | |
1397 | const StepEnabled se = (d->buttonSymbols == NoButtons) ? StepEnabled(StepNone) : stepEnabled(); |
1398 | if ((se & StepUpEnabled) && d->hoverControl == QStyle::SC_SpinBoxUp) { |
1399 | d->updateState(true); |
1400 | } else if ((se & StepDownEnabled) && d->hoverControl == QStyle::SC_SpinBoxDown) { |
1401 | d->updateState(false); |
1402 | } else { |
1403 | event->ignore(); |
1404 | } |
1405 | } |
1406 | |
1407 | /*! |
1408 | \reimp |
1409 | */ |
1410 | void QAbstractSpinBox::mouseReleaseEvent(QMouseEvent *event) |
1411 | { |
1412 | Q_D(QAbstractSpinBox); |
1413 | |
1414 | d->keyboardModifiers = event->modifiers(); |
1415 | if ((d->buttonState & Mouse) != 0) |
1416 | d->reset(); |
1417 | event->accept(); |
1418 | } |
1419 | |
1420 | // --- QAbstractSpinBoxPrivate --- |
1421 | |
1422 | /*! |
1423 | \internal |
1424 | Constructs a QAbstractSpinBoxPrivate object |
1425 | */ |
1426 | |
1427 | QAbstractSpinBoxPrivate::QAbstractSpinBoxPrivate() |
1428 | : pendingEmit(false), readOnly(false), wrapping(false), |
1429 | ignoreCursorPositionChanged(false), frame(true), accelerate(false), keyboardTracking(true), |
1430 | cleared(false), ignoreUpdateEdit(false), showGroupSeparator(false) |
1431 | { |
1432 | } |
1433 | |
1434 | /* |
1435 | \internal |
1436 | Called when the QAbstractSpinBoxPrivate is destroyed |
1437 | */ |
1438 | QAbstractSpinBoxPrivate::~QAbstractSpinBoxPrivate() |
1439 | { |
1440 | } |
1441 | |
1442 | /*! |
1443 | \internal |
1444 | Updates the old and new hover control. Does nothing if the hover |
1445 | control has not changed. |
1446 | */ |
1447 | bool QAbstractSpinBoxPrivate::updateHoverControl(const QPoint &pos) |
1448 | { |
1449 | Q_Q(QAbstractSpinBox); |
1450 | QRect lastHoverRect = hoverRect; |
1451 | QStyle::SubControl lastHoverControl = hoverControl; |
1452 | bool doesHover = q->testAttribute(Qt::WA_Hover); |
1453 | if (lastHoverControl != newHoverControl(pos) && doesHover) { |
1454 | q->update(lastHoverRect); |
1455 | q->update(hoverRect); |
1456 | return true; |
1457 | } |
1458 | return !doesHover; |
1459 | } |
1460 | |
1461 | /*! |
1462 | \internal |
1463 | Returns the hover control at \a pos. |
1464 | This will update the hoverRect and hoverControl. |
1465 | */ |
1466 | QStyle::SubControl QAbstractSpinBoxPrivate::newHoverControl(const QPoint &pos) |
1467 | { |
1468 | Q_Q(QAbstractSpinBox); |
1469 | |
1470 | QStyleOptionSpinBox opt; |
1471 | q->initStyleOption(&opt); |
1472 | opt.subControls = QStyle::SC_All; |
1473 | hoverControl = q->style()->hitTestComplexControl(QStyle::CC_SpinBox, &opt, pos, q); |
1474 | hoverRect = q->style()->subControlRect(QStyle::CC_SpinBox, &opt, hoverControl, q); |
1475 | return hoverControl; |
1476 | } |
1477 | |
1478 | /*! |
1479 | \internal |
1480 | Strips any prefix/suffix from \a text. |
1481 | */ |
1482 | |
1483 | QString QAbstractSpinBoxPrivate::stripped(const QString &t, int *pos) const |
1484 | { |
1485 | QStringView text(t); |
1486 | if (specialValueText.size() == 0 || text != specialValueText) { |
1487 | int from = 0; |
1488 | int size = text.size(); |
1489 | bool changed = false; |
1490 | if (prefix.size() && text.startsWith(prefix)) { |
1491 | from += prefix.size(); |
1492 | size -= from; |
1493 | changed = true; |
1494 | } |
1495 | if (suffix.size() && text.endsWith(suffix)) { |
1496 | size -= suffix.size(); |
1497 | changed = true; |
1498 | } |
1499 | if (changed) |
1500 | text = text.mid(from, size); |
1501 | } |
1502 | |
1503 | const int s = text.size(); |
1504 | text = text.trimmed(); |
1505 | if (pos) |
1506 | (*pos) -= (s - text.size()); |
1507 | return text.toString(); |
1508 | |
1509 | } |
1510 | |
1511 | void QAbstractSpinBoxPrivate::updateEditFieldGeometry() |
1512 | { |
1513 | Q_Q(QAbstractSpinBox); |
1514 | QStyleOptionSpinBox opt; |
1515 | q->initStyleOption(&opt); |
1516 | opt.subControls = QStyle::SC_SpinBoxEditField; |
1517 | edit->setGeometry(q->style()->subControlRect(QStyle::CC_SpinBox, &opt, |
1518 | QStyle::SC_SpinBoxEditField, q)); |
1519 | } |
1520 | /*! |
1521 | \internal |
1522 | Returns \c true if a specialValueText has been set and the current value is minimum. |
1523 | */ |
1524 | |
1525 | bool QAbstractSpinBoxPrivate::specialValue() const |
1526 | { |
1527 | return (value == minimum && !specialValueText.isEmpty()); |
1528 | } |
1529 | |
1530 | /*! |
1531 | \internal Virtual function that emits signals when the value |
1532 | changes. Reimplemented in the different subclasses. |
1533 | */ |
1534 | |
1535 | void QAbstractSpinBoxPrivate::emitSignals(EmitPolicy, const QVariant &) |
1536 | { |
1537 | } |
1538 | |
1539 | /*! |
1540 | \internal |
1541 | |
1542 | Slot connected to the line edit's textChanged(const QString &) |
1543 | signal. |
1544 | */ |
1545 | |
1546 | void QAbstractSpinBoxPrivate::_q_editorTextChanged(const QString &t) |
1547 | { |
1548 | Q_Q(QAbstractSpinBox); |
1549 | |
1550 | if (keyboardTracking) { |
1551 | QString tmp = t; |
1552 | int pos = edit->cursorPosition(); |
1553 | QValidator::State state = q->validate(tmp, pos); |
1554 | if (state == QValidator::Acceptable) { |
1555 | const QVariant v = valueFromText(tmp); |
1556 | setValue(v, EmitIfChanged, tmp != t); |
1557 | pendingEmit = false; |
1558 | } else { |
1559 | pendingEmit = true; |
1560 | } |
1561 | } else { |
1562 | pendingEmit = true; |
1563 | } |
1564 | } |
1565 | |
1566 | /*! |
1567 | \internal |
1568 | |
1569 | Virtual slot connected to the line edit's |
1570 | cursorPositionChanged(int, int) signal. Will move the cursor to a |
1571 | valid position if the new one is invalid. E.g. inside the prefix. |
1572 | Reimplemented in Q[Date|Time|DateTime]EditPrivate to account for |
1573 | the different sections etc. |
1574 | */ |
1575 | |
1576 | void QAbstractSpinBoxPrivate::_q_editorCursorPositionChanged(int oldpos, int newpos) |
1577 | { |
1578 | if (!edit->hasSelectedText() && !ignoreCursorPositionChanged && !specialValue()) { |
1579 | ignoreCursorPositionChanged = true; |
1580 | |
1581 | bool allowSelection = true; |
1582 | int pos = -1; |
1583 | if (newpos < prefix.size() && newpos != 0) { |
1584 | if (oldpos == 0) { |
1585 | allowSelection = false; |
1586 | pos = prefix.size(); |
1587 | } else { |
1588 | pos = oldpos; |
1589 | } |
1590 | } else if (newpos > edit->text().size() - suffix.size() |
1591 | && newpos != edit->text().size()) { |
1592 | if (oldpos == edit->text().size()) { |
1593 | pos = edit->text().size() - suffix.size(); |
1594 | allowSelection = false; |
1595 | } else { |
1596 | pos = edit->text().size(); |
1597 | } |
1598 | } |
1599 | if (pos != -1) { |
1600 | const int selSize = edit->selectionStart() >= 0 && allowSelection |
1601 | ? (edit->selectedText().size() |
1602 | * (newpos < pos ? -1 : 1)) - newpos + pos |
1603 | : 0; |
1604 | |
1605 | const QSignalBlocker blocker(edit); |
1606 | if (selSize != 0) { |
1607 | edit->setSelection(pos - selSize, selSize); |
1608 | } else { |
1609 | edit->setCursorPosition(pos); |
1610 | } |
1611 | } |
1612 | ignoreCursorPositionChanged = false; |
1613 | } |
1614 | } |
1615 | |
1616 | /*! |
1617 | \internal |
1618 | |
1619 | Initialises the QAbstractSpinBoxPrivate object. |
1620 | */ |
1621 | |
1622 | void QAbstractSpinBoxPrivate::init() |
1623 | { |
1624 | Q_Q(QAbstractSpinBox); |
1625 | |
1626 | q->setLineEdit(new QLineEdit(q)); |
1627 | edit->setObjectName(QLatin1String("qt_spinbox_lineedit" )); |
1628 | validator = new QSpinBoxValidator(q, this); |
1629 | edit->setValidator(validator); |
1630 | |
1631 | QStyleOptionSpinBox opt; |
1632 | // ### This is called from the ctor and thus we shouldn't call initStyleOption yet |
1633 | // ### as we only call the base class implementation of initStyleOption called. |
1634 | q->initStyleOption(&opt); |
1635 | spinClickTimerInterval = q->style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatRate, &opt, q); |
1636 | spinClickThresholdTimerInterval = q->style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatThreshold, &opt, q); |
1637 | q->setFocusPolicy(Qt::WheelFocus); |
1638 | q->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::SpinBox)); |
1639 | q->setAttribute(Qt::WA_InputMethodEnabled); |
1640 | |
1641 | q->setAttribute(Qt::WA_MacShowFocusRect); |
1642 | } |
1643 | |
1644 | /*! |
1645 | \internal |
1646 | |
1647 | Resets the state of the spinbox. E.g. the state is set to |
1648 | (Keyboard|Up) if Key up is currently pressed. |
1649 | */ |
1650 | |
1651 | void QAbstractSpinBoxPrivate::reset() |
1652 | { |
1653 | Q_Q(QAbstractSpinBox); |
1654 | |
1655 | buttonState = None; |
1656 | if (q) { |
1657 | if (spinClickTimerId != -1) |
1658 | q->killTimer(spinClickTimerId); |
1659 | if (spinClickThresholdTimerId != -1) |
1660 | q->killTimer(spinClickThresholdTimerId); |
1661 | spinClickTimerId = spinClickThresholdTimerId = -1; |
1662 | acceleration = 0; |
1663 | q->update(); |
1664 | } |
1665 | } |
1666 | |
1667 | /*! |
1668 | \internal |
1669 | |
1670 | Updates the state of the spinbox. |
1671 | */ |
1672 | |
1673 | void QAbstractSpinBoxPrivate::updateState(bool up, bool fromKeyboard /* = false */) |
1674 | { |
1675 | Q_Q(QAbstractSpinBox); |
1676 | if ((up && (buttonState & Up)) || (!up && (buttonState & Down))) |
1677 | return; |
1678 | reset(); |
1679 | if (q && (q->stepEnabled() & (up ? QAbstractSpinBox::StepUpEnabled |
1680 | : QAbstractSpinBox::StepDownEnabled))) { |
1681 | buttonState = (up ? Up : Down) | (fromKeyboard ? Keyboard : Mouse); |
1682 | int steps = up ? 1 : -1; |
1683 | if (keyboardModifiers & stepModifier) |
1684 | steps *= 10; |
1685 | q->stepBy(steps); |
1686 | spinClickThresholdTimerId = q->startTimer(spinClickThresholdTimerInterval); |
1687 | #ifndef QT_NO_ACCESSIBILITY |
1688 | QAccessibleValueChangeEvent event(q, value); |
1689 | QAccessible::updateAccessibility(&event); |
1690 | #endif |
1691 | } |
1692 | } |
1693 | |
1694 | |
1695 | /*! |
1696 | Initialize \a option with the values from this QSpinBox. This method |
1697 | is useful for subclasses when they need a QStyleOptionSpinBox, but don't want |
1698 | to fill in all the information themselves. |
1699 | |
1700 | \sa QStyleOption::initFrom() |
1701 | */ |
1702 | void QAbstractSpinBox::initStyleOption(QStyleOptionSpinBox *option) const |
1703 | { |
1704 | if (!option) |
1705 | return; |
1706 | |
1707 | Q_D(const QAbstractSpinBox); |
1708 | option->initFrom(this); |
1709 | option->activeSubControls = QStyle::SC_None; |
1710 | option->buttonSymbols = d->buttonSymbols; |
1711 | option->subControls = QStyle::SC_SpinBoxEditField; |
1712 | if (style()->styleHint(QStyle::SH_SpinBox_ButtonsInsideFrame, nullptr, this)) |
1713 | option->subControls |= QStyle::SC_SpinBoxFrame; |
1714 | if (d->buttonSymbols != QAbstractSpinBox::NoButtons) { |
1715 | option->subControls |= QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown; |
1716 | if (d->buttonState & Up) { |
1717 | option->activeSubControls = QStyle::SC_SpinBoxUp; |
1718 | } else if (d->buttonState & Down) { |
1719 | option->activeSubControls = QStyle::SC_SpinBoxDown; |
1720 | } |
1721 | } |
1722 | |
1723 | if (d->buttonState) { |
1724 | option->state |= QStyle::State_Sunken; |
1725 | } else { |
1726 | option->activeSubControls = d->hoverControl; |
1727 | } |
1728 | |
1729 | option->stepEnabled = style()->styleHint(QStyle::SH_SpinControls_DisableOnBounds, nullptr, this) |
1730 | ? stepEnabled() |
1731 | : (QAbstractSpinBox::StepDownEnabled|QAbstractSpinBox::StepUpEnabled); |
1732 | |
1733 | option->frame = d->frame; |
1734 | } |
1735 | |
1736 | /*! |
1737 | \internal |
1738 | |
1739 | Bounds \a val to be within minimum and maximum. Also tries to be |
1740 | clever about setting it at min and max depending on what it was |
1741 | and what direction it was changed etc. |
1742 | */ |
1743 | |
1744 | QVariant QAbstractSpinBoxPrivate::bound(const QVariant &val, const QVariant &old, int steps) const |
1745 | { |
1746 | QVariant v = val; |
1747 | if (!wrapping || steps == 0 || old.isNull()) { |
1748 | if (variantCompare(v, minimum) < 0) { |
1749 | v = wrapping ? maximum : minimum; |
1750 | } |
1751 | if (variantCompare(v, maximum) > 0) { |
1752 | v = wrapping ? minimum : maximum; |
1753 | } |
1754 | } else { |
1755 | const bool wasMin = old == minimum; |
1756 | const bool wasMax = old == maximum; |
1757 | const int oldcmp = variantCompare(v, old); |
1758 | const int maxcmp = variantCompare(v, maximum); |
1759 | const int mincmp = variantCompare(v, minimum); |
1760 | const bool wrapped = (oldcmp > 0 && steps < 0) || (oldcmp < 0 && steps > 0); |
1761 | if (maxcmp > 0) { |
1762 | v = ((wasMax && !wrapped && steps > 0) || (steps < 0 && !wasMin && wrapped)) |
1763 | ? minimum : maximum; |
1764 | } else if (wrapped && (maxcmp > 0 || mincmp < 0)) { |
1765 | v = ((wasMax && steps > 0) || (!wasMin && steps < 0)) ? minimum : maximum; |
1766 | } else if (mincmp < 0) { |
1767 | v = (!wasMax && !wasMin ? minimum : maximum); |
1768 | } |
1769 | } |
1770 | |
1771 | return v; |
1772 | } |
1773 | |
1774 | /*! |
1775 | \internal |
1776 | |
1777 | Sets the value of the spin box to \a val. Depending on the value |
1778 | of \a ep it will also emit signals. |
1779 | */ |
1780 | |
1781 | void QAbstractSpinBoxPrivate::setValue(const QVariant &val, EmitPolicy ep, |
1782 | bool doUpdate) |
1783 | { |
1784 | Q_Q(QAbstractSpinBox); |
1785 | const QVariant old = value; |
1786 | value = bound(val); |
1787 | pendingEmit = false; |
1788 | cleared = false; |
1789 | if (doUpdate) { |
1790 | updateEdit(); |
1791 | } |
1792 | q->update(); |
1793 | |
1794 | if (ep == AlwaysEmit || (ep == EmitIfChanged && old != value)) { |
1795 | emitSignals(ep, old); |
1796 | } |
1797 | } |
1798 | |
1799 | /*! |
1800 | \internal |
1801 | |
1802 | Updates the line edit to reflect the current value of the spin box. |
1803 | */ |
1804 | |
1805 | void QAbstractSpinBoxPrivate::updateEdit() |
1806 | { |
1807 | Q_Q(QAbstractSpinBox); |
1808 | if (type == QMetaType::UnknownType) |
1809 | return; |
1810 | const QString newText = specialValue() ? specialValueText : prefix + textFromValue(value) + suffix; |
1811 | if (newText == edit->displayText() || cleared) |
1812 | return; |
1813 | |
1814 | const bool empty = edit->text().isEmpty(); |
1815 | int cursor = edit->cursorPosition(); |
1816 | int selsize = edit->selectedText().size(); |
1817 | const QSignalBlocker blocker(edit); |
1818 | edit->setText(newText); |
1819 | |
1820 | if (!specialValue()) { |
1821 | cursor = qBound(prefix.size(), cursor, edit->displayText().size() - suffix.size()); |
1822 | |
1823 | if (selsize > 0) { |
1824 | edit->setSelection(cursor, selsize); |
1825 | } else { |
1826 | edit->setCursorPosition(empty ? prefix.size() : cursor); |
1827 | } |
1828 | } |
1829 | q->update(); |
1830 | } |
1831 | |
1832 | /*! |
1833 | \internal |
1834 | |
1835 | Convenience function to set min/max values. |
1836 | */ |
1837 | |
1838 | void QAbstractSpinBoxPrivate::setRange(const QVariant &min, const QVariant &max) |
1839 | { |
1840 | Q_Q(QAbstractSpinBox); |
1841 | |
1842 | clearCache(); |
1843 | minimum = min; |
1844 | maximum = (variantCompare(min, max) < 0 ? max : min); |
1845 | cachedSizeHint = QSize(); |
1846 | cachedMinimumSizeHint = QSize(); // minimumSizeHint cares about min/max |
1847 | |
1848 | reset(); |
1849 | if (!(bound(value) == value)) { |
1850 | setValue(bound(value), EmitIfChanged); |
1851 | } else if (value == minimum && !specialValueText.isEmpty()) { |
1852 | updateEdit(); |
1853 | } |
1854 | |
1855 | q->updateGeometry(); |
1856 | } |
1857 | |
1858 | /*! |
1859 | \internal |
1860 | |
1861 | Convenience function to get a variant of the right type. |
1862 | */ |
1863 | |
1864 | QVariant QAbstractSpinBoxPrivate::getZeroVariant() const |
1865 | { |
1866 | QVariant ret; |
1867 | switch (type) { |
1868 | case QMetaType::Int: ret = QVariant(0); break; |
1869 | case QMetaType::Double: ret = QVariant(0.0); break; |
1870 | default: break; |
1871 | } |
1872 | return ret; |
1873 | } |
1874 | |
1875 | /*! |
1876 | \internal |
1877 | |
1878 | Virtual method called that calls the public textFromValue() |
1879 | functions in the subclasses. Needed to change signature from |
1880 | QVariant to int/double/QDateTime etc. Used when needing to display |
1881 | a value textually. |
1882 | |
1883 | This method is reimeplemented in the various subclasses. |
1884 | */ |
1885 | |
1886 | QString QAbstractSpinBoxPrivate::textFromValue(const QVariant &) const |
1887 | { |
1888 | return QString(); |
1889 | } |
1890 | |
1891 | /*! |
1892 | \internal |
1893 | |
1894 | Virtual method called that calls the public valueFromText() |
1895 | functions in the subclasses. Needed to change signature from |
1896 | QVariant to int/double/QDateTime etc. Used when needing to |
1897 | interpret a string as another type. |
1898 | |
1899 | This method is reimeplemented in the various subclasses. |
1900 | */ |
1901 | |
1902 | QVariant QAbstractSpinBoxPrivate::valueFromText(const QString &) const |
1903 | { |
1904 | return QVariant(); |
1905 | } |
1906 | /*! |
1907 | \internal |
1908 | |
1909 | Interprets text and emits signals. Called when the spinbox needs |
1910 | to interpret the text on the lineedit. |
1911 | */ |
1912 | |
1913 | void QAbstractSpinBoxPrivate::interpret(EmitPolicy ep) |
1914 | { |
1915 | Q_Q(QAbstractSpinBox); |
1916 | if (type == QMetaType::UnknownType || cleared) |
1917 | return; |
1918 | |
1919 | QVariant v = getZeroVariant(); |
1920 | bool doInterpret = true; |
1921 | QString tmp = edit->displayText(); |
1922 | int pos = edit->cursorPosition(); |
1923 | const int oldpos = pos; |
1924 | |
1925 | if (q->validate(tmp, pos) != QValidator::Acceptable) { |
1926 | const QString copy = tmp; |
1927 | q->fixup(tmp); |
1928 | QASBDEBUG() << "QAbstractSpinBoxPrivate::interpret() text '" |
1929 | << edit->displayText() |
1930 | << "' >> '" << copy << '\'' |
1931 | << "' >> '" << tmp << '\''; |
1932 | |
1933 | doInterpret = tmp != copy && (q->validate(tmp, pos) == QValidator::Acceptable); |
1934 | if (!doInterpret) { |
1935 | v = (correctionMode == QAbstractSpinBox::CorrectToNearestValue |
1936 | ? variantBound(minimum, v, maximum) : value); |
1937 | } |
1938 | } |
1939 | if (doInterpret) { |
1940 | v = valueFromText(tmp); |
1941 | } |
1942 | clearCache(); |
1943 | setValue(v, ep, true); |
1944 | if (oldpos != pos) |
1945 | edit->setCursorPosition(pos); |
1946 | } |
1947 | |
1948 | void QAbstractSpinBoxPrivate::clearCache() const |
1949 | { |
1950 | cachedText.clear(); |
1951 | cachedValue.clear(); |
1952 | cachedState = QValidator::Acceptable; |
1953 | } |
1954 | |
1955 | QVariant QAbstractSpinBoxPrivate::calculateAdaptiveDecimalStep(int steps) const |
1956 | { |
1957 | Q_UNUSED(steps); |
1958 | return singleStep; |
1959 | } |
1960 | |
1961 | // --- QSpinBoxValidator --- |
1962 | |
1963 | /*! |
1964 | \internal |
1965 | Constructs a QSpinBoxValidator object |
1966 | */ |
1967 | |
1968 | QSpinBoxValidator::QSpinBoxValidator(QAbstractSpinBox *qp, QAbstractSpinBoxPrivate *dp) |
1969 | : QValidator(qp), qptr(qp), dptr(dp) |
1970 | { |
1971 | setObjectName(QLatin1String("qt_spinboxvalidator" )); |
1972 | } |
1973 | |
1974 | /*! |
1975 | \internal |
1976 | |
1977 | Checks for specialValueText, prefix, suffix and calls |
1978 | the virtual QAbstractSpinBox::validate function. |
1979 | */ |
1980 | |
1981 | QValidator::State QSpinBoxValidator::validate(QString &input, int &pos) const |
1982 | { |
1983 | if (dptr->specialValueText.size() > 0 && input == dptr->specialValueText) |
1984 | return QValidator::Acceptable; |
1985 | |
1986 | if (!dptr->prefix.isEmpty() && !input.startsWith(dptr->prefix)) { |
1987 | input.prepend(dptr->prefix); |
1988 | pos += dptr->prefix.length(); |
1989 | } |
1990 | |
1991 | if (!dptr->suffix.isEmpty() && !input.endsWith(dptr->suffix)) |
1992 | input.append(dptr->suffix); |
1993 | |
1994 | return qptr->validate(input, pos); |
1995 | } |
1996 | /*! |
1997 | \internal |
1998 | Calls the virtual QAbstractSpinBox::fixup function. |
1999 | */ |
2000 | |
2001 | void QSpinBoxValidator::fixup(QString &input) const |
2002 | { |
2003 | qptr->fixup(input); |
2004 | } |
2005 | |
2006 | // --- global --- |
2007 | |
2008 | /*! |
2009 | \internal |
2010 | Adds two variants together and returns the result. |
2011 | */ |
2012 | |
2013 | QVariant operator+(const QVariant &arg1, const QVariant &arg2) |
2014 | { |
2015 | QVariant ret; |
2016 | if (Q_UNLIKELY(arg1.userType() != arg2.userType())) |
2017 | qWarning("QAbstractSpinBox: Internal error: Different types (%s vs %s) (%s:%d)" , |
2018 | arg1.typeName(), arg2.typeName(), __FILE__, __LINE__); |
2019 | switch (arg1.userType()) { |
2020 | case QMetaType::Int: { |
2021 | const int int1 = arg1.toInt(); |
2022 | const int int2 = arg2.toInt(); |
2023 | if (int1 > 0 && (int2 >= INT_MAX - int1)) { |
2024 | // The increment overflows |
2025 | ret = QVariant(INT_MAX); |
2026 | } else if (int1 < 0 && (int2 <= INT_MIN - int1)) { |
2027 | // The increment underflows |
2028 | ret = QVariant(INT_MIN); |
2029 | } else { |
2030 | ret = QVariant(int1 + int2); |
2031 | } |
2032 | break; |
2033 | } |
2034 | case QMetaType::Double: ret = QVariant(arg1.toDouble() + arg2.toDouble()); break; |
2035 | #if QT_CONFIG(datetimeparser) |
2036 | case QMetaType::QDateTime: { |
2037 | QDateTime a2 = arg2.toDateTime(); |
2038 | QDateTime a1 = arg1.toDateTime().addDays(QDATETIMEEDIT_DATE_MIN.daysTo(a2.date())); |
2039 | a1.setTime(a1.time().addMSecs(a2.time().msecsSinceStartOfDay())); |
2040 | ret = QVariant(a1); |
2041 | break; |
2042 | } |
2043 | #endif // datetimeparser |
2044 | default: break; |
2045 | } |
2046 | return ret; |
2047 | } |
2048 | |
2049 | |
2050 | /*! |
2051 | \internal |
2052 | Subtracts two variants and returns the result. |
2053 | */ |
2054 | |
2055 | QVariant operator-(const QVariant &arg1, const QVariant &arg2) |
2056 | { |
2057 | QVariant ret; |
2058 | if (Q_UNLIKELY(arg1.userType() != arg2.userType())) |
2059 | qWarning("QAbstractSpinBox: Internal error: Different types (%s vs %s) (%s:%d)" , |
2060 | arg1.typeName(), arg2.typeName(), __FILE__, __LINE__); |
2061 | switch (arg1.userType()) { |
2062 | case QMetaType::Int: ret = QVariant(arg1.toInt() - arg2.toInt()); break; |
2063 | case QMetaType::Double: ret = QVariant(arg1.toDouble() - arg2.toDouble()); break; |
2064 | case QMetaType::QDateTime: { |
2065 | QDateTime a1 = arg1.toDateTime(); |
2066 | QDateTime a2 = arg2.toDateTime(); |
2067 | int days = a2.daysTo(a1); |
2068 | int secs = a2.secsTo(a1); |
2069 | int msecs = qMax(0, a1.time().msec() - a2.time().msec()); |
2070 | if (days < 0 || secs < 0 || msecs < 0) { |
2071 | ret = arg1; |
2072 | } else { |
2073 | QDateTime dt = a2.addDays(days).addSecs(secs); |
2074 | if (msecs > 0) |
2075 | dt.setTime(dt.time().addMSecs(msecs)); |
2076 | ret = QVariant(dt); |
2077 | } |
2078 | } |
2079 | default: break; |
2080 | } |
2081 | return ret; |
2082 | } |
2083 | |
2084 | /*! |
2085 | \internal |
2086 | Multiplies \a arg1 by \a multiplier and returns the result. |
2087 | */ |
2088 | |
2089 | QVariant operator*(const QVariant &arg1, double multiplier) |
2090 | { |
2091 | QVariant ret; |
2092 | |
2093 | switch (arg1.userType()) { |
2094 | case QMetaType::Int: |
2095 | ret = static_cast<int>(qBound<double>(INT_MIN, arg1.toInt() * multiplier, INT_MAX)); |
2096 | break; |
2097 | case QMetaType::Double: ret = QVariant(arg1.toDouble() * multiplier); break; |
2098 | #if QT_CONFIG(datetimeparser) |
2099 | case QMetaType::QDateTime: { |
2100 | double days = QDATETIMEEDIT_DATE_MIN.daysTo(arg1.toDateTime().date()) * multiplier; |
2101 | const qint64 daysInt = qint64(days); |
2102 | days -= daysInt; |
2103 | qint64 msecs = qint64(arg1.toDateTime().time().msecsSinceStartOfDay() * multiplier |
2104 | + days * (24 * 3600 * 1000)); |
2105 | ret = QDATETIMEEDIT_DATE_MIN.addDays(daysInt).startOfDay().addMSecs(msecs); |
2106 | break; |
2107 | } |
2108 | #endif // datetimeparser |
2109 | default: ret = arg1; break; |
2110 | } |
2111 | |
2112 | return ret; |
2113 | } |
2114 | |
2115 | |
2116 | |
2117 | double operator/(const QVariant &arg1, const QVariant &arg2) |
2118 | { |
2119 | double a1 = 0; |
2120 | double a2 = 0; |
2121 | |
2122 | switch (arg1.userType()) { |
2123 | case QMetaType::Int: |
2124 | a1 = (double)arg1.toInt(); |
2125 | a2 = (double)arg2.toInt(); |
2126 | break; |
2127 | case QMetaType::Double: |
2128 | a1 = arg1.toDouble(); |
2129 | a2 = arg2.toDouble(); |
2130 | break; |
2131 | #if QT_CONFIG(datetimeparser) |
2132 | case QMetaType::QDateTime: |
2133 | a1 = QDATETIMEEDIT_DATE_MIN.daysTo(arg1.toDate()); |
2134 | a2 = QDATETIMEEDIT_DATE_MIN.daysTo(arg2.toDate()); |
2135 | a1 += arg1.toDateTime().time().msecsSinceStartOfDay() / (36e5 * 24); |
2136 | a2 += arg2.toDateTime().time().msecsSinceStartOfDay() / (36e5 * 24); |
2137 | break; |
2138 | #endif // datetimeparser |
2139 | default: break; |
2140 | } |
2141 | |
2142 | return (a1 != 0 && a2 != 0) ? (a1 / a2) : 0.0; |
2143 | } |
2144 | |
2145 | int QAbstractSpinBoxPrivate::variantCompare(const QVariant &arg1, const QVariant &arg2) |
2146 | { |
2147 | switch (arg2.userType()) { |
2148 | case QMetaType::QDate: |
2149 | Q_ASSERT_X(arg1.userType() == QMetaType::QDate, "QAbstractSpinBoxPrivate::variantCompare" , |
2150 | qPrintable(QString::fromLatin1("Internal error 1 (%1)" ). |
2151 | arg(QString::fromLatin1(arg1.typeName())))); |
2152 | if (arg1.toDate() == arg2.toDate()) { |
2153 | return 0; |
2154 | } else if (arg1.toDate() < arg2.toDate()) { |
2155 | return -1; |
2156 | } else { |
2157 | return 1; |
2158 | } |
2159 | case QMetaType::QTime: |
2160 | Q_ASSERT_X(arg1.userType() == QMetaType::QTime, "QAbstractSpinBoxPrivate::variantCompare" , |
2161 | qPrintable(QString::fromLatin1("Internal error 2 (%1)" ). |
2162 | arg(QString::fromLatin1(arg1.typeName())))); |
2163 | if (arg1.toTime() == arg2.toTime()) { |
2164 | return 0; |
2165 | } else if (arg1.toTime() < arg2.toTime()) { |
2166 | return -1; |
2167 | } else { |
2168 | return 1; |
2169 | } |
2170 | |
2171 | |
2172 | case QMetaType::QDateTime: |
2173 | if (arg1.toDateTime() == arg2.toDateTime()) { |
2174 | return 0; |
2175 | } else if (arg1.toDateTime() < arg2.toDateTime()) { |
2176 | return -1; |
2177 | } else { |
2178 | return 1; |
2179 | } |
2180 | case QMetaType::Int: |
2181 | if (arg1.toInt() == arg2.toInt()) { |
2182 | return 0; |
2183 | } else if (arg1.toInt() < arg2.toInt()) { |
2184 | return -1; |
2185 | } else { |
2186 | return 1; |
2187 | } |
2188 | case QMetaType::Double: |
2189 | if (arg1.toDouble() == arg2.toDouble()) { |
2190 | return 0; |
2191 | } else if (arg1.toDouble() < arg2.toDouble()) { |
2192 | return -1; |
2193 | } else { |
2194 | return 1; |
2195 | } |
2196 | case QMetaType::UnknownType: |
2197 | if (arg2.userType() == QMetaType::UnknownType) |
2198 | return 0; |
2199 | Q_FALLTHROUGH(); |
2200 | default: |
2201 | Q_ASSERT_X(0, "QAbstractSpinBoxPrivate::variantCompare" , |
2202 | qPrintable(QString::fromLatin1("Internal error 3 (%1 %2)" ). |
2203 | arg(QString::fromLatin1(arg1.typeName()), |
2204 | QString::fromLatin1(arg2.typeName())))); |
2205 | } |
2206 | return -2; |
2207 | } |
2208 | |
2209 | QVariant QAbstractSpinBoxPrivate::variantBound(const QVariant &min, |
2210 | const QVariant &value, |
2211 | const QVariant &max) |
2212 | { |
2213 | Q_ASSERT(variantCompare(min, max) <= 0); |
2214 | if (variantCompare(min, value) < 0) { |
2215 | const int compMax = variantCompare(value, max); |
2216 | return (compMax < 0 ? value : max); |
2217 | } else { |
2218 | return min; |
2219 | } |
2220 | } |
2221 | |
2222 | |
2223 | QT_END_NAMESPACE |
2224 | |
2225 | #include "moc_qabstractspinbox.cpp" |
2226 | |