Add QStyle::SH_SpinBox_StepModifier style hint

This patch allows the developer to pick which keyboard modifier
increases the number of steps a QAbstractSpinBox takes when the user
interacts with it.

The modifier can be either Qt::ControlModifier (default),
Qt::ShiftModifier or Qt::NoModifier. Qt::NoModifier disables the step
modifier. Note that on macOS, Control corresponds to the Command key.

Holding the modifier increases the step rate when:
- scrolling;
- pressing the up/down keys;
- pressing the spin box up/down buttons.

[ChangeLog][QtWidgets][QStyle] QStyle::SH_SpinBox_StepModifier allows
the developer to pick which keyboard modifier increases the number of
steps a QAbstractSpinBox takes for the following interactions:
scrolling, up/down keyboard keys and the spin box buttons. The
Qt::ShiftModifier can now be used, or the feature can be disabled
using Qt::NoModifier. Previously, only Qt::ControlModifier could be
used as the modifier.

Change-Id: Ib5518127e86a8f67798a9a1d6e860c6e35896e6f
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
Nathan Collins 2018-05-18 16:27:43 +01:00 committed by Mitch Curtis
parent 21291d78c5
commit e40f23f098
7 changed files with 626 additions and 272 deletions

View File

@ -5306,6 +5306,9 @@ int QCommonStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget
case SH_SpinBox_ButtonsInsideFrame:
ret = true;
break;
case SH_SpinBox_StepModifier:
ret = Qt::ControlModifier;
break;
default:
ret = 0;
break;

View File

@ -2001,6 +2001,13 @@ void QStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment,
Determnines if the spin box buttons are inside the line edit frame.
This enum value has been introduced in Qt 5.11.
\value SH_SpinBox_StepModifier
Determines which Qt::KeyboardModifier increases the step rate of
QAbstractSpinBox. Possible values are Qt::NoModifier,
Qt::ControlModifier (default) or Qt::ShiftModifier. Qt::NoModifier
disables this feature.
This enum value has been introduced in Qt 5.12.
\sa styleHint()
*/

View File

@ -741,6 +741,7 @@ public:
SH_Widget_Animation_Duration,
SH_ComboBox_AllowWheelScrolling,
SH_SpinBox_ButtonsInsideFrame,
SH_SpinBox_StepModifier,
// Add new style hint values here
SH_CustomBase = 0xf0000000

View File

@ -106,6 +106,10 @@ QT_BEGIN_NAMESPACE
the spinbox buttons. Note that on macOS, Control corresponds to the
Command key.
Since Qt 5.12, QStyle::SH_SpinBox_StepModifier can be used to select
which Qt::KeyboardModifier increases the step rate. Qt::NoModifier
disables this feature.
QAbstractSpinBox also provide a virtual function stepEnabled() to
determine whether stepping up/down is allowed at any point. This
function returns a bitset of StepEnabled.
@ -840,6 +844,7 @@ void QAbstractSpinBox::changeEvent(QEvent *event)
style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatThreshold, 0, this);
if (d->edit)
d->edit->setFrame(!style()->styleHint(QStyle::SH_SpinBox_ButtonsInsideFrame, nullptr, this));
d->stepModifier = static_cast<Qt::KeyboardModifier>(style()->styleHint(QStyle::SH_SpinBox_StepModifier, nullptr, this));
d->reset();
d->updateEditFieldGeometry();
break;

View File

@ -114,6 +114,26 @@ public:
}
};
class StepModifierStyle : 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_StepModifier:
return stepModifier;
default:
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
}
Qt::KeyboardModifier stepModifier = Qt::ControlModifier;
};
class tst_QDateTimeEdit : public QObject
{
Q_OBJECT
@ -295,6 +315,12 @@ static QLatin1String modifierToName(Qt::KeyboardModifier modifier)
case Qt::ShiftModifier:
return QLatin1Literal("Shift");
break;
case Qt::AltModifier:
return QLatin1Literal("Alt");
break;
case Qt::MetaModifier:
return QLatin1Literal("Meta");
break;
default:
qFatal("Unexpected keyboard modifier");
return QLatin1String();
@ -3110,6 +3136,7 @@ void tst_QDateTimeEdit::wheelEvent_data()
#if QT_CONFIG(wheelevent)
QTest::addColumn<QPoint>("angleDelta");
QTest::addColumn<int>("qt4Delta");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<Qt::MouseEventSource>("source");
QTest::addColumn<QDateTimeEdit::Section>("section");
@ -3121,6 +3148,12 @@ void tst_QDateTimeEdit::wheelEvent_data()
const auto directions = {true, false};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
@ -3148,7 +3181,13 @@ void tst_QDateTimeEdit::wheelEvent_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
for (auto source : sources) {
@ -3202,14 +3241,16 @@ void tst_QDateTimeEdit::wheelEvent_data()
const QLatin1String sectionName = sectionToName(section);
QTest::addRow("%s%s%sWith%sKeyboardModifier%s",
QTest::addRow("%s%s%s%sWith%sKeyboardModifier%s",
fraction ? "half" : "full",
up ? "Up" : "Down",
stepModifierName.latin1(),
sectionName.latin1(),
modifierName.latin1(),
sourceName.latin1())
<< angleDelta
<< units
<< static_cast<int>(stepModifier)
<< modifiers
<< source
<< section
@ -3220,6 +3261,7 @@ void tst_QDateTimeEdit::wheelEvent_data()
}
}
}
}
#else
QSKIP("Built with --no-feature-wheelevent");
#endif
@ -3230,6 +3272,7 @@ void tst_QDateTimeEdit::wheelEvent()
#if QT_CONFIG(wheelevent)
QFETCH(QPoint, angleDelta);
QFETCH(int, qt4Delta);
QFETCH(int, stepModifier);
QFETCH(Qt::KeyboardModifiers, modifiers);
QFETCH(Qt::MouseEventSource, source);
QFETCH(QDateTimeEdit::Section, section);
@ -3240,6 +3283,11 @@ void tst_QDateTimeEdit::wheelEvent()
edit.setDate(startDate);
edit.setCurrentSection(section);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
new StepModifierStyle);
style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
edit.setStyle(style.data());
QWheelEvent event(QPointF(), QPointF(), QPoint(), angleDelta, qt4Delta,
Qt::Vertical, Qt::NoButton, modifiers, Qt::NoScrollPhase,
source);
@ -3977,6 +4025,7 @@ void tst_QDateTimeEdit::dateEditCorrectSectionSize()
void tst_QDateTimeEdit::stepModifierKeys_data()
{
QTest::addColumn<QDate>("startDate");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<QDateTimeEdit::Section>("section");
QTest::addColumn<QTestEventList>("keys");
QTest::addColumn<QDate>("expectedDate");
@ -3984,6 +4033,12 @@ void tst_QDateTimeEdit::stepModifierKeys_data()
const auto keyList = {Qt::Key_Up, Qt::Key_Down};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
@ -4007,7 +4062,13 @@ void tst_QDateTimeEdit::stepModifierKeys_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
for (const auto section : sections) {
@ -4018,11 +4079,13 @@ void tst_QDateTimeEdit::stepModifierKeys_data()
const auto sectionName = sectionToName(section);
QTest::addRow("%s%sWith%sKeyboardModifier",
QTest::addRow("%s%s%sWith%sKeyboardModifier",
up ? "up" : "down",
stepModifierName.latin1(),
sectionName.latin1(),
modifierName.latin1())
<< startDate
<< static_cast<int>(stepModifier)
<< section
<< keys
<< expectedDate;
@ -4030,10 +4093,12 @@ void tst_QDateTimeEdit::stepModifierKeys_data()
}
}
}
}
void tst_QDateTimeEdit::stepModifierKeys()
{
QFETCH(QDate, startDate);
QFETCH(int, stepModifier);
QFETCH(QDateTimeEdit::Section, section);
QFETCH(QTestEventList, keys);
QFETCH(QDate, expectedDate);
@ -4044,6 +4109,11 @@ void tst_QDateTimeEdit::stepModifierKeys()
QVERIFY(QTest::qWaitForWindowActive(&edit));
edit.setCurrentSection(section);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
new StepModifierStyle);
style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
edit.setStyle(style.data());
QCOMPARE(edit.date(), startDate);
keys.simulate(&edit);
QCOMPARE(edit.date(), expectedDate);
@ -4052,6 +4122,7 @@ void tst_QDateTimeEdit::stepModifierKeys()
void tst_QDateTimeEdit::stepModifierButtons_data()
{
QTest::addColumn<QStyle::SubControl>("subControl");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<QDateTimeEdit::Section>("section");
QTest::addColumn<QTime>("startTime");
@ -4060,6 +4131,12 @@ void tst_QDateTimeEdit::stepModifierButtons_data()
const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
@ -4082,7 +4159,13 @@ void tst_QDateTimeEdit::stepModifierButtons_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
for (const auto section : sections) {
@ -4093,11 +4176,13 @@ void tst_QDateTimeEdit::stepModifierButtons_data()
const auto sectionName = sectionToName(section);
QTest::addRow("%s%sWith%sKeyboardModifier",
QTest::addRow("%s%s%sWith%sKeyboardModifier",
up ? "up" : "down",
stepModifierName.latin1(),
sectionName.latin1(),
modifierName.latin1())
<< subControl
<< static_cast<int>(stepModifier)
<< modifiers
<< section
<< startTime
@ -4106,10 +4191,12 @@ void tst_QDateTimeEdit::stepModifierButtons_data()
}
}
}
}
void tst_QDateTimeEdit::stepModifierButtons()
{
QFETCH(QStyle::SubControl, subControl);
QFETCH(int, stepModifier);
QFETCH(Qt::KeyboardModifiers, modifiers);
QFETCH(QDateTimeEdit::Section, section);
QFETCH(QTime, startTime);
@ -4121,6 +4208,11 @@ void tst_QDateTimeEdit::stepModifierButtons()
QVERIFY(QTest::qWaitForWindowActive(&edit));
edit.setCurrentSection(section);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
new StepModifierStyle);
style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
edit.setStyle(style.data());
QStyleOptionSpinBox spinBoxStyleOption;
edit.initStyleOption(&spinBoxStyleOption);
@ -4135,12 +4227,19 @@ void tst_QDateTimeEdit::stepModifierButtons()
void tst_QDateTimeEdit::stepModifierPressAndHold_data()
{
QTest::addColumn<QStyle::SubControl>("subControl");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<int>("expectedStepModifier");
const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
@ -4157,33 +4256,45 @@ void tst_QDateTimeEdit::stepModifierPressAndHold_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
QTest::addRow("%sWith%sKeyboardModifier",
QTest::addRow("%s%sWith%sKeyboardModifier",
up ? "up" : "down",
stepModifierName.latin1(),
modifierName.latin1())
<< subControl
<< static_cast<int>(stepModifier)
<< modifiers
<< steps;
}
}
}
}
void tst_QDateTimeEdit::stepModifierPressAndHold()
{
QFETCH(QStyle::SubControl, subControl);
QFETCH(int, stepModifier);
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);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> stepModifierStyle(
new StepModifierStyle(new PressAndHoldStyle));
stepModifierStyle->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
edit.setStyle(stepModifierStyle.data());
QSignalSpy spy(&edit, &EditorDateEdit::dateChanged);
edit.show();

View File

@ -103,6 +103,26 @@ public:
}
};
class StepModifierStyle : 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_StepModifier:
return stepModifier;
default:
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
}
Qt::KeyboardModifier stepModifier = Qt::ControlModifier;
};
class tst_QDoubleSpinBox : public QObject
{
@ -209,6 +229,12 @@ static QLatin1String modifierToName(Qt::KeyboardModifier modifier)
case Qt::ShiftModifier:
return QLatin1Literal("Shift");
break;
case Qt::AltModifier:
return QLatin1Literal("Alt");
break;
case Qt::MetaModifier:
return QLatin1Literal("Meta");
break;
default:
qFatal("Unexpected keyboard modifier");
return QLatin1String();
@ -1338,6 +1364,7 @@ void tst_QDoubleSpinBox::wheelEvents_data()
#if QT_CONFIG(wheelevent)
QTest::addColumn<QPoint>("angleDelta");
QTest::addColumn<int>("qt4Delta");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<Qt::KeyboardModifiers>("modifier");
QTest::addColumn<Qt::MouseEventSource>("source");
QTest::addColumn<double>("start");
@ -1348,6 +1375,12 @@ void tst_QDoubleSpinBox::wheelEvents_data()
const auto directions = {true, false};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
@ -1371,7 +1404,13 @@ void tst_QDoubleSpinBox::wheelEvents_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
for (auto source : sources) {
@ -1416,13 +1455,15 @@ void tst_QDoubleSpinBox::wheelEvents_data()
expectedValues << startValue;
expectedValues << startValue + steps;
QTest::addRow("%s%sWith%sKeyboardModifier%s",
QTest::addRow("%s%s%sWith%sKeyboardModifier%s",
fraction ? "half" : "full",
up ? "Up" : "Down",
stepModifierName.latin1(),
modifierName.latin1(),
sourceName.latin1())
<< angleDelta
<< units
<< static_cast<int>(stepModifier)
<< modifiers
<< source
<< startValue
@ -1431,6 +1472,7 @@ void tst_QDoubleSpinBox::wheelEvents_data()
}
}
}
}
#else
QSKIP("Built with --no-feature-wheelevent");
#endif
@ -1441,6 +1483,7 @@ void tst_QDoubleSpinBox::wheelEvents()
#if QT_CONFIG(wheelevent)
QFETCH(QPoint, angleDelta);
QFETCH(int, qt4Delta);
QFETCH(int, stepModifier);
QFETCH(Qt::KeyboardModifiers, modifier);
QFETCH(Qt::MouseEventSource, source);
QFETCH(double, start);
@ -1450,6 +1493,11 @@ void tst_QDoubleSpinBox::wheelEvents()
spinBox.setRange(-20, 20);
spinBox.setValue(start);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
new StepModifierStyle);
style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
spinBox.setStyle(style.data());
QWheelEvent event(QPointF(), QPointF(), QPoint(), angleDelta, qt4Delta,
Qt::Vertical, Qt::NoButton, modifier, Qt::NoScrollPhase,
source);
@ -1465,15 +1513,23 @@ void tst_QDoubleSpinBox::wheelEvents()
void tst_QDoubleSpinBox::stepModifierKeys_data()
{
QTest::addColumn<double>("startValue");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<QTestEventList>("keys");
QTest::addColumn<double>("expectedValue");
const auto keyList = {Qt::Key_Up, Qt::Key_Down};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
for (auto key : keyList) {
const bool up = key == Qt::Key_Up;
@ -1490,29 +1546,45 @@ void tst_QDoubleSpinBox::stepModifierKeys_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
const double expectedValue = startValue + steps;
QTest::addRow("%sWith%sKeyboardModifier",
QTest::addRow("%s%sWith%sKeyboardModifier",
up ? "up" : "down",
stepModifierName.latin1(),
modifierName.latin1())
<< startValue
<< static_cast<int>(stepModifier)
<< keys
<< expectedValue;
}
}
}
}
void tst_QDoubleSpinBox::stepModifierKeys()
{
QFETCH(double, startValue);
QFETCH(int, stepModifier);
QFETCH(QTestEventList, keys);
QFETCH(double, expectedValue);
QDoubleSpinBox spin(0);
spin.setValue(startValue);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
new StepModifierStyle);
style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
spin.setStyle(style.data());
spin.show();
QVERIFY(QTest::qWaitForWindowActive(&spin));
@ -1524,6 +1596,7 @@ void tst_QDoubleSpinBox::stepModifierKeys()
void tst_QDoubleSpinBox::stepModifierButtons_data()
{
QTest::addColumn<QStyle::SubControl>("subControl");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<double>("startValue");
QTest::addColumn<double>("expectedValue");
@ -1531,6 +1604,12 @@ void tst_QDoubleSpinBox::stepModifierButtons_data()
const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
@ -1549,25 +1628,35 @@ void tst_QDoubleSpinBox::stepModifierButtons_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
const double expectedValue = startValue + steps;
QTest::addRow("%sWith%sKeyboardModifier",
QTest::addRow("%s%sWith%sKeyboardModifier",
up ? "up" : "down",
stepModifierName.latin1(),
modifierName.latin1())
<< subControl
<< static_cast<int>(stepModifier)
<< modifiers
<< startValue
<< expectedValue;
}
}
}
}
void tst_QDoubleSpinBox::stepModifierButtons()
{
QFETCH(QStyle::SubControl, subControl);
QFETCH(int, stepModifier);
QFETCH(Qt::KeyboardModifiers, modifiers);
QFETCH(double, startValue);
QFETCH(double, expectedValue);
@ -1575,6 +1664,12 @@ void tst_QDoubleSpinBox::stepModifierButtons()
DoubleSpinBox spin(0);
spin.setRange(-20, 20);
spin.setValue(startValue);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
new StepModifierStyle);
style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
spin.setStyle(style.data());
spin.show();
QVERIFY(QTest::qWaitForWindowActive(&spin));
@ -1592,12 +1687,19 @@ void tst_QDoubleSpinBox::stepModifierButtons()
void tst_QDoubleSpinBox::stepModifierPressAndHold_data()
{
QTest::addColumn<QStyle::SubControl>("subControl");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<int>("expectedStepModifier");
const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
@ -1614,32 +1716,44 @@ void tst_QDoubleSpinBox::stepModifierPressAndHold_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
QTest::addRow("%sWith%sKeyboardModifier",
QTest::addRow("%s%sWith%sKeyboardModifier",
up ? "up" : "down",
stepModifierName.latin1(),
modifierName.latin1())
<< subControl
<< static_cast<int>(stepModifier)
<< modifiers
<< steps;
}
}
}
}
void tst_QDoubleSpinBox::stepModifierPressAndHold()
{
QFETCH(QStyle::SubControl, subControl);
QFETCH(int, stepModifier);
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);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> stepModifierStyle(
new StepModifierStyle(new PressAndHoldStyle));
stepModifierStyle->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
spin.setStyle(stepModifierStyle.data());
QSignalSpy spy(&spin, QOverload<double>::of(&DoubleSpinBox::valueChanged));
spin.show();

View File

@ -106,6 +106,26 @@ public:
}
};
class StepModifierStyle : 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_StepModifier:
return stepModifier;
default:
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
}
Qt::KeyboardModifier stepModifier = Qt::ControlModifier;
};
class tst_QSpinBox : public QObject
{
Q_OBJECT
@ -208,6 +228,12 @@ static QLatin1String modifierToName(Qt::KeyboardModifier modifier)
case Qt::ShiftModifier:
return QLatin1Literal("Shift");
break;
case Qt::AltModifier:
return QLatin1Literal("Alt");
break;
case Qt::MetaModifier:
return QLatin1Literal("Meta");
break;
default:
qFatal("Unexpected keyboard modifier");
return QLatin1String();
@ -1280,6 +1306,7 @@ void tst_QSpinBox::wheelEvents_data()
#if QT_CONFIG(wheelevent)
QTest::addColumn<QPoint>("angleDelta");
QTest::addColumn<int>("qt4Delta");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<Qt::KeyboardModifiers>("modifier");
QTest::addColumn<Qt::MouseEventSource>("source");
QTest::addColumn<int>("start");
@ -1290,6 +1317,12 @@ void tst_QSpinBox::wheelEvents_data()
const auto directions = {true, false};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
@ -1313,7 +1346,13 @@ void tst_QSpinBox::wheelEvents_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
for (auto source : sources) {
@ -1358,13 +1397,15 @@ void tst_QSpinBox::wheelEvents_data()
expectedValues << startValue;
expectedValues << startValue + steps;
QTest::addRow("%s%sWith%sKeyboardModifier%s",
QTest::addRow("%s%s%sWith%sKeyboardModifier%s",
fraction ? "half" : "full",
up ? "Up" : "Down",
stepModifierName.latin1(),
modifierName.latin1(),
sourceName.latin1())
<< angleDelta
<< units
<< static_cast<int>(stepModifier)
<< modifiers
<< source
<< startValue
@ -1373,6 +1414,7 @@ void tst_QSpinBox::wheelEvents_data()
}
}
}
}
#else
QSKIP("Built with --no-feature-wheelevent");
#endif
@ -1383,6 +1425,7 @@ void tst_QSpinBox::wheelEvents()
#if QT_CONFIG(wheelevent)
QFETCH(QPoint, angleDelta);
QFETCH(int, qt4Delta);
QFETCH(int, stepModifier);
QFETCH(Qt::KeyboardModifiers, modifier);
QFETCH(Qt::MouseEventSource, source);
QFETCH(int, start);
@ -1392,6 +1435,11 @@ void tst_QSpinBox::wheelEvents()
spinBox.setRange(-20, 20);
spinBox.setValue(start);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
new StepModifierStyle);
style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
spinBox.setStyle(style.data());
QWheelEvent event(QPointF(), QPointF(), QPoint(), angleDelta, qt4Delta,
Qt::Vertical, Qt::NoButton, modifier, Qt::NoScrollPhase,
source);
@ -1486,12 +1534,19 @@ void tst_QSpinBox::adaptiveDecimalStep()
void tst_QSpinBox::stepModifierKeys_data()
{
QTest::addColumn<int>("startValue");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<QTestEventList>("keys");
QTest::addColumn<int>("expectedValue");
const auto keyList = {Qt::Key_Up, Qt::Key_Down};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
@ -1511,29 +1566,45 @@ void tst_QSpinBox::stepModifierKeys_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
const int expectedValue = startValue + steps;
QTest::addRow("%sWith%sKeyboardModifier",
QTest::addRow("%s%sWith%sKeyboardModifier",
up ? "up" : "down",
stepModifierName.latin1(),
modifierName.latin1())
<< startValue
<< static_cast<int>(stepModifier)
<< keys
<< expectedValue;
}
}
}
}
void tst_QSpinBox::stepModifierKeys()
{
QFETCH(int, startValue);
QFETCH(int, stepModifier);
QFETCH(QTestEventList, keys);
QFETCH(int, expectedValue);
QSpinBox spin(0);
spin.setValue(startValue);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
new StepModifierStyle);
style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
spin.setStyle(style.data());
spin.show();
QVERIFY(QTest::qWaitForWindowActive(&spin));
@ -1545,6 +1616,7 @@ void tst_QSpinBox::stepModifierKeys()
void tst_QSpinBox::stepModifierButtons_data()
{
QTest::addColumn<QStyle::SubControl>("subControl");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<int>("startValue");
QTest::addColumn<int>("expectedValue");
@ -1552,6 +1624,12 @@ void tst_QSpinBox::stepModifierButtons_data()
const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
@ -1570,25 +1648,35 @@ void tst_QSpinBox::stepModifierButtons_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
const int expectedValue = startValue + steps;
QTest::addRow("%sWith%sKeyboardModifier",
QTest::addRow("%s%sWith%sKeyboardModifier",
up ? "up" : "down",
stepModifierName.latin1(),
modifierName.latin1())
<< subControl
<< static_cast<int>(stepModifier)
<< modifiers
<< startValue
<< expectedValue;
}
}
}
}
void tst_QSpinBox::stepModifierButtons()
{
QFETCH(QStyle::SubControl, subControl);
QFETCH(int, stepModifier);
QFETCH(Qt::KeyboardModifiers, modifiers);
QFETCH(int, startValue);
QFETCH(int, expectedValue);
@ -1596,6 +1684,12 @@ void tst_QSpinBox::stepModifierButtons()
SpinBox spin(0);
spin.setRange(-20, 20);
spin.setValue(startValue);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
new StepModifierStyle);
style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
spin.setStyle(style.data());
spin.show();
QVERIFY(QTest::qWaitForWindowActive(&spin));
@ -1613,12 +1707,19 @@ void tst_QSpinBox::stepModifierButtons()
void tst_QSpinBox::stepModifierPressAndHold_data()
{
QTest::addColumn<QStyle::SubControl>("subControl");
QTest::addColumn<int>("stepModifier");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<int>("expectedStepModifier");
const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown};
const auto modifierList = {Qt::NoModifier,
Qt::ShiftModifier,
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier};
const auto validStepModifierList = {Qt::NoModifier,
Qt::ControlModifier,
Qt::ShiftModifier};
@ -1635,32 +1736,44 @@ void tst_QSpinBox::stepModifierPressAndHold_data()
if (modifierName.isEmpty())
continue;
const int steps = (modifier & Qt::ControlModifier ? 10 : 1)
for (auto stepModifier : validStepModifierList) {
const auto stepModifierName = modifierToName(stepModifier);
if (stepModifierName.isEmpty())
continue;
const int steps = (modifier & stepModifier ? 10 : 1)
* (up ? 1 : -1);
QTest::addRow("%sWith%sKeyboardModifier",
QTest::addRow("%s%sWith%sKeyboardModifier",
up ? "up" : "down",
stepModifierName.latin1(),
modifierName.latin1())
<< subControl
<< static_cast<int>(stepModifier)
<< modifiers
<< steps;
}
}
}
}
void tst_QSpinBox::stepModifierPressAndHold()
{
QFETCH(QStyle::SubControl, subControl);
QFETCH(int, stepModifier);
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);
QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> stepModifierStyle(
new StepModifierStyle(new PressAndHoldStyle));
stepModifierStyle->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
spin.setStyle(stepModifierStyle.data());
QSignalSpy spy(&spin, QOverload<int>::of(&SpinBox::valueChanged));
spin.show();