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() ) )

Reply via email to