From 2d1e1f569b6de3edacd828c5e2452cfe1040192c Mon Sep 17 00:00:00 2001 From: Nathan Collins Date: Mon, 14 May 2018 19:22:22 +0100 Subject: Apply Qt::ControlModifier to all spin box interactions A step modifier already existed when scrolling with the Qt::ControlModifier held. This patch applies this functionality to other methods of stepping a spin box. Holding the modifier increases the step rate when: - scrolling; - pressing the up/down keys; - pressing the spin box up/down buttons. [ChangeLog][QtWidgets][QAbstractSpinBox] The Qt::ControlModifier increases the number of steps a QAbstractSpinBox takes for the following interactions: scrolling, up/down keyboard keys and the spin box buttons. Previously, Qt::ControlModifier only affected scrolling. Task-number: QTBUG-67380 Change-Id: Icc8754d5c007da0771bfaef113603a2f334dd494 Reviewed-by: Mitch Curtis --- .../widgets/qdatetimeedit/tst_qdatetimeedit.cpp | 496 ++++++++++++++++++++- .../widgets/qdoublespinbox/tst_qdoublespinbox.cpp | 390 ++++++++++++++++ .../auto/widgets/widgets/qspinbox/tst_qspinbox.cpp | 389 +++++++++++++++- 3 files changed, 1247 insertions(+), 28 deletions(-) (limited to 'tests/auto/widgets/widgets') diff --git a/tests/auto/widgets/widgets/qdatetimeedit/tst_qdatetimeedit.cpp b/tests/auto/widgets/widgets/qdatetimeedit/tst_qdatetimeedit.cpp index fa28ec2575..0fe5b48341 100644 --- a/tests/auto/widgets/widgets/qdatetimeedit/tst_qdatetimeedit.cpp +++ b/tests/auto/widgets/widgets/qdatetimeedit/tst_qdatetimeedit.cpp @@ -70,6 +70,7 @@ #include #include #include +#include #include @@ -93,6 +94,26 @@ public: friend class tst_QDateTimeEdit; }; +class PressAndHoldStyle : public QProxyStyle +{ + Q_OBJECT +public: + using QProxyStyle::QProxyStyle; + + int styleHint(QStyle::StyleHint hint, const QStyleOption *option = nullptr, + const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const override + { + switch (hint) { + case QStyle::SH_SpinBox_ClickAutoRepeatRate: + return 5; + case QStyle::SH_SpinBox_ClickAutoRepeatThreshold: + return 10; + default: + return QProxyStyle::styleHint(hint, option, widget, returnData); + } + } +}; + class tst_QDateTimeEdit : public QObject { Q_OBJECT @@ -205,9 +226,8 @@ private slots: void reverseTest(); void ddMMMMyyyy(); -#if QT_CONFIG(wheelevent) + void wheelEvent_data(); void wheelEvent(); -#endif void specialValueCornerCase(); void cursorPositionOnInit(); @@ -245,14 +265,99 @@ private slots: void dateEditCorrectSectionSize_data(); void dateEditCorrectSectionSize(); #endif + + void stepModifierKeys_data(); + void stepModifierKeys(); + + void stepModifierButtons_data(); + void stepModifierButtons(); + + void stepModifierPressAndHold_data(); + void stepModifierPressAndHold(); private: EditorDateEdit* testWidget; QWidget *testFocusWidget; }; +typedef QList DateList; typedef QList TimeList; typedef QList KeyList; +static QLatin1String modifierToName(Qt::KeyboardModifier modifier) +{ + switch (modifier) { + case Qt::NoModifier: + return QLatin1Literal("No"); + break; + case Qt::ControlModifier: + return QLatin1Literal("Ctrl"); + break; + case Qt::ShiftModifier: + return QLatin1Literal("Shift"); + break; + default: + qFatal("Unexpected keyboard modifier"); + return QLatin1String(); + } +} + +static QLatin1String sectionToName(const QDateTimeEdit::Section section) +{ + switch (section) { + case QDateTimeEdit::SecondSection: + return QLatin1Literal("Second"); + case QDateTimeEdit::MinuteSection: + return QLatin1Literal("Minute"); + case QDateTimeEdit::HourSection: + return QLatin1Literal("Hours"); + case QDateTimeEdit::DaySection: + return QLatin1Literal("Day"); + case QDateTimeEdit::MonthSection: + return QLatin1Literal("Month"); + case QDateTimeEdit::YearSection: + return QLatin1Literal("Year"); + default: + qFatal("Unexpected section"); + return QLatin1String(); + } +} + +static QDate stepDate(const QDate& startDate, const QDateTimeEdit::Section section, + const int steps) +{ + switch (section) { + case QDateTimeEdit::DaySection: + return startDate.addDays(steps); + case QDateTimeEdit::MonthSection: + return startDate.addMonths(steps); + case QDateTimeEdit::YearSection: + return startDate.addYears(steps); + default: + qFatal("Unexpected section"); + return QDate(); + } +} + +static QTime stepTime(const QTime& startTime, const QDateTimeEdit::Section section, + const int steps) +{ + switch (section) { + case QDateTimeEdit::SecondSection: + return startTime.addSecs(steps); + case QDateTimeEdit::MinuteSection: + return QTime(startTime.hour(), + startTime.minute() + steps, + startTime.second()); + case QDateTimeEdit::HourSection: + return QTime(startTime.hour() + steps, + startTime.minute(), + startTime.second()); + default: + qFatal("Unexpected section"); + return QTime(); + } +} + // Testing get/set functions void tst_QDateTimeEdit::getSetCheck() { @@ -3000,20 +3105,154 @@ void tst_QDateTimeEdit::ddMMMMyyyy() QCOMPARE(testWidget->lineEdit()->text(), "01." + QDate::longMonthName(1) + ".200"); } +void tst_QDateTimeEdit::wheelEvent_data() +{ #if QT_CONFIG(wheelevent) + QTest::addColumn("angleDelta"); + QTest::addColumn("qt4Delta"); + QTest::addColumn("modifiers"); + QTest::addColumn("source"); + QTest::addColumn("section"); + QTest::addColumn("startDate"); + QTest::addColumn("expectedDates"); + + const auto fractions = {false, true}; + + const auto directions = {true, false}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + const auto sources = {Qt::MouseEventNotSynthesized, + Qt::MouseEventSynthesizedBySystem, + Qt::MouseEventSynthesizedByQt, + Qt::MouseEventSynthesizedByApplication}; + + const auto sections = {QDateTimeEdit::DaySection, + QDateTimeEdit::MonthSection, + QDateTimeEdit::YearSection}; + + for (auto fraction : fractions) { + for (auto up : directions) { + + const QDate startDate(2000, up ? 2 : 12, 17); + + const int units = (fraction ? 60 : 120) * (up ? 1 : -1); + + for (auto modifier : modifierList) { + + const Qt::KeyboardModifiers modifiers(modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + for (auto source : sources) { + +#ifdef Q_OS_MACOS + QPoint angleDelta; + if ((modifier & Qt::ShiftModifier) && + source == Qt::MouseEventNotSynthesized) { + // On macOS the Shift modifier converts vertical + // mouse wheel events to horizontal. + angleDelta = { units, 0 }; + } else { + // However, this is not the case for trackpad scroll + // events. + angleDelta = { 0, units }; + } +#else + const QPoint angleDelta(0, units); +#endif + + QLatin1String sourceName; + switch (source) { + case Qt::MouseEventNotSynthesized: + sourceName = QLatin1Literal("NotSynthesized"); + break; + case Qt::MouseEventSynthesizedBySystem: + sourceName = QLatin1Literal("SynthesizedBySystem"); + break; + case Qt::MouseEventSynthesizedByQt: + sourceName = QLatin1Literal("SynthesizedByQt"); + break; + case Qt::MouseEventSynthesizedByApplication: + sourceName = QLatin1Literal("SynthesizedByApplication"); + break; + default: + qFatal("Unexpected wheel event source"); + continue; + } + + for (const auto section : sections) { + + DateList expectedDates; + if (fraction) + expectedDates << startDate; + + const auto expectedDate = stepDate(startDate, section, steps); + if (!expectedDate.isValid()) + continue; + + expectedDates << expectedDate; + + const QLatin1String sectionName = sectionToName(section); + + QTest::addRow("%s%s%sWith%sKeyboardModifier%s", + fraction ? "half" : "full", + up ? "Up" : "Down", + sectionName.latin1(), + modifierName.latin1(), + sourceName.latin1()) + << angleDelta + << units + << modifiers + << source + << section + << startDate + << expectedDates; + } + } + } + } + } +#else + QSKIP("Built with --no-feature-wheelevent"); +#endif +} + void tst_QDateTimeEdit::wheelEvent() { - testWidget->setDisplayFormat("dddd/MM"); - testWidget->setDate(QDate(2000, 2, 21)); - testWidget->setCurrentSection(QDateTimeEdit::DaySection); - QWheelEvent w(testWidget->lineEdit()->geometry().center(), 120, 0, 0); - qApp->sendEvent(testWidget, &w); - QCOMPARE(testWidget->date(), QDate(2000, 2, 22)); - testWidget->setCurrentSection(QDateTimeEdit::MonthSection); - qApp->sendEvent(testWidget, &w); - QCOMPARE(testWidget->date(), QDate(2000, 3, 22)); -} +#if QT_CONFIG(wheelevent) + QFETCH(QPoint, angleDelta); + QFETCH(int, qt4Delta); + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(Qt::MouseEventSource, source); + QFETCH(QDateTimeEdit::Section, section); + QFETCH(QDate, startDate); + QFETCH(DateList, expectedDates); + + EditorDateEdit edit(0); + edit.setDate(startDate); + edit.setCurrentSection(section); + + QWheelEvent event(QPointF(), QPointF(), QPoint(), angleDelta, qt4Delta, + Qt::Vertical, Qt::NoButton, modifiers, Qt::NoScrollPhase, + source); + + QCOMPARE(edit.date(), startDate); + for (QDate expected : expectedDates) { + qApp->sendEvent(&edit, &event); + QCOMPARE(edit.date(), expected); + } +#else + QSKIP("Built with --no-feature-wheelevent"); #endif // QT_CONFIG(wheelevent) +} void tst_QDateTimeEdit::specialValueCornerCase() { @@ -3735,5 +3974,238 @@ void tst_QDateTimeEdit::dateEditCorrectSectionSize() } #endif +void tst_QDateTimeEdit::stepModifierKeys_data() +{ + QTest::addColumn("startDate"); + QTest::addColumn("section"); + QTest::addColumn("keys"); + QTest::addColumn("expectedDate"); + + const auto keyList = {Qt::Key_Up, Qt::Key_Down}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + const auto sections = {QDateTimeEdit::DaySection, + QDateTimeEdit::MonthSection, + QDateTimeEdit::YearSection}; + + for (auto key : keyList) { + + const bool up = key == Qt::Key_Up; + Q_ASSERT(up || key == Qt::Key_Down); + + const QDate startDate(2000, up ? 2 : 12, 17); + + for (auto modifier : modifierList) { + + QTestEventList keys; + keys.addKeyClick(key, modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + for (const auto section : sections) { + + const auto expectedDate = stepDate(startDate, section, steps); + if (!expectedDate.isValid()) + continue; + + const auto sectionName = sectionToName(section); + + QTest::addRow("%s%sWith%sKeyboardModifier", + up ? "up" : "down", + sectionName.latin1(), + modifierName.latin1()) + << startDate + << section + << keys + << expectedDate; + } + } + } +} + +void tst_QDateTimeEdit::stepModifierKeys() +{ + QFETCH(QDate, startDate); + QFETCH(QDateTimeEdit::Section, section); + QFETCH(QTestEventList, keys); + QFETCH(QDate, expectedDate); + + QDateTimeEdit edit(0); + edit.setDate(startDate); + edit.show(); + QVERIFY(QTest::qWaitForWindowActive(&edit)); + edit.setCurrentSection(section); + + QCOMPARE(edit.date(), startDate); + keys.simulate(&edit); + QCOMPARE(edit.date(), expectedDate); +} + +void tst_QDateTimeEdit::stepModifierButtons_data() +{ + QTest::addColumn("subControl"); + QTest::addColumn("modifiers"); + QTest::addColumn("section"); + QTest::addColumn("startTime"); + QTest::addColumn("expectedTime"); + + const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + const auto sections = {QDateTimeEdit::SecondSection, + QDateTimeEdit::MinuteSection, + QDateTimeEdit::HourSection}; + + const QTime startTime(12, 36, 24); + + for (auto subControl : subControls) { + + const bool up = subControl == QStyle::SC_SpinBoxUp; + Q_ASSERT(up || subControl == QStyle::SC_SpinBoxDown); + + for (auto modifier : modifierList) { + + const Qt::KeyboardModifiers modifiers(modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + for (const auto section : sections) { + + const auto expectedTime = stepTime(startTime, section, steps); + if (!expectedTime.isValid()) + continue; + + const auto sectionName = sectionToName(section); + + QTest::addRow("%s%sWith%sKeyboardModifier", + up ? "up" : "down", + sectionName.latin1(), + modifierName.latin1()) + << subControl + << modifiers + << section + << startTime + << expectedTime; + } + } + } +} + +void tst_QDateTimeEdit::stepModifierButtons() +{ + QFETCH(QStyle::SubControl, subControl); + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(QDateTimeEdit::Section, section); + QFETCH(QTime, startTime); + QFETCH(QTime, expectedTime); + + EditorDateEdit edit(0); + edit.setTime(startTime); + edit.show(); + QVERIFY(QTest::qWaitForWindowActive(&edit)); + edit.setCurrentSection(section); + + QStyleOptionSpinBox spinBoxStyleOption; + edit.initStyleOption(&spinBoxStyleOption); + + const QRect buttonRect = edit.style()->subControlRect( + QStyle::CC_SpinBox, &spinBoxStyleOption, subControl, &edit); + + QCOMPARE(edit.time(), startTime); + QTest::mouseClick(&edit, Qt::LeftButton, modifiers, buttonRect.center()); + QCOMPARE(edit.time(), expectedTime); +} + +void tst_QDateTimeEdit::stepModifierPressAndHold_data() +{ + QTest::addColumn("subControl"); + QTest::addColumn("modifiers"); + QTest::addColumn("expectedStepModifier"); + + const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + for (auto subControl : subControls) { + + const bool up = subControl == QStyle::SC_SpinBoxUp; + Q_ASSERT(up || subControl == QStyle::SC_SpinBoxDown); + + for (auto modifier : modifierList) { + + const Qt::KeyboardModifiers modifiers(modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + QTest::addRow("%sWith%sKeyboardModifier", + up ? "up" : "down", + modifierName.latin1()) + << subControl + << modifiers + << steps; + } + } +} + +void tst_QDateTimeEdit::stepModifierPressAndHold() +{ + QFETCH(QStyle::SubControl, subControl); + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(int, expectedStepModifier); + + const QDate startDate(2000, 1, 1); + + EditorDateEdit edit(0); + QScopedPointer pressAndHoldStyle( + new PressAndHoldStyle); + edit.setStyle(pressAndHoldStyle.data()); + edit.setDate(startDate); + + QSignalSpy spy(&edit, &EditorDateEdit::dateChanged); + + edit.show(); + QVERIFY(QTest::qWaitForWindowActive(&edit)); + edit.setCurrentSection(QDateTimeEdit::YearSection); + + QStyleOptionSpinBox spinBoxStyleOption; + edit.initStyleOption(&spinBoxStyleOption); + + const QRect buttonRect = edit.style()->subControlRect( + QStyle::CC_SpinBox, &spinBoxStyleOption, subControl, &edit); + + QTest::mousePress(&edit, Qt::LeftButton, modifiers, buttonRect.center()); + QTRY_VERIFY(spy.length() >= 3); + QTest::mouseRelease(&edit, Qt::LeftButton, modifiers, buttonRect.center()); + + const auto value = spy.last().at(0); + QVERIFY(value.type() == QVariant::Date); + const QDate expectedDate = startDate.addYears(spy.length() * + expectedStepModifier); + QCOMPARE(value.toDate(), expectedDate); +} + QTEST_MAIN(tst_QDateTimeEdit) #include "tst_qdatetimeedit.moc" diff --git a/tests/auto/widgets/widgets/qdoublespinbox/tst_qdoublespinbox.cpp b/tests/auto/widgets/widgets/qdoublespinbox/tst_qdoublespinbox.cpp index 9c56d30dfb..36fd0eb54a 100644 --- a/tests/auto/widgets/widgets/qdoublespinbox/tst_qdoublespinbox.cpp +++ b/tests/auto/widgets/widgets/qdoublespinbox/tst_qdoublespinbox.cpp @@ -41,6 +41,10 @@ #include #include +#include +#include +#include + class DoubleSpinBox : public QDoubleSpinBox { Q_OBJECT @@ -60,6 +64,16 @@ public: { return QDoubleSpinBox::valueFromText(text); } +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent *event) + { + QDoubleSpinBox::wheelEvent(event); + } +#endif + void initStyleOption(QStyleOptionSpinBox *option) const + { + QDoubleSpinBox::initStyleOption(option); + } QLineEdit* lineEdit() const { return QDoubleSpinBox::lineEdit(); } public slots: @@ -69,6 +83,26 @@ public slots: } }; +class PressAndHoldStyle : public QProxyStyle +{ + Q_OBJECT +public: + using QProxyStyle::QProxyStyle; + + int styleHint(QStyle::StyleHint hint, const QStyleOption *option = nullptr, + const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const override + { + switch (hint) { + case QStyle::SH_SpinBox_ClickAutoRepeatRate: + return 5; + case QStyle::SH_SpinBox_ClickAutoRepeatThreshold: + return 10; + default: + return QProxyStyle::styleHint(hint, option, widget, returnData); + } + } +}; + class tst_QDoubleSpinBox : public QObject { @@ -138,6 +172,17 @@ private slots: void adaptiveDecimalStep(); + void wheelEvents_data(); + void wheelEvents(); + + void stepModifierKeys_data(); + void stepModifierKeys(); + + void stepModifierButtons_data(); + void stepModifierButtons(); + + void stepModifierPressAndHold_data(); + void stepModifierPressAndHold(); public slots: void valueChangedHelper(const QString &); void valueChangedHelper(double); @@ -152,6 +197,24 @@ typedef QList DoubleList; Q_DECLARE_METATYPE(QLocale::Language) Q_DECLARE_METATYPE(QLocale::Country) +static QLatin1String modifierToName(Qt::KeyboardModifier modifier) +{ + switch (modifier) { + case Qt::NoModifier: + return QLatin1Literal("No"); + break; + case Qt::ControlModifier: + return QLatin1Literal("Ctrl"); + break; + case Qt::ShiftModifier: + return QLatin1Literal("Shift"); + break; + default: + qFatal("Unexpected keyboard modifier"); + return QLatin1String(); + } +} + tst_QDoubleSpinBox::tst_QDoubleSpinBox() { @@ -1270,5 +1333,332 @@ void tst_QDoubleSpinBox::adaptiveDecimalStep() } } +void tst_QDoubleSpinBox::wheelEvents_data() +{ +#if QT_CONFIG(wheelevent) + QTest::addColumn("angleDelta"); + QTest::addColumn("qt4Delta"); + QTest::addColumn("modifier"); + QTest::addColumn("source"); + QTest::addColumn("start"); + QTest::addColumn("expectedValues"); + + const auto fractions = {false, true}; + + const auto directions = {true, false}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + const auto sources = {Qt::MouseEventNotSynthesized, + Qt::MouseEventSynthesizedBySystem, + Qt::MouseEventSynthesizedByQt, + Qt::MouseEventSynthesizedByApplication}; + + const double startValue = 0.0; + + for (auto fraction : fractions) { + for (auto up : directions) { + + const int units = (fraction ? 60 : 120) * (up ? 1 : -1); + + for (auto modifier : modifierList) { + + const Qt::KeyboardModifiers modifiers(modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + for (auto source : sources) { + +#ifdef Q_OS_MACOS + QPoint angleDelta; + if ((modifier & Qt::ShiftModifier) && + source == Qt::MouseEventNotSynthesized) { + // On macOS the Shift modifier converts vertical + // mouse wheel events to horizontal. + angleDelta = { units, 0 }; + } else { + // However, this is not the case for trackpad scroll + // events. + angleDelta = { 0, units }; + } +#else + const QPoint angleDelta(0, units); +#endif + + QLatin1String sourceName; + switch (source) { + case Qt::MouseEventNotSynthesized: + sourceName = QLatin1Literal("NotSynthesized"); + break; + case Qt::MouseEventSynthesizedBySystem: + sourceName = QLatin1Literal("SynthesizedBySystem"); + break; + case Qt::MouseEventSynthesizedByQt: + sourceName = QLatin1Literal("SynthesizedByQt"); + break; + case Qt::MouseEventSynthesizedByApplication: + sourceName = QLatin1Literal("SynthesizedByApplication"); + break; + default: + qFatal("Unexpected wheel event source"); + continue; + } + + DoubleList expectedValues; + if (fraction) + expectedValues << startValue; + expectedValues << startValue + steps; + + QTest::addRow("%s%sWith%sKeyboardModifier%s", + fraction ? "half" : "full", + up ? "Up" : "Down", + modifierName.latin1(), + sourceName.latin1()) + << angleDelta + << units + << modifiers + << source + << startValue + << expectedValues; + } + } + } + } +#else + QSKIP("Built with --no-feature-wheelevent"); +#endif +} + +void tst_QDoubleSpinBox::wheelEvents() +{ +#if QT_CONFIG(wheelevent) + QFETCH(QPoint, angleDelta); + QFETCH(int, qt4Delta); + QFETCH(Qt::KeyboardModifiers, modifier); + QFETCH(Qt::MouseEventSource, source); + QFETCH(double, start); + QFETCH(DoubleList, expectedValues); + + DoubleSpinBox spinBox; + spinBox.setRange(-20, 20); + spinBox.setValue(start); + + QWheelEvent event(QPointF(), QPointF(), QPoint(), angleDelta, qt4Delta, + Qt::Vertical, Qt::NoButton, modifier, Qt::NoScrollPhase, + source); + for (int expected : expectedValues) { + qApp->sendEvent(&spinBox, &event); + QCOMPARE(spinBox.value(), expected); + } +#else + QSKIP("Built with --no-feature-wheelevent"); +#endif +} + +void tst_QDoubleSpinBox::stepModifierKeys_data() +{ + QTest::addColumn("startValue"); + QTest::addColumn("keys"); + QTest::addColumn("expectedValue"); + + const auto keyList = {Qt::Key_Up, Qt::Key_Down}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + for (auto key : keyList) { + + const bool up = key == Qt::Key_Up; + Q_ASSERT(up || key == Qt::Key_Down); + + const double startValue = up ? 0.0 : 10.0; + + for (auto modifier : modifierList) { + + QTestEventList keys; + keys.addKeyClick(key, modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + const double expectedValue = startValue + steps; + + QTest::addRow("%sWith%sKeyboardModifier", + up ? "up" : "down", + modifierName.latin1()) + << startValue + << keys + << expectedValue; + } + } +} + +void tst_QDoubleSpinBox::stepModifierKeys() +{ + QFETCH(double, startValue); + QFETCH(QTestEventList, keys); + QFETCH(double, expectedValue); + + QDoubleSpinBox spin(0); + spin.setValue(startValue); + spin.show(); + QVERIFY(QTest::qWaitForWindowActive(&spin)); + + QCOMPARE(spin.value(), startValue); + keys.simulate(&spin); + QCOMPARE(spin.value(), expectedValue); +} + +void tst_QDoubleSpinBox::stepModifierButtons_data() +{ + QTest::addColumn("subControl"); + QTest::addColumn("modifiers"); + QTest::addColumn("startValue"); + QTest::addColumn("expectedValue"); + + const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + for (auto subControl : subControls) { + + const bool up = subControl == QStyle::SC_SpinBoxUp; + Q_ASSERT(up || subControl == QStyle::SC_SpinBoxDown); + + const double startValue = up ? 0 : 10; + + for (auto modifier : modifierList) { + + const Qt::KeyboardModifiers modifiers(modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + const double expectedValue = startValue + steps; + + QTest::addRow("%sWith%sKeyboardModifier", + up ? "up" : "down", + modifierName.latin1()) + << subControl + << modifiers + << startValue + << expectedValue; + } + } +} + +void tst_QDoubleSpinBox::stepModifierButtons() +{ + QFETCH(QStyle::SubControl, subControl); + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(double, startValue); + QFETCH(double, expectedValue); + + DoubleSpinBox spin(0); + spin.setRange(-20, 20); + spin.setValue(startValue); + spin.show(); + QVERIFY(QTest::qWaitForWindowActive(&spin)); + + QStyleOptionSpinBox spinBoxStyleOption; + spin.initStyleOption(&spinBoxStyleOption); + + const QRect buttonRect = spin.style()->subControlRect( + QStyle::CC_SpinBox, &spinBoxStyleOption, subControl, &spin); + + QCOMPARE(spin.value(), startValue); + QTest::mouseClick(&spin, Qt::LeftButton, modifiers, buttonRect.center()); + QCOMPARE(spin.value(), expectedValue); +} + +void tst_QDoubleSpinBox::stepModifierPressAndHold_data() +{ + QTest::addColumn("subControl"); + QTest::addColumn("modifiers"); + QTest::addColumn("expectedStepModifier"); + + const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + for (auto subControl : subControls) { + + const bool up = subControl == QStyle::SC_SpinBoxUp; + Q_ASSERT(up || subControl == QStyle::SC_SpinBoxDown); + + for (auto modifier : modifierList) { + + const Qt::KeyboardModifiers modifiers(modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + QTest::addRow("%sWith%sKeyboardModifier", + up ? "up" : "down", + modifierName.latin1()) + << subControl + << modifiers + << steps; + } + } +} + +void tst_QDoubleSpinBox::stepModifierPressAndHold() +{ + QFETCH(QStyle::SubControl, subControl); + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(int, expectedStepModifier); + + DoubleSpinBox spin(0); + QScopedPointer pressAndHoldStyle( + new PressAndHoldStyle); + spin.setStyle(pressAndHoldStyle.data()); + spin.setRange(-100.0, 100.0); + spin.setValue(0.0); + + QSignalSpy spy(&spin, QOverload::of(&DoubleSpinBox::valueChanged)); + + spin.show(); + QVERIFY(QTest::qWaitForWindowExposed(&spin)); + + QStyleOptionSpinBox spinBoxStyleOption; + spin.initStyleOption(&spinBoxStyleOption); + + const QRect buttonRect = spin.style()->subControlRect( + QStyle::CC_SpinBox, &spinBoxStyleOption, subControl, &spin); + + QTest::mousePress(&spin, Qt::LeftButton, modifiers, buttonRect.center()); + QTRY_VERIFY(spy.length() >= 3); + QTest::mouseRelease(&spin, Qt::LeftButton, modifiers, buttonRect.center()); + + const auto value = spy.last().at(0); + QVERIFY(value.type() == QVariant::Double); + QCOMPARE(value.toDouble(), spy.length() * expectedStepModifier); +} + QTEST_MAIN(tst_QDoubleSpinBox) #include "tst_qdoublespinbox.moc" diff --git a/tests/auto/widgets/widgets/qspinbox/tst_qspinbox.cpp b/tests/auto/widgets/widgets/qspinbox/tst_qspinbox.cpp index 2223fb55cc..0eb2cf5cdc 100644 --- a/tests/auto/widgets/widgets/qspinbox/tst_qspinbox.cpp +++ b/tests/auto/widgets/widgets/qspinbox/tst_qspinbox.cpp @@ -50,6 +50,9 @@ #include #include #include +#include +#include +#include class SpinBox : public QSpinBox { @@ -75,10 +78,34 @@ public: QSpinBox::wheelEvent(event); } #endif + void initStyleOption(QStyleOptionSpinBox *option) const + { + QSpinBox::initStyleOption(option); + } QLineEdit *lineEdit() const { return QSpinBox::lineEdit(); } }; +class PressAndHoldStyle : public QProxyStyle +{ + Q_OBJECT +public: + using QProxyStyle::QProxyStyle; + + int styleHint(QStyle::StyleHint hint, const QStyleOption *option = nullptr, + const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const override + { + switch (hint) { + case QStyle::SH_SpinBox_ClickAutoRepeatRate: + return 5; + case QStyle::SH_SpinBox_ClickAutoRepeatThreshold: + return 10; + default: + return QProxyStyle::styleHint(hint, option, widget, returnData); + } + } +}; + class tst_QSpinBox : public QObject { Q_OBJECT @@ -143,10 +170,19 @@ private slots: void setGroupSeparatorShown_data(); void setGroupSeparatorShown(); + void wheelEvents_data(); void wheelEvents(); void adaptiveDecimalStep(); + void stepModifierKeys_data(); + void stepModifierKeys(); + + void stepModifierButtons_data(); + void stepModifierButtons(); + + void stepModifierPressAndHold_data(); + void stepModifierPressAndHold(); public slots: void valueChangedHelper(const QString &); void valueChangedHelper(int); @@ -160,6 +196,24 @@ typedef QList IntList; Q_DECLARE_METATYPE(QLocale::Language) Q_DECLARE_METATYPE(QLocale::Country) +static QLatin1String modifierToName(Qt::KeyboardModifier modifier) +{ + switch (modifier) { + case Qt::NoModifier: + return QLatin1Literal("No"); + break; + case Qt::ControlModifier: + return QLatin1Literal("Ctrl"); + break; + case Qt::ShiftModifier: + return QLatin1Literal("Shift"); + break; + default: + qFatal("Unexpected keyboard modifier"); + return QLatin1String(); + } +} + // Testing get/set functions void tst_QSpinBox::getSetCheck() { @@ -1217,27 +1271,132 @@ void tst_QSpinBox::setGroupSeparatorShown() QCOMPARE(spinBox.value()+1000, 33000); } -void tst_QSpinBox::wheelEvents() +void tst_QSpinBox::wheelEvents_data() { #if QT_CONFIG(wheelevent) - SpinBox spinBox; - spinBox.setRange(-20, 20); - spinBox.setValue(0); + QTest::addColumn("angleDelta"); + QTest::addColumn("qt4Delta"); + QTest::addColumn("modifier"); + QTest::addColumn("source"); + QTest::addColumn("start"); + QTest::addColumn("expectedValues"); + + const auto fractions = {false, true}; + + const auto directions = {true, false}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + const auto sources = {Qt::MouseEventNotSynthesized, + Qt::MouseEventSynthesizedBySystem, + Qt::MouseEventSynthesizedByQt, + Qt::MouseEventSynthesizedByApplication}; + + const int startValue = 0; + + for (auto fraction : fractions) { + for (auto up : directions) { + + const int units = (fraction ? 60 : 120) * (up ? 1 : -1); + + for (auto modifier : modifierList) { + + const Qt::KeyboardModifiers modifiers(modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + for (auto source : sources) { - QWheelEvent wheelUp(QPointF(), QPointF(), QPoint(), QPoint(0, 120), 120, Qt::Vertical, Qt::NoButton, Qt::NoModifier); - spinBox.wheelEvent(&wheelUp); - QCOMPARE(spinBox.value(), 1); +#ifdef Q_OS_MACOS + QPoint angleDelta; + if ((modifier & Qt::ShiftModifier) && + source == Qt::MouseEventNotSynthesized) { + // On macOS the Shift modifier converts vertical + // mouse wheel events to horizontal. + angleDelta = { units, 0 }; + } else { + // However, this is not the case for trackpad scroll + // events. + angleDelta = { 0, units }; + } +#else + const QPoint angleDelta(0, units); +#endif - QWheelEvent wheelDown(QPointF(), QPointF(), QPoint(), QPoint(0, -120), -120, Qt::Vertical, Qt::NoButton, Qt::NoModifier); - spinBox.wheelEvent(&wheelDown); - spinBox.wheelEvent(&wheelDown); - QCOMPARE(spinBox.value(), -1); + QLatin1String sourceName; + switch (source) { + case Qt::MouseEventNotSynthesized: + sourceName = QLatin1Literal("NotSynthesized"); + break; + case Qt::MouseEventSynthesizedBySystem: + sourceName = QLatin1Literal("SynthesizedBySystem"); + break; + case Qt::MouseEventSynthesizedByQt: + sourceName = QLatin1Literal("SynthesizedByQt"); + break; + case Qt::MouseEventSynthesizedByApplication: + sourceName = QLatin1Literal("SynthesizedByApplication"); + break; + default: + qFatal("Unexpected wheel event source"); + continue; + } + + IntList expectedValues; + if (fraction) + expectedValues << startValue; + expectedValues << startValue + steps; + + QTest::addRow("%s%sWith%sKeyboardModifier%s", + fraction ? "half" : "full", + up ? "Up" : "Down", + modifierName.latin1(), + sourceName.latin1()) + << angleDelta + << units + << modifiers + << source + << startValue + << expectedValues; + } + } + } + } +#else + QSKIP("Built with --no-feature-wheelevent"); +#endif +} - QWheelEvent wheelHalfUp(QPointF(), QPointF(), QPoint(), QPoint(0, 60), 60, Qt::Vertical, Qt::NoButton, Qt::NoModifier); - spinBox.wheelEvent(&wheelHalfUp); - QCOMPARE(spinBox.value(), -1); - spinBox.wheelEvent(&wheelHalfUp); - QCOMPARE(spinBox.value(), 0); +void tst_QSpinBox::wheelEvents() +{ +#if QT_CONFIG(wheelevent) + QFETCH(QPoint, angleDelta); + QFETCH(int, qt4Delta); + QFETCH(Qt::KeyboardModifiers, modifier); + QFETCH(Qt::MouseEventSource, source); + QFETCH(int, start); + QFETCH(IntList, expectedValues); + + SpinBox spinBox; + spinBox.setRange(-20, 20); + spinBox.setValue(start); + + QWheelEvent event(QPointF(), QPointF(), QPoint(), angleDelta, qt4Delta, + Qt::Vertical, Qt::NoButton, modifier, Qt::NoScrollPhase, + source); + for (int expected : expectedValues) { + qApp->sendEvent(&spinBox, &event); + QCOMPARE(spinBox.value(), expected); + } +#else + QSKIP("Built with --no-feature-wheelevent"); #endif } @@ -1320,5 +1479,203 @@ void tst_QSpinBox::adaptiveDecimalStep() } } +void tst_QSpinBox::stepModifierKeys_data() +{ + QTest::addColumn("startValue"); + QTest::addColumn("keys"); + QTest::addColumn("expectedValue"); + + const auto keyList = {Qt::Key_Up, Qt::Key_Down}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + for (auto key : keyList) { + + const bool up = key == Qt::Key_Up; + Q_ASSERT(up || key == Qt::Key_Down); + + const int startValue = up ? 0.0 : 10.0; + + for (auto modifier : modifierList) { + + QTestEventList keys; + keys.addKeyClick(key, modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + const int expectedValue = startValue + steps; + + QTest::addRow("%sWith%sKeyboardModifier", + up ? "up" : "down", + modifierName.latin1()) + << startValue + << keys + << expectedValue; + } + } +} + +void tst_QSpinBox::stepModifierKeys() +{ + QFETCH(int, startValue); + QFETCH(QTestEventList, keys); + QFETCH(int, expectedValue); + + QSpinBox spin(0); + spin.setValue(startValue); + spin.show(); + QVERIFY(QTest::qWaitForWindowActive(&spin)); + + QCOMPARE(spin.value(), startValue); + keys.simulate(&spin); + QCOMPARE(spin.value(), expectedValue); +} + +void tst_QSpinBox::stepModifierButtons_data() +{ + QTest::addColumn("subControl"); + QTest::addColumn("modifiers"); + QTest::addColumn("startValue"); + QTest::addColumn("expectedValue"); + + const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + for (auto subControl : subControls) { + + const bool up = subControl == QStyle::SC_SpinBoxUp; + Q_ASSERT(up || subControl == QStyle::SC_SpinBoxDown); + + const int startValue = up ? 0 : 10; + + for (auto modifier : modifierList) { + + const Qt::KeyboardModifiers modifiers(modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + const int expectedValue = startValue + steps; + + QTest::addRow("%sWith%sKeyboardModifier", + up ? "up" : "down", + modifierName.latin1()) + << subControl + << modifiers + << startValue + << expectedValue; + } + } +} + +void tst_QSpinBox::stepModifierButtons() +{ + QFETCH(QStyle::SubControl, subControl); + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(int, startValue); + QFETCH(int, expectedValue); + + SpinBox spin(0); + spin.setRange(-20, 20); + spin.setValue(startValue); + spin.show(); + QVERIFY(QTest::qWaitForWindowActive(&spin)); + + QStyleOptionSpinBox spinBoxStyleOption; + spin.initStyleOption(&spinBoxStyleOption); + + const QRect buttonRect = spin.style()->subControlRect( + QStyle::CC_SpinBox, &spinBoxStyleOption, subControl, &spin); + + QCOMPARE(spin.value(), startValue); + QTest::mouseClick(&spin, Qt::LeftButton, modifiers, buttonRect.center()); + QCOMPARE(spin.value(), expectedValue); +} + +void tst_QSpinBox::stepModifierPressAndHold_data() +{ + QTest::addColumn("subControl"); + QTest::addColumn("modifiers"); + QTest::addColumn("expectedStepModifier"); + + const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown}; + + const auto modifierList = {Qt::NoModifier, + Qt::ControlModifier, + Qt::ShiftModifier}; + + for (auto subControl : subControls) { + + const bool up = subControl == QStyle::SC_SpinBoxUp; + Q_ASSERT(up || subControl == QStyle::SC_SpinBoxDown); + + for (auto modifier : modifierList) { + + const Qt::KeyboardModifiers modifiers(modifier); + + const auto modifierName = modifierToName(modifier); + if (modifierName.isEmpty()) + continue; + + const int steps = (modifier & Qt::ControlModifier ? 10 : 1) + * (up ? 1 : -1); + + QTest::addRow("%sWith%sKeyboardModifier", + up ? "up" : "down", + modifierName.latin1()) + << subControl + << modifiers + << steps; + } + } +} + +void tst_QSpinBox::stepModifierPressAndHold() +{ + QFETCH(QStyle::SubControl, subControl); + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(int, expectedStepModifier); + + SpinBox spin(0); + QScopedPointer pressAndHoldStyle( + new PressAndHoldStyle); + spin.setStyle(pressAndHoldStyle.data()); + spin.setRange(-100, 100); + spin.setValue(0); + + QSignalSpy spy(&spin, QOverload::of(&SpinBox::valueChanged)); + + spin.show(); + QVERIFY(QTest::qWaitForWindowActive(&spin)); + + QStyleOptionSpinBox spinBoxStyleOption; + spin.initStyleOption(&spinBoxStyleOption); + + const QRect buttonRect = spin.style()->subControlRect( + QStyle::CC_SpinBox, &spinBoxStyleOption, subControl, &spin); + + QTest::mousePress(&spin, Qt::LeftButton, modifiers, buttonRect.center()); + QTRY_VERIFY(spy.length() >= 3); + QTest::mouseRelease(&spin, Qt::LeftButton, modifiers, buttonRect.center()); + + const auto value = spy.last().at(0); + QVERIFY(value.type() == QVariant::Int); + QCOMPARE(value.toInt(), spy.length() * expectedStepModifier); +} + QTEST_MAIN(tst_QSpinBox) #include "tst_qspinbox.moc" -- cgit v1.2.3