Allow adaptive decimal stepping for QSpinBox and QDoubleSpinBox
Adds the feature of adaptive decimal step sizes for the QSpinBox and QDoubleSpinBox. By performing a calculation in QAbstractSpinBox::stepBy() we continuously set the step size one power of ten below the current value. So when the value is 1100, the step is set to 100, so stepping up once increases it to 1200. For 1200 stepping up takes it to 1300. For negative values stepping down from -1100 goes to -1200. It also works for all decimal values. 0.041 is increased to 0.042 by stepping once, and so on. The step direction is taken into account to handle edges cases, so that stepping down from 100 takes the value to 99 instead of 90. Thus, a step up followed by a step down -- or vice versa -- lands on the starting value; 99 -> 100 -> 99. Setting this property effectively disregards singleStep, but preserves its value so that it takes effect again when adaptive decimal step is disabled. Adaptive decimal step allows values to be easily set to reasonable levels. If the spin box value is 12000, changing to 13000 often makes more sense than to 12001. The feature is turned off be default, when single stepping is desired. The accelerated property allows values to be changed quickly, as well, but it is imprecise. Holding down the button makes it hard to land on an even thousand, like 12000 or 13000. Often you end up somewhere nearby and would need a second adjustment to get to an even hundred or thousand. [ChangeLog][QtWidgets] Add option of adaptive decimal step size for QSpinBox and QDoubleSpinBox. Change-Id: I9f286479b821e240c8ea05c238932fc128c582bb Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>bb10
parent
a692d7cd28
commit
97e40a5409
|
|
@ -639,7 +639,15 @@ void QAbstractSpinBox::stepBy(int steps)
|
|||
e = AlwaysEmit;
|
||||
}
|
||||
if (!dontstep) {
|
||||
d->setValue(d->bound(d->value + (d->singleStep * steps), old, steps), e);
|
||||
QVariant singleStep;
|
||||
switch (d->stepType) {
|
||||
case QAbstractSpinBox::StepType::AdaptiveDecimalStepType:
|
||||
singleStep = d->calculateAdaptiveDecimalStep(steps);
|
||||
break;
|
||||
default:
|
||||
singleStep = d->singleStep;
|
||||
}
|
||||
d->setValue(d->bound(d->value + (singleStep * steps), old, steps), e);
|
||||
} else if (e == AlwaysEmit) {
|
||||
d->emitSignals(e, old);
|
||||
}
|
||||
|
|
@ -1898,6 +1906,11 @@ void QAbstractSpinBoxPrivate::clearCache() const
|
|||
cachedState = QValidator::Acceptable;
|
||||
}
|
||||
|
||||
QVariant QAbstractSpinBoxPrivate::calculateAdaptiveDecimalStep(int steps) const
|
||||
{
|
||||
Q_UNUSED(steps)
|
||||
return singleStep;
|
||||
}
|
||||
|
||||
// --- QSpinBoxValidator ---
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,11 @@ public:
|
|||
virtual void fixup(QString &input) const;
|
||||
|
||||
virtual void stepBy(int steps);
|
||||
|
||||
enum StepType {
|
||||
DefaultStepType,
|
||||
AdaptiveDecimalStepType
|
||||
};
|
||||
public Q_SLOTS:
|
||||
void stepUp();
|
||||
void stepDown();
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ public:
|
|||
static int variantCompare(const QVariant &arg1, const QVariant &arg2);
|
||||
static QVariant variantBound(const QVariant &min, const QVariant &value, const QVariant &max);
|
||||
|
||||
virtual QVariant calculateAdaptiveDecimalStep(int steps) const;
|
||||
|
||||
QLineEdit *edit;
|
||||
QString prefix, suffix, specialValueText;
|
||||
QVariant value, minimum, maximum, singleStep;
|
||||
|
|
@ -143,6 +145,7 @@ public:
|
|||
uint cleared : 1;
|
||||
uint ignoreUpdateEdit : 1;
|
||||
QAbstractSpinBox::CorrectionMode correctionMode;
|
||||
QAbstractSpinBox::StepType stepType = QAbstractSpinBox::StepType::DefaultStepType;
|
||||
int acceleration;
|
||||
QStyle::SubControl hoverControl;
|
||||
QRect hoverRect;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@
|
|||
#include <qvalidator.h>
|
||||
#include <qdebug.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <float.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
|
@ -75,6 +77,8 @@ public:
|
|||
}
|
||||
|
||||
int displayIntegerBase;
|
||||
|
||||
QVariant calculateAdaptiveDecimalStep(int steps) const override;
|
||||
};
|
||||
|
||||
class QDoubleSpinBoxPrivate : public QAbstractSpinBoxPrivate
|
||||
|
|
@ -100,6 +104,8 @@ public:
|
|||
// When fiddling with the decimals property, we may lose precision in these properties.
|
||||
double actualMin;
|
||||
double actualMax;
|
||||
|
||||
QVariant calculateAdaptiveDecimalStep(int steps) const override;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -414,6 +420,41 @@ void QSpinBox::setRange(int minimum, int maximum)
|
|||
d->setRange(QVariant(minimum), QVariant(maximum));
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the step type for the spin box: single step or adaptive
|
||||
decimal step.
|
||||
|
||||
Adaptive decimal step means that the step size will continuously be
|
||||
adjusted to one power of ten below the current \l value. So when
|
||||
the value is 1100, the step is set to 100, so stepping up once
|
||||
increases it to 1200. For 1200 stepping up takes it to 1300. For
|
||||
negative values, stepping down from -1100 goes to -1200.
|
||||
|
||||
Step direction is taken into account to handle edges cases, so
|
||||
that stepping down from 100 takes the value to 99 instead of 90.
|
||||
Thus a step up followed by a step down -- or vice versa -- always
|
||||
lands on the starting value; 99 -> 100 -> 99.
|
||||
|
||||
Setting this will cause the spin box to disregard the value of
|
||||
\l singleStep, although it is preserved so that \l singleStep
|
||||
comes into effect if adaptive decimal step is later turned off.
|
||||
|
||||
\sa QAbstractSpinBox::groupSeparator()
|
||||
\since 5.12
|
||||
*/
|
||||
|
||||
void QSpinBox::setStepType(QAbstractSpinBox::StepType stepType)
|
||||
{
|
||||
Q_D(QSpinBox);
|
||||
d->stepType = stepType;
|
||||
}
|
||||
|
||||
QAbstractSpinBox::StepType QSpinBox::stepType() const
|
||||
{
|
||||
Q_D(const QSpinBox);
|
||||
return d->stepType;
|
||||
}
|
||||
|
||||
/*!
|
||||
\property QSpinBox::displayIntegerBase
|
||||
|
||||
|
|
@ -846,6 +887,44 @@ void QDoubleSpinBox::setRange(double minimum, double maximum)
|
|||
d->setRange(QVariant(d->round(minimum)), QVariant(d->round(maximum)));
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the step type for the spin box: single step or adaptive
|
||||
decimal step.
|
||||
|
||||
Adaptive decimal step means that the step size will continuously be
|
||||
adjusted to one power of ten below the current \l value. So when
|
||||
the value is 1100, the step is set to 100, so stepping up once
|
||||
increases it to 1200. For 1200 stepping up takes it to 1300. For
|
||||
negative values, stepping down from -1100 goes to -1200.
|
||||
|
||||
It also works for any decimal values, 0.041 is increased to 0.042
|
||||
by stepping once.
|
||||
|
||||
Step direction is taken into account to handle edges cases, so
|
||||
that stepping down from 100 takes the value to 99 instead of 90.
|
||||
Thus a step up followed by a step down -- or vice versa -- always
|
||||
lands on the starting value; 99 -> 100 -> 99.
|
||||
|
||||
Setting this will cause the spin box to disregard the value of
|
||||
\l singleStep, although it is preserved so that \l singleStep
|
||||
comes into effect if adaptive decimal step is later turned off.
|
||||
|
||||
\sa QAbstractSpinBox::groupSeparator()
|
||||
\since 5.12
|
||||
*/
|
||||
|
||||
void QDoubleSpinBox::setStepType(StepType stepType)
|
||||
{
|
||||
Q_D(QDoubleSpinBox);
|
||||
d->stepType = stepType;
|
||||
}
|
||||
|
||||
QAbstractSpinBox::StepType QDoubleSpinBox::stepType() const
|
||||
{
|
||||
Q_D(const QDoubleSpinBox);
|
||||
return d->stepType;
|
||||
}
|
||||
|
||||
/*!
|
||||
\property QDoubleSpinBox::decimals
|
||||
|
||||
|
|
@ -1078,6 +1157,22 @@ QVariant QSpinBoxPrivate::validateAndInterpret(QString &input, int &pos,
|
|||
return cachedValue;
|
||||
}
|
||||
|
||||
QVariant QSpinBoxPrivate::calculateAdaptiveDecimalStep(int steps) const
|
||||
{
|
||||
const int intValue = value.toInt();
|
||||
const int absValue = qAbs(intValue);
|
||||
|
||||
if (absValue < 100)
|
||||
return 1;
|
||||
|
||||
const bool valueNegative = intValue < 0;
|
||||
const bool stepsNegative = steps < 0;
|
||||
const int signCompensation = (valueNegative == stepsNegative) ? 0 : 1;
|
||||
|
||||
const int log = static_cast<int>(std::log10(absValue - signCompensation)) - 1;
|
||||
return static_cast<int>(std::pow(10, log));
|
||||
}
|
||||
|
||||
// --- QDoubleSpinBoxPrivate ---
|
||||
|
||||
/*!
|
||||
|
|
@ -1303,6 +1398,27 @@ QString QDoubleSpinBoxPrivate::textFromValue(const QVariant &f) const
|
|||
return q->textFromValue(f.toDouble());
|
||||
}
|
||||
|
||||
QVariant QDoubleSpinBoxPrivate::calculateAdaptiveDecimalStep(int steps) const
|
||||
{
|
||||
const double doubleValue = value.toDouble();
|
||||
const double minStep = std::pow(10, -decimals);
|
||||
double absValue = qAbs(doubleValue);
|
||||
|
||||
if (absValue < minStep)
|
||||
return minStep;
|
||||
|
||||
const bool valueNegative = doubleValue < 0;
|
||||
const bool stepsNegative = steps < 0;
|
||||
if (valueNegative != stepsNegative)
|
||||
absValue /= 1.01;
|
||||
|
||||
const double shift = std::pow(10, 1 - std::floor(std::log10(absValue)));
|
||||
const double absRounded = round(absValue * shift) / shift;
|
||||
const double log = floorf(std::log10(absRounded)) - 1;
|
||||
|
||||
return std::max(minStep, std::pow(10, log));
|
||||
}
|
||||
|
||||
/*! \reimp */
|
||||
bool QSpinBox::event(QEvent *event)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ class Q_WIDGETS_EXPORT QSpinBox : public QAbstractSpinBox
|
|||
Q_PROPERTY(int minimum READ minimum WRITE setMinimum)
|
||||
Q_PROPERTY(int maximum READ maximum WRITE setMaximum)
|
||||
Q_PROPERTY(int singleStep READ singleStep WRITE setSingleStep)
|
||||
Q_PROPERTY(StepType stepType READ stepType WRITE setStepType)
|
||||
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged USER true)
|
||||
Q_PROPERTY(int displayIntegerBase READ displayIntegerBase WRITE setDisplayIntegerBase)
|
||||
|
||||
|
|
@ -86,6 +87,9 @@ public:
|
|||
|
||||
void setRange(int min, int max);
|
||||
|
||||
StepType stepType() const;
|
||||
void setStepType(StepType stepType);
|
||||
|
||||
int displayIntegerBase() const;
|
||||
void setDisplayIntegerBase(int base);
|
||||
|
||||
|
|
@ -121,6 +125,7 @@ class Q_WIDGETS_EXPORT QDoubleSpinBox : public QAbstractSpinBox
|
|||
Q_PROPERTY(double minimum READ minimum WRITE setMinimum)
|
||||
Q_PROPERTY(double maximum READ maximum WRITE setMaximum)
|
||||
Q_PROPERTY(double singleStep READ singleStep WRITE setSingleStep)
|
||||
Q_PROPERTY(StepType stepType READ stepType WRITE setStepType)
|
||||
Q_PROPERTY(double value READ value WRITE setValue NOTIFY valueChanged USER true)
|
||||
public:
|
||||
explicit QDoubleSpinBox(QWidget *parent = nullptr);
|
||||
|
|
@ -147,6 +152,9 @@ public:
|
|||
|
||||
void setRange(double min, double max);
|
||||
|
||||
StepType stepType() const;
|
||||
void setStepType(StepType stepType);
|
||||
|
||||
int decimals() const;
|
||||
void setDecimals(int prec);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include <qapplication.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <float.h>
|
||||
|
||||
#include <qspinbox.h>
|
||||
|
|
@ -135,6 +136,8 @@ private slots:
|
|||
void setGroupSeparatorShown_data();
|
||||
void setGroupSeparatorShown();
|
||||
|
||||
void adaptiveDecimalStep();
|
||||
|
||||
public slots:
|
||||
void valueChangedHelper(const QString &);
|
||||
void valueChangedHelper(double);
|
||||
|
|
@ -1139,5 +1142,133 @@ void tst_QDoubleSpinBox::setGroupSeparatorShown()
|
|||
QCOMPARE(spinBox.value()+1000, 33000.44);
|
||||
}
|
||||
|
||||
void tst_QDoubleSpinBox::adaptiveDecimalStep()
|
||||
{
|
||||
DoubleSpinBox spinBox;
|
||||
spinBox.setRange(-100000, 100000);
|
||||
spinBox.setDecimals(4);
|
||||
spinBox.setStepType(DoubleSpinBox::StepType::AdaptiveDecimalStepType);
|
||||
|
||||
// Positive values
|
||||
|
||||
spinBox.setValue(0);
|
||||
|
||||
// Go from 0 to 0.01
|
||||
for (double i = 0; i < 0.00999; i += 0.0001) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Go from 0.01 to 0.1
|
||||
for (double i = 0.01; i < 0.0999; i += 0.001) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Go from 0.1 to 1
|
||||
for (double i = 0.1; i < 0.999; i += 0.01) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Go from 1 to 10
|
||||
for (double i = 1; i < 9.99; i += 0.1) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Go from 10 to 100
|
||||
for (int i = 10; i < 100; i++) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Go from 100 to 1000
|
||||
for (int i = 100; i < 1000; i += 10) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Go from 1000 to 10000
|
||||
for (int i = 1000; i < 10000; i += 100) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Test decreasing the values now
|
||||
|
||||
// Go from 10000 down to 1000
|
||||
for (int i = 10000; i > 1000; i -= 100) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Go from 1000 down to 100
|
||||
for (int i = 1000; i > 100; i -= 10) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Negative values
|
||||
|
||||
spinBox.setValue(0);
|
||||
|
||||
// Go from 0 to -0.01
|
||||
for (double i = 0; i > -0.00999; i -= 0.0001) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Go from -0.01 to -0.1
|
||||
for (double i = -0.01; i > -0.0999; i -= 0.001) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Go from -0.1 to -1
|
||||
for (double i = -0.1; i > -0.999; i -= 0.01) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Go from -1 to -10
|
||||
for (double i = -1; i > -9.99; i -= 0.1) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Go from -10 to -100
|
||||
for (int i = -10; i > -100; i--) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Go from -100 to -1000
|
||||
for (int i = -100; i > -1000; i -= 10) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Go from 1000 to 10000
|
||||
for (int i = -1000; i > -10000; i -= 100) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Test increasing the values now
|
||||
|
||||
// Go from -10000 up to -1000
|
||||
for (int i = -10000; i < -1000; i += 100) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Go from -1000 up to -100
|
||||
for (int i = -1000; i < -100; i += 10) {
|
||||
QVERIFY(qFuzzyCompare(spinBox.value(), i));
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QDoubleSpinBox)
|
||||
#include "tst_qdoublespinbox.moc"
|
||||
|
|
|
|||
|
|
@ -145,6 +145,8 @@ private slots:
|
|||
|
||||
void wheelEvents();
|
||||
|
||||
void adaptiveDecimalStep();
|
||||
|
||||
public slots:
|
||||
void valueChangedHelper(const QString &);
|
||||
void valueChangedHelper(int);
|
||||
|
|
@ -1240,5 +1242,84 @@ void tst_QSpinBox::wheelEvents()
|
|||
#endif
|
||||
}
|
||||
|
||||
void tst_QSpinBox::adaptiveDecimalStep()
|
||||
{
|
||||
SpinBox spinBox;
|
||||
spinBox.setRange(-100000, 100000);
|
||||
spinBox.setStepType(SpinBox::StepType::AdaptiveDecimalStepType);
|
||||
|
||||
// Positive values
|
||||
|
||||
spinBox.setValue(0);
|
||||
|
||||
// Go from 0 to 100
|
||||
for (int i = 0; i < 100; i++) {
|
||||
QCOMPARE(spinBox.value(), i);
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Go from 100 to 1000
|
||||
for (int i = 100; i < 1000; i += 10) {
|
||||
QCOMPARE(spinBox.value(), i);
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Go from 1000 to 10000
|
||||
for (int i = 1000; i < 10000; i += 100) {
|
||||
QCOMPARE(spinBox.value(), i);
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Test decreasing the values now
|
||||
|
||||
// Go from 10000 down to 1000
|
||||
for (int i = 10000; i > 1000; i -= 100) {
|
||||
QCOMPARE(spinBox.value(), i);
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Go from 1000 down to 100
|
||||
for (int i = 1000; i > 100; i -= 10) {
|
||||
QCOMPARE(spinBox.value(), i);
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Negative values
|
||||
|
||||
spinBox.setValue(0);
|
||||
|
||||
// Go from 0 to -100
|
||||
for (int i = 0; i > -100; i--) {
|
||||
QCOMPARE(spinBox.value(), i);
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Go from -100 to -1000
|
||||
for (int i = -100; i > -1000; i -= 10) {
|
||||
QCOMPARE(spinBox.value(), i);
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Go from 1000 to 10000
|
||||
for (int i = -1000; i > -10000; i -= 100) {
|
||||
QCOMPARE(spinBox.value(), i);
|
||||
spinBox.stepBy(-1);
|
||||
}
|
||||
|
||||
// Test increasing the values now
|
||||
|
||||
// Go from -10000 up to -1000
|
||||
for (int i = -10000; i < -1000; i += 100) {
|
||||
QCOMPARE(spinBox.value(), i);
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
|
||||
// Go from -1000 up to -100
|
||||
for (int i = -1000; i < -100; i += 10) {
|
||||
QCOMPARE(spinBox.value(), i);
|
||||
spinBox.stepBy(1);
|
||||
}
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QSpinBox)
|
||||
#include "tst_qspinbox.moc"
|
||||
|
|
|
|||
Loading…
Reference in New Issue