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 <mitch.curtis@qt.io>
This commit is contained in:
Nathan Collins 2018-05-14 19:22:22 +01:00
parent cb3e85fc9a
commit 2d1e1f569b
5 changed files with 1279 additions and 33 deletions

View File

@ -100,6 +100,12 @@ QT_BEGIN_NAMESPACE
integer value to signify how many steps were taken. E.g. Pressing integer value to signify how many steps were taken. E.g. Pressing
Qt::Key_Down will trigger a call to stepBy(-1). Qt::Key_Down will trigger a call to stepBy(-1).
When the user triggers a step whilst holding the Qt::ControlModifier,
QAbstractSpinBox steps by 10 instead of making a single step. This
step modifier affects wheel events, key events and interaction with
the spinbox buttons. Note that on macOS, Control corresponds to the
Command key.
QAbstractSpinBox also provide a virtual function stepEnabled() to QAbstractSpinBox also provide a virtual function stepEnabled() to
determine whether stepping up/down is allowed at any point. This determine whether stepping up/down is allowed at any point. This
function returns a bitset of StepEnabled. function returns a bitset of StepEnabled.
@ -1015,6 +1021,8 @@ void QAbstractSpinBox::keyPressEvent(QKeyEvent *event)
const bool up = (event->key() == Qt::Key_PageUp || event->key() == Qt::Key_Up); const bool up = (event->key() == Qt::Key_PageUp || event->key() == Qt::Key_Up);
if (!(stepEnabled() & (up ? StepUpEnabled : StepDownEnabled))) if (!(stepEnabled() & (up ? StepUpEnabled : StepDownEnabled)))
return; return;
if (!isPgUpOrDown && (event->modifiers() & d->stepModifier))
steps *= 10;
if (!up) if (!up)
steps *= -1; steps *= -1;
if (style()->styleHint(QStyle::SH_SpinBox_AnimateButton, 0, this)) { if (style()->styleHint(QStyle::SH_SpinBox_AnimateButton, 0, this)) {
@ -1141,11 +1149,24 @@ void QAbstractSpinBox::keyReleaseEvent(QKeyEvent *event)
void QAbstractSpinBox::wheelEvent(QWheelEvent *event) void QAbstractSpinBox::wheelEvent(QWheelEvent *event)
{ {
Q_D(QAbstractSpinBox); Q_D(QAbstractSpinBox);
#ifdef Q_OS_MACOS
// If the event comes from a real mouse wheel, rather than a track pad
// (Qt::MouseEventSynthesizedBySystem), the shift modifier changes the
// scroll orientation to horizontal.
// Convert horizontal events back to vertical whilst shift is held.
if ((event->modifiers() & Qt::ShiftModifier)
&& event->source() == Qt::MouseEventNotSynthesized) {
d->wheelDeltaRemainder += event->angleDelta().x();
} else {
d->wheelDeltaRemainder += event->angleDelta().y();
}
#else
d->wheelDeltaRemainder += event->angleDelta().y(); d->wheelDeltaRemainder += event->angleDelta().y();
#endif
const int steps = d->wheelDeltaRemainder / 120; const int steps = d->wheelDeltaRemainder / 120;
d->wheelDeltaRemainder -= steps * 120; d->wheelDeltaRemainder -= steps * 120;
if (stepEnabled() & (steps > 0 ? StepUpEnabled : StepDownEnabled)) if (stepEnabled() & (steps > 0 ? StepUpEnabled : StepDownEnabled))
stepBy(event->modifiers() & Qt::ControlModifier ? steps * 10 : steps); stepBy(event->modifiers() & d->stepModifier ? steps * 10 : steps);
event->accept(); event->accept();
} }
#endif #endif
@ -1245,18 +1266,19 @@ void QAbstractSpinBox::timerEvent(QTimerEvent *event)
} }
if (doStep) { if (doStep) {
const bool increaseStepRate = QGuiApplication::keyboardModifiers() & d->stepModifier;
const StepEnabled st = stepEnabled(); const StepEnabled st = stepEnabled();
if (d->buttonState & Up) { if (d->buttonState & Up) {
if (!(st & StepUpEnabled)) { if (!(st & StepUpEnabled)) {
d->reset(); d->reset();
} else { } else {
stepBy(1); stepBy(increaseStepRate ? 10 : 1);
} }
} else if (d->buttonState & Down) { } else if (d->buttonState & Down) {
if (!(st & StepDownEnabled)) { if (!(st & StepDownEnabled)) {
d->reset(); d->reset();
} else { } else {
stepBy(-1); stepBy(increaseStepRate ? -10 : -1);
} }
} }
return; return;
@ -1384,8 +1406,9 @@ QAbstractSpinBoxPrivate::QAbstractSpinBoxPrivate()
cachedState(QValidator::Invalid), pendingEmit(false), readOnly(false), wrapping(false), cachedState(QValidator::Invalid), pendingEmit(false), readOnly(false), wrapping(false),
ignoreCursorPositionChanged(false), frame(true), accelerate(false), keyboardTracking(true), ignoreCursorPositionChanged(false), frame(true), accelerate(false), keyboardTracking(true),
cleared(false), ignoreUpdateEdit(false), correctionMode(QAbstractSpinBox::CorrectToPreviousValue), cleared(false), ignoreUpdateEdit(false), correctionMode(QAbstractSpinBox::CorrectToPreviousValue),
acceleration(0), hoverControl(QStyle::SC_None), buttonSymbols(QAbstractSpinBox::UpDownArrows), validator(0), stepModifier(Qt::ControlModifier), acceleration(0), hoverControl(QStyle::SC_None),
showGroupSeparator(0), wheelDeltaRemainder(0) buttonSymbols(QAbstractSpinBox::UpDownArrows), validator(0), showGroupSeparator(0),
wheelDeltaRemainder(0)
{ {
} }
@ -1636,7 +1659,10 @@ void QAbstractSpinBoxPrivate::updateState(bool up, bool fromKeyboard /* = false
: QAbstractSpinBox::StepDownEnabled))) { : QAbstractSpinBox::StepDownEnabled))) {
spinClickThresholdTimerId = q->startTimer(spinClickThresholdTimerInterval); spinClickThresholdTimerId = q->startTimer(spinClickThresholdTimerInterval);
buttonState = (up ? Up : Down) | (fromKeyboard ? Keyboard : Mouse); buttonState = (up ? Up : Down) | (fromKeyboard ? Keyboard : Mouse);
q->stepBy(up ? 1 : -1); int steps = up ? 1 : -1;
if (QGuiApplication::keyboardModifiers() & stepModifier)
steps *= 10;
q->stepBy(steps);
#ifndef QT_NO_ACCESSIBILITY #ifndef QT_NO_ACCESSIBILITY
QAccessibleValueChangeEvent event(q, value); QAccessibleValueChangeEvent event(q, value);
QAccessible::updateAccessibility(&event); QAccessible::updateAccessibility(&event);

View File

@ -146,6 +146,7 @@ public:
uint ignoreUpdateEdit : 1; uint ignoreUpdateEdit : 1;
QAbstractSpinBox::CorrectionMode correctionMode; QAbstractSpinBox::CorrectionMode correctionMode;
QAbstractSpinBox::StepType stepType = QAbstractSpinBox::StepType::DefaultStepType; QAbstractSpinBox::StepType stepType = QAbstractSpinBox::StepType::DefaultStepType;
Qt::KeyboardModifier stepModifier = Qt::ControlModifier;
int acceleration; int acceleration;
QStyle::SubControl hoverControl; QStyle::SubControl hoverControl;
QRect hoverRect; QRect hoverRect;

View File

@ -70,6 +70,7 @@
#include <QSignalSpy> #include <QSignalSpy>
#include <QTestEventList> #include <QTestEventList>
#include <QDateEdit> #include <QDateEdit>
#include <QProxyStyle>
#include <private/qdatetimeedit_p.h> #include <private/qdatetimeedit_p.h>
@ -93,6 +94,26 @@ public:
friend class tst_QDateTimeEdit; 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 class tst_QDateTimeEdit : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -205,9 +226,8 @@ private slots:
void reverseTest(); void reverseTest();
void ddMMMMyyyy(); void ddMMMMyyyy();
#if QT_CONFIG(wheelevent) void wheelEvent_data();
void wheelEvent(); void wheelEvent();
#endif
void specialValueCornerCase(); void specialValueCornerCase();
void cursorPositionOnInit(); void cursorPositionOnInit();
@ -245,14 +265,99 @@ private slots:
void dateEditCorrectSectionSize_data(); void dateEditCorrectSectionSize_data();
void dateEditCorrectSectionSize(); void dateEditCorrectSectionSize();
#endif #endif
void stepModifierKeys_data();
void stepModifierKeys();
void stepModifierButtons_data();
void stepModifierButtons();
void stepModifierPressAndHold_data();
void stepModifierPressAndHold();
private: private:
EditorDateEdit* testWidget; EditorDateEdit* testWidget;
QWidget *testFocusWidget; QWidget *testFocusWidget;
}; };
typedef QList<QDate> DateList;
typedef QList<QTime> TimeList; typedef QList<QTime> TimeList;
typedef QList<Qt::Key> KeyList; typedef QList<Qt::Key> 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 // Testing get/set functions
void tst_QDateTimeEdit::getSetCheck() void tst_QDateTimeEdit::getSetCheck()
{ {
@ -3000,20 +3105,154 @@ void tst_QDateTimeEdit::ddMMMMyyyy()
QCOMPARE(testWidget->lineEdit()->text(), "01." + QDate::longMonthName(1) + ".200"); QCOMPARE(testWidget->lineEdit()->text(), "01." + QDate::longMonthName(1) + ".200");
} }
void tst_QDateTimeEdit::wheelEvent_data()
{
#if QT_CONFIG(wheelevent) #if QT_CONFIG(wheelevent)
QTest::addColumn<QPoint>("angleDelta");
QTest::addColumn<int>("qt4Delta");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<Qt::MouseEventSource>("source");
QTest::addColumn<QDateTimeEdit::Section>("section");
QTest::addColumn<QDate>("startDate");
QTest::addColumn<DateList>("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() void tst_QDateTimeEdit::wheelEvent()
{ {
testWidget->setDisplayFormat("dddd/MM"); #if QT_CONFIG(wheelevent)
testWidget->setDate(QDate(2000, 2, 21)); QFETCH(QPoint, angleDelta);
testWidget->setCurrentSection(QDateTimeEdit::DaySection); QFETCH(int, qt4Delta);
QWheelEvent w(testWidget->lineEdit()->geometry().center(), 120, 0, 0); QFETCH(Qt::KeyboardModifiers, modifiers);
qApp->sendEvent(testWidget, &w); QFETCH(Qt::MouseEventSource, source);
QCOMPARE(testWidget->date(), QDate(2000, 2, 22)); QFETCH(QDateTimeEdit::Section, section);
testWidget->setCurrentSection(QDateTimeEdit::MonthSection); QFETCH(QDate, startDate);
qApp->sendEvent(testWidget, &w); QFETCH(DateList, expectedDates);
QCOMPARE(testWidget->date(), QDate(2000, 3, 22));
} 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) #endif // QT_CONFIG(wheelevent)
}
void tst_QDateTimeEdit::specialValueCornerCase() void tst_QDateTimeEdit::specialValueCornerCase()
{ {
@ -3735,5 +3974,238 @@ void tst_QDateTimeEdit::dateEditCorrectSectionSize()
} }
#endif #endif
void tst_QDateTimeEdit::stepModifierKeys_data()
{
QTest::addColumn<QDate>("startDate");
QTest::addColumn<QDateTimeEdit::Section>("section");
QTest::addColumn<QTestEventList>("keys");
QTest::addColumn<QDate>("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<QStyle::SubControl>("subControl");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<QDateTimeEdit::Section>("section");
QTest::addColumn<QTime>("startTime");
QTest::addColumn<QTime>("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<QStyle::SubControl>("subControl");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<int>("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, QScopedPointerDeleteLater> 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) QTEST_MAIN(tst_QDateTimeEdit)
#include "tst_qdatetimeedit.moc" #include "tst_qdatetimeedit.moc"

View File

@ -41,6 +41,10 @@
#include <qlineedit.h> #include <qlineedit.h>
#include <qdebug.h> #include <qdebug.h>
#include <QStyleOptionSpinBox>
#include <QStyle>
#include <QProxyStyle>
class DoubleSpinBox : public QDoubleSpinBox class DoubleSpinBox : public QDoubleSpinBox
{ {
Q_OBJECT Q_OBJECT
@ -60,6 +64,16 @@ public:
{ {
return QDoubleSpinBox::valueFromText(text); 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(); } QLineEdit* lineEdit() const { return QDoubleSpinBox::lineEdit(); }
public slots: 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 class tst_QDoubleSpinBox : public QObject
{ {
@ -138,6 +172,17 @@ private slots:
void adaptiveDecimalStep(); void adaptiveDecimalStep();
void wheelEvents_data();
void wheelEvents();
void stepModifierKeys_data();
void stepModifierKeys();
void stepModifierButtons_data();
void stepModifierButtons();
void stepModifierPressAndHold_data();
void stepModifierPressAndHold();
public slots: public slots:
void valueChangedHelper(const QString &); void valueChangedHelper(const QString &);
void valueChangedHelper(double); void valueChangedHelper(double);
@ -152,6 +197,24 @@ typedef QList<double> DoubleList;
Q_DECLARE_METATYPE(QLocale::Language) Q_DECLARE_METATYPE(QLocale::Language)
Q_DECLARE_METATYPE(QLocale::Country) 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() tst_QDoubleSpinBox::tst_QDoubleSpinBox()
{ {
@ -1270,5 +1333,332 @@ void tst_QDoubleSpinBox::adaptiveDecimalStep()
} }
} }
void tst_QDoubleSpinBox::wheelEvents_data()
{
#if QT_CONFIG(wheelevent)
QTest::addColumn<QPoint>("angleDelta");
QTest::addColumn<int>("qt4Delta");
QTest::addColumn<Qt::KeyboardModifiers>("modifier");
QTest::addColumn<Qt::MouseEventSource>("source");
QTest::addColumn<double>("start");
QTest::addColumn<DoubleList>("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<double>("startValue");
QTest::addColumn<QTestEventList>("keys");
QTest::addColumn<double>("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<QStyle::SubControl>("subControl");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<double>("startValue");
QTest::addColumn<double>("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<QStyle::SubControl>("subControl");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<int>("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, QScopedPointerDeleteLater> pressAndHoldStyle(
new PressAndHoldStyle);
spin.setStyle(pressAndHoldStyle.data());
spin.setRange(-100.0, 100.0);
spin.setValue(0.0);
QSignalSpy spy(&spin, QOverload<double>::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) QTEST_MAIN(tst_QDoubleSpinBox)
#include "tst_qdoublespinbox.moc" #include "tst_qdoublespinbox.moc"

View File

@ -50,6 +50,9 @@
#include <QKeySequence> #include <QKeySequence>
#include <QStackedWidget> #include <QStackedWidget>
#include <QDebug> #include <QDebug>
#include <QStyleOptionSpinBox>
#include <QStyle>
#include <QProxyStyle>
class SpinBox : public QSpinBox class SpinBox : public QSpinBox
{ {
@ -75,10 +78,34 @@ public:
QSpinBox::wheelEvent(event); QSpinBox::wheelEvent(event);
} }
#endif #endif
void initStyleOption(QStyleOptionSpinBox *option) const
{
QSpinBox::initStyleOption(option);
}
QLineEdit *lineEdit() const { return QSpinBox::lineEdit(); } 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 class tst_QSpinBox : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -143,10 +170,19 @@ private slots:
void setGroupSeparatorShown_data(); void setGroupSeparatorShown_data();
void setGroupSeparatorShown(); void setGroupSeparatorShown();
void wheelEvents_data();
void wheelEvents(); void wheelEvents();
void adaptiveDecimalStep(); void adaptiveDecimalStep();
void stepModifierKeys_data();
void stepModifierKeys();
void stepModifierButtons_data();
void stepModifierButtons();
void stepModifierPressAndHold_data();
void stepModifierPressAndHold();
public slots: public slots:
void valueChangedHelper(const QString &); void valueChangedHelper(const QString &);
void valueChangedHelper(int); void valueChangedHelper(int);
@ -160,6 +196,24 @@ typedef QList<int> IntList;
Q_DECLARE_METATYPE(QLocale::Language) Q_DECLARE_METATYPE(QLocale::Language)
Q_DECLARE_METATYPE(QLocale::Country) 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 // Testing get/set functions
void tst_QSpinBox::getSetCheck() void tst_QSpinBox::getSetCheck()
{ {
@ -1217,27 +1271,132 @@ void tst_QSpinBox::setGroupSeparatorShown()
QCOMPARE(spinBox.value()+1000, 33000); QCOMPARE(spinBox.value()+1000, 33000);
} }
void tst_QSpinBox::wheelEvents_data()
{
#if QT_CONFIG(wheelevent)
QTest::addColumn<QPoint>("angleDelta");
QTest::addColumn<int>("qt4Delta");
QTest::addColumn<Qt::KeyboardModifiers>("modifier");
QTest::addColumn<Qt::MouseEventSource>("source");
QTest::addColumn<int>("start");
QTest::addColumn<IntList>("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) {
#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;
}
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
}
void tst_QSpinBox::wheelEvents() void tst_QSpinBox::wheelEvents()
{ {
#if QT_CONFIG(wheelevent) #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 spinBox;
spinBox.setRange(-20, 20); spinBox.setRange(-20, 20);
spinBox.setValue(0); spinBox.setValue(start);
QWheelEvent wheelUp(QPointF(), QPointF(), QPoint(), QPoint(0, 120), 120, Qt::Vertical, Qt::NoButton, Qt::NoModifier); QWheelEvent event(QPointF(), QPointF(), QPoint(), angleDelta, qt4Delta,
spinBox.wheelEvent(&wheelUp); Qt::Vertical, Qt::NoButton, modifier, Qt::NoScrollPhase,
QCOMPARE(spinBox.value(), 1); source);
for (int expected : expectedValues) {
QWheelEvent wheelDown(QPointF(), QPointF(), QPoint(), QPoint(0, -120), -120, Qt::Vertical, Qt::NoButton, Qt::NoModifier); qApp->sendEvent(&spinBox, &event);
spinBox.wheelEvent(&wheelDown); QCOMPARE(spinBox.value(), expected);
spinBox.wheelEvent(&wheelDown); }
QCOMPARE(spinBox.value(), -1); #else
QSKIP("Built with --no-feature-wheelevent");
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);
#endif #endif
} }
@ -1320,5 +1479,203 @@ void tst_QSpinBox::adaptiveDecimalStep()
} }
} }
void tst_QSpinBox::stepModifierKeys_data()
{
QTest::addColumn<int>("startValue");
QTest::addColumn<QTestEventList>("keys");
QTest::addColumn<int>("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<QStyle::SubControl>("subControl");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<int>("startValue");
QTest::addColumn<int>("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<QStyle::SubControl>("subControl");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<int>("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, QScopedPointerDeleteLater> pressAndHoldStyle(
new PressAndHoldStyle);
spin.setStyle(pressAndHoldStyle.data());
spin.setRange(-100, 100);
spin.setValue(0);
QSignalSpy spy(&spin, QOverload<int>::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) QTEST_MAIN(tst_QSpinBox)
#include "tst_qspinbox.moc" #include "tst_qspinbox.moc"