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 test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | |
30 | #include <QtTest/QtTest> |
31 | #include <qdebug.h> |
32 | #include <qapplication.h> |
33 | #include <limits.h> |
34 | |
35 | #include <qspinbox.h> |
36 | #include <qlocale.h> |
37 | #include <qlineedit.h> |
38 | #include <qlayout.h> |
39 | #include <QSpinBox> |
40 | #include <QWidget> |
41 | #include <QString> |
42 | #include <QValidator> |
43 | #include <QLineEdit> |
44 | #include <QObject> |
45 | #include <QStringList> |
46 | #include <QList> |
47 | #include <QLocale> |
48 | #include <QDoubleSpinBox> |
49 | #include <QVBoxLayout> |
50 | #include <QKeySequence> |
51 | #include <QStackedWidget> |
52 | #include <QDebug> |
53 | #include <QStyleOptionSpinBox> |
54 | #include <QStyle> |
55 | #include <QProxyStyle> |
56 | #include <QScreen> |
57 | |
58 | |
59 | class SpinBox : public QSpinBox |
60 | { |
61 | public: |
62 | SpinBox(QWidget *parent = 0) |
63 | : QSpinBox(parent) |
64 | {} |
65 | QString textFromValue(int v) const |
66 | { |
67 | return QSpinBox::textFromValue(val: v); |
68 | } |
69 | QValidator::State validate(QString &text, int &pos) const |
70 | { |
71 | return QSpinBox::validate(input&: text, pos); |
72 | } |
73 | int valueFromText(const QString &text) const |
74 | { |
75 | return QSpinBox::valueFromText(text); |
76 | } |
77 | #if QT_CONFIG(wheelevent) |
78 | void wheelEvent(QWheelEvent *event) |
79 | { |
80 | QSpinBox::wheelEvent(event); |
81 | } |
82 | #endif |
83 | void initStyleOption(QStyleOptionSpinBox *option) const |
84 | { |
85 | QSpinBox::initStyleOption(option); |
86 | } |
87 | |
88 | QLineEdit *lineEdit() const { return QSpinBox::lineEdit(); } |
89 | }; |
90 | |
91 | class PressAndHoldStyle : public QProxyStyle |
92 | { |
93 | Q_OBJECT |
94 | public: |
95 | using QProxyStyle::QProxyStyle; |
96 | |
97 | int styleHint(QStyle::StyleHint hint, const QStyleOption *option = nullptr, |
98 | const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const override |
99 | { |
100 | switch (hint) { |
101 | case QStyle::SH_SpinBox_ClickAutoRepeatRate: |
102 | return 5; |
103 | case QStyle::SH_SpinBox_ClickAutoRepeatThreshold: |
104 | return 10; |
105 | default: |
106 | return QProxyStyle::styleHint(hint, option, widget, returnData); |
107 | } |
108 | } |
109 | }; |
110 | |
111 | class StepModifierStyle : public QProxyStyle |
112 | { |
113 | Q_OBJECT |
114 | public: |
115 | using QProxyStyle::QProxyStyle; |
116 | |
117 | int styleHint(QStyle::StyleHint hint, const QStyleOption *option = nullptr, |
118 | const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const override |
119 | { |
120 | switch (hint) { |
121 | case QStyle::SH_SpinBox_StepModifier: |
122 | return stepModifier; |
123 | default: |
124 | return QProxyStyle::styleHint(hint, option, widget, returnData); |
125 | } |
126 | } |
127 | |
128 | Qt::KeyboardModifier stepModifier = Qt::ControlModifier; |
129 | }; |
130 | |
131 | class tst_QSpinBox : public QObject |
132 | { |
133 | Q_OBJECT |
134 | public: |
135 | tst_QSpinBox(); |
136 | public slots: |
137 | void init(); |
138 | private slots: |
139 | void getSetCheck(); |
140 | void setValue_data(); |
141 | void setValue(); |
142 | |
143 | void setDisplayIntegerBase_data(); |
144 | void setDisplayIntegerBase(); |
145 | |
146 | void setPrefixSuffix_data(); |
147 | void setPrefixSuffix(); |
148 | |
149 | void setReadOnly(); |
150 | |
151 | void setTracking_data(); |
152 | void setTracking(); |
153 | |
154 | void locale_data(); |
155 | void locale(); |
156 | |
157 | void setWrapping_data(); |
158 | void setWrapping(); |
159 | |
160 | void setSpecialValueText_data(); |
161 | void setSpecialValueText(); |
162 | |
163 | void setSingleStep_data(); |
164 | void setSingleStep(); |
165 | |
166 | void setMinMax_data(); |
167 | void setMinMax(); |
168 | |
169 | void editingFinished(); |
170 | |
171 | void valueFromTextAndValidate_data(); |
172 | void valueFromTextAndValidate(); |
173 | |
174 | void removeAll(); |
175 | void startWithDash(); |
176 | void undoRedo(); |
177 | |
178 | void specialValue(); |
179 | void textFromValue(); |
180 | |
181 | void sizeHint(); |
182 | |
183 | void integerOverflow(); |
184 | |
185 | void taskQTBUG_5008_textFromValueAndValidate(); |
186 | void lineEditReturnPressed(); |
187 | |
188 | void positiveSign(); |
189 | |
190 | void interpretOnLosingFocus(); |
191 | |
192 | void setGroupSeparatorShown_data(); |
193 | void setGroupSeparatorShown(); |
194 | |
195 | void wheelEvents_data(); |
196 | void wheelEvents(); |
197 | |
198 | void adaptiveDecimalStep(); |
199 | |
200 | void stepModifierKeys_data(); |
201 | void stepModifierKeys(); |
202 | |
203 | void stepModifierButtons_data(); |
204 | void stepModifierButtons(); |
205 | |
206 | void stepModifierPressAndHold_data(); |
207 | void stepModifierPressAndHold(); |
208 | public slots: |
209 | void textChangedHelper(const QString &); |
210 | void valueChangedHelper(int); |
211 | private: |
212 | QStringList actualTexts; |
213 | QList<int> actualValues; |
214 | }; |
215 | |
216 | typedef QList<int> IntList; |
217 | |
218 | Q_DECLARE_METATYPE(QLocale::Language) |
219 | Q_DECLARE_METATYPE(QLocale::Country) |
220 | |
221 | static QLatin1String modifierToName(Qt::KeyboardModifier modifier) |
222 | { |
223 | switch (modifier) { |
224 | case Qt::NoModifier: |
225 | return QLatin1String("No" ); |
226 | break; |
227 | case Qt::ControlModifier: |
228 | return QLatin1String("Ctrl" ); |
229 | break; |
230 | case Qt::ShiftModifier: |
231 | return QLatin1String("Shift" ); |
232 | break; |
233 | case Qt::AltModifier: |
234 | return QLatin1String("Alt" ); |
235 | break; |
236 | case Qt::MetaModifier: |
237 | return QLatin1String("Meta" ); |
238 | break; |
239 | default: |
240 | qFatal(msg: "Unexpected keyboard modifier" ); |
241 | return QLatin1String(); |
242 | } |
243 | } |
244 | |
245 | // Testing get/set functions |
246 | void tst_QSpinBox::getSetCheck() |
247 | { |
248 | QSpinBox obj1; |
249 | QCOMPARE(obj1.inputMethodQuery(Qt::ImHints), QVariant(int(Qt::ImhDigitsOnly))); |
250 | // int QSpinBox::singleStep() |
251 | // void QSpinBox::setSingleStep(int) |
252 | obj1.setSingleStep(0); |
253 | QCOMPARE(0, obj1.singleStep()); |
254 | obj1.setSingleStep(INT_MIN); |
255 | QCOMPARE(0, obj1.singleStep()); // Can't have negative steps => keep old value |
256 | obj1.setSingleStep(INT_MAX); |
257 | QCOMPARE(INT_MAX, obj1.singleStep()); |
258 | |
259 | // int QSpinBox::minimum() |
260 | // void QSpinBox::setMinimum(int) |
261 | obj1.setMinimum(0); |
262 | QCOMPARE(0, obj1.minimum()); |
263 | obj1.setMinimum(INT_MIN); |
264 | QCOMPARE(INT_MIN, obj1.minimum()); |
265 | obj1.setMinimum(INT_MAX); |
266 | QCOMPARE(INT_MAX, obj1.minimum()); |
267 | |
268 | // int QSpinBox::maximum() |
269 | // void QSpinBox::setMaximum(int) |
270 | obj1.setMaximum(0); |
271 | QCOMPARE(0, obj1.maximum()); |
272 | obj1.setMaximum(INT_MIN); |
273 | QCOMPARE(INT_MIN, obj1.maximum()); |
274 | obj1.setMaximum(INT_MAX); |
275 | QCOMPARE(INT_MAX, obj1.maximum()); |
276 | |
277 | // int QSpinBox::value() |
278 | // void QSpinBox::setValue(int) |
279 | obj1.setValue(0); |
280 | QCOMPARE(0, obj1.value()); |
281 | obj1.setValue(INT_MIN); |
282 | QCOMPARE(INT_MIN, obj1.value()); |
283 | obj1.setValue(INT_MAX); |
284 | QCOMPARE(INT_MAX, obj1.value()); |
285 | |
286 | QDoubleSpinBox obj2; |
287 | QCOMPARE(obj2.inputMethodQuery(Qt::ImHints), QVariant(int(Qt::ImhFormattedNumbersOnly))); |
288 | // double QDoubleSpinBox::singleStep() |
289 | // void QDoubleSpinBox::setSingleStep(double) |
290 | obj2.setSingleStep(0.0); |
291 | QCOMPARE(0.0, obj2.singleStep()); |
292 | obj2.setSingleStep(1.0); |
293 | QCOMPARE(1.0, obj2.singleStep()); |
294 | |
295 | // double QDoubleSpinBox::minimum() |
296 | // void QDoubleSpinBox::setMinimum(double) |
297 | obj2.setMinimum(1.0); |
298 | QCOMPARE(1.0, obj2.minimum()); |
299 | obj2.setMinimum(0.0); |
300 | QCOMPARE(0.0, obj2.minimum()); |
301 | obj2.setMinimum(-1.0); |
302 | QCOMPARE(-1.0, obj2.minimum()); |
303 | |
304 | // double QDoubleSpinBox::maximum() |
305 | // void QDoubleSpinBox::setMaximum(double) |
306 | obj2.setMaximum(-1.0); |
307 | QCOMPARE(-1.0, obj2.maximum()); |
308 | obj2.setMaximum(0.0); |
309 | QCOMPARE(0.0, obj2.maximum()); |
310 | obj2.setMaximum(1.0); |
311 | QCOMPARE(1.0, obj2.maximum()); |
312 | |
313 | // int QDoubleSpinBox::decimals() |
314 | // void QDoubleSpinBox::setDecimals(int) |
315 | obj2.setDecimals(0); |
316 | QCOMPARE(0, obj2.decimals()); |
317 | obj2.setDecimals(INT_MIN); |
318 | QCOMPARE(0, obj2.decimals()); // Range<0, 13> |
319 | |
320 | //obj2.setDecimals(INT_MAX); |
321 | //QCOMPARE(13, obj2.decimals()); // Range<0, 13> |
322 | obj2.setDecimals(128); |
323 | QCOMPARE(obj2.decimals(), 128); // Range<0, 128> |
324 | |
325 | // double QDoubleSpinBox::value() |
326 | // void QDoubleSpinBox::setValue(double) |
327 | obj2.setValue(-1.0); |
328 | QCOMPARE(-1.0, obj2.value()); |
329 | obj2.setValue(0.0); |
330 | QCOMPARE(0.0, obj2.value()); |
331 | obj2.setValue(1.0); |
332 | QCOMPARE(1.0, obj2.value()); |
333 | |
334 | // Make sure we update line edit geometry when updating |
335 | // buttons - see task 235747 |
336 | QRect oldEditGeometry = obj1.childrenRect(); |
337 | obj1.setButtonSymbols(QAbstractSpinBox::NoButtons); |
338 | QVERIFY(obj1.childrenRect() != oldEditGeometry); |
339 | } |
340 | |
341 | tst_QSpinBox::tst_QSpinBox() |
342 | { |
343 | } |
344 | |
345 | void tst_QSpinBox::init() |
346 | { |
347 | QLocale::setDefault(QLocale(QLocale::C)); |
348 | |
349 | #if QT_CONFIG(cursor) |
350 | // Ensure mouse cursor was not left by previous tests where widgets |
351 | // will appear, as it could cause events and interfere with the tests. |
352 | const QScreen *screen = QGuiApplication::primaryScreen(); |
353 | const QRect availableGeometry = screen->availableGeometry(); |
354 | QCursor::setPos(availableGeometry.topLeft()); |
355 | #endif |
356 | } |
357 | |
358 | void tst_QSpinBox::setValue_data() |
359 | { |
360 | QTest::addColumn<int>(name: "set" ); |
361 | QTest::addColumn<int>(name: "expected" ); |
362 | |
363 | QTest::newRow(dataTag: "data0" ) << 0 << 0; |
364 | QTest::newRow(dataTag: "data1" ) << 100 << 100; |
365 | QTest::newRow(dataTag: "data2" ) << -100 << -100; |
366 | QTest::newRow(dataTag: "data3" ) << INT_MIN << INT_MIN; |
367 | QTest::newRow(dataTag: "data4" ) << INT_MAX << INT_MAX; |
368 | } |
369 | |
370 | void tst_QSpinBox::setValue() |
371 | { |
372 | QFETCH(int, set); |
373 | QFETCH(int, expected); |
374 | QSpinBox spin(0); |
375 | spin.setRange(INT_MIN, INT_MAX); |
376 | spin.setValue(set); |
377 | QCOMPARE(spin.value(), expected); |
378 | } |
379 | |
380 | void tst_QSpinBox::setDisplayIntegerBase_data() |
381 | { |
382 | QTest::addColumn<int>(name: "value" ); |
383 | QTest::addColumn<int>(name: "base" ); |
384 | QTest::addColumn<QString>(name: "string" ); |
385 | |
386 | QTest::newRow(dataTag: "base 10" ) << 42 << 10 << "42" ; |
387 | QTest::newRow(dataTag: "base 2" ) << 42 << 2 << "101010" ; |
388 | QTest::newRow(dataTag: "base 8" ) << 42 << 8 << "52" ; |
389 | QTest::newRow(dataTag: "base 16" ) << 42 << 16 << "2a" ; |
390 | QTest::newRow(dataTag: "base 0" ) << 42 << 0 << "42" ; |
391 | QTest::newRow(dataTag: "base -4" ) << 42 << -4 << "42" ; |
392 | QTest::newRow(dataTag: "base 40" ) << 42 << 40 << "42" ; |
393 | |
394 | QTest::newRow(dataTag: "negative base 10" ) << -42 << 10 << "-42" ; |
395 | QTest::newRow(dataTag: "negative base 2" ) << -42 << 2 << "-101010" ; |
396 | QTest::newRow(dataTag: "negative base 8" ) << -42 << 8 << "-52" ; |
397 | QTest::newRow(dataTag: "negative base 16" ) << -42 << 16 << "-2a" ; |
398 | QTest::newRow(dataTag: "negative base 0" ) << -42 << 0 << "-42" ; |
399 | QTest::newRow(dataTag: "negative base -4" ) << -42 << -4 << "-42" ; |
400 | QTest::newRow(dataTag: "negative base 40" ) << -42 << 40 << "-42" ; |
401 | |
402 | QTest::newRow(dataTag: "0 base 10" ) << 0 << 10 << "0" ; |
403 | QTest::newRow(dataTag: "0 base 2" ) << 0 << 2 << "0" ; |
404 | QTest::newRow(dataTag: "0 base 8" ) << 0 << 8 << "0" ; |
405 | QTest::newRow(dataTag: "0 base 16" ) << 0 << 16 << "0" ; |
406 | QTest::newRow(dataTag: "0 base 0" ) << 0 << 0 << "0" ; |
407 | QTest::newRow(dataTag: "0 base -4" ) << 0 << -4 << "0" ; |
408 | QTest::newRow(dataTag: "0 base 40" ) << 0 << 40 << "0" ; |
409 | } |
410 | |
411 | void tst_QSpinBox::setDisplayIntegerBase() |
412 | { |
413 | QFETCH(int, value); |
414 | QFETCH(int, base); |
415 | QFETCH(QString, string); |
416 | |
417 | SpinBox spin; |
418 | spin.setRange(INT_MIN, INT_MAX); |
419 | |
420 | spin.setValue(value); |
421 | QCOMPARE(spin.lineEdit()->text(), QString::number(value)); |
422 | |
423 | spin.setDisplayIntegerBase(base); |
424 | QCOMPARE(spin.lineEdit()->text(), string); |
425 | |
426 | spin.setValue(0); |
427 | QCOMPARE(spin.value(), 0); |
428 | QCOMPARE(spin.lineEdit()->text(), QString::number(0, base)); |
429 | |
430 | spin.lineEdit()->clear(); |
431 | QTest::keyClicks(widget: spin.lineEdit(), sequence: string); |
432 | QCOMPARE(spin.value(), value); |
433 | } |
434 | |
435 | void tst_QSpinBox::setPrefixSuffix_data() |
436 | { |
437 | QTest::addColumn<QString>(name: "prefix" ); |
438 | QTest::addColumn<QString>(name: "suffix" ); |
439 | QTest::addColumn<int>(name: "value" ); |
440 | QTest::addColumn<QString>(name: "expectedText" ); |
441 | QTest::addColumn<QString>(name: "expectedCleanText" ); |
442 | QTest::addColumn<bool>(name: "show" ); |
443 | |
444 | QTest::newRow(dataTag: "data0" ) << QString() << QString() << 10 << "10" << "10" << false; |
445 | QTest::newRow(dataTag: "data1" ) << QString() << "cm" << 10 << "10cm" << "10" << false; |
446 | QTest::newRow(dataTag: "data2" ) << "cm: " << QString() << 10 << "cm: 10" << "10" << false; |
447 | QTest::newRow(dataTag: "data3" ) << "length: " << "cm" << 10 << "length: 10cm" << "10" << false; |
448 | |
449 | QTest::newRow(dataTag: "data4" ) << QString() << QString() << 10 << "10" << "10" << true; |
450 | QTest::newRow(dataTag: "data5" ) << QString() << "cm" << 10 << "10cm" << "10" << true; |
451 | QTest::newRow(dataTag: "data6" ) << "cm: " << QString() << 10 << "cm: 10" << "10" << true; |
452 | QTest::newRow(dataTag: "data7" ) << "length: " << "cm" << 10 << "length: 10cm" << "10" << true; |
453 | } |
454 | |
455 | void tst_QSpinBox::setPrefixSuffix() |
456 | { |
457 | QFETCH(QString, prefix); |
458 | QFETCH(QString, suffix); |
459 | QFETCH(int, value); |
460 | QFETCH(QString, expectedText); |
461 | QFETCH(QString, expectedCleanText); |
462 | QFETCH(bool, show); |
463 | |
464 | QSpinBox spin; |
465 | if (show) { |
466 | spin.show(); |
467 | spin.setPrefix(QString()); // trigger a recalc of sizeHint |
468 | } |
469 | const QSize size1 = spin.sizeHint(); |
470 | spin.setPrefix(prefix); |
471 | const QSize size2 = spin.sizeHint(); |
472 | spin.setSuffix(suffix); |
473 | const QSize size3 = spin.sizeHint(); |
474 | spin.setValue(value); |
475 | |
476 | QCOMPARE(spin.prefix(), prefix); |
477 | QCOMPARE(spin.suffix(), suffix); |
478 | QCOMPARE(spin.text(), expectedText); |
479 | QCOMPARE(spin.cleanText(), expectedCleanText); |
480 | |
481 | if (!suffix.isEmpty()) { |
482 | QVERIFY(size2.width() < size3.width()); |
483 | spin.setSuffix(QString()); |
484 | QCOMPARE(spin.sizeHint(), size2); |
485 | } |
486 | if (!prefix.isEmpty()) { |
487 | QVERIFY(size1.width() < size2.width()); |
488 | spin.setPrefix(QString()); |
489 | QCOMPARE(spin.sizeHint(), size1); |
490 | } |
491 | } |
492 | |
493 | void tst_QSpinBox::textChangedHelper(const QString &text) |
494 | { |
495 | actualTexts << text; |
496 | } |
497 | |
498 | void tst_QSpinBox::valueChangedHelper(int value) |
499 | { |
500 | actualValues << value; |
501 | } |
502 | |
503 | class ReadOnlyChangeTracker: public QSpinBox |
504 | { |
505 | public: |
506 | ReadOnlyChangeTracker(QWidget *parent = 0) : QSpinBox(parent) {} |
507 | |
508 | void changeEvent(QEvent *ev) { |
509 | if (ev->type() == QEvent::ReadOnlyChange) |
510 | ++readOnlyChangeEventCount; |
511 | } |
512 | int readOnlyChangeEventCount = 0; |
513 | }; |
514 | |
515 | void tst_QSpinBox::setReadOnly() |
516 | { |
517 | ReadOnlyChangeTracker spin(0); |
518 | spin.show(); |
519 | QTest::keyClick(widget: &spin, key: Qt::Key_Up); |
520 | QCOMPARE(spin.value(), 1); |
521 | spin.setReadOnly(true); |
522 | #ifndef Q_OS_WINRT // QTBUG-68297 |
523 | QCOMPARE(spin.readOnlyChangeEventCount, 1); |
524 | #endif |
525 | QTest::keyClick(widget: &spin, key: Qt::Key_Up); |
526 | QCOMPARE(spin.value(), 1); |
527 | spin.stepBy(steps: 1); |
528 | QCOMPARE(spin.value(), 2); |
529 | spin.setReadOnly(false); |
530 | #ifndef Q_OS_WINRT // QTBUG-68297 |
531 | QCOMPARE(spin.readOnlyChangeEventCount, 2); |
532 | #endif |
533 | QTest::keyClick(widget: &spin, key: Qt::Key_Up); |
534 | QCOMPARE(spin.value(), 3); |
535 | } |
536 | void tst_QSpinBox::setTracking_data() |
537 | { |
538 | QTest::addColumn<QTestEventList>(name: "keys" ); |
539 | QTest::addColumn<QStringList>(name: "texts" ); |
540 | QTest::addColumn<bool>(name: "tracking" ); |
541 | |
542 | QTestEventList keys; |
543 | QStringList texts1; |
544 | QStringList texts2; |
545 | |
546 | #ifdef Q_OS_MAC |
547 | keys.addKeyClick(Qt::Key_Right, Qt::ControlModifier); |
548 | #else |
549 | keys.addKeyClick(qtKey: Qt::Key_End); |
550 | #endif |
551 | keys.addKeyClick(ascii: '7'); |
552 | keys.addKeyClick(ascii: '9'); |
553 | keys.addKeyClick(qtKey: Qt::Key_Enter); |
554 | keys.addKeyClick(qtKey: Qt::Key_Enter); |
555 | keys.addKeyClick(qtKey: Qt::Key_Enter); |
556 | texts1 << "07" << "079" << "79" << "79" << "79" ; |
557 | texts2 << "79" ; |
558 | QTest::newRow(dataTag: "data1" ) << keys << texts1 << true; |
559 | QTest::newRow(dataTag: "data2" ) << keys << texts2 << false; |
560 | } |
561 | |
562 | void tst_QSpinBox::setTracking() |
563 | { |
564 | actualTexts.clear(); |
565 | QFETCH(QTestEventList, keys); |
566 | QFETCH(QStringList, texts); |
567 | QFETCH(bool, tracking); |
568 | |
569 | QSpinBox spin(0); |
570 | spin.setKeyboardTracking(tracking); |
571 | spin.show(); |
572 | connect(sender: &spin, signal: &QSpinBox::textChanged, receiver: this, slot: &tst_QSpinBox::textChangedHelper); |
573 | |
574 | keys.simulate(w: &spin); |
575 | QCOMPARE(actualTexts, texts); |
576 | } |
577 | |
578 | void tst_QSpinBox::setWrapping_data() |
579 | { |
580 | QTest::addColumn<bool>(name: "wrapping" ); |
581 | QTest::addColumn<int>(name: "minimum" ); |
582 | QTest::addColumn<int>(name: "maximum" ); |
583 | QTest::addColumn<int>(name: "startValue" ); |
584 | QTest::addColumn<QTestEventList>(name: "keys" ); |
585 | QTest::addColumn<IntList>(name: "expected" ); |
586 | |
587 | QTestEventList keys; |
588 | IntList values; |
589 | keys.addKeyClick(qtKey: Qt::Key_Up); |
590 | values << 10; |
591 | keys.addKeyClick(qtKey: Qt::Key_Up); |
592 | QTest::newRow(dataTag: "data0" ) << false << 0 << 10 << 9 << keys << values; |
593 | |
594 | keys.clear(); |
595 | values.clear(); |
596 | keys.addKeyClick(qtKey: Qt::Key_Up); |
597 | values << 10; |
598 | keys.addKeyClick(qtKey: Qt::Key_Up); |
599 | values << 0; |
600 | QTest::newRow(dataTag: "data1" ) << true << 0 << 10 << 9 << keys << values; |
601 | |
602 | keys.clear(); |
603 | values.clear(); |
604 | keys.addKeyClick(qtKey: Qt::Key_Delete); // doesn't emit because lineedit is empty so intermediate |
605 | keys.addKeyClick(ascii: '1'); |
606 | keys.addKeyClick(qtKey: Qt::Key_Down); |
607 | keys.addKeyClick(qtKey: Qt::Key_Down); |
608 | values << 1 << 0; |
609 | QTest::newRow(dataTag: "data2" ) << false << 0 << 10 << 9 << keys << values; |
610 | |
611 | keys.clear(); |
612 | values.clear(); |
613 | keys.addKeyClick(qtKey: Qt::Key_Delete); |
614 | keys.addKeyClick(ascii: '1'); |
615 | keys.addKeyClick(qtKey: Qt::Key_Down); |
616 | keys.addKeyClick(qtKey: Qt::Key_Down); |
617 | values << 1 << 0 << 10; |
618 | QTest::newRow(dataTag: "data3" ) << true << 0 << 10 << 9 << keys << values; |
619 | |
620 | keys.clear(); |
621 | values.clear(); |
622 | keys.addKeyClick(qtKey: Qt::Key_PageDown); |
623 | keys.addKeyClick(qtKey: Qt::Key_Down); |
624 | values << 0; |
625 | QTest::newRow(dataTag: "data4" ) << false << 0 << 10 << 6 << keys << values; |
626 | |
627 | keys.clear(); |
628 | values.clear(); |
629 | keys.addKeyClick(qtKey: Qt::Key_PageDown); |
630 | keys.addKeyClick(qtKey: Qt::Key_Down); |
631 | values << 0 << 10; |
632 | QTest::newRow(dataTag: "data5" ) << true << 0 << 10 << 6 << keys << values; |
633 | |
634 | keys.clear(); |
635 | values.clear(); |
636 | keys.addKeyClick(qtKey: Qt::Key_PageUp); |
637 | keys.addKeyClick(qtKey: Qt::Key_PageDown); |
638 | keys.addKeyClick(qtKey: Qt::Key_Down); |
639 | keys.addKeyClick(qtKey: Qt::Key_Up); |
640 | keys.addKeyClick(qtKey: Qt::Key_PageDown); |
641 | keys.addKeyClick(qtKey: Qt::Key_PageDown); |
642 | values << 10 << 0 << 10 << 0 << 10 << 0; |
643 | QTest::newRow(dataTag: "data6" ) << true << 0 << 10 << 6 << keys << values; |
644 | |
645 | } |
646 | |
647 | |
648 | void tst_QSpinBox::setWrapping() |
649 | { |
650 | QFETCH(bool, wrapping); |
651 | QFETCH(int, minimum); |
652 | QFETCH(int, maximum); |
653 | QFETCH(int, startValue); |
654 | QFETCH(QTestEventList, keys); |
655 | QFETCH(IntList, expected); |
656 | |
657 | QSpinBox spin(0); |
658 | QVERIFY(!spin.wrapping()); |
659 | spin.setMinimum(minimum); |
660 | spin.setMaximum(maximum); |
661 | spin.setValue(startValue); |
662 | spin.setWrapping(wrapping); |
663 | spin.show(); |
664 | actualValues.clear(); |
665 | connect(sender: &spin, SIGNAL(valueChanged(int)), receiver: this, SLOT(valueChangedHelper(int))); |
666 | |
667 | keys.simulate(w: &spin); |
668 | |
669 | QCOMPARE(actualValues.size(), expected.size()); |
670 | for (int i=0; i<qMin(a: actualValues.size(), b: expected.size()); ++i) { |
671 | QCOMPARE(actualValues.at(i), expected.at(i)); |
672 | } |
673 | } |
674 | |
675 | void tst_QSpinBox::setSpecialValueText_data() |
676 | { |
677 | QTest::addColumn<QString>(name: "specialValueText" ); |
678 | QTest::addColumn<int>(name: "minimum" ); |
679 | QTest::addColumn<int>(name: "maximum" ); |
680 | QTest::addColumn<int>(name: "value" ); |
681 | QTest::addColumn<QString>(name: "expected" ); |
682 | QTest::addColumn<bool>(name: "show" ); |
683 | |
684 | QTest::newRow(dataTag: "data0" ) << QString() << 0 << 10 << 1 << "1" << false; |
685 | QTest::newRow(dataTag: "data1" ) << QString() << 0 << 10 << 1 << "1" << true; |
686 | QTest::newRow(dataTag: "data2" ) << "foo" << 0 << 10 << 0 << "foo" << false; |
687 | QTest::newRow(dataTag: "data3" ) << "foo" << 0 << 10 << 0 << "foo" << true; |
688 | } |
689 | |
690 | void tst_QSpinBox::setSpecialValueText() |
691 | { |
692 | QFETCH(QString, specialValueText); |
693 | QFETCH(int, minimum); |
694 | QFETCH(int, maximum); |
695 | QFETCH(int, value); |
696 | QFETCH(QString, expected); |
697 | QFETCH(bool, show); |
698 | |
699 | QSpinBox spin(0); |
700 | spin.setSpecialValueText(specialValueText); |
701 | QCOMPARE(spin.specialValueText(), specialValueText); |
702 | spin.setMinimum(minimum); |
703 | spin.setMaximum(maximum); |
704 | spin.setValue(value); |
705 | if (show) |
706 | spin.show(); |
707 | |
708 | QCOMPARE(spin.text(), expected); |
709 | } |
710 | |
711 | void tst_QSpinBox::setSingleStep_data() |
712 | { |
713 | QTest::addColumn<int>(name: "singleStep" ); |
714 | QTest::addColumn<int>(name: "startValue" ); |
715 | QTest::addColumn<QTestEventList>(name: "keys" ); |
716 | QTest::addColumn<IntList>(name: "expected" ); |
717 | QTest::addColumn<bool>(name: "show" ); |
718 | |
719 | QTestEventList keys; |
720 | IntList values; |
721 | keys.addKeyClick(qtKey: Qt::Key_Up); |
722 | keys.addKeyClick(qtKey: Qt::Key_Down); |
723 | keys.addKeyClick(qtKey: Qt::Key_Up); |
724 | values << 11 << 10 << 11; |
725 | QTest::newRow(dataTag: "data0" ) << 1 << 10 << keys << values << false; |
726 | QTest::newRow(dataTag: "data1" ) << 1 << 10 << keys << values << true; |
727 | |
728 | keys.clear(); |
729 | values.clear(); |
730 | keys.addKeyClick(qtKey: Qt::Key_Up); |
731 | keys.addKeyClick(qtKey: Qt::Key_Down); |
732 | keys.addKeyClick(qtKey: Qt::Key_Up); |
733 | values << 12 << 10 << 12; |
734 | QTest::newRow(dataTag: "data2" ) << 2 << 10 << keys << values << false; |
735 | QTest::newRow(dataTag: "data3" ) << 2 << 10 << keys << values << true; |
736 | } |
737 | |
738 | void tst_QSpinBox::setSingleStep() |
739 | { |
740 | QFETCH(int, singleStep); |
741 | QFETCH(int, startValue); |
742 | QFETCH(QTestEventList, keys); |
743 | QFETCH(IntList, expected); |
744 | QFETCH(bool, show); |
745 | |
746 | QSpinBox spin(0); |
747 | actualValues.clear(); |
748 | spin.setSingleStep(singleStep); |
749 | QCOMPARE(spin.singleStep(), singleStep); |
750 | spin.setValue(startValue); |
751 | if (show) |
752 | spin.show(); |
753 | connect(sender: &spin, SIGNAL(valueChanged(int)), receiver: this, SLOT(valueChangedHelper(int))); |
754 | |
755 | QCOMPARE(actualValues.size(), 0); |
756 | keys.simulate(w: &spin); |
757 | QCOMPARE(actualValues.size(), expected.size()); |
758 | for (int i=0; i<qMin(a: actualValues.size(), b: expected.size()); ++i) { |
759 | QCOMPARE(actualValues.at(i), expected.at(i)); |
760 | } |
761 | } |
762 | |
763 | void tst_QSpinBox::setMinMax_data() |
764 | { |
765 | QTest::addColumn<int>(name: "startValue" ); |
766 | QTest::addColumn<int>(name: "mini" ); |
767 | QTest::addColumn<int>(name: "maxi" ); |
768 | QTest::addColumn<QTestEventList>(name: "keys" ); |
769 | QTest::addColumn<int>(name: "expected" ); |
770 | QTest::addColumn<bool>(name: "show" ); |
771 | |
772 | QTestEventList keys; |
773 | keys.addKeyClick(qtKey: Qt::Key_Up); |
774 | keys.addKeyClick(qtKey: Qt::Key_Up); |
775 | keys.addKeyClick(qtKey: Qt::Key_Up); |
776 | keys.addKeyClick(qtKey: Qt::Key_Up); |
777 | keys.addKeyClick(qtKey: Qt::Key_Up); |
778 | QTest::newRow(dataTag: "data0" ) << 1 << INT_MIN << 2 << keys << 2 << false; |
779 | QTest::newRow(dataTag: "data1" ) << 1 << INT_MIN << 2 << keys << 2 << true; |
780 | |
781 | keys.clear(); |
782 | QTest::newRow(dataTag: "data2" ) << 2 << INT_MAX - 2 << INT_MAX << keys << INT_MAX - 2 << false; |
783 | QTest::newRow(dataTag: "data3" ) << 2 << INT_MAX - 2 << INT_MAX << keys << INT_MAX - 2 << true; |
784 | } |
785 | |
786 | void tst_QSpinBox::setMinMax() |
787 | { |
788 | QFETCH(int, startValue); |
789 | QFETCH(int, mini); |
790 | QFETCH(int, maxi); |
791 | QFETCH(QTestEventList, keys); |
792 | QFETCH(int, expected); |
793 | QFETCH(bool, show); |
794 | |
795 | QSpinBox spin(0); |
796 | spin.setValue(startValue); |
797 | spin.setMinimum(mini); |
798 | spin.setMaximum(maxi); |
799 | QCOMPARE(spin.minimum(), mini); |
800 | QCOMPARE(spin.maximum(), maxi); |
801 | if (show) |
802 | spin.show(); |
803 | keys.simulate(w: &spin); |
804 | QCOMPARE(spin.value(), expected); |
805 | } |
806 | |
807 | void tst_QSpinBox::valueFromTextAndValidate_data() |
808 | { |
809 | const int Intermediate = QValidator::Intermediate; |
810 | const int Invalid = QValidator::Invalid; |
811 | const int Acceptable = QValidator::Acceptable; |
812 | |
813 | QTest::addColumn<QString>(name: "txt" ); |
814 | QTest::addColumn<int>(name: "state" ); |
815 | QTest::addColumn<int>(name: "mini" ); |
816 | QTest::addColumn<int>(name: "maxi" ); |
817 | QTest::addColumn<QString>(name: "expectedText" ); // if empty we don't check |
818 | |
819 | QTest::newRow(dataTag: "data0" ) << QString("2" ) << Intermediate << 3 << 5 << QString(); |
820 | QTest::newRow(dataTag: "data1" ) << QString() << Intermediate << 0 << 100 << QString(); |
821 | QTest::newRow(dataTag: "data2" ) << QString("asd" ) << Invalid << 0 << 100 << QString(); |
822 | QTest::newRow(dataTag: "data3" ) << QString("2" ) << Acceptable << 0 << 100 << QString(); |
823 | QTest::newRow(dataTag: "data4" ) << QString() << Intermediate << 0 << 1 << QString(); |
824 | QTest::newRow(dataTag: "data5" ) << QString() << Invalid << 0 << 0 << QString(); |
825 | QTest::newRow(dataTag: "data6" ) << QString("5" ) << Intermediate << 2004 << 2005 << QString(); |
826 | QTest::newRow(dataTag: "data7" ) << QString("50" ) << Intermediate << 2004 << 2005 << QString(); |
827 | QTest::newRow(dataTag: "data8" ) << QString("205" ) << Intermediate << 2004 << 2005 << QString(); |
828 | QTest::newRow(dataTag: "data9" ) << QString("2005" ) << Acceptable << 2004 << 2005 << QString(); |
829 | QTest::newRow(dataTag: "data10" ) << QString("3" ) << Intermediate << 2004 << 2005 << QString(); |
830 | QTest::newRow(dataTag: "data11" ) << QString("-" ) << Intermediate << -20 << -10 << QString(); |
831 | QTest::newRow(dataTag: "data12" ) << QString("-1" ) << Intermediate << -20 << -10 << QString(); |
832 | QTest::newRow(dataTag: "data13" ) << QString("-5" ) << Intermediate << -20 << -10 << QString(); |
833 | QTest::newRow(dataTag: "data14" ) << QString("-5" ) << Intermediate << -20 << -16 << QString(); |
834 | QTest::newRow(dataTag: "data15" ) << QString("-2" ) << Intermediate << -20 << -16 << QString(); |
835 | QTest::newRow(dataTag: "data16" ) << QString("2" ) << Invalid << -20 << -16 << QString(); |
836 | QTest::newRow(dataTag: "data17" ) << QString() << Intermediate << -20 << -16 << QString(); |
837 | QTest::newRow(dataTag: "data18" ) << QString(" 22" ) << Acceptable << 0 << 1000 << QString("22" ); |
838 | QTest::newRow(dataTag: "data19" ) << QString("22 " ) << Acceptable << 0 << 1000 << QString("22" ); |
839 | QTest::newRow(dataTag: "data20" ) << QString(" 22 " ) << Acceptable << 0 << 1000 << QString("22" ); |
840 | QTest::newRow(dataTag: "data21" ) << QString("2 2" ) << Invalid << 0 << 1000 << QString(); |
841 | } |
842 | |
843 | static QString stateName(int state) |
844 | { |
845 | switch (state) { |
846 | case QValidator::Acceptable: return QString("Acceptable" ); |
847 | case QValidator::Intermediate: return QString("Intermediate" ); |
848 | case QValidator::Invalid: return QString("Invalid" ); |
849 | default: break; |
850 | } |
851 | qWarning(msg: "%s %d: this should never happen" , __FILE__, __LINE__); |
852 | return QString(); |
853 | } |
854 | |
855 | void tst_QSpinBox::valueFromTextAndValidate() |
856 | { |
857 | QFETCH(QString, txt); |
858 | QFETCH(int, state); |
859 | QFETCH(int, mini); |
860 | QFETCH(int, maxi); |
861 | QFETCH(QString, expectedText); |
862 | |
863 | SpinBox sb(0); |
864 | sb.show(); |
865 | sb.setRange(min: mini, max: maxi); |
866 | int unused = 0; |
867 | QCOMPARE(stateName(sb.validate(txt, unused)), stateName(state)); |
868 | if (!expectedText.isEmpty()) |
869 | QCOMPARE(txt, expectedText); |
870 | } |
871 | |
872 | void tst_QSpinBox::locale_data() |
873 | { |
874 | QTest::addColumn<QLocale>(name: "loc" ); |
875 | QTest::addColumn<int>(name: "value" ); |
876 | QTest::addColumn<QString>(name: "textFromVal" ); |
877 | QTest::addColumn<QString>(name: "text" ); |
878 | QTest::addColumn<int>(name: "valFromText" ); |
879 | |
880 | QTest::newRow(dataTag: "data0" ) << QLocale(QLocale::NorwegianBokmal, QLocale::Norway) << 1234 << QString("1234" ) << QString("2345" ) << 2345; |
881 | QTest::newRow(dataTag: "data1" ) << QLocale(QLocale::German, QLocale::Germany) << 1234 << QString("1234" ) << QString("2345" ) << 2345; |
882 | } |
883 | |
884 | void tst_QSpinBox::locale() |
885 | { |
886 | QFETCH(QLocale, loc); |
887 | QFETCH(int, value); |
888 | QFETCH(QString, textFromVal); |
889 | QFETCH(QString, text); |
890 | QFETCH(int, valFromText); |
891 | |
892 | QLocale old; |
893 | |
894 | QLocale::setDefault(loc); |
895 | SpinBox box; |
896 | box.setMaximum(100000); |
897 | box.setValue(value); |
898 | QCOMPARE(box.cleanText(), textFromVal); |
899 | |
900 | box.lineEdit()->setText(text); |
901 | QCOMPARE(box.cleanText(), text); |
902 | box.interpretText(); |
903 | |
904 | QCOMPARE(box.value(), valFromText); |
905 | } |
906 | |
907 | |
908 | void tst_QSpinBox::editingFinished() |
909 | { |
910 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
911 | QSKIP("Wayland: This fails. Figure out why." ); |
912 | |
913 | QWidget testFocusWidget; |
914 | testFocusWidget.setObjectName(QLatin1String("tst_qspinbox" )); |
915 | testFocusWidget.setWindowTitle(objectName()); |
916 | testFocusWidget.resize(w: 200, h: 100); |
917 | |
918 | QVBoxLayout *layout = new QVBoxLayout(&testFocusWidget); |
919 | QSpinBox *box = new QSpinBox(&testFocusWidget); |
920 | layout->addWidget(box); |
921 | QSpinBox *box2 = new QSpinBox(&testFocusWidget); |
922 | layout->addWidget(box2); |
923 | |
924 | testFocusWidget.show(); |
925 | QApplication::setActiveWindow(&testFocusWidget); |
926 | QVERIFY(QTest::qWaitForWindowActive(&testFocusWidget)); |
927 | box->activateWindow(); |
928 | box->setFocus(); |
929 | |
930 | QTRY_COMPARE(qApp->focusWidget(), (QWidget *)box); |
931 | |
932 | QSignalSpy editingFinishedSpy1(box, SIGNAL(editingFinished())); |
933 | QSignalSpy editingFinishedSpy2(box2, SIGNAL(editingFinished())); |
934 | |
935 | box->setFocus(); |
936 | QTest::keyClick(widget: box, key: Qt::Key_Up); |
937 | QTest::keyClick(widget: box, key: Qt::Key_Up); |
938 | |
939 | QCOMPARE(editingFinishedSpy1.count(), 0); |
940 | QCOMPARE(editingFinishedSpy2.count(), 0); |
941 | |
942 | QTest::keyClick(widget: box2, key: Qt::Key_Up); |
943 | QTest::keyClick(widget: box2, key: Qt::Key_Up); |
944 | box2->setFocus(); |
945 | QCOMPARE(editingFinishedSpy1.count(), 1); |
946 | box->setFocus(); |
947 | QCOMPARE(editingFinishedSpy1.count(), 1); |
948 | QCOMPARE(editingFinishedSpy2.count(), 1); |
949 | QTest::keyClick(widget: box, key: Qt::Key_Up); |
950 | QCOMPARE(editingFinishedSpy1.count(), 1); |
951 | QCOMPARE(editingFinishedSpy2.count(), 1); |
952 | QTest::keyClick(widget: box, key: Qt::Key_Enter); |
953 | QCOMPARE(editingFinishedSpy1.count(), 2); |
954 | QCOMPARE(editingFinishedSpy2.count(), 1); |
955 | QTest::keyClick(widget: box, key: Qt::Key_Return); |
956 | QCOMPARE(editingFinishedSpy1.count(), 3); |
957 | QCOMPARE(editingFinishedSpy2.count(), 1); |
958 | box2->setFocus(); |
959 | QCOMPARE(editingFinishedSpy1.count(), 4); |
960 | QCOMPARE(editingFinishedSpy2.count(), 1); |
961 | QTest::keyClick(widget: box2, key: Qt::Key_Enter); |
962 | QCOMPARE(editingFinishedSpy1.count(), 4); |
963 | QCOMPARE(editingFinishedSpy2.count(), 2); |
964 | QTest::keyClick(widget: box2, key: Qt::Key_Return); |
965 | QCOMPARE(editingFinishedSpy1.count(), 4); |
966 | QCOMPARE(editingFinishedSpy2.count(), 3); |
967 | |
968 | testFocusWidget.hide(); |
969 | QCOMPARE(editingFinishedSpy1.count(), 4); |
970 | QCOMPARE(editingFinishedSpy2.count(), 4); |
971 | |
972 | //task203285 |
973 | editingFinishedSpy1.clear(); |
974 | testFocusWidget.show(); |
975 | QVERIFY(QTest::qWaitForWindowActive(&testFocusWidget)); |
976 | box->setKeyboardTracking(false); |
977 | qApp->setActiveWindow(&testFocusWidget); |
978 | testFocusWidget.activateWindow(); |
979 | box->setFocus(); |
980 | QTRY_VERIFY(box->hasFocus()); |
981 | box->setValue(0); |
982 | QTest::keyClick(widget: box, key: '2'); |
983 | QCOMPARE(box->text(), QLatin1String("20" )); |
984 | box2->setFocus(); |
985 | QTRY_VERIFY(qApp->focusWidget() != box); |
986 | QCOMPARE(box->text(), QLatin1String("20" )); |
987 | QCOMPARE(editingFinishedSpy1.count(), 1); |
988 | } |
989 | |
990 | void tst_QSpinBox::removeAll() |
991 | { |
992 | SpinBox spin(0); |
993 | spin.setPrefix("foo" ); |
994 | spin.setSuffix("bar" ); |
995 | spin.setValue(2); |
996 | spin.show(); |
997 | #ifdef Q_OS_MAC |
998 | QTest::keyClick(&spin, Qt::Key_Left, Qt::ControlModifier); |
999 | #else |
1000 | QTest::keyClick(widget: &spin, key: Qt::Key_Home); |
1001 | #endif |
1002 | |
1003 | #ifdef Q_OS_MAC |
1004 | QTest::keyClick(&spin, Qt::Key_Right, Qt::ControlModifier|Qt::ShiftModifier); |
1005 | #else |
1006 | QTest::keyClick(widget: &spin, key: Qt::Key_End, modifier: Qt::ShiftModifier); |
1007 | #endif |
1008 | |
1009 | QCOMPARE(spin.lineEdit()->selectedText(), QString("foo2bar" )); |
1010 | QTest::keyClick(widget: &spin, key: Qt::Key_1); |
1011 | QCOMPARE(spin.text(), QString("foo1bar" )); |
1012 | } |
1013 | |
1014 | void tst_QSpinBox::startWithDash() |
1015 | { |
1016 | SpinBox spin(0); |
1017 | spin.show(); |
1018 | #ifdef Q_OS_MAC |
1019 | QTest::keyClick(&spin, Qt::Key_Left, Qt::ControlModifier); |
1020 | #else |
1021 | QTest::keyClick(widget: &spin, key: Qt::Key_Home); |
1022 | #endif |
1023 | QCOMPARE(spin.text(), QString("0" )); |
1024 | QTest::keyClick(widget: &spin, key: Qt::Key_Minus); |
1025 | QCOMPARE(spin.text(), QString("0" )); |
1026 | } |
1027 | |
1028 | void tst_QSpinBox::undoRedo() |
1029 | { |
1030 | //test undo/redo feature (in conjunction with the "undoRedoEnabled" property) |
1031 | SpinBox spin(0); |
1032 | spin.show(); |
1033 | |
1034 | //the undo/redo is disabled by default |
1035 | |
1036 | QCOMPARE(spin.value(), 0); //this is the default value |
1037 | QVERIFY(!spin.lineEdit()->isUndoAvailable()); |
1038 | QVERIFY(!spin.lineEdit()->isRedoAvailable()); |
1039 | |
1040 | spin.lineEdit()->selectAll(); //ensures everything is selected and will be cleared by typing a key |
1041 | QTest::keyClick(widget: &spin, key: Qt::Key_1); //we put 1 into the spinbox |
1042 | QCOMPARE(spin.value(), 1); |
1043 | QVERIFY(spin.lineEdit()->isUndoAvailable()); |
1044 | |
1045 | //testing CTRL+Z (undo) |
1046 | int val = QKeySequence(QKeySequence::Undo)[0]; |
1047 | Qt::KeyboardModifiers mods = (Qt::KeyboardModifiers)(val & Qt::KeyboardModifierMask); |
1048 | QTest::keyClick(widget: &spin, key: val & ~mods, modifier: mods); |
1049 | |
1050 | QCOMPARE(spin.value(), 0); |
1051 | QVERIFY(!spin.lineEdit()->isUndoAvailable()); |
1052 | QVERIFY(spin.lineEdit()->isRedoAvailable()); |
1053 | |
1054 | //testing CTRL+Y (redo) |
1055 | val = QKeySequence(QKeySequence::Redo)[0]; |
1056 | mods = (Qt::KeyboardModifiers)(val & Qt::KeyboardModifierMask); |
1057 | QTest::keyClick(widget: &spin, key: val & ~mods, modifier: mods); |
1058 | QCOMPARE(spin.value(), 1); |
1059 | QVERIFY(!spin.lineEdit()->isRedoAvailable()); |
1060 | QVERIFY(spin.lineEdit()->isUndoAvailable()); |
1061 | |
1062 | spin.setValue(55); |
1063 | QVERIFY(!spin.lineEdit()->isUndoAvailable()); |
1064 | QVERIFY(!spin.lineEdit()->isRedoAvailable()); |
1065 | |
1066 | QTest::keyClick(widget: &spin, key: Qt::Key_Return); |
1067 | QTest::keyClick(widget: &spin, key: '1'); |
1068 | QVERIFY(spin.lineEdit()->isUndoAvailable()); |
1069 | QVERIFY(!spin.lineEdit()->isRedoAvailable()); |
1070 | spin.lineEdit()->undo(); |
1071 | QCOMPARE(spin.value(), 55); |
1072 | QVERIFY(!spin.lineEdit()->isUndoAvailable()); |
1073 | QVERIFY(spin.lineEdit()->isRedoAvailable()); |
1074 | spin.lineEdit()->redo(); |
1075 | QCOMPARE(spin.value(), 1); |
1076 | QVERIFY(spin.lineEdit()->isUndoAvailable()); |
1077 | QVERIFY(!spin.lineEdit()->isRedoAvailable()); |
1078 | } |
1079 | |
1080 | void tst_QSpinBox::specialValue() |
1081 | { |
1082 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1083 | QSKIP("Wayland: This fails. Figure out why." ); |
1084 | |
1085 | QString specialText="foo" ; |
1086 | |
1087 | QWidget topWidget; |
1088 | QVBoxLayout layout(&topWidget); |
1089 | SpinBox spin(&topWidget); |
1090 | layout.addWidget(&spin); |
1091 | SpinBox box2(&topWidget); |
1092 | layout.addWidget(&box2); |
1093 | |
1094 | spin.setSpecialValueText(specialText); |
1095 | spin.setMinimum(0); |
1096 | spin.setMaximum(100); |
1097 | spin.setValue(50); |
1098 | topWidget.show(); |
1099 | //make sure we have the focus (even if editingFinished fails) |
1100 | qApp->setActiveWindow(&topWidget); |
1101 | topWidget.activateWindow(); |
1102 | QVERIFY(QTest::qWaitForWindowActive(&topWidget)); |
1103 | spin.setFocus(); |
1104 | |
1105 | QTest::keyClick(widget: &spin, key: Qt::Key_Return); |
1106 | QTest::keyClick(widget: &spin, key: '0'); |
1107 | QCOMPARE(spin.text(), QString("0" )); |
1108 | QTest::keyClick(widget: &spin, key: Qt::Key_Return); |
1109 | QCOMPARE(spin.text(), specialText); |
1110 | |
1111 | spin.setValue(50); |
1112 | QTest::keyClick(widget: &spin, key: Qt::Key_Return); |
1113 | QTest::keyClick(widget: &spin, key: '0'); |
1114 | QCOMPARE(spin.text(), QString("0" )); |
1115 | QTest::keyClick(widget: spin.lineEdit(), key: Qt::Key_Tab); |
1116 | QCOMPARE(spin.text(), specialText); |
1117 | |
1118 | spin.setValue(50); |
1119 | spin.setFocus(); |
1120 | QTest::keyClick(widget: &spin, key: Qt::Key_Return); |
1121 | QTest::keyClick(widget: &spin, key: '0'); |
1122 | QCOMPARE(spin.text(), QString("0" )); |
1123 | box2.setFocus(); |
1124 | QCOMPARE(spin.text(), specialText); |
1125 | } |
1126 | |
1127 | void tst_QSpinBox::textFromValue() |
1128 | { |
1129 | SpinBox spinBox; |
1130 | QCOMPARE(spinBox.textFromValue(INT_MIN), QString::number(INT_MIN)); |
1131 | } |
1132 | |
1133 | class sizeHint_SpinBox : public QSpinBox |
1134 | { |
1135 | public: |
1136 | QSize sizeHint() const |
1137 | { |
1138 | ++sizeHintRequests; |
1139 | return QSpinBox::sizeHint(); |
1140 | } |
1141 | mutable int sizeHintRequests; |
1142 | }; |
1143 | |
1144 | void tst_QSpinBox::sizeHint() |
1145 | { |
1146 | QWidget *widget = new QWidget; |
1147 | QHBoxLayout *layout = new QHBoxLayout(widget); |
1148 | sizeHint_SpinBox *spinBox = new sizeHint_SpinBox; |
1149 | layout->addWidget(spinBox); |
1150 | widget->show(); |
1151 | QVERIFY(QTest::qWaitForWindowExposed(widget)); |
1152 | |
1153 | // Prefix |
1154 | spinBox->sizeHintRequests = 0; |
1155 | spinBox->setPrefix(QLatin1String("abcdefghij" )); |
1156 | qApp->processEvents(); |
1157 | QTRY_VERIFY(spinBox->sizeHintRequests > 0); |
1158 | |
1159 | // Suffix |
1160 | spinBox->sizeHintRequests = 0; |
1161 | spinBox->setSuffix(QLatin1String("abcdefghij" )); |
1162 | qApp->processEvents(); |
1163 | QTRY_VERIFY(spinBox->sizeHintRequests > 0); |
1164 | |
1165 | // Range |
1166 | spinBox->sizeHintRequests = 0; |
1167 | spinBox->setRange(min: 0, max: 1234567890); |
1168 | spinBox->setValue(spinBox->maximum()); |
1169 | qApp->processEvents(); |
1170 | QTRY_VERIFY(spinBox->sizeHintRequests > 0); |
1171 | |
1172 | delete widget; |
1173 | } |
1174 | |
1175 | void tst_QSpinBox::taskQTBUG_5008_textFromValueAndValidate() |
1176 | { |
1177 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1178 | QSKIP("Wayland: This fails. Figure out why." ); |
1179 | |
1180 | class DecoratedSpinBox : public QSpinBox |
1181 | { |
1182 | public: |
1183 | DecoratedSpinBox() |
1184 | { |
1185 | setLocale(QLocale::French); |
1186 | setMaximum(100000000); |
1187 | setValue(1000000); |
1188 | } |
1189 | |
1190 | QLineEdit *lineEdit() const |
1191 | { |
1192 | return QSpinBox::lineEdit(); |
1193 | } |
1194 | |
1195 | //we use the French delimiters here |
1196 | QString textFromValue (int value) const |
1197 | { |
1198 | return locale().toString(i: value); |
1199 | } |
1200 | |
1201 | } spinbox; |
1202 | spinbox.show(); |
1203 | spinbox.activateWindow(); |
1204 | spinbox.setFocus(); |
1205 | QApplication::setActiveWindow(&spinbox); |
1206 | QVERIFY(QTest::qWaitForWindowActive(&spinbox)); |
1207 | QVERIFY(spinbox.hasFocus()); |
1208 | QTRY_COMPARE(static_cast<QWidget *>(&spinbox), QApplication::activeWindow()); |
1209 | QCOMPARE(spinbox.text(), spinbox.locale().toString(spinbox.value())); |
1210 | spinbox.lineEdit()->setCursorPosition(2); //just after the first thousand separator |
1211 | QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_0); // let's insert a 0 |
1212 | QCOMPARE(spinbox.value(), 10000000); //it's been multiplied by 10 |
1213 | spinbox.clearFocus(); //make sure the value is correctly formatted |
1214 | QCOMPARE(spinbox.text(), spinbox.locale().toString(spinbox.value())); |
1215 | } |
1216 | |
1217 | void tst_QSpinBox::integerOverflow() |
1218 | { |
1219 | QSpinBox sb; |
1220 | sb.setRange(INT_MIN, INT_MAX); |
1221 | |
1222 | sb.setValue(INT_MAX - 1); |
1223 | sb.stepUp(); |
1224 | QCOMPARE(sb.value(), INT_MAX); |
1225 | sb.stepUp(); |
1226 | QCOMPARE(sb.value(), INT_MAX); |
1227 | |
1228 | sb.setValue(INT_MIN + 1); |
1229 | sb.stepDown(); |
1230 | QCOMPARE(sb.value(), INT_MIN); |
1231 | sb.stepDown(); |
1232 | QCOMPARE(sb.value(), INT_MIN); |
1233 | |
1234 | sb.setValue(0); |
1235 | QCOMPARE(sb.value(), 0); |
1236 | sb.setSingleStep(INT_MAX); |
1237 | sb.stepUp(); |
1238 | QCOMPARE(sb.value(), INT_MAX); |
1239 | sb.stepDown(); |
1240 | QCOMPARE(sb.value(), 0); |
1241 | sb.stepDown(); |
1242 | QCOMPARE(sb.value(), INT_MIN + 1); |
1243 | sb.stepDown(); |
1244 | QCOMPARE(sb.value(), INT_MIN); |
1245 | } |
1246 | |
1247 | void tst_QSpinBox::lineEditReturnPressed() |
1248 | { |
1249 | SpinBox spinBox; |
1250 | QSignalSpy spyCurrentChanged(spinBox.lineEdit(), SIGNAL(returnPressed())); |
1251 | spinBox.show(); |
1252 | QTest::keyClick(widget: &spinBox, key: Qt::Key_Return); |
1253 | QCOMPARE(spyCurrentChanged.count(), 1); |
1254 | } |
1255 | |
1256 | void tst_QSpinBox::positiveSign() |
1257 | { |
1258 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1259 | QSKIP("Wayland: This fails. Figure out why." ); |
1260 | |
1261 | QSpinBox spinBox; |
1262 | spinBox.setRange(min: -20, max: 20); |
1263 | spinBox.setValue(-20); |
1264 | spinBox.show(); |
1265 | QVERIFY(QTest::qWaitForWindowActive(&spinBox)); |
1266 | |
1267 | QTest::keyClick(widget: &spinBox, key: Qt::Key_End, modifier: Qt::ShiftModifier); |
1268 | QTest::keyClick(widget: &spinBox, key: Qt::Key_Plus, modifier: Qt::ShiftModifier); |
1269 | QTest::keyClick(widget: &spinBox, key: Qt::Key_2); |
1270 | QTest::keyClick(widget: &spinBox, key: Qt::Key_0); |
1271 | QCOMPARE(spinBox.text(), QLatin1String("+20" )); |
1272 | } |
1273 | |
1274 | void tst_QSpinBox::interpretOnLosingFocus() |
1275 | { |
1276 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1277 | QSKIP("Wayland: This fails. Figure out why." ); |
1278 | |
1279 | // QTBUG-55249: When typing an invalid value after QSpinBox::clear(), |
1280 | // it should be fixed up on losing focus. |
1281 | |
1282 | static const int minimumValue = 10; |
1283 | static const int maximumValue = 20; |
1284 | |
1285 | QWidget widget; |
1286 | widget.setWindowTitle(QTest::currentTestFunction()); |
1287 | QVBoxLayout *layout = new QVBoxLayout(&widget); |
1288 | QLineEdit *focusDummy = new QLineEdit("focusDummy" , &widget); |
1289 | layout->addWidget(focusDummy); |
1290 | SpinBox *spinBox = new SpinBox(&widget); |
1291 | spinBox->setRange(min: minimumValue, max: maximumValue); |
1292 | spinBox->setValue(minimumValue); |
1293 | layout->addWidget(spinBox); |
1294 | spinBox->clear(); |
1295 | spinBox->setFocus(); |
1296 | widget.show(); |
1297 | QVERIFY(QTest::qWaitForWindowActive(&widget)); |
1298 | QTest::keyClick(widget: spinBox, key: Qt::Key_1); // Too small |
1299 | focusDummy->setFocus(); |
1300 | QCOMPARE(spinBox->value(), minimumValue); |
1301 | QCOMPARE(spinBox->lineEdit()->text().toInt(), minimumValue); |
1302 | } |
1303 | |
1304 | void tst_QSpinBox::setGroupSeparatorShown_data() |
1305 | { |
1306 | QTest::addColumn<QLocale::Language>(name: "lang" ); |
1307 | QTest::addColumn<QLocale::Country>(name: "country" ); |
1308 | |
1309 | QTest::newRow(dataTag: "data0" ) << QLocale::English << QLocale::UnitedStates; |
1310 | QTest::newRow(dataTag: "data1" ) << QLocale::Swedish << QLocale::Sweden; |
1311 | QTest::newRow(dataTag: "data2" ) << QLocale::German << QLocale::Germany; |
1312 | QTest::newRow(dataTag: "data3" ) << QLocale::Georgian << QLocale::Georgia; |
1313 | QTest::newRow(dataTag: "data3" ) << QLocale::Macedonian << QLocale::Macedonia; |
1314 | } |
1315 | |
1316 | void tst_QSpinBox::setGroupSeparatorShown() |
1317 | { |
1318 | QFETCH(QLocale::Language, lang); |
1319 | QFETCH(QLocale::Country, country); |
1320 | |
1321 | QLocale loc(lang, country); |
1322 | QLocale::setDefault(loc); |
1323 | SpinBox spinBox; |
1324 | spinBox.setMaximum(99999); |
1325 | spinBox.setValue(13000); |
1326 | spinBox.setGroupSeparatorShown(true); |
1327 | QCOMPARE(spinBox.lineEdit()->text(), spinBox.locale().toString(13000)); |
1328 | QCOMPARE(spinBox.isGroupSeparatorShown(), true); |
1329 | QCOMPARE(spinBox.textFromValue(23421),spinBox.locale().toString(23421)); |
1330 | |
1331 | spinBox.setGroupSeparatorShown(false); |
1332 | QCOMPARE(spinBox.lineEdit()->text(), QStringLiteral("13000" )); |
1333 | QCOMPARE(spinBox.isGroupSeparatorShown(), false); |
1334 | |
1335 | spinBox.setMaximum(72000); |
1336 | spinBox.lineEdit()->setText(spinBox.locale().toString(i: 32000)); |
1337 | QCOMPARE(spinBox.value()+1000, 33000); |
1338 | |
1339 | spinBox.lineEdit()->setText(QStringLiteral("32000" )); |
1340 | QCOMPARE(spinBox.value()+1000, 33000); |
1341 | |
1342 | spinBox.lineEdit()->setText(QStringLiteral("32,000" )); |
1343 | QCOMPARE(spinBox.value()+1000, 33000); |
1344 | } |
1345 | |
1346 | void tst_QSpinBox::wheelEvents_data() |
1347 | { |
1348 | #if QT_CONFIG(wheelevent) |
1349 | QTest::addColumn<QPoint>(name: "angleDelta" ); |
1350 | QTest::addColumn<int>(name: "stepModifier" ); |
1351 | QTest::addColumn<Qt::KeyboardModifiers>(name: "modifier" ); |
1352 | QTest::addColumn<Qt::MouseEventSource>(name: "source" ); |
1353 | QTest::addColumn<int>(name: "start" ); |
1354 | QTest::addColumn<IntList>(name: "expectedValues" ); |
1355 | |
1356 | const auto fractions = {false, true}; |
1357 | |
1358 | const auto directions = {true, false}; |
1359 | |
1360 | const auto modifierList = {Qt::NoModifier, |
1361 | Qt::ShiftModifier, |
1362 | Qt::ControlModifier, |
1363 | Qt::AltModifier, |
1364 | Qt::MetaModifier}; |
1365 | |
1366 | const auto validStepModifierList = {Qt::NoModifier, |
1367 | Qt::ControlModifier, |
1368 | Qt::ShiftModifier}; |
1369 | |
1370 | const auto sources = {Qt::MouseEventNotSynthesized, |
1371 | Qt::MouseEventSynthesizedBySystem, |
1372 | Qt::MouseEventSynthesizedByQt, |
1373 | Qt::MouseEventSynthesizedByApplication}; |
1374 | |
1375 | const int startValue = 0; |
1376 | |
1377 | for (auto fraction : fractions) { |
1378 | for (auto up : directions) { |
1379 | |
1380 | const int units = (fraction ? 60 : 120) * (up ? 1 : -1); |
1381 | |
1382 | for (auto modifier : modifierList) { |
1383 | |
1384 | const Qt::KeyboardModifiers modifiers(modifier); |
1385 | |
1386 | const auto modifierName = modifierToName(modifier); |
1387 | if (modifierName.isEmpty()) |
1388 | continue; |
1389 | |
1390 | for (auto stepModifier : validStepModifierList) { |
1391 | |
1392 | const auto stepModifierName = modifierToName(modifier: stepModifier); |
1393 | if (stepModifierName.isEmpty()) |
1394 | continue; |
1395 | |
1396 | const int steps = (modifier & stepModifier ? 10 : 1) |
1397 | * (up ? 1 : -1); |
1398 | |
1399 | for (auto source : sources) { |
1400 | |
1401 | #ifdef Q_OS_MACOS |
1402 | QPoint angleDelta; |
1403 | if ((modifier & Qt::ShiftModifier) && |
1404 | source == Qt::MouseEventNotSynthesized) { |
1405 | // On macOS the Shift modifier converts vertical |
1406 | // mouse wheel events to horizontal. |
1407 | angleDelta = { units, 0 }; |
1408 | } else { |
1409 | // However, this is not the case for trackpad scroll |
1410 | // events. |
1411 | angleDelta = { 0, units }; |
1412 | } |
1413 | #else |
1414 | const QPoint angleDelta(0, units); |
1415 | #endif |
1416 | |
1417 | QLatin1String sourceName; |
1418 | switch (source) { |
1419 | case Qt::MouseEventNotSynthesized: |
1420 | sourceName = QLatin1String("NotSynthesized" ); |
1421 | break; |
1422 | case Qt::MouseEventSynthesizedBySystem: |
1423 | sourceName = QLatin1String("SynthesizedBySystem" ); |
1424 | break; |
1425 | case Qt::MouseEventSynthesizedByQt: |
1426 | sourceName = QLatin1String("SynthesizedByQt" ); |
1427 | break; |
1428 | case Qt::MouseEventSynthesizedByApplication: |
1429 | sourceName = QLatin1String("SynthesizedByApplication" ); |
1430 | break; |
1431 | default: |
1432 | qFatal(msg: "Unexpected wheel event source" ); |
1433 | continue; |
1434 | } |
1435 | |
1436 | IntList expectedValues; |
1437 | if (fraction) |
1438 | expectedValues << startValue; |
1439 | expectedValues << startValue + steps; |
1440 | |
1441 | QTest::addRow(format: "%s%s%sWith%sKeyboardModifier%s" , |
1442 | fraction ? "half" : "full" , |
1443 | up ? "Up" : "Down" , |
1444 | stepModifierName.latin1(), |
1445 | modifierName.latin1(), |
1446 | sourceName.latin1()) |
1447 | << angleDelta |
1448 | << static_cast<int>(stepModifier) |
1449 | << modifiers |
1450 | << source |
1451 | << startValue |
1452 | << expectedValues; |
1453 | } |
1454 | } |
1455 | } |
1456 | } |
1457 | } |
1458 | #else |
1459 | QSKIP("Built with --no-feature-wheelevent" ); |
1460 | #endif |
1461 | } |
1462 | |
1463 | void tst_QSpinBox::wheelEvents() |
1464 | { |
1465 | #if QT_CONFIG(wheelevent) |
1466 | QFETCH(QPoint, angleDelta); |
1467 | QFETCH(int, stepModifier); |
1468 | QFETCH(Qt::KeyboardModifiers, modifier); |
1469 | QFETCH(Qt::MouseEventSource, source); |
1470 | QFETCH(int, start); |
1471 | QFETCH(IntList, expectedValues); |
1472 | |
1473 | SpinBox spinBox; |
1474 | spinBox.setRange(min: -20, max: 20); |
1475 | spinBox.setValue(start); |
1476 | |
1477 | QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style( |
1478 | new StepModifierStyle); |
1479 | style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier); |
1480 | spinBox.setStyle(style.data()); |
1481 | |
1482 | QWheelEvent event(QPointF(), QPointF(), QPoint(), angleDelta, |
1483 | Qt::NoButton, modifier, Qt::NoScrollPhase, false, source); |
1484 | for (int expected : expectedValues) { |
1485 | qApp->sendEvent(receiver: &spinBox, event: &event); |
1486 | QCOMPARE(spinBox.value(), expected); |
1487 | } |
1488 | #else |
1489 | QSKIP("Built with --no-feature-wheelevent" ); |
1490 | #endif |
1491 | } |
1492 | |
1493 | void tst_QSpinBox::adaptiveDecimalStep() |
1494 | { |
1495 | SpinBox spinBox; |
1496 | spinBox.setRange(min: -100000, max: 100000); |
1497 | spinBox.setStepType(SpinBox::StepType::AdaptiveDecimalStepType); |
1498 | |
1499 | // Positive values |
1500 | |
1501 | spinBox.setValue(0); |
1502 | |
1503 | // Go from 0 to 100 |
1504 | for (int i = 0; i < 100; i++) { |
1505 | QCOMPARE(spinBox.value(), i); |
1506 | spinBox.stepBy(steps: 1); |
1507 | } |
1508 | |
1509 | // Go from 100 to 1000 |
1510 | for (int i = 100; i < 1000; i += 10) { |
1511 | QCOMPARE(spinBox.value(), i); |
1512 | spinBox.stepBy(steps: 1); |
1513 | } |
1514 | |
1515 | // Go from 1000 to 10000 |
1516 | for (int i = 1000; i < 10000; i += 100) { |
1517 | QCOMPARE(spinBox.value(), i); |
1518 | spinBox.stepBy(steps: 1); |
1519 | } |
1520 | |
1521 | // Test decreasing the values now |
1522 | |
1523 | // Go from 10000 down to 1000 |
1524 | for (int i = 10000; i > 1000; i -= 100) { |
1525 | QCOMPARE(spinBox.value(), i); |
1526 | spinBox.stepBy(steps: -1); |
1527 | } |
1528 | |
1529 | // Go from 1000 down to 100 |
1530 | for (int i = 1000; i > 100; i -= 10) { |
1531 | QCOMPARE(spinBox.value(), i); |
1532 | spinBox.stepBy(steps: -1); |
1533 | } |
1534 | |
1535 | // Negative values |
1536 | |
1537 | spinBox.setValue(0); |
1538 | |
1539 | // Go from 0 to -100 |
1540 | for (int i = 0; i > -100; i--) { |
1541 | QCOMPARE(spinBox.value(), i); |
1542 | spinBox.stepBy(steps: -1); |
1543 | } |
1544 | |
1545 | // Go from -100 to -1000 |
1546 | for (int i = -100; i > -1000; i -= 10) { |
1547 | QCOMPARE(spinBox.value(), i); |
1548 | spinBox.stepBy(steps: -1); |
1549 | } |
1550 | |
1551 | // Go from 1000 to 10000 |
1552 | for (int i = -1000; i > -10000; i -= 100) { |
1553 | QCOMPARE(spinBox.value(), i); |
1554 | spinBox.stepBy(steps: -1); |
1555 | } |
1556 | |
1557 | // Test increasing the values now |
1558 | |
1559 | // Go from -10000 up to -1000 |
1560 | for (int i = -10000; i < -1000; i += 100) { |
1561 | QCOMPARE(spinBox.value(), i); |
1562 | spinBox.stepBy(steps: 1); |
1563 | } |
1564 | |
1565 | // Go from -1000 up to -100 |
1566 | for (int i = -1000; i < -100; i += 10) { |
1567 | QCOMPARE(spinBox.value(), i); |
1568 | spinBox.stepBy(steps: 1); |
1569 | } |
1570 | } |
1571 | |
1572 | void tst_QSpinBox::stepModifierKeys_data() |
1573 | { |
1574 | QTest::addColumn<int>(name: "startValue" ); |
1575 | QTest::addColumn<int>(name: "stepModifier" ); |
1576 | QTest::addColumn<QTestEventList>(name: "keys" ); |
1577 | QTest::addColumn<int>(name: "expectedValue" ); |
1578 | |
1579 | const auto keyList = {Qt::Key_Up, Qt::Key_Down}; |
1580 | |
1581 | const auto modifierList = {Qt::NoModifier, |
1582 | Qt::ShiftModifier, |
1583 | Qt::ControlModifier, |
1584 | Qt::AltModifier, |
1585 | Qt::MetaModifier}; |
1586 | |
1587 | const auto validStepModifierList = {Qt::NoModifier, |
1588 | Qt::ControlModifier, |
1589 | Qt::ShiftModifier}; |
1590 | |
1591 | for (auto key : keyList) { |
1592 | |
1593 | const bool up = key == Qt::Key_Up; |
1594 | Q_ASSERT(up || key == Qt::Key_Down); |
1595 | |
1596 | const int startValue = up ? 0.0 : 10.0; |
1597 | |
1598 | for (auto modifier : modifierList) { |
1599 | |
1600 | QTestEventList keys; |
1601 | keys.addKeyClick(qtKey: key, modifiers: modifier); |
1602 | |
1603 | const auto modifierName = modifierToName(modifier); |
1604 | if (modifierName.isEmpty()) |
1605 | continue; |
1606 | |
1607 | for (auto stepModifier : validStepModifierList) { |
1608 | |
1609 | const auto stepModifierName = modifierToName(modifier: stepModifier); |
1610 | if (stepModifierName.isEmpty()) |
1611 | continue; |
1612 | |
1613 | const int steps = (modifier & stepModifier ? 10 : 1) |
1614 | * (up ? 1 : -1); |
1615 | |
1616 | const int expectedValue = startValue + steps; |
1617 | |
1618 | QTest::addRow(format: "%s%sWith%sKeyboardModifier" , |
1619 | up ? "up" : "down" , |
1620 | stepModifierName.latin1(), |
1621 | modifierName.latin1()) |
1622 | << startValue |
1623 | << static_cast<int>(stepModifier) |
1624 | << keys |
1625 | << expectedValue; |
1626 | } |
1627 | } |
1628 | } |
1629 | } |
1630 | |
1631 | void tst_QSpinBox::stepModifierKeys() |
1632 | { |
1633 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1634 | QSKIP("Wayland: This fails. Figure out why." ); |
1635 | |
1636 | QFETCH(int, startValue); |
1637 | QFETCH(int, stepModifier); |
1638 | QFETCH(QTestEventList, keys); |
1639 | QFETCH(int, expectedValue); |
1640 | |
1641 | QSpinBox spin(0); |
1642 | spin.setValue(startValue); |
1643 | |
1644 | QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style( |
1645 | new StepModifierStyle); |
1646 | style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier); |
1647 | spin.setStyle(style.data()); |
1648 | |
1649 | spin.show(); |
1650 | QVERIFY(QTest::qWaitForWindowActive(&spin)); |
1651 | |
1652 | QCOMPARE(spin.value(), startValue); |
1653 | keys.simulate(w: &spin); |
1654 | QCOMPARE(spin.value(), expectedValue); |
1655 | } |
1656 | |
1657 | void tst_QSpinBox::stepModifierButtons_data() |
1658 | { |
1659 | QTest::addColumn<QStyle::SubControl>(name: "subControl" ); |
1660 | QTest::addColumn<int>(name: "stepModifier" ); |
1661 | QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers" ); |
1662 | QTest::addColumn<int>(name: "startValue" ); |
1663 | QTest::addColumn<int>(name: "expectedValue" ); |
1664 | |
1665 | const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown}; |
1666 | |
1667 | const auto modifierList = {Qt::NoModifier, |
1668 | Qt::ShiftModifier, |
1669 | Qt::ControlModifier, |
1670 | Qt::AltModifier, |
1671 | Qt::MetaModifier}; |
1672 | |
1673 | const auto validStepModifierList = {Qt::NoModifier, |
1674 | Qt::ControlModifier, |
1675 | Qt::ShiftModifier}; |
1676 | |
1677 | for (auto subControl : subControls) { |
1678 | |
1679 | const bool up = subControl == QStyle::SC_SpinBoxUp; |
1680 | Q_ASSERT(up || subControl == QStyle::SC_SpinBoxDown); |
1681 | |
1682 | const int startValue = up ? 0 : 10; |
1683 | |
1684 | for (auto modifier : modifierList) { |
1685 | |
1686 | const Qt::KeyboardModifiers modifiers(modifier); |
1687 | |
1688 | const auto modifierName = modifierToName(modifier); |
1689 | if (modifierName.isEmpty()) |
1690 | continue; |
1691 | |
1692 | for (auto stepModifier : validStepModifierList) { |
1693 | |
1694 | const auto stepModifierName = modifierToName(modifier: stepModifier); |
1695 | if (stepModifierName.isEmpty()) |
1696 | continue; |
1697 | |
1698 | const int steps = (modifier & stepModifier ? 10 : 1) |
1699 | * (up ? 1 : -1); |
1700 | |
1701 | const int expectedValue = startValue + steps; |
1702 | |
1703 | QTest::addRow(format: "%s%sWith%sKeyboardModifier" , |
1704 | up ? "up" : "down" , |
1705 | stepModifierName.latin1(), |
1706 | modifierName.latin1()) |
1707 | << subControl |
1708 | << static_cast<int>(stepModifier) |
1709 | << modifiers |
1710 | << startValue |
1711 | << expectedValue; |
1712 | } |
1713 | } |
1714 | } |
1715 | } |
1716 | |
1717 | void tst_QSpinBox::stepModifierButtons() |
1718 | { |
1719 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1720 | QSKIP("Wayland: This fails. Figure out why." ); |
1721 | |
1722 | QFETCH(QStyle::SubControl, subControl); |
1723 | QFETCH(int, stepModifier); |
1724 | QFETCH(Qt::KeyboardModifiers, modifiers); |
1725 | QFETCH(int, startValue); |
1726 | QFETCH(int, expectedValue); |
1727 | |
1728 | SpinBox spin(0); |
1729 | spin.setRange(min: -20, max: 20); |
1730 | spin.setValue(startValue); |
1731 | |
1732 | QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style( |
1733 | new StepModifierStyle); |
1734 | style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier); |
1735 | spin.setStyle(style.data()); |
1736 | |
1737 | spin.show(); |
1738 | QVERIFY(QTest::qWaitForWindowActive(&spin)); |
1739 | |
1740 | QStyleOptionSpinBox spinBoxStyleOption; |
1741 | spin.initStyleOption(option: &spinBoxStyleOption); |
1742 | |
1743 | const QRect buttonRect = spin.style()->subControlRect( |
1744 | cc: QStyle::CC_SpinBox, opt: &spinBoxStyleOption, sc: subControl, widget: &spin); |
1745 | |
1746 | QCOMPARE(spin.value(), startValue); |
1747 | QTest::mouseClick(widget: &spin, button: Qt::LeftButton, stateKey: modifiers, pos: buttonRect.center()); |
1748 | QCOMPARE(spin.value(), expectedValue); |
1749 | } |
1750 | |
1751 | void tst_QSpinBox::stepModifierPressAndHold_data() |
1752 | { |
1753 | QTest::addColumn<QStyle::SubControl>(name: "subControl" ); |
1754 | QTest::addColumn<int>(name: "stepModifier" ); |
1755 | QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers" ); |
1756 | QTest::addColumn<int>(name: "expectedStepModifier" ); |
1757 | |
1758 | const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown}; |
1759 | |
1760 | const auto modifierList = {Qt::NoModifier, |
1761 | Qt::ShiftModifier, |
1762 | Qt::ControlModifier, |
1763 | Qt::AltModifier, |
1764 | Qt::MetaModifier}; |
1765 | |
1766 | const auto validStepModifierList = {Qt::NoModifier, |
1767 | Qt::ControlModifier, |
1768 | Qt::ShiftModifier}; |
1769 | |
1770 | for (auto subControl : subControls) { |
1771 | |
1772 | const bool up = subControl == QStyle::SC_SpinBoxUp; |
1773 | Q_ASSERT(up || subControl == QStyle::SC_SpinBoxDown); |
1774 | |
1775 | for (auto modifier : modifierList) { |
1776 | |
1777 | const Qt::KeyboardModifiers modifiers(modifier); |
1778 | |
1779 | const auto modifierName = modifierToName(modifier); |
1780 | if (modifierName.isEmpty()) |
1781 | continue; |
1782 | |
1783 | for (auto stepModifier : validStepModifierList) { |
1784 | |
1785 | const auto stepModifierName = modifierToName(modifier: stepModifier); |
1786 | if (stepModifierName.isEmpty()) |
1787 | continue; |
1788 | |
1789 | const int steps = (modifier & stepModifier ? 10 : 1) |
1790 | * (up ? 1 : -1); |
1791 | |
1792 | QTest::addRow(format: "%s%sWith%sKeyboardModifier" , |
1793 | up ? "up" : "down" , |
1794 | stepModifierName.latin1(), |
1795 | modifierName.latin1()) |
1796 | << subControl |
1797 | << static_cast<int>(stepModifier) |
1798 | << modifiers |
1799 | << steps; |
1800 | } |
1801 | } |
1802 | } |
1803 | } |
1804 | |
1805 | void tst_QSpinBox::stepModifierPressAndHold() |
1806 | { |
1807 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
1808 | QSKIP("Wayland: This fails. Figure out why." ); |
1809 | |
1810 | QFETCH(QStyle::SubControl, subControl); |
1811 | QFETCH(int, stepModifier); |
1812 | QFETCH(Qt::KeyboardModifiers, modifiers); |
1813 | QFETCH(int, expectedStepModifier); |
1814 | |
1815 | SpinBox spin(0); |
1816 | spin.setRange(min: -100, max: 100); |
1817 | spin.setValue(0); |
1818 | |
1819 | QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> stepModifierStyle( |
1820 | new StepModifierStyle(new PressAndHoldStyle)); |
1821 | stepModifierStyle->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier); |
1822 | spin.setStyle(stepModifierStyle.data()); |
1823 | |
1824 | QSignalSpy spy(&spin, QOverload<int>::of(ptr: &SpinBox::valueChanged)); |
1825 | // TODO: remove debug output when QTBUG-69492 is fixed |
1826 | connect(sender: &spin, signal: QOverload<int>::of(ptr: &SpinBox::valueChanged), slot: [=]() { |
1827 | qDebug() << QTime::currentTime() << "valueChanged emitted" ; |
1828 | }); |
1829 | |
1830 | spin.show(); |
1831 | QVERIFY(QTest::qWaitForWindowActive(&spin)); |
1832 | |
1833 | QStyleOptionSpinBox spinBoxStyleOption; |
1834 | spin.initStyleOption(option: &spinBoxStyleOption); |
1835 | |
1836 | const QRect buttonRect = spin.style()->subControlRect( |
1837 | cc: QStyle::CC_SpinBox, opt: &spinBoxStyleOption, sc: subControl, widget: &spin); |
1838 | |
1839 | // TODO: remove debug output when QTBUG-69492 is fixed |
1840 | qDebug() << "QGuiApplication::focusWindow():" << QGuiApplication::focusWindow(); |
1841 | qDebug() << "QGuiApplication::topLevelWindows():" << QGuiApplication::topLevelWindows(); |
1842 | QTest::mousePress(widget: &spin, button: Qt::LeftButton, stateKey: modifiers, pos: buttonRect.center()); |
1843 | QTRY_VERIFY2(spy.length() >= 3, qPrintable(QString::fromLatin1( |
1844 | "Expected valueChanged() to be emitted 3 or more times, but it was only emitted %1 times" ).arg(spy.length()))); |
1845 | QTest::mouseRelease(widget: &spin, button: Qt::LeftButton, stateKey: modifiers, pos: buttonRect.center()); |
1846 | |
1847 | const auto value = spy.last().at(i: 0); |
1848 | QVERIFY(value.type() == QVariant::Int); |
1849 | QCOMPARE(value.toInt(), spy.length() * expectedStepModifier); |
1850 | } |
1851 | |
1852 | QTEST_MAIN(tst_QSpinBox) |
1853 | #include "tst_qspinbox.moc" |
1854 | |