include/vcl/fieldvalues.hxx | 2 include/vcl/toolkit/field.hxx | 14 ++-- vcl/source/control/field.cxx | 139 +++++++++++++++++++++++------------------- 3 files changed, 86 insertions(+), 69 deletions(-)
New commits: commit d22f90fec0fb2375ac336af0f696b6703afaa310 Author: Mike Kaganski <[email protected]> AuthorDate: Tue Oct 14 17:06:52 2025 +0500 Commit: Mike Kaganski <[email protected]> CommitDate: Sun Oct 19 11:18:46 2025 +0200 Related: tdf#168842 Don't truncate source precision in vcl::TextToValue When user enters a value like "2.54cm", and current input box is points, which has only one decimal digit, it does not make sense to apply that precision to the input string, making it mean "2.5cm" (and resulting in 70.9 pt). This change adds a function to convert such strings to double, instead of sal_Int64; then the double is handled using vcl::ConvertDoubleValue, as before. Now entering "2.54cm" produces the expected 72.0 pt. This needed some OUString->u16string_view for loplugin:stringviewparam. Change-Id: I5afa4edada904c4d8b8bcd9df65243546cb6bd00 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192637 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> diff --git a/include/vcl/fieldvalues.hxx b/include/vcl/fieldvalues.hxx index c21b52c14e4f..5abe5facc9ef 100644 --- a/include/vcl/fieldvalues.hxx +++ b/include/vcl/fieldvalues.hxx @@ -35,7 +35,7 @@ namespace vcl { VCL_DLLPUBLIC FieldUnit EnglishStringToMetric(std::u16string_view rEnglishMetricString); -VCL_DLLPUBLIC bool TextToValue(const OUString& rStr, double& rValue, sal_Int64 nBaseValue, +VCL_DLLPUBLIC bool TextToValue(std::u16string_view rStr, double& rValue, sal_Int64 nBaseValue, sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper, FieldUnit eUnit); VCL_DLLPUBLIC FieldUnit GetTextMetricUnit(std::u16string_view aStr); diff --git a/include/vcl/toolkit/field.hxx b/include/vcl/toolkit/field.hxx index ddf38463af8e..ab1ca80d57d2 100644 --- a/include/vcl/toolkit/field.hxx +++ b/include/vcl/toolkit/field.hxx @@ -153,7 +153,7 @@ protected: SAL_DLLPRIVATE void ImplNewFieldValue( sal_Int64 nNewValue ); SAL_DLLPRIVATE void ImplSetUserValue( sal_Int64 nNewValue, Selection const * pNewSelection = nullptr ); - virtual sal_Int64 GetValueFromString(const OUString& rStr) const; + virtual sal_Int64 GetValueFromString(std::u16string_view rStr) const; private: sal_uInt16 mnDecimalDigits; @@ -196,10 +196,10 @@ protected: SAL_DLLPRIVATE MetricFormatter(Edit* pEdit); - SAL_DLLPRIVATE void ImplMetricReformat( const OUString& rStr, double& rValue, OUString& rOutStr ); + SAL_DLLPRIVATE void ImplMetricReformat( std::u16string_view rStr, double& rValue, OUString& rOutStr ); - SAL_DLLPRIVATE virtual sal_Int64 GetValueFromString(const OUString& rStr) const override; - SAL_DLLPRIVATE sal_Int64 GetValueFromStringUnit(const OUString& rStr, FieldUnit eOutUnit) const; + SAL_DLLPRIVATE virtual sal_Int64 GetValueFromString(std::u16string_view rStr) const override; + SAL_DLLPRIVATE sal_Int64 GetValueFromStringUnit(std::u16string_view rStr, FieldUnit eOutUnit) const; private: OUString maCustomUnitText; @@ -262,8 +262,8 @@ class UNLESS_MERGELIBS(VCL_DLLPUBLIC) CurrencyFormatter : public NumericFormatte { protected: CurrencyFormatter(Edit* pEdit); - SAL_DLLPRIVATE void ImplCurrencyReformat( const OUString& rStr, OUString& rOutStr ); - virtual sal_Int64 GetValueFromString(const OUString& rStr) const override; + SAL_DLLPRIVATE void ImplCurrencyReformat( std::u16string_view rStr, OUString& rOutStr ); + virtual sal_Int64 GetValueFromString(std::u16string_view rStr) const override; public: virtual ~CurrencyFormatter() override; @@ -508,7 +508,7 @@ public: class UNLESS_MERGELIBS(VCL_DLLPUBLIC) NumericBox final : public ComboBox, public NumericFormatter { - SAL_DLLPRIVATE void ImplNumericReformat( const OUString& rStr, sal_Int64& rValue, OUString& rOutStr ); + SAL_DLLPRIVATE void ImplNumericReformat( std::u16string_view rStr, sal_Int64& rValue, OUString& rOutStr ); public: explicit NumericBox( vcl::Window* pParent, WinBits nWinStyle ); diff --git a/vcl/source/control/field.cxx b/vcl/source/control/field.cxx index 87a1eae19675..d75c2e8f16ab 100644 --- a/vcl/source/control/field.cxx +++ b/vcl/source/control/field.cxx @@ -149,58 +149,56 @@ bool ImplNumericProcessKeyInput( const KeyEvent& rKEvt, } } -bool ImplNumericGetValue( const OUString& rStr, sal_Int64& rValue, - sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper, - bool bCurrency = false ) +// Takes a string with a number, which may be an integer, a floating-point with locale-specified +// decimal separator, or a fraction (and if allowed, where negatives can be represented in currency +// format - in parentheses); pre-processes the string to be a floating-point scaled by nDecDigits; +// returns a pair { scaled_whole_part_string, decimal_part_string }. +std::pair<OUString, OUString> ToScaledWholeAndDec(std::u16string_view aStr, sal_uInt16 nDecDigits, + const LocaleDataWrapper& rLocaleDataWrapper, + bool bCurrency) { - OUString aStr = rStr; + // remove leading and trailing spaces + aStr = o3tl::trim(aStr); OUStringBuffer aStr1, aStr2, aStrNum, aStrDenom; bool bNegative = false; bool bFrac = false; - sal_Int32 nDecPos, nFracDivPos; - sal_Int64 nValue; // react on empty string - if ( rStr.isEmpty() ) - return false; - - // remove leading and trailing spaces - aStr = aStr.trim(); - + if (aStr.empty()) + return {}; // find position of decimal point - nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSep() ); - if (nDecPos < 0 && !rLocaleDataWrapper.getNumDecimalSepAlt().isEmpty()) - nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSepAlt() ); - // find position of fraction - nFracDivPos = aStr.indexOf( '/' ); + auto nDecPos = aStr.find(rLocaleDataWrapper.getNumDecimalSep()); + if (nDecPos == std::u16string_view::npos && !rLocaleDataWrapper.getNumDecimalSepAlt().isEmpty()) + nDecPos = aStr.find( rLocaleDataWrapper.getNumDecimalSepAlt() ); // parse fractional strings - if (nFracDivPos > 0) + if (auto nFracDivPos = aStr.find('/'); + nFracDivPos > 0 && nFracDivPos != std::u16string_view::npos) { bFrac = true; - sal_Int32 nFracNumPos = aStr.lastIndexOf(' ', nFracDivPos); + auto nFracNumPos = aStr.rfind(' ', nFracDivPos); // If in "a b/c" format. - if(nFracNumPos != -1 ) + if (nFracNumPos != std::u16string_view::npos) { - aStr1.append(aStr.subView(0, nFracNumPos)); - aStrNum.append(aStr.subView(nFracNumPos+1, nFracDivPos-nFracNumPos-1)); - aStrDenom.append(aStr.subView(nFracDivPos+1)); + aStr1.append(aStr.substr(0, nFracNumPos)); + aStrNum.append(aStr.substr(nFracNumPos+1, nFracDivPos-nFracNumPos-1)); + aStrDenom.append(aStr.substr(nFracDivPos+1)); } // "a/b" format, or not a fraction at all else { - aStrNum.append(aStr.subView(0, nFracDivPos)); - aStrDenom.append(aStr.subView(nFracDivPos+1)); + aStrNum.append(aStr.substr(0, nFracDivPos)); + aStrDenom.append(aStr.substr(nFracDivPos+1)); } } // parse decimal strings - else if ( nDecPos >= 0) + else if (nDecPos != std::u16string_view::npos) { - aStr1.append(aStr.subView(0, nDecPos)); - aStr2.append(aStr.subView(nDecPos+1)); + aStr1.append(aStr.substr(0, nDecPos)); + aStr2.append(aStr.substr(nDecPos+1)); } else aStr1 = aStr; @@ -208,11 +206,11 @@ bool ImplNumericGetValue( const OUString& rStr, sal_Int64& rValue, // negative? if ( bCurrency ) { - if ( aStr.startsWith("(") && aStr.endsWith(")") ) + if ( aStr.starts_with('(') && aStr.ends_with(')') ) bNegative = true; if ( !bNegative ) { - for (sal_Int32 i=0; i < aStr.getLength(); i++ ) + for (size_t i = 0; i < aStr.size(); i++) { if ( (aStr[i] >= '0') && (aStr[i] <= '9') ) break; @@ -223,13 +221,13 @@ bool ImplNumericGetValue( const OUString& rStr, sal_Int64& rValue, } } } - if (!bNegative && !aStr.isEmpty()) + if (!bNegative && !aStr.empty()) { sal_uInt16 nFormat = rLocaleDataWrapper.getCurrNegativeFormat(); if ( (nFormat == 3) || (nFormat == 6) || // $1- || 1-$ (nFormat == 7) || (nFormat == 10) ) // 1$- || 1 $- { - for (sal_Int32 i = aStr.getLength()-1; i > 0; --i ) + for (size_t i = aStr.size() - 1; i > 0; --i) { if ( (aStr[i] >= '0') && (aStr[i] <= '9') ) break; @@ -290,9 +288,9 @@ bool ImplNumericGetValue( const OUString& rStr, sal_Int64& rValue, if ( !bFrac && aStr1.isEmpty() && aStr2.isEmpty() ) - return false; + return {}; else if ( bFrac && aStr1.isEmpty() && (aStrNum.isEmpty() || aStrDenom.isEmpty()) ) - return false; + return {}; if ( aStr1.isEmpty() ) aStr1 = "0"; @@ -306,12 +304,12 @@ bool ImplNumericGetValue( const OUString& rStr, sal_Int64& rValue, aStr1.setLength(0); sal_Int64 nNum = o3tl::toInt64(aStrNum); sal_Int64 nDenom = o3tl::toInt64(aStrDenom); - if (nDenom == 0) return false; // Division by zero + if (nDenom == 0) return {}; // Division by zero double nFrac2Dec = nWholeNum + static_cast<double>(nNum)/nDenom; // Convert to double for floating point precision OUStringBuffer aStrFrac(OUString::number(nFrac2Dec)); // Reconvert division result to string and parse nDecPos = aStrFrac.indexOf('.'); - if ( nDecPos >= 0) + if (nDecPos != std::u16string_view::npos) { aStr1.append(aStrFrac.getStr(), nDecPos); aStr2.append(aStrFrac.getStr()+nDecPos+1); @@ -320,28 +318,36 @@ bool ImplNumericGetValue( const OUString& rStr, sal_Int64& rValue, aStr1 = std::move(aStrFrac); } - // prune and round fraction - bool bRound = false; - if (aStr2.getLength() > nDecDigits) + if (nDecDigits) { - if (aStr2[nDecDigits] >= '5') - bRound = true; - comphelper::string::truncateToLength(aStr2, nDecDigits); + const sal_Int32 moveTo1 = std::min(static_cast<sal_Int32>(nDecDigits), aStr2.getLength()); + aStr1.append(aStr2.subView(0, moveTo1) + RepeatedUChar('0', nDecDigits - moveTo1)); + aStr2.remove(0, moveTo1); } - if (aStr2.getLength() < nDecDigits) - comphelper::string::padToLength(aStr2, nDecDigits, '0'); - aStr = aStr1 + aStr2; + return { aStr1.makeStringAndClear(), aStr2.makeStringAndClear() }; +} + +bool ImplNumericGetValue(std::u16string_view rStr, sal_Int64& rValue, sal_uInt16 nDecDigits, + const LocaleDataWrapper& rLocaleDataWrapper, bool bCurrency = false) +{ + const auto [whole, dec] = ToScaledWholeAndDec(rStr, nDecDigits, rLocaleDataWrapper, bCurrency); + if (whole.isEmpty() && dec.isEmpty()) + return false; + + // prune and round fraction + const bool bRound = !dec.isEmpty() && dec[0] >= '5'; // check range - nValue = aStr.toInt64(); + sal_Int64 nValue = whole.toInt64(); + const bool bNegative = whole.startsWith("-"); if( nValue == 0 ) { // check if string is equivalent to zero sal_Int32 nIndex = bNegative ? 1 : 0; - while (nIndex < aStr.getLength() && aStr[nIndex] == '0') + while (nIndex < whole.getLength() && whole[nIndex] == '0') ++nIndex; - if( nIndex < aStr.getLength() ) + if (nIndex < whole.getLength()) { rValue = bNegative ? SAL_MIN_INT64 : SAL_MAX_INT64; return true; @@ -360,6 +366,18 @@ bool ImplNumericGetValue( const OUString& rStr, sal_Int64& rValue, return true; } +// The returned double is scaled according to nDecDigits, same way as ImplNumericGetValue +bool ImplNumericGetDoubleValue(std::u16string_view rStr, double& rValue, sal_uInt16 nDecDigits, + const LocaleDataWrapper& rLocaleDataWrapper) +{ + const auto [whole, dec] = ToScaledWholeAndDec(rStr, nDecDigits, rLocaleDataWrapper, false); + if (whole.isEmpty() && dec.isEmpty()) + return false; + + rValue = o3tl::toDouble(Concat2View(whole + "." + dec)); + return true; +} + void ImplUpdateSeparatorString( OUString& io_rText, std::u16string_view rOldDecSep, std::u16string_view rNewDecSep, std::u16string_view rOldThSep, std::u16string_view rNewThSep ) @@ -627,7 +645,7 @@ void NumericFormatter::SetUserValue( sal_Int64 nNewValue ) ImplSetUserValue( nNewValue ); } -sal_Int64 NumericFormatter::GetValueFromString(const OUString& rStr) const +sal_Int64 NumericFormatter::GetValueFromString(std::u16string_view rStr) const { sal_Int64 nTempValue; @@ -899,7 +917,7 @@ void NumericBox::Modify() ComboBox::Modify(); } -void NumericBox::ImplNumericReformat( const OUString& rStr, sal_Int64& rValue, +void NumericBox::ImplNumericReformat( std::u16string_view rStr, sal_Int64& rValue, OUString& rOutStr ) { if (ImplNumericGetValue(rStr, rValue, GetDecimalDigits(), ImplGetLocaleDataWrapper())) @@ -1193,20 +1211,19 @@ namespace vcl namespace vcl { - bool TextToValue(const OUString& rStr, double& rValue, sal_Int64 nBaseValue, + bool TextToValue(std::u16string_view rStr, double& rValue, sal_Int64 nBaseValue, sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper, FieldUnit eUnit) { // Get value - sal_Int64 nValue; - if ( !ImplNumericGetValue( rStr, nValue, nDecDigits, rLocaleDataWrapper ) ) + double nValue; + if (!ImplNumericGetDoubleValue(rStr, nValue, nDecDigits, rLocaleDataWrapper)) return false; // Determine unit FieldUnit eEntryUnit = ImplMetricGetUnit( rStr ); // Recalculate unit - // caution: conversion to double loses precision - rValue = vcl::ConvertDoubleValue(static_cast<double>(nValue), nBaseValue, nDecDigits, eEntryUnit, eUnit); + rValue = vcl::ConvertDoubleValue(nValue, nBaseValue, nDecDigits, eEntryUnit, eUnit); return true; } @@ -1214,7 +1231,7 @@ namespace vcl FieldUnit GetTextMetricUnit(std::u16string_view aStr) { return ImplMetricGetUnit(aStr); } } -void MetricFormatter::ImplMetricReformat( const OUString& rStr, double& rValue, OUString& rOutStr ) +void MetricFormatter::ImplMetricReformat( std::u16string_view rStr, double& rValue, OUString& rOutStr ) { if (!vcl::TextToValue(rStr, rValue, 0, GetDecimalDigits(), ImplGetLocaleDataWrapper(), meUnit)) return; @@ -1305,7 +1322,7 @@ void MetricFormatter::SetUserValue( sal_Int64 nNewValue, FieldUnit eInUnit ) NumericFormatter::SetUserValue( nNewValue ); } -sal_Int64 MetricFormatter::GetValueFromStringUnit(const OUString& rStr, FieldUnit eOutUnit) const +sal_Int64 MetricFormatter::GetValueFromStringUnit(std::u16string_view rStr, FieldUnit eOutUnit) const { double nTempValue; // caution: precision loss in double cast @@ -1316,7 +1333,7 @@ sal_Int64 MetricFormatter::GetValueFromStringUnit(const OUString& rStr, FieldUni return vcl::ConvertValue(ClipDoubleAgainstMinMax(nTempValue), 0, GetDecimalDigits(), meUnit, eOutUnit); } -sal_Int64 MetricFormatter::GetValueFromString(const OUString& rStr) const +sal_Int64 MetricFormatter::GetValueFromString(std::u16string_view rStr) const { return GetValueFromStringUnit(rStr, FieldUnit::NONE); } @@ -1637,14 +1654,14 @@ static bool ImplCurrencyProcessKeyInput( const KeyEvent& rKEvt, return ImplNumericProcessKeyInput( rKEvt, false, bUseThousandSep, rWrapper ); } -static bool ImplCurrencyGetValue( const OUString& rStr, sal_Int64& rValue, +static bool ImplCurrencyGetValue( std::u16string_view rStr, sal_Int64& rValue, sal_uInt16 nDecDigits, const LocaleDataWrapper& rWrapper ) { // fetch number return ImplNumericGetValue( rStr, rValue, nDecDigits, rWrapper, true ); } -void CurrencyFormatter::ImplCurrencyReformat( const OUString& rStr, OUString& rOutStr ) +void CurrencyFormatter::ImplCurrencyReformat(std::u16string_view rStr, OUString& rOutStr) { sal_Int64 nValue; if ( !ImplNumericGetValue( rStr, nValue, GetDecimalDigits(), ImplGetLocaleDataWrapper(), true ) ) @@ -1674,7 +1691,7 @@ OUString CurrencyFormatter::CreateFieldText( sal_Int64 nValue ) const IsUseThousandSep() ); } -sal_Int64 CurrencyFormatter::GetValueFromString(const OUString& rStr) const +sal_Int64 CurrencyFormatter::GetValueFromString(std::u16string_view rStr) const { sal_Int64 nTempValue; if ( ImplCurrencyGetValue( rStr, nTempValue, GetDecimalDigits(), ImplGetLocaleDataWrapper() ) )
