From e3bf4f771458590dd003fc710803c1c1babea68e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 10 Oct 2015 16:07:28 +0200 Subject: Re-introduce SpinBox It came up in discussions at the QtWS that even if we have Tumbler, people still want and expect to have the good old SpinBox control. SpinBox has it pros, such as that it might work better inside a vertical Flickable, and that in multi-field forms it might visually align better with other controls like TextFields. An early mockup of SpinBox was removed in 1c0edf0. A quote from the commit message: SpinBox is a desktop centric control. It won't be provided in Qt Quick Controls 2.0, but maybe later when desktop support is re-considered. Qt Quick Controls 2.0 will focus on embedded and mobile. SpinBox is still available in 1.x. While it is true that SpinBox might not be optimal for touch or mobile, the real reason for the removal was that validating decimal number input is very complicated. Even though locales have well- defined thousand separators and decimal points, users have very different expectations on how strict or relaxed the input validation should be. This change re-introduces a touch-optimized integer-based SpinBox. What makes it more touch friendly than the earlier version is that it has now auto-repeating buttons. Limiting it to integers avoids the decimal number input validation problem. We can introduce a separate DoubleSpinBox later if necessary - just like in QtWidgets. Change-Id: I2819060eb5d1ae6a8c00b0f12be703456085079d Reviewed-by: Mitch Curtis --- tests/auto/accessibility/data/spinbox.qml | 16 ++ tests/auto/accessibility/tst_accessibility.cpp | 1 + .../activeFocusOnTab/data/activeFocusOnTab.qml | 5 + .../auto/activeFocusOnTab/tst_activeFocusOnTab.cpp | 33 ++- tests/auto/controls/data/tst_spinbox.qml | 301 +++++++++++++++++++++ .../data/qtlabscontrols-spinbox-background.qml | 11 + .../data/qtlabscontrols-spinbox-contentItem.qml | 11 + .../snippets/data/qtlabscontrols-spinbox-down.qml | 11 + .../snippets/data/qtlabscontrols-spinbox-up.qml | 11 + .../auto/snippets/data/qtlabscontrols-spinbox.qml | 8 + tests/benchmarks/objectcount/tst_objectcount.cpp | 4 + 11 files changed, 409 insertions(+), 3 deletions(-) create mode 100644 tests/auto/accessibility/data/spinbox.qml create mode 100644 tests/auto/controls/data/tst_spinbox.qml create mode 100644 tests/auto/snippets/data/qtlabscontrols-spinbox-background.qml create mode 100644 tests/auto/snippets/data/qtlabscontrols-spinbox-contentItem.qml create mode 100644 tests/auto/snippets/data/qtlabscontrols-spinbox-down.qml create mode 100644 tests/auto/snippets/data/qtlabscontrols-spinbox-up.qml create mode 100644 tests/auto/snippets/data/qtlabscontrols-spinbox.qml (limited to 'tests') diff --git a/tests/auto/accessibility/data/spinbox.qml b/tests/auto/accessibility/data/spinbox.qml new file mode 100644 index 00000000..abaee9ca --- /dev/null +++ b/tests/auto/accessibility/data/spinbox.qml @@ -0,0 +1,16 @@ +import QtQuick 2.5 +import QtQuick.Window 2.2 +import Qt.labs.controls 1.0 + +Window { + visible: true + + SpinBox { + id: spinbox + objectName: "spinbox" + from: 0 + to: 100 + value: 50 + stepSize: 1 + } +} diff --git a/tests/auto/accessibility/tst_accessibility.cpp b/tests/auto/accessibility/tst_accessibility.cpp index 873a86c1..a9e04d59 100644 --- a/tests/auto/accessibility/tst_accessibility.cpp +++ b/tests/auto/accessibility/tst_accessibility.cpp @@ -82,6 +82,7 @@ void tst_accessibility::a11y_data() QTest::newRow("ScrollBar") << "scrollbar" << 0x00000003 << ""; //QAccessible::ScrollBar QTest::newRow("ScrollIndicator") << "scrollindicator" << 0x00000027 << ""; //QAccessible::Indicator QTest::newRow("Slider") << "slider" << 0x00000033 << ""; //QAccessible::Slider + QTest::newRow("SpinBox") << "spinbox" << 0x00000034 << ""; //QAccessible::SpinBox // StackView QTest::newRow("Switch") << "switch" << 0x0000002B << "Switch"; //QAccessible::Button QTest::newRow("TabBar") << "tabbar" << 0x0000003C << ""; //QAccessible::PageTabList diff --git a/tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml b/tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml index 5ab0191e..cf107373 100644 --- a/tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml +++ b/tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml @@ -141,6 +141,11 @@ Item { objectName: "slider" value: 0.5 } + SpinBox { + id: spinbox + objectName: "spinbox" + value: 50 + } // StackView Switch { id: swtich // switch diff --git a/tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp b/tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp index 88607514..c618a2d5 100644 --- a/tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp +++ b/tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp @@ -191,7 +191,16 @@ void tst_activeFocusOnTab::allControls() QVERIFY(item); QVERIFY(item->hasActiveFocus()); - // Tab: slider->switch + // Tab: slider->spinbox + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem(window->rootObject(), "spinbox"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // Tab: spinbox->switch key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1); QGuiApplication::sendEvent(window, &key); QVERIFY(key.isAccepted()); @@ -290,7 +299,16 @@ void tst_activeFocusOnTab::allControls() QVERIFY(item); QVERIFY(item->hasActiveFocus()); - // BackTab: switch->switch + // BackTab: switch->spinbox + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem(window->rootObject(), "spinbox"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // BackTab: spinbox->slider key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); QGuiApplication::sendEvent(window, &key); QVERIFY(key.isAccepted()); @@ -450,7 +468,16 @@ void tst_activeFocusOnTab::textControls() QVERIFY(item); QVERIFY(item->hasActiveFocus()); - // BackTab: textfield->textarea + // BackTab: textfield->spinbox + key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); + QGuiApplication::sendEvent(window, &key); + QVERIFY(key.isAccepted()); + + item = findItem(window->rootObject(), "spinbox"); + QVERIFY(item); + QVERIFY(item->hasActiveFocus()); + + // BackTab: spinbox->textarea key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1); QGuiApplication::sendEvent(window, &key); QVERIFY(key.isAccepted()); diff --git a/tests/auto/controls/data/tst_spinbox.qml b/tests/auto/controls/data/tst_spinbox.qml new file mode 100644 index 00000000..b5d85dbd --- /dev/null +++ b/tests/auto/controls/data/tst_spinbox.qml @@ -0,0 +1,301 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtTest 1.0 +import Qt.labs.controls 1.0 + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "SpinBox" + + SignalSpy{ + id: upPressedSpy + signalName: "pressedChanged" + } + + SignalSpy{ + id: downPressedSpy + signalName: "pressedChanged" + } + + Component { + id: spinBox + SpinBox { } + } + + function init() { + verify(!upPressedSpy.target) + compare(upPressedSpy.count, 0) + verify(!downPressedSpy.target) + compare(downPressedSpy.count, 0) + } + + function cleanup() { + upPressedSpy.target = null + upPressedSpy.clear() + downPressedSpy.target = null + downPressedSpy.clear() + } + + function test_defaults() { + var control = spinBox.createObject(testCase) + verify(control) + + compare(control.from, 0) + compare(control.to, 99) + compare(control.value, 0) + compare(control.stepSize, 1) + compare(control.up.pressed, false) + compare(control.down.pressed, false) + + control.destroy() + } + + function test_value() { + var control = spinBox.createObject(testCase) + verify(control) + + compare(control.value, 0) + control.value = 50 + compare(control.value, 50) + control.value = 99 + compare(control.value, 99) + control.value = -99 + compare(control.value, 0) + control.value = 100 + compare(control.value, 99) + + control.destroy() + } + + function test_range() { + var control = spinBox.createObject(testCase, {from: 0, to: 100, value: 50}) + verify(control) + + compare(control.from, 0) + compare(control.to, 100) + compare(control.value, 50) + + control.value = 1000 + compare(control.value, 100) + + control.value = -1 + compare(control.value, 0) + + control.from = 25 + compare(control.from, 25) + compare(control.value, 25) + + control.to = 75 + compare(control.to, 75) + compare(control.value, 25) + + control.value = 50 + compare(control.value, 50) + + control.destroy() + } + + function test_inverted() { + var control = spinBox.createObject(testCase, {from: 100, to: -100}) + verify(control) + + compare(control.from, 100) + compare(control.to, -100) + compare(control.value, 0) + + control.value = 200 + compare(control.value, 100) + + control.value = -200 + compare(control.value, -100) + + control.value = 0 + compare(control.value, 0) + + control.destroy() + } + + function test_mouse() { + var control = spinBox.createObject(testCase, {stepSize: 50}) + verify(control) + + upPressedSpy.target = control.up + verify(upPressedSpy.valid) + + mousePress(control.up.indicator) + compare(upPressedSpy.count, 1) + compare(control.up.pressed, true) + compare(downPressedSpy.count, 0) + compare(control.down.pressed, false) + compare(control.value, 0) + + mouseRelease(control.up.indicator) + compare(upPressedSpy.count, 2) + compare(control.up.pressed, false) + compare(downPressedSpy.count, 0) + compare(control.down.pressed, false) + compare(control.value, 50) + + downPressedSpy.target = control.down + verify(downPressedSpy.valid) + + mousePress(control.down.indicator) + compare(downPressedSpy.count, 1) + compare(control.down.pressed, true) + compare(upPressedSpy.count, 2) + compare(control.up.pressed, false) + compare(control.value, 50) + + mouseRelease(control.down.indicator) + compare(downPressedSpy.count, 2) + compare(control.down.pressed, false) + compare(upPressedSpy.count, 2) + compare(control.up.pressed, false) + compare(control.value, 0) + + control.destroy() + } + + function test_keys() { + var control = spinBox.createObject(testCase) + verify(control) + + var upPressedCount = 0 + var downPressedCount = 0 + + upPressedSpy.target = control.up + verify(upPressedSpy.valid) + + downPressedSpy.target = control.down + verify(downPressedSpy.valid) + + control.forceActiveFocus() + verify(control.activeFocus) + + control.value = 50 + compare(control.value, 50) + + for (var d1 = 1; d1 <= 10; ++d1) { + keyPress(Qt.Key_Down) + compare(control.down.pressed, true) + compare(control.up.pressed, false) + compare(downPressedSpy.count, ++downPressedCount) + + compare(control.value, 50 - d1) + + keyRelease(Qt.Key_Down) + compare(control.down.pressed, false) + compare(control.up.pressed, false) + compare(downPressedSpy.count, ++downPressedCount) + } + compare(control.value, 40) + + for (var i1 = 1; i1 <= 10; ++i1) { + keyPress(Qt.Key_Up) + compare(control.up.pressed, true) + compare(control.down.pressed, false) + compare(upPressedSpy.count, ++upPressedCount) + + compare(control.value, 40 + i1) + + keyRelease(Qt.Key_Up) + compare(control.down.pressed, false) + compare(control.up.pressed, false) + compare(upPressedSpy.count, ++upPressedCount) + } + compare(control.value, 50) + + control.stepSize = 25 + compare(control.stepSize, 25) + + for (var d2 = 1; d2 <= 10; ++d2) { + keyPress(Qt.Key_Down) + compare(control.down.pressed, true) + compare(control.up.pressed, false) + compare(downPressedSpy.count, ++downPressedCount) + + compare(control.value, Math.max(0, 50 - d2 * 25)) + + keyRelease(Qt.Key_Down) + compare(control.down.pressed, false) + compare(control.up.pressed, false) + compare(downPressedSpy.count, ++downPressedCount) + } + compare(control.value, 0) + + for (var i2 = 1; i2 <= 10; ++i2) { + keyPress(Qt.Key_Up) + compare(control.up.pressed, true) + compare(control.down.pressed, false) + compare(upPressedSpy.count, ++upPressedCount) + + compare(control.value, Math.min(99, i2 * 25)) + + keyRelease(Qt.Key_Up) + compare(control.down.pressed, false) + compare(control.up.pressed, false) + compare(upPressedSpy.count, ++upPressedCount) + } + compare(control.value, 99) + + control.destroy() + } + + function test_locale() { + var control = spinBox.createObject(testCase) + verify(control) + + control.locale = Qt.locale("ar_EG") // Arabic, Egypt + + var numbers = ["٠", "١", "٢", "٣", "٤", "٥", "٦", "٧", "٨", "٩"] + for (var i = 0; i < 10; ++i) { + control.value = i + compare(control.contentItem.text, numbers[i]) + } + + control.destroy() + } +} diff --git a/tests/auto/snippets/data/qtlabscontrols-spinbox-background.qml b/tests/auto/snippets/data/qtlabscontrols-spinbox-background.qml new file mode 100644 index 00000000..65545970 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-spinbox-background.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +SpinBox { + value: 50 + Rectangle { + anchors.fill: parent + color: "transparent" + border.color: "red" + } +} diff --git a/tests/auto/snippets/data/qtlabscontrols-spinbox-contentItem.qml b/tests/auto/snippets/data/qtlabscontrols-spinbox-contentItem.qml new file mode 100644 index 00000000..e0fb39f4 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-spinbox-contentItem.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +SpinBox { + value: 50 + Rectangle { + anchors.fill: contentItem + color: "transparent" + border.color: "red" + } +} diff --git a/tests/auto/snippets/data/qtlabscontrols-spinbox-down.qml b/tests/auto/snippets/data/qtlabscontrols-spinbox-down.qml new file mode 100644 index 00000000..b6e78c83 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-spinbox-down.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +SpinBox { + value: 50 + Rectangle { + anchors.fill: down.indicator + color: "transparent" + border.color: "red" + } +} diff --git a/tests/auto/snippets/data/qtlabscontrols-spinbox-up.qml b/tests/auto/snippets/data/qtlabscontrols-spinbox-up.qml new file mode 100644 index 00000000..6f150b56 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-spinbox-up.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +SpinBox { + value: 50 + Rectangle { + anchors.fill: up.indicator + color: "transparent" + border.color: "red" + } +} diff --git a/tests/auto/snippets/data/qtlabscontrols-spinbox.qml b/tests/auto/snippets/data/qtlabscontrols-spinbox.qml new file mode 100644 index 00000000..588dce65 --- /dev/null +++ b/tests/auto/snippets/data/qtlabscontrols-spinbox.qml @@ -0,0 +1,8 @@ +import QtQuick 2.0 +import Qt.labs.controls 1.0 + +//! [1] +SpinBox { + value: 50 +} +//! [1] diff --git a/tests/benchmarks/objectcount/tst_objectcount.cpp b/tests/benchmarks/objectcount/tst_objectcount.cpp index 3b5db87d..b2170cd5 100644 --- a/tests/benchmarks/objectcount/tst_objectcount.cpp +++ b/tests/benchmarks/objectcount/tst_objectcount.cpp @@ -209,6 +209,10 @@ void tst_ObjectCount::testCount_data() << QByteArray("import QtQuick.Controls 1.3; Slider { }") << QByteArray("import Qt.labs.controls 1.0; Slider { }"); + QTest::newRow("SpinBox") + << QByteArray("import QtQuick.Controls 1.3; SpinBox { }") + << QByteArray("import Qt.labs.controls 1.0; SpinBox { }"); + QTest::newRow("StackView") << QByteArray("import QtQuick.Controls 1.3; StackView { }") << QByteArray("import Qt.labs.controls 1.0; StackView { }"); -- cgit v1.2.3