Introduce QDoubleValidator::fixup()

The provided implementation tries to fix positions for the group
separator.
In case of scientific notation it can also converts the value to
normalized form.
It uses QLocale::FloatingPointShortest internally to convert the
double value back to string, so the number of decimals may change
after calling this method.

Change-Id: I963bc5f97b653e2bb912f4b95b09a4d1ee201e7f
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
bb10
Ivan Solovev 2021-09-14 11:45:24 +02:00
parent d47278fd09
commit fb3549fc47
4 changed files with 328 additions and 2 deletions

View File

@ -57,6 +57,7 @@ struct Wrapper : public QWidget {
void wrapper0();
void wrapper1();
void wrapper2();
void wrapper3();
};
void Wrapper::wrapper0() {
@ -164,4 +165,22 @@ s = "readm"; v.validate(s, pos); // Returns Intermediate
} // Wrapper::wrapper2
void Wrapper::wrapper3()
{
//! [7]
QString input = "0.98765e2";
QDoubleValidator val;
val.setLocale(QLocale::C);
val.setNotation(QDoubleValidator::ScientificNotation);
val.fixup(input); // input == "9.8765e+01"
//! [7]
//! [8]
input = "-1234.6789";
val.setDecimals(2);
val.setLocale(QLocale::C);
val.setNotation(QDoubleValidator::StandardNotation);
val.fixup(input); // input == "-1234.68"
//! [8]
} // Wrapper::wrapper3
} // src_gui_util_qvalidator

View File

@ -543,6 +543,8 @@ public:
QDoubleValidator::Notation notation;
QValidator::State validateWithLocale(QString & input, QLocaleData::NumberMode numMode, const QLocale &locale) const;
void fixupWithLocale(QString &input, QLocaleData::NumberMode numMode,
const QLocale &locale) const;
};
@ -554,8 +556,7 @@ public:
\inmodule QtGui
QDoubleValidator provides an upper bound, a lower bound, and a
limit on the number of digits after the decimal point. It does not
provide a fixup() function.
limit on the number of digits after the decimal point.
You can set the acceptable range in one call with setRange(), or
with setBottom() and setTop(). Set the number of decimal places
@ -711,6 +712,91 @@ QValidator::State QDoubleValidatorPrivate::validateWithLocale(QString &input, QL
return QValidator::Intermediate;
}
/*!
\since 6.3
\overload
Attempts to fix the \a input string to an \l Acceptable representation of a
double.
The format of the number is determined by \l notation(), \l decimals(),
\l locale() and the latter's \l {QLocale::}{numberOptions()}.
To comply with \l notation(), when \l ScientificNotation is used, the fixed
value will be represented in its normalized form, which means that any
non-zero value will have one non-zero digit before the decimal point.
\snippet code/src_gui_util_qvalidator.cpp 7
To comply with \l decimals(), when it is \c {-1} the number of digits used
will be determined by \l QLocale::FloatingPointShortest. Otherwise, the
fractional part of the number is truncated (with rounding, as appropriate)
if its length exceeds \l decimals(). When \l notation() is
\l ScientificNotation this is done after the number has been put into its
normalized form.
\snippet code/src_gui_util_qvalidator.cpp 8
\note If \l decimals() is set to, and the string provides, more than
\c {std::numeric_limits<double>::digits10}, digits beyond that many in the
fractional part may be changed. The resulting string shall encode the same
floating-point number, when parsed to a \c double.
*/
void QDoubleValidator::fixup(QString &input) const
{
Q_D(const QDoubleValidator);
const auto numberMode = d->notation == StandardNotation ? QLocaleData::DoubleStandardMode
: QLocaleData::DoubleScientificMode;
d->fixupWithLocale(input, numberMode, locale());
}
void QDoubleValidatorPrivate::fixupWithLocale(QString &input, QLocaleData::NumberMode numMode,
const QLocale &locale) const
{
Q_Q(const QDoubleValidator);
QByteArray buff;
// Passing -1 as the number of decimals, because fixup() exists to improve
// an Intermediate value, if it can.
if (!locale.d->m_data->validateChars(input, numMode, &buff, -1, locale.numberOptions()))
return;
// buff now contains data in C locale.
bool ok = false;
const double entered = buff.toDouble(&ok);
if (ok) {
// Here we need to adjust the output format accordingly
char mode;
if (numMode == QLocaleData::DoubleStandardMode) {
mode = 'f';
} else {
// scientific mode can be either 'e' or 'E'
mode = input.contains(QChar::fromLatin1('E')) ? 'E' : 'e';
}
int precision;
if (q->dec < 0) {
precision = QLocale::FloatingPointShortest;
} else {
if (mode == 'f') {
const auto decimalPointIndex = buff.indexOf('.');
precision = decimalPointIndex >= 0 ? buff.size() - decimalPointIndex - 1 : 0;
} else {
auto eIndex = buff.indexOf('e');
// No need to check for 'E' because we can get only 'e' after a
// call to validateChars()
if (eIndex < 0)
eIndex = buff.size();
precision = eIndex - (buff.contains('.') ? 1 : 0)
- (buff.startsWith('-') || buff.startsWith('+') ? 1 : 0);
}
// Use q->dec to limit the number of decimals, because we want the
// fixup() result to pass validate().
precision = qMin(precision, q->dec);
}
input = locale.toString(entered, mode, precision);
}
}
/*!
Sets the validator to accept doubles from \a minimum to \a maximum
inclusive, with at most \a decimals digits after the decimal

View File

@ -140,6 +140,7 @@ public:
};
Q_ENUM(Notation)
QValidator::State validate(QString &, int &) const override;
void fixup(QString &input) const override;
void setRange(double bottom, double top, int decimals = 0);
void setBottom(double);

View File

@ -45,6 +45,8 @@ private slots:
void validateIntEquiv_data();
void validateIntEquiv();
void notifySignals();
void fixup();
void fixup_data();
};
Q_DECLARE_METATYPE(QValidator::State);
@ -393,6 +395,224 @@ void tst_QDoubleValidator::notifySignals()
QCOMPARE(changedSpy.count(), 9);
}
void tst_QDoubleValidator::fixup()
{
QFETCH(QString, localeName);
QFETCH(QDoubleValidator::Notation, notation);
QFETCH(int, decimals);
QFETCH(QString, input);
QFETCH(QString, output);
QDoubleValidator val;
val.setLocale(QLocale(localeName));
val.setNotation(notation);
val.setDecimals(decimals);
val.fixup(input);
QCOMPARE(input, output);
}
void tst_QDoubleValidator::fixup_data()
{
QTest::addColumn<QString>("localeName");
QTest::addColumn<QDoubleValidator::Notation>("notation");
QTest::addColumn<int>("decimals");
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
// C locale uses '.' as decimal point and ',' as grouping separator.
// C locale does not group digits by default.
QTest::newRow("C standard no digit grouping")
<< "C" << QDoubleValidator::StandardNotation << -1 << "12.345"
<< "12.345";
QTest::newRow("C standard with digit grouping")
<< "C" << QDoubleValidator::StandardNotation << -1 << "-12,345.678"
<< "-12345.678";
QTest::newRow("C standard with invalid digit grouping")
<< "C" << QDoubleValidator::StandardNotation << -1 << "1,234,5.678"
<< "12345.678";
QTest::newRow("C standard with invalid number of decimals")
<< "C" << QDoubleValidator::StandardNotation << 2 << "-12,34.678"
<< "-1234.68";
QTest::newRow("C standard truncate decimals")
<< "C" << QDoubleValidator::StandardNotation << -1
<< "1.23456789012345678901234567890"
<< "1.2345678901234567";
QTest::newRow("C standard skip trailing zeroes")
<< "C" << QDoubleValidator::StandardNotation << -1 << "1,234.5670000"
<< "1234.567";
QTest::newRow("C standard zero value")
<< "C" << QDoubleValidator::StandardNotation << -1 << "0.0"
<< "0";
QTest::newRow("C standard scientific value")
<< "C" << QDoubleValidator::StandardNotation << -1 << "1.23e-2"
<< "1.23e-2";
QTest::newRow("C standard no fractional part")
<< "C" << QDoubleValidator::StandardNotation << -1 << "-1,234"
<< "-1234";
QTest::newRow("C scientific no digit grouping")
<< "C" << QDoubleValidator::ScientificNotation << -1 << "0.98765e2"
<< "9.8765e+01";
QTest::newRow("C scientific with digit grouping")
<< "C" << QDoubleValidator::ScientificNotation << -1 << "-1,234.98765E-4"
<< "-1.23498765E-01";
QTest::newRow("C scientific with invalid digit grouping")
<< "C" << QDoubleValidator::ScientificNotation << -1 << "12,34.98765e2"
<< "1.23498765e+05";
QTest::newRow("C scientific with invalid number of decimals")
<< "C" << QDoubleValidator::ScientificNotation << 2 << "-12,34.98765e2"
<< "-1.23e+05";
QTest::newRow("C scientific truncate decimals")
<< "C" << QDoubleValidator::ScientificNotation << -1
<< "1.23456789012345678901234567890E5"
<< "1.2345678901234567E+05";
QTest::newRow("C scientific skip trailing zeroes")
<< "C" << QDoubleValidator::ScientificNotation << -1 << "1,234.5670000e3"
<< "1.234567e+06";
QTest::newRow("C scientific zero value")
<< "C" << QDoubleValidator::ScientificNotation << -1 << "0.0"
<< "0e+00";
QTest::newRow("C scientific standard value")
<< "C" << QDoubleValidator::ScientificNotation << -1 << "12.345"
<< "1.2345e+01";
QTest::newRow("C scientific no fractional part")
<< "C" << QDoubleValidator::ScientificNotation << -1 << "1,234e2"
<< "1.234e+05";
QTest::newRow("C scientific negative no fractional part")
<< "C" << QDoubleValidator::ScientificNotation << -1 << "-1,234e2"
<< "-1.234e+05";
QTest::newRow("C scientific no fractional and exponent")
<< "C" << QDoubleValidator::ScientificNotation << -1 << "1,234"
<< "1.234e+03";
QTest::newRow("C scientific negative no fractional and exponent")
<< "C" << QDoubleValidator::ScientificNotation << -1 << "-1,234"
<< "-1.234e+03";
// en locale uses '.' as decimal point and ',' as grouping separator.
// en locale groups digits by default. 'E' is used in scientific notation.
QTest::newRow("en standard no digit grouping")
<< "en" << QDoubleValidator::StandardNotation << -1 << "-12.345"
<< "-12.345";
QTest::newRow("en standard with digit grouping")
<< "en" << QDoubleValidator::StandardNotation << -1 << "12,345.678"
<< "12,345.678";
QTest::newRow("en standard with invalid digit grouping")
<< "en" << QDoubleValidator::StandardNotation << -1 << "-1,234,5.678"
<< "-12,345.678";
QTest::newRow("en standard with invalid number of decimals")
<< "en" << QDoubleValidator::StandardNotation << 2 << "12,34.678"
<< "1,234.68";
QTest::newRow("en standard no fractional part")
<< "en" << QDoubleValidator::StandardNotation << -1 << "-12,34"
<< "-1,234";
QTest::newRow("en scientific no digit grouping")
<< "en" << QDoubleValidator::ScientificNotation << -1 << "-0.98765e2"
<< "-9.8765E+01";
QTest::newRow("en scientific with digit grouping")
<< "en" << QDoubleValidator::ScientificNotation << -1 << "1,234.98765E-4"
<< "1.23498765E-01";
QTest::newRow("en scientific with invalid digit grouping")
<< "en" << QDoubleValidator::ScientificNotation << -1 << "-12,34.98765e2"
<< "-1.23498765E+05";
QTest::newRow("en scientific with invalid number of decimals")
<< "en" << QDoubleValidator::ScientificNotation << 2 << "12,34.98765e2"
<< "1.23E+05";
QTest::newRow("en scientific no fractional part")
<< "en" << QDoubleValidator::ScientificNotation << -1 << "12,34e2"
<< "1.234E+05";
QTest::newRow("en scientific negative no fractional part")
<< "en" << QDoubleValidator::ScientificNotation << -1 << "-12,34e2"
<< "-1.234E+05";
QTest::newRow("en scientific no fractional and exponent")
<< "en" << QDoubleValidator::ScientificNotation << -1 << "1,234"
<< "1.234E+03";
QTest::newRow("en scientific negative no fractional and exponent")
<< "en" << QDoubleValidator::ScientificNotation << -1 << "-1,234"
<< "-1.234E+03";
// de locale uses ',' as decimal point and '.' as grouping separator.
// de locale groups digits by default. 'E' is used in scientific notation.
QTest::newRow("de standard no digit grouping")
<< "de" << QDoubleValidator::StandardNotation << -1 << "12,345"
<< "12,345";
QTest::newRow("de standard with digit grouping")
<< "de" << QDoubleValidator::StandardNotation << -1 << "-12.345,678"
<< "-12.345,678";
QTest::newRow("de standard with invalid digit grouping")
<< "de" << QDoubleValidator::StandardNotation << -1 << "1.234.5,678"
<< "12.345,678";
QTest::newRow("de standard with invalid number of decimals")
<< "de" << QDoubleValidator::StandardNotation << 2 << "-12.34,678"
<< "-1.234,68";
QTest::newRow("de standard no fractional part")
<< "de" << QDoubleValidator::StandardNotation << -1 << "12.34" << "1.234";
QTest::newRow("de scientific no digit grouping")
<< "de" << QDoubleValidator::ScientificNotation << -1 << "0,98765e2"
<< "9,8765E+01";
QTest::newRow("de scientific with digit grouping")
<< "de" << QDoubleValidator::ScientificNotation << -1 << "-1.234,98765E-4"
<< "-1,23498765E-01";
QTest::newRow("de scientific with invalid digit grouping")
<< "de" << QDoubleValidator::ScientificNotation << -1 << "12.34,98765e2"
<< "1,23498765E+05";
QTest::newRow("de scientific with invalid number of decimals")
<< "de" << QDoubleValidator::ScientificNotation << 2 << "-12.34,98765e2"
<< "-1,23E+05";
QTest::newRow("de scientific no fractional part")
<< "de" << QDoubleValidator::ScientificNotation << -1 << "1.234e2"
<< "1,234E+05";
QTest::newRow("de scientific negative no fractional part")
<< "de" << QDoubleValidator::ScientificNotation << -1 << "-1.234e2"
<< "-1,234E+05";
QTest::newRow("de scientific no fractional and exponent")
<< "de" << QDoubleValidator::ScientificNotation << -1 << "12.34"
<< "1,234E+03";
QTest::newRow("de scientific negative no fractional and exponent")
<< "de" << QDoubleValidator::ScientificNotation << -1 << "-12.34"
<< "-1,234E+03";
// hi locale uses '.' as decimal point and ',' as grouping separator.
// The rightmost group is of three digits, all the others contain two
// digits.
QTest::newRow("hi standard no digit grouping")
<< "hi" << QDoubleValidator::StandardNotation << -1 << "123456.78"
<< "1,23,456.78";
QTest::newRow("hi standard with digit grouping")
<< "hi" << QDoubleValidator::StandardNotation << -1 << "-12,345.678"
<< "-12,345.678";
QTest::newRow("hi standard with invalid digit grouping")
<< "hi" << QDoubleValidator::StandardNotation << -1 << "12,34,56.78"
<< "1,23,456.78";
QTest::newRow("hi standard no fractional part")
<< "hi" << QDoubleValidator::StandardNotation << -1 << "-12,345,6"
<< "-1,23,456";
QTest::newRow("hi scientific no digit grouping")
<< "hi" << QDoubleValidator::ScientificNotation << -1 << "-0.123e-2"
<< "-1.23E-03";
QTest::newRow("hi scientific with digit grouping")
<< "hi" << QDoubleValidator::ScientificNotation << -1 << "12,345.678e-2"
<< "1.2345678E+02";
QTest::newRow("hi scientific with invalid digit grouping")
<< "hi" << QDoubleValidator::ScientificNotation << -1 << "-1,23,45.678e-2"
<< "-1.2345678E+02";
QTest::newRow("hi scientific no fractional part")
<< "hi" << QDoubleValidator::ScientificNotation << -1 << "1,23,456e2"
<< "1.23456E+07";
QTest::newRow("hi scientific negative no fractional part")
<< "hi" << QDoubleValidator::ScientificNotation << -1 << "-1,23,456e2"
<< "-1.23456E+07";
QTest::newRow("hi scientific no fractional and exponent")
<< "hi" << QDoubleValidator::ScientificNotation << -1 << "1,234,56"
<< "1.23456E+05";
QTest::newRow("hi scientific negative no fractional and exponent")
<< "hi" << QDoubleValidator::ScientificNotation << -1 << "-1,234,56"
<< "-1.23456E+05";
}
void tst_QDoubleValidator::validateIntEquiv_data()
{
QTest::addColumn<double>("minimum");