diff --git a/src/framework/uicomponents/qml/Muse/UiComponents/tests/doubleinputvalidator_tests.cpp b/src/framework/uicomponents/qml/Muse/UiComponents/tests/doubleinputvalidator_tests.cpp index d48405bbb76ac..1cd9b2bcfa20c 100644 --- a/src/framework/uicomponents/qml/Muse/UiComponents/tests/doubleinputvalidator_tests.cpp +++ b/src/framework/uicomponents/qml/Muse/UiComponents/tests/doubleinputvalidator_tests.cpp @@ -23,6 +23,13 @@ #include "../validators/doubleinputvalidator.h" +// Teach GoogleTest how to print QString so failure diffs are readable +// instead of a UTF-16 byte dump. +inline void PrintTo(const QString& s, std::ostream* os) +{ + *os << '"' << s.toStdString() << '"'; +} + using namespace muse; using namespace muse::uicomponents; @@ -56,8 +63,8 @@ TEST_F(DoubleInputValidatorTests, ValidateDotLocale) { QLocale prev = QLocale(); QLocale::setDefault(QLocale("en_US")); - m_validator->setTop(100.0); - m_validator->setBottom(-100.0); + m_validator->setTop(1000.0); + m_validator->setBottom(-1000.0); m_validator->setDecimal(2); std::vector inputs = { @@ -71,9 +78,11 @@ TEST_F(DoubleInputValidatorTests, ValidateDotLocale) { { "00.", QValidator::Intermediate, "0" }, { "2.00", QValidator::Intermediate, "2" }, { "2.", QValidator::Intermediate, "2" }, - { "-100.1", QValidator::Intermediate, "-100" }, - { "100.1", QValidator::Intermediate, "100" }, + { "-1000.1", QValidator::Intermediate, "-1,000" }, + { "1000.1", QValidator::Intermediate, "1,000" }, { "1.123", QValidator::Invalid }, // more than 2 decimal places + { "1000", QValidator::Acceptable, "1,000" }, + { "10000", QValidator::Invalid }, // more than top { "abc", QValidator::Invalid }, { "", QValidator::Intermediate, "0" } }; @@ -109,8 +118,8 @@ TEST_F(DoubleInputValidatorTests, ValidateCommaLocale) { QLocale prev = QLocale(); QLocale::setDefault(QLocale("ro_RO")); - m_validator->setTop(100.0); - m_validator->setBottom(-100.0); + m_validator->setTop(1000.0); + m_validator->setBottom(-1000.0); m_validator->setDecimal(2); std::vector inputs = { @@ -124,8 +133,8 @@ TEST_F(DoubleInputValidatorTests, ValidateCommaLocale) { { "00,", QValidator::Intermediate, "0" }, { "2,00", QValidator::Intermediate, "2" }, { "2,", QValidator::Intermediate, "2" }, - { "-100,1", QValidator::Intermediate, "-100" }, - { "100,1", QValidator::Intermediate, "100" }, + { "-1000,1", QValidator::Intermediate, "-1.000" }, + { "1000,1", QValidator::Intermediate, "1.000" }, { "1,123", QValidator::Invalid }, // more than 2 decimal places { "abc", QValidator::Invalid }, { "", QValidator::Intermediate, "0" } @@ -149,4 +158,52 @@ TEST_F(DoubleInputValidatorTests, ValidateCommaLocale) { // Restore previous locale QLocale::setDefault(prev); } + +TEST_F(DoubleInputValidatorTests, ValidateSmallRange) { + struct Input + { + QString str; + QValidator::State expectedState; + QString fixedStr = {}; + }; + + QLocale prev = QLocale(); + QLocale::setDefault(QLocale("en_US")); + + m_validator->setTop(1.0); + m_validator->setBottom(0.0); + m_validator->setDecimal(2); + + std::vector inputs = { + { "0", QValidator::Acceptable }, + { "1", QValidator::Acceptable }, + { "0.5", QValidator::Acceptable }, + { "0.99", QValidator::Acceptable }, + { "-0.1", QValidator::Intermediate, "0" }, // below bottom, clamp to 0 + { "2", QValidator::Intermediate, "1" }, // above top, clamp to 1 + { "1.", QValidator::Intermediate, "1" }, + { "0.0", QValidator::Intermediate, "0" }, + { "10", QValidator::Invalid }, // too many integer digits + { "1.123", QValidator::Invalid }, // more than 2 decimal places + { "abc", QValidator::Invalid }, + { "", QValidator::Intermediate, "0" } + }; + + int pos = 0; + for (Input& input : inputs) { + EXPECT_EQ(m_validator->validate(input.str, pos), input.expectedState); + + if (QValidator::Invalid == input.expectedState) { + continue; + } + + QString fixInput = input.str; + m_validator->fixup(fixInput); + + QString expectedStr = QValidator::Acceptable == input.expectedState ? input.str : input.fixedStr; + EXPECT_EQ(expectedStr, fixInput); + } + + QLocale::setDefault(prev); +} } diff --git a/src/framework/uicomponents/qml/Muse/UiComponents/validators/doubleinputvalidator.cpp b/src/framework/uicomponents/qml/Muse/UiComponents/validators/doubleinputvalidator.cpp index 799877f538c23..5eecee0605058 100644 --- a/src/framework/uicomponents/qml/Muse/UiComponents/validators/doubleinputvalidator.cpp +++ b/src/framework/uicomponents/qml/Muse/UiComponents/validators/doubleinputvalidator.cpp @@ -22,8 +22,27 @@ #include "doubleinputvalidator.h" #include "global/realfn.h" +#include + using namespace muse::uicomponents; +namespace { +int maxIntegerDigits(qreal top, qreal bottom) +{ + const qreal maxAbs = std::max(std::abs(top), std::abs(bottom)); + if (maxAbs < 1.0) { + return 1; + } + int digits = 1; + qreal v = std::floor(maxAbs); + while (v >= 10.0) { + v /= 10.0; + ++digits; + } + return digits; +} +} + DoubleInputValidator::DoubleInputValidator(QObject* parent) : QValidator(parent) { @@ -72,9 +91,10 @@ void DoubleInputValidator::fixup(QString& string) const QString intPart = strList.at(0); QString floatPart = strList.size() > 1 ? strList.at(1) : 0; - if (intPart.contains(QRegularExpression("^0{1,3}$"))) { + const int maxIntDigits = maxIntegerDigits(m_top, m_bottom); + if (intPart.contains(QRegularExpression(QString("^0{1,%1}$").arg(maxIntDigits)))) { intPart = QString("0"); - } else if (intPart.contains(QRegularExpression("^\\-0{0,3}$"))) { + } else if (intPart.contains(QRegularExpression(QString("^\\-0{0,%1}$").arg(maxIntDigits)))) { intPart = QString("-0"); } @@ -111,10 +131,12 @@ QValidator::State DoubleInputValidator::validate(QString& inputStr, int& cursorP QValidator::State state = Invalid; QString decimalSep = QRegularExpression::escape(locale.decimalPoint()); - QRegularExpression validRegex(QString("^\\-?\\d{1,3}(" + decimalSep + "\\d{1,%1})?$").arg(m_decimal)); + const int maxIntDigits = maxIntegerDigits(m_top, m_bottom); + QRegularExpression validRegex(QString("^\\-?\\d{1,%1}(" + decimalSep + "\\d{1,%2})?$") + .arg(maxIntDigits).arg(m_decimal)); if (inputStr.contains(validRegex)) { - QRegularExpression invalidZeroRegex("^\\-?0{2,3}" + decimalSep); // for '-000,' or '-000.' + QRegularExpression invalidZeroRegex(QString("^\\-?0{2,%1}").arg(std::max(maxIntDigits, 2)) + decimalSep); // e.g. '-000,' or '-000.' QRegularExpression invalidTrailingZeroRegex("^\\-?\\d+" + decimalSep + "0{1,}$"); // for '1,00' or '1.00' QRegularExpression invalidTrailingDotRegex("^\\-?\\d+" + decimalSep + "$"); // for '1,' or '1.' @@ -128,8 +150,9 @@ QValidator::State DoubleInputValidator::validate(QString& inputStr, int& cursorP } else { state = Acceptable; } - } else if (inputStr.contains(QRegularExpression("^\\-?\\d{0,3}" + decimalSep + "?$")) - || inputStr.contains(QRegularExpression(QString("^\\-?\\d{0,3}" + decimalSep + "\\d{0,%1}$").arg(m_decimal)))) { + } else if (inputStr.contains(QRegularExpression(QString("^\\-?\\d{0,%1}" + decimalSep + "?$").arg(maxIntDigits))) + || inputStr.contains(QRegularExpression(QString("^\\-?\\d{0,%1}" + decimalSep + + "\\d{0,%2}$").arg(maxIntDigits).arg(m_decimal)))) { state = Intermediate; } else { cursorPos = 0;