/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <sal/config.h>
 
#include <osl/diagnose.hxx>
#include <comphelper/string.hxx>
#include <tools/json_writer.hxx>
#include <unotools/localedatawrapper.hxx>
#include <o3tl/string_view.hxx>
#include <i18nutil/unicode.hxx>
 
#include <vcl/event.hxx>
#include <vcl/fieldvalues.hxx>
#include <vcl/toolkit/field.hxx>
#include <vcl/uitest/metricfielduiobject.hxx>
 
#include <svdata.hxx>
 
#include <boost/property_tree/ptree.hpp>
 
#include <cmath>
#include <string_view>
 
namespace
{
 
std::string FieldUnitToString(FieldUnit unit)
{
    switch(unit)
    {
        case FieldUnit::NONE:
            return "";
 
        case FieldUnit::MM:
            return "mm";
 
        case FieldUnit::CM:
            return "cm";
 
        case FieldUnit::M:
            return "m";
 
        case FieldUnit::KM:
            return "km";
 
        case FieldUnit::TWIP:
            return "twip";
 
        case FieldUnit::POINT:
            return "point";
 
        case FieldUnit::PICA:
            return "pica";
 
        case FieldUnit::INCH:
            return "inch";
 
        case FieldUnit::FOOT:
            return "foot";
 
        case FieldUnit::MILE:
            return "mile";
 
        case FieldUnit::CHAR:
            return "char";
 
        case FieldUnit::LINE:
            return "line";
 
        case FieldUnit::CUSTOM:
            return "custom";
 
        case FieldUnit::PERCENT:
            return "percent";
 
        case FieldUnit::MM_100TH:
            return "mm100th";
 
        case FieldUnit::PIXEL:
            return "pixel";
 
        case FieldUnit::DEGREE:
            return "degree";
 
        case FieldUnit::SECOND:
            return "second";
 
        case FieldUnit::MILLISECOND:
            return "millisecond";
 
        case FieldUnit::FONT_EM:
            return "em";
 
        case FieldUnit::FONT_CJK_ADVANCE:
            return "ic";
    }
 
    return "";
}
 
sal_Int64 ImplPower10( sal_uInt16 n )
{
    sal_Int64 nValue = 1;
 
    for (sal_uInt16 i = 0; i < n; i++)
    {
        nValue *= 10;
    }
 
    return nValue;
}
 
bool ImplNumericProcessKeyInput( const KeyEvent& rKEvt,
                                 bool bStrictFormat, bool bThousandSep,
                                 const LocaleDataWrapper& rLocaleDataWrapper )
{
    if ( !bStrictFormat )
        return false;
    else
    {
        sal_Unicode cChar = rKEvt.GetCharCode();
        sal_uInt16      nGroup = rKEvt.GetKeyCode().GetGroup();
 
        return !((nGroup == KEYGROUP_FKEYS) ||
                 (nGroup == KEYGROUP_CURSOR) ||
                 (nGroup == KEYGROUP_MISC) ||
                 ((cChar >= '0') && (cChar <= '9')) ||
                 rLocaleDataWrapper.getNumDecimalSep() == OUStringChar(cChar) ||
                 (bThousandSep && rLocaleDataWrapper.getNumThousandSep() == OUStringChar(cChar)) ||
                 rLocaleDataWrapper.getNumDecimalSepAlt() == OUStringChar(cChar) ||
                 (cChar == '-'));
    }
}
 
// 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)
{
    // remove leading and trailing spaces
    aStr = o3tl::trim(aStr);
    OUStringBuffer      aStr1, aStr2, aStrNum, aStrDenom;
    bool                bNegative = false;
    bool                bFrac = false;
 
    // react on empty string
    if (aStr.empty())
        return {};
 
    // find position of decimal point
    auto nDecPos = aStr.find(rLocaleDataWrapper.getNumDecimalSep());
    if (nDecPos == std::u16string_view::npos && !rLocaleDataWrapper.getNumDecimalSepAlt().isEmpty())
        nDecPos = aStr.find( rLocaleDataWrapper.getNumDecimalSepAlt() );
 
    // parse fractional strings
    if (auto nFracDivPos = aStr.find('/');
        nFracDivPos > 0 && nFracDivPos != std::u16string_view::npos)
    {
        bFrac = true;
        auto nFracNumPos = aStr.rfind(' ', nFracDivPos);
 
        // If in "a b/c" format.
        if (nFracNumPos != std::u16string_view::npos)
        {
            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.substr(0, nFracDivPos));
            aStrDenom.append(aStr.substr(nFracDivPos+1));
        }
 
    }
    // parse decimal strings
    else if (nDecPos != std::u16string_view::npos)
    {
        aStr1.append(aStr.substr(0, nDecPos));
        aStr2.append(aStr.substr(nDecPos+1));
    }
    else
        aStr1 = aStr;
 
    // negative?
    if ( bCurrency )
    {
        if ( aStr.starts_with('(') && aStr.ends_with(')') )
            bNegative = true;
        if ( !bNegative )
        {
            for (size_t i = 0; i < aStr.size(); i++)
            {
                if ( (aStr[i] >= '0') && (aStr[i] <= '9') )
                    break;
                else if ( aStr[i] == '-' )
                {
                    bNegative = true;
                    break;
                }
            }
        }
        if (!bNegative && !aStr.empty())
        {
            sal_uInt16 nFormat = rLocaleDataWrapper.getCurrNegativeFormat();
            if ( (nFormat == 3) || (nFormat == 6)  || // $1- || 1-$
                 (nFormat == 7) || (nFormat == 10) )  // 1$- || 1 $-
            {
                for (size_t i = aStr.size() - 1; i > 0; --i)
                {
                    if ( (aStr[i] >= '0') && (aStr[i] <= '9') )
                        break;
                    else if ( aStr[i] == '-' )
                    {
                        bNegative = true;
                        break;
                    }
                }
            }
        }
    }
    else
    {
        if ( !aStr1.isEmpty() && aStr1[0] == '-')
            bNegative = true;
        if ( !aStrNum.isEmpty() && aStrNum[0] == '-') // For non-mixed fractions
            bNegative = true;
    }
 
    // remove all unwanted characters
    // For whole number
    for (sal_Int32 i=0; i < aStr1.getLength(); )
    {
        if ( (aStr1[i] >= '0') && (aStr1[i] <= '9') )
            i++;
        else
            aStr1.remove( i, 1 );
    }
    // For decimal
    if (!bFrac) {
        for (sal_Int32 i=0; i < aStr2.getLength(); )
        {
            if ((aStr2[i] >= '0') && (aStr2[i] <= '9'))
                ++i;
            else
                aStr2.remove(i, 1);
        }
    }
    else {
        // for numerator
        for (sal_Int32 i=0; i < aStrNum.getLength(); )
        {
            if ((aStrNum[i] >= '0') && (aStrNum[i] <= '9'))
                ++i;
            else
                aStrNum.remove(i, 1);
        }
        // for denominator
        for (sal_Int32 i=0; i < aStrDenom.getLength(); )
        {
            if ((aStrDenom[i] >= '0') && (aStrDenom[i] <= '9'))
                ++i;
            else
                aStrDenom.remove(i, 1);
        }
    }
 
 
    if ( !bFrac && aStr1.isEmpty() && aStr2.isEmpty() )
        return {};
    else if ( bFrac && aStr1.isEmpty() && (aStrNum.isEmpty() || aStrDenom.isEmpty()) )
        return {};
 
    if ( aStr1.isEmpty() )
        aStr1 = "0";
    if ( bNegative )
        aStr1.insert(0, "-");
 
    // Convert fractional strings
    if (bFrac) {
        // Convert to fraction
        sal_Int64 nWholeNum = o3tl::toInt64(aStr1);
        aStr1.setLength(0);
        sal_Int64 nNum = o3tl::toInt64(aStrNum);
        sal_Int64 nDenom = o3tl::toInt64(aStrDenom);
        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 != std::u16string_view::npos)
        {
            aStr1.append(aStrFrac.getStr(), nDecPos);
            aStr2.append(aStrFrac.getStr()+nDecPos+1);
        }
        else
            aStr1 = std::move(aStrFrac);
    }
 
    if (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);
    }
 
    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
    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 < whole.getLength() && whole[nIndex] == '0')
            ++nIndex;
        if (nIndex < whole.getLength())
        {
            rValue = bNegative ? SAL_MIN_INT64 : SAL_MAX_INT64;
            return true;
        }
    }
    if (bRound)
    {
        if ( !bNegative )
            nValue++;
        else
            nValue--;
    }
 
    rValue = nValue;
 
    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 )
{
    OUStringBuffer aBuf( io_rText.getLength() );
    sal_Int32 nIndexDec = 0, nIndexTh = 0, nIndex = 0;
 
    const sal_Unicode* pBuffer = io_rText.getStr();
    while( nIndex != -1 )
    {
        nIndexDec = io_rText.indexOf( rOldDecSep, nIndex );
        nIndexTh = io_rText.indexOf( rOldThSep, nIndex );
        if(   (nIndexTh != -1 && nIndexDec != -1 && nIndexTh < nIndexDec )
           || (nIndexTh != -1 && nIndexDec == -1)
           )
        {
            aBuf.append( OUString::Concat(std::u16string_view(pBuffer + nIndex, nIndexTh - nIndex )) + rNewThSep );
            nIndex = nIndexTh + rOldThSep.size();
        }
        else if( nIndexDec != -1 )
        {
            aBuf.append( OUString::Concat(std::u16string_view(pBuffer + nIndex, nIndexDec - nIndex )) + rNewDecSep );
            nIndex = nIndexDec + rOldDecSep.size();
        }
        else
        {
            aBuf.append( pBuffer + nIndex );
            nIndex = -1;
        }
    }
 
    io_rText = aBuf.makeStringAndClear();
}
 
void ImplUpdateSeparators( std::u16string_view rOldDecSep, std::u16string_view rNewDecSep,
                                  std::u16string_view rOldThSep, std::u16string_view rNewThSep,
                                  Edit* pEdit )
{
    bool bChangeDec = (rOldDecSep != rNewDecSep);
    bool bChangeTh = (rOldThSep != rNewThSep );
 
    if( !(bChangeDec || bChangeTh) )
        return;
 
    bool bUpdateMode = pEdit->IsUpdateMode();
    pEdit->SetUpdateMode( false );
    OUString aText = pEdit->GetText();
    ImplUpdateSeparatorString( aText, rOldDecSep, rNewDecSep, rOldThSep, rNewThSep );
    pEdit->SetText( aText );
 
    ComboBox* pCombo = dynamic_cast<ComboBox*>(pEdit);
    if( pCombo )
    {
        // update box entries
        sal_Int32 nEntryCount = pCombo->GetEntryCount();
        for ( sal_Int32 i=0; i < nEntryCount; i++ )
        {
            aText = pCombo->GetEntry( i );
            void* pEntryData = pCombo->GetEntryData( i );
            ImplUpdateSeparatorString( aText, rOldDecSep, rNewDecSep, rOldThSep, rNewThSep );
            pCombo->RemoveEntryAt(i);
            pCombo->InsertEntry( aText, i );
            pCombo->SetEntryData( i, pEntryData );
        }
    }
    if( bUpdateMode )
        pEdit->SetUpdateMode( bUpdateMode );
}
 
sal_Int64 clipDoubleAgainstMinMax(double n, sal_Int64 nMin, sal_Int64 nMax)
{
    // caution: precision loss in double cast
    if (n <= static_cast<double>(nMin))
        return nMin;
    else if (n >= static_cast<double>(nMax))
        return nMax;
    else
        return static_cast<sal_Int64>(std::round(n));
}
 
} // namespace
 
FormatterBase::FormatterBase(Edit* pField)
{
    mpField                     = pField;
    mpLocaleDataWrapper         = nullptr;
    mbReformat                  = false;
    mbStrictFormat              = false;
    mbEmptyFieldValue           = false;
    mbEmptyFieldValueEnabled    = false;
}
 
FormatterBase::~FormatterBase()
{
}
 
LocaleDataWrapper& FormatterBase::ImplGetLocaleDataWrapper() const
{
    if ( !mpLocaleDataWrapper )
    {
        mpLocaleDataWrapper.reset( new LocaleDataWrapper( GetLanguageTag() ) );
    }
    return *mpLocaleDataWrapper;
}
 
/** reset the LocaleDataWrapper when the language tag changes */
void FormatterBase::ImplResetLocaleDataWrapper() const
{
    // just get rid of, the next time it is requested, it will get loaded with the right
    // language tag
    mpLocaleDataWrapper.reset();
}
 
const LocaleDataWrapper& FormatterBase::GetLocaleDataWrapper() const
{
    return ImplGetLocaleDataWrapper();
}
 
void FormatterBase::Reformat()
{
}
 
void FormatterBase::ReformatAll()
{
    Reformat();
};
 
void FormatterBase::SetStrictFormat( bool bStrict )
{
    if ( bStrict != mbStrictFormat )
    {
        mbStrictFormat = bStrict;
        if ( mbStrictFormat )
            ReformatAll();
    }
}
 
const css::lang::Locale& FormatterBase::GetLocale() const
{
    if ( mpField )
        return mpField->GetSettings().GetLanguageTag().getLocale();
    else
        return Application::GetSettings().GetLanguageTag().getLocale();
}
 
const LanguageTag& FormatterBase::GetLanguageTag() const
{
    if ( mpField )
        return mpField->GetSettings().GetLanguageTag();
    else
        return Application::GetSettings().GetLanguageTag();
}
 
void FormatterBase::ImplSetText( const OUString& rText, Selection const * pNewSelection )
{
    if ( mpField )
    {
        if (pNewSelection)
            mpField->SetText(rText, *pNewSelection);
        else
        {
            Selection aSel = mpField->GetSelection();
            aSel.Min() = aSel.Max();
            mpField->SetText(rText, aSel);
        }
        MarkToBeReformatted( false );
    }
}
 
void FormatterBase::SetEmptyFieldValue()
{
    if ( mpField )
        mpField->SetText( OUString() );
    mbEmptyFieldValue = true;
}
 
bool FormatterBase::IsEmptyFieldValue() const
{
    return (!mpField || mpField->GetText().isEmpty());
}
 
void NumericFormatter::FormatValue(Selection const * pNewSelection)
{
    mbFormatting = true;
    ImplSetText(CreateFieldText(mnLastValue), pNewSelection);
    mbFormatting = false;
}
 
void NumericFormatter::ImplNumericReformat()
{
    mnLastValue = GetValue();
    FormatValue();
}
 
NumericFormatter::NumericFormatter(Edit* pEdit)
    : FormatterBase(pEdit)
    , mnLastValue(0)
    , mnMin(0)
    // a "large" value substantially smaller than SAL_MAX_INT64, to avoid
    // overflow in computations using this "dummy" value
    , mnMax(SAL_MAX_INT32)
    , mbFormatting(false)
    , mnSpinSize(1)
    // for fields
    , mnFirst(mnMin)
    , mnLast(mnMax)
    , mnDecimalDigits(0)
    , mbThousandSep(true)
{
    ReformatAll();
}
 
NumericFormatter::~NumericFormatter()
{
}
 
void NumericFormatter::SetMin( sal_Int64 nNewMin )
{
    mnMin = nNewMin;
    if ( !IsEmptyFieldValue() )
        ReformatAll();
}
 
void NumericFormatter::SetMax( sal_Int64 nNewMax )
{
    mnMax = nNewMax;
    if ( !IsEmptyFieldValue() )
        ReformatAll();
}
 
void NumericFormatter::SetUseThousandSep( bool bValue )
{
    mbThousandSep = bValue;
    ReformatAll();
}
 
void NumericFormatter::SetDecimalDigits( sal_uInt16 nDigits )
{
    mnDecimalDigits = nDigits;
    ReformatAll();
}
 
void NumericFormatter::SetValue( sal_Int64 nNewValue )
{
    SetUserValue( nNewValue );
    SetEmptyFieldValueData( false );
}
 
OUString NumericFormatter::CreateFieldText( sal_Int64 nValue ) const
{
    return ImplGetLocaleDataWrapper().getNum( nValue, GetDecimalDigits(), IsUseThousandSep(), /*ShowTrailingZeros*/true );
}
 
void NumericFormatter::ImplSetUserValue( sal_Int64 nNewValue, Selection const * pNewSelection )
{
    nNewValue = ClipAgainstMinMax(nNewValue);
    mnLastValue = nNewValue;
 
    if ( GetField() )
        FormatValue(pNewSelection);
}
 
void NumericFormatter::SetUserValue( sal_Int64 nNewValue )
{
    ImplSetUserValue( nNewValue );
}
 
sal_Int64 NumericFormatter::GetValueFromString(std::u16string_view rStr) const
{
    sal_Int64 nTempValue;
 
    if (ImplNumericGetValue(rStr, nTempValue,
        GetDecimalDigits(), ImplGetLocaleDataWrapper()))
    {
        return ClipAgainstMinMax(nTempValue);
    }
    else
        return mnLastValue;
}
 
OUString NumericFormatter::GetValueString() const
{
    return Application::GetSettings().GetNeutralLocaleDataWrapper().
        getNum(GetValue(), GetDecimalDigits(), false, false);
}
 
// currently used by online
void NumericFormatter::SetValueFromString(const OUString& rStr)
{
    sal_Int64 nValue;
 
    if (ImplNumericGetValue(rStr, nValue, GetDecimalDigits(),
        Application::GetSettings().GetNeutralLocaleDataWrapper()))
    {
        ImplNewFieldValue(nValue);
    }
    else
    {
        SAL_WARN("vcl", "fail to convert the value: " << rStr );
    }
}
 
sal_Int64 NumericFormatter::GetValue() const
{
    if (mbFormatting) //don't parse the entry if we're currently formatting what to put in it
        return mnLastValue;
 
    return GetField() ? GetValueFromString(GetField()->GetText()) : 0;
}
 
sal_Int64 NumericFormatter::Normalize( sal_Int64 nValue ) const
{
    return (nValue * ImplPower10( GetDecimalDigits() ) );
}
 
sal_Int64 NumericFormatter::Denormalize( sal_Int64 nValue ) const
{
    sal_Int64 nFactor = ImplPower10( GetDecimalDigits() );
 
    if ((nValue < ( SAL_MIN_INT64 + nFactor )) ||
        (nValue > ( SAL_MAX_INT64 - nFactor )))
    {
        return ( nValue / nFactor );
    }
 
    if( nValue < 0 )
    {
        sal_Int64 nHalf = nFactor / 2;
        return ((nValue - nHalf) / nFactor );
    }
    else
    {
        sal_Int64 nHalf = nFactor / 2;
        return ((nValue + nHalf) / nFactor );
    }
}
 
void NumericFormatter::Reformat()
{
    if ( !GetField() )
        return;
 
    if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
        return;
 
    ImplNumericReformat();
}
 
void NumericFormatter::FieldUp()
{
    sal_Int64 nValue = GetValue();
    sal_Int64 nRemainder = nValue % mnSpinSize;
    if (nValue >= 0)
        nValue = (nRemainder == 0) ? nValue + mnSpinSize : nValue + mnSpinSize - nRemainder;
    else
        nValue = (nRemainder == 0) ? nValue + mnSpinSize : nValue - nRemainder;
 
    nValue = ClipAgainstMinMax(nValue);
 
    ImplNewFieldValue( nValue );
}
 
void NumericFormatter::FieldDown()
{
    sal_Int64 nValue = GetValue();
    sal_Int64 nRemainder = nValue % mnSpinSize;
    if (nValue >= 0)
        nValue = (nRemainder == 0) ? nValue - mnSpinSize : nValue - nRemainder;
    else
        nValue = (nRemainder == 0) ? nValue - mnSpinSize : nValue - mnSpinSize - nRemainder;
 
    nValue = ClipAgainstMinMax(nValue);
 
    ImplNewFieldValue( nValue );
}
 
void NumericFormatter::FieldFirst()
{
    ImplNewFieldValue( mnFirst );
}
 
void NumericFormatter::FieldLast()
{
    ImplNewFieldValue( mnLast );
}
 
void NumericFormatter::ImplNewFieldValue( sal_Int64 nNewValue )
{
    if ( !GetField() )
        return;
 
    // !!! We should check why we do not validate in ImplSetUserValue() if the value was
    // changed. This should be done there as well since otherwise the call to Modify would not
    // be allowed. Anyway, the paths from ImplNewFieldValue, ImplSetUserValue, and ImplSetText
    // should be checked and clearly traced (with comment) in order to find out what happens.
 
    Selection aSelection = GetField()->GetSelection();
    aSelection.Normalize();
    OUString aText = GetField()->GetText();
    // leave it as is if selected until end
    if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() )
    {
        if ( !aSelection.Len() )
            aSelection.Min() = SELECTION_MAX;
        aSelection.Max() = SELECTION_MAX;
    }
 
    sal_Int64 nOldLastValue  = mnLastValue;
    ImplSetUserValue( nNewValue, &aSelection );
    mnLastValue = nOldLastValue;
 
    // Modify during Edit is only set during KeyInput
    if ( GetField()->GetText() != aText )
    {
        GetField()->SetModifyFlag();
        GetField()->Modify();
    }
}
 
sal_Int64 NumericFormatter::ClipAgainstMinMax(sal_Int64 nValue) const
{
    return std::clamp(nValue, GetMin(), GetMax());
}
 
sal_Int64 NumericFormatter::ClipDoubleAgainstMinMax(double nValue) const
{
    return clipDoubleAgainstMinMax(nValue, GetMin(), GetMax());
}
 
namespace
{
    Size calcMinimumSize(const Edit &rSpinField, const NumericFormatter &rFormatter)
    {
        OUStringBuffer aBuf;
        sal_Int32 nTextLen;
 
        nTextLen = OUString::number(rFormatter.GetMin()).length;
        comphelper::string::padToLength(aBuf, nTextLen, '9');
        Size aMinTextSize = rSpinField.CalcMinimumSizeForText(
            rFormatter.CreateFieldText(OUString::unacquired(aBuf).toInt64()));
        aBuf.setLength(0);
 
        nTextLen = OUString::number(rFormatter.GetMax()).length;
        comphelper::string::padToLength(aBuf, nTextLen, '9');
        Size aMaxTextSize = rSpinField.CalcMinimumSizeForText(
            rFormatter.CreateFieldText(OUString::unacquired(aBuf).toInt64()));
        aBuf.setLength(0);
 
        Size aRet(std::max(aMinTextSize.Width(), aMaxTextSize.Width()),
                  std::max(aMinTextSize.Height(), aMaxTextSize.Height()));
 
        aBuf = "999999999";
        sal_uInt16 nDigits = rFormatter.GetDecimalDigits();
        if (nDigits)
        {
            aBuf.append("." + RepeatedUChar('9', nDigits));
        }
        aMaxTextSize = rSpinField.CalcMinimumSizeForText(aBuf.makeStringAndClear());
        aRet.setWidth( std::min(aRet.Width(), aMaxTextSize.Width()) );
 
        return aRet;
    }
}
 
NumericBox::NumericBox(vcl::Window* pParent, WinBits nWinStyle)
    : ComboBox(pParent, nWinStyle)
    , NumericFormatter(this)
{
    Reformat();
    if ( !(nWinStyle & WB_HIDE ) )
        Show();
}
 
void NumericBox::dispose()
{
    ClearField();
    ComboBox::dispose();
}
 
Size NumericBox::CalcMinimumSize() const
{
    Size aRet(calcMinimumSize(*this, *this));
 
    if (IsDropDownBox())
    {
        Size aComboSugg(ComboBox::CalcMinimumSize());
        aRet.setWidth( std::max(aRet.Width(), aComboSugg.Width()) );
        aRet.setHeight( std::max(aRet.Height(), aComboSugg.Height()) );
    }
 
    return aRet;
}
 
bool NumericBox::PreNotify( NotifyEvent& rNEvt )
{
    if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
    {
        if ( ImplNumericProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
            return true;
    }
 
    return ComboBox::PreNotify( rNEvt );
}
 
bool NumericBox::EventNotify( NotifyEvent& rNEvt )
{
    if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
        MarkToBeReformatted( false );
    else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
    {
        if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
            Reformat();
    }
 
    return ComboBox::EventNotify( rNEvt );
}
 
void NumericBox::DataChanged( const DataChangedEvent& rDCEvt )
{
    ComboBox::DataChanged( rDCEvt );
 
    if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
    {
        OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
        OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
        ImplResetLocaleDataWrapper();
        OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
        OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
        ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
        ReformatAll();
    }
}
 
void NumericBox::Modify()
{
    MarkToBeReformatted( true );
    ComboBox::Modify();
}
 
void NumericBox::ImplNumericReformat( std::u16string_view rStr, sal_Int64& rValue,
                                                OUString& rOutStr )
{
    if (ImplNumericGetValue(rStr, rValue, GetDecimalDigits(), ImplGetLocaleDataWrapper()))
    {
        sal_Int64 nTempVal = ClipAgainstMinMax(rValue);
        rOutStr = CreateFieldText( nTempVal );
    }
}
 
void NumericBox::ReformatAll()
{
    sal_Int64 nValue;
    OUString aStr;
    SetUpdateMode( false );
    sal_Int32 nEntryCount = GetEntryCount();
    for ( sal_Int32 i=0; i < nEntryCount; i++ )
    {
        ImplNumericReformat( GetEntry( i ), nValue, aStr );
        RemoveEntryAt(i);
        InsertEntry( aStr, i );
    }
    NumericFormatter::Reformat();
    SetUpdateMode( true );
}
 
static OUString ImplMetricGetUnitText(std::u16string_view rStr)
{
    // fetch unit text
    OUStringBuffer aStr;
    for (sal_Int32 i = static_cast<sal_Int32>(rStr.size())-1; i >= 0; --i)
    {
        sal_Unicode c = rStr[i];
        if ( (c == '\'') || (c == '\"') || (c == '%') || (c == 0x2032) || (c == 0x2033) || unicode::isAlpha(c) || unicode::isControl(c) )
            aStr.insert(0, c);
        else
        {
            if (!aStr.isEmpty())
                break;
        }
    }
    return aStr.makeStringAndClear();
}
 
// #104355# support localized measurements
 
static OUString ImplMetricToString( FieldUnit rUnit )
{
    // return unit's default string (ie, the first one )
    for (auto const& elem : ImplGetFieldUnits())
    {
        if (elem.second == rUnit)
            return elem.first;
    }
 
    return OUString();
}
 
namespace
{
    FieldUnit StringToMetric(const OUString &rMetricString)
    {
        // return FieldUnit
        OUString aStr = rMetricString.toAsciiLowerCase().replaceAll(" ", "");
        for (auto const& elem : ImplGetCleanedFieldUnits())
        {
            if ( elem.first == aStr )
                return elem.second;
        }
 
        return FieldUnit::NONE;
    }
}
 
static FieldUnit ImplMetricGetUnit(std::u16string_view rStr)
{
    OUString aStr = ImplMetricGetUnitText(rStr);
    return StringToMetric(aStr);
}
 
static FieldUnit ImplMap2FieldUnit( MapUnit meUnit, tools::Long& nDecDigits )
{
    switch( meUnit )
    {
        case MapUnit::Map100thMM :
            nDecDigits -= 2;
            return FieldUnit::MM;
        case MapUnit::Map10thMM :
            nDecDigits -= 1;
            return FieldUnit::MM;
        case MapUnit::MapMM :
            return FieldUnit::MM;
        case MapUnit::MapCM :
            return FieldUnit::CM;
        case MapUnit::Map1000thInch :
            nDecDigits -= 3;
            return FieldUnit::INCH;
        case MapUnit::Map100thInch :
            nDecDigits -= 2;
            return FieldUnit::INCH;
        case MapUnit::Map10thInch :
            nDecDigits -= 1;
            return FieldUnit::INCH;
        case MapUnit::MapInch :
            return FieldUnit::INCH;
        case MapUnit::MapPoint :
            return FieldUnit::POINT;
        case MapUnit::MapTwip :
            return FieldUnit::TWIP;
        default:
            OSL_FAIL( "default eInUnit" );
            break;
    }
    return FieldUnit::NONE;
}
 
static double nonValueDoubleToValueDouble( double nValue )
{
    return std::isfinite( nValue ) ? nValue : 0.0;
}
 
static double ConvertDoubleValue(double nValue, sal_Int64 mnBaseValue, sal_uInt16 nDecDigits,
                                 FieldUnit eInUnit, FieldUnit eOutUnit)
{
    if ( eInUnit != eOutUnit )
    {
        if (eInUnit == FieldUnit::PERCENT && mnBaseValue > 0 && nValue > 0)
        {
            sal_Int64 nDiv = 100 * ImplPower10(nDecDigits);
 
            if (mnBaseValue != 1)
                nValue *= mnBaseValue;
 
            nValue += nDiv / 2;
            nValue /= nDiv;
        }
        else
        {
            const o3tl::Length eFrom = FieldToO3tlLength(eInUnit, o3tl::Length::invalid);
            const o3tl::Length eTo = FieldToO3tlLength(eOutUnit, o3tl::Length::invalid);
            if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
                nValue = o3tl::convert(nValue, eFrom, eTo);
        }
    }
 
    return nValue;
}
 
namespace vcl
{
    sal_Int64 ConvertValue(sal_Int64 nValue, sal_Int64 mnBaseValue, sal_uInt16 nDecDigits,
                           FieldUnit eInUnit, FieldUnit eOutUnit)
    {
        double nDouble = nonValueDoubleToValueDouble(ConvertDoubleValue(
                    static_cast<double>(nValue), mnBaseValue, nDecDigits, eInUnit, eOutUnit));
 
        return clipDoubleAgainstMinMax(nDouble, SAL_MIN_INT64, SAL_MAX_INT64);
    }
}
 
namespace {
 
bool checkConversionUnits(FieldUnit eInUnit, FieldUnit eOutUnit)
{
    return FieldToO3tlLength(eInUnit, o3tl::Length::invalid) != o3tl::Length::invalid
        && FieldToO3tlLength(eOutUnit, o3tl::Length::invalid) != o3tl::Length::invalid;
}
 
double convertValue( double nValue, tools::Long nDigits, FieldUnit eInUnit, FieldUnit eOutUnit )
{
    if ( nDigits < 0 )
    {
        nValue /= ImplPower10(-nDigits);
    }
    else
    {
        nValue *= ImplPower10(nDigits);
    }
 
    if ( eInUnit != eOutUnit )
    {
        const o3tl::Length eFrom = FieldToO3tlLength(eInUnit), eTo = FieldToO3tlLength(eOutUnit);
        if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
            nValue = o3tl::convert(nValue, eFrom, eTo);
    }
 
    return nValue;
}
 
}
 
namespace vcl
{
    // nValue is not scaled to nDecDigits; the result is scaled
    sal_Int64 ConvertAndScaleValue( sal_Int64 nValue, sal_uInt16 nDigits,
                                         MapUnit eInUnit, FieldUnit eOutUnit )
    {
        tools::Long nDecDigits = nDigits;
        FieldUnit eFieldUnit = ImplMap2FieldUnit( eInUnit, nDecDigits );
 
        if ( !checkConversionUnits(eFieldUnit, eOutUnit) )
        {
            OSL_FAIL( "invalid parameters" );
            return nValue;
        }
 
        // Avoid sal_Int64 <-> double conversion issues if possible:
        if (eFieldUnit == eOutUnit && nDecDigits == 0)
        {
            return nValue;
        }
 
        return static_cast<sal_Int64>(
            nonValueDoubleToValueDouble(
                convertValue( nValue, nDecDigits, eFieldUnit, eOutUnit ) ) );
    }
 
    // nValue is already scaled to nDecDigits; the result is unscaled
    sal_Int64 ConvertAndUnscaleValue(sal_Int64 nValue, sal_uInt16 nDigits, FieldUnit eInUnit,
                                     FieldUnit eOutUnit)
    {
        if (!checkConversionUnits(eInUnit, eOutUnit))
        {
            SAL_WARN("vcl", "invalid parameters");
            return nValue;
        }
 
        // Avoid sal_Int64 <-> double conversion issues if possible:
        if (eInUnit == eOutUnit && nDigits == 0)
        {
            return nValue;
        }
 
        return static_cast<sal_Int64>(
            nonValueDoubleToValueDouble(
                convertValue( nValue, -static_cast<tools::Long>(nDigits), eInUnit, eOutUnit ) ) );
    }
}
 
namespace vcl
{
    bool TextToValue(std::u16string_view rStr, double& rValue, sal_Int64 nBaseValue,
                     sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper, FieldUnit eUnit)
    {
        // Get value
        double nValue;
        if (!ImplNumericGetDoubleValue(rStr, nValue, nDecDigits, rLocaleDataWrapper))
            return false;
 
        // Determine unit
        FieldUnit eEntryUnit = ImplMetricGetUnit( rStr );
 
        // Recalculate unit
        rValue = ConvertDoubleValue(nValue, nBaseValue, nDecDigits, eEntryUnit, eUnit);
 
        return true;
    }
 
    FieldUnit GetTextMetricUnit(std::u16string_view aStr) { return ImplMetricGetUnit(aStr); }
}
 
void MetricFormatter::ImplMetricReformat( std::u16string_view rStr, double& rValue, OUString& rOutStr )
{
    if (!vcl::TextToValue(rStr, rValue, 0, GetDecimalDigits(), ImplGetLocaleDataWrapper(), meUnit))
        return;
 
    rOutStr = CreateFieldText(ClipDoubleAgainstMinMax(rValue));
}
 
MetricFormatter::MetricFormatter(Edit* pEdit)
    : NumericFormatter(pEdit)
    , meUnit(FieldUnit::NONE)
{
}
 
MetricFormatter::~MetricFormatter()
{
}
 
void MetricFormatter::SetUnit( FieldUnit eNewUnit )
{
    if (eNewUnit == FieldUnit::MM_100TH)
    {
        SetDecimalDigits( GetDecimalDigits() + 2 );
        meUnit = FieldUnit::MM;
    }
    else
        meUnit = eNewUnit;
    ReformatAll();
}
 
void MetricFormatter::SetCustomUnitText( const OUString& rStr )
{
    maCustomUnitText = rStr;
    ReformatAll();
}
 
void MetricFormatter::SetValue( sal_Int64 nNewValue, FieldUnit eInUnit )
{
    SetUserValue( nNewValue, eInUnit );
}
 
OUString MetricFormatter::CreateFieldText( sal_Int64 nValue ) const
{
    //whether percent is separated from its number is locale
    //specific, pawn it off to icu to decide
    if (meUnit == FieldUnit::PERCENT)
    {
        double dValue = nValue;
        dValue /= ImplPower10(GetDecimalDigits());
        return unicode::formatPercent(dValue, GetLanguageTag());
    }
 
    OUString aStr = NumericFormatter::CreateFieldText( nValue );
 
    if( meUnit == FieldUnit::CUSTOM )
        aStr += maCustomUnitText;
    else
    {
        OUString aSuffix = ImplMetricToString( meUnit );
        if (meUnit != FieldUnit::NONE && meUnit != FieldUnit::DEGREE && meUnit != FieldUnit::INCH && meUnit != FieldUnit::FOOT)
            aStr += " ";
        if (meUnit == FieldUnit::INCH)
        {
            OUString sDoublePrime = u"\u2033"_ustr;
            if (aSuffix != "\"" && aSuffix != sDoublePrime)
                aStr += " ";
            else
                aSuffix = sDoublePrime;
        }
        else if (meUnit == FieldUnit::FOOT)
        {
            OUString sPrime = u"\u2032"_ustr;
            if (aSuffix != "'" && aSuffix != sPrime)
                aStr += " ";
            else
                aSuffix = sPrime;
        }
 
        assert(meUnit != FieldUnit::PERCENT);
        aStr += aSuffix;
    }
    return aStr;
}
 
void MetricFormatter::SetUserValue( sal_Int64 nNewValue, FieldUnit eInUnit )
{
    // convert to previously configured units
    nNewValue = vcl::ConvertValue( nNewValue, 0, GetDecimalDigits(), eInUnit, meUnit );
    NumericFormatter::SetUserValue( nNewValue );
}
 
sal_Int64 MetricFormatter::GetValueFromStringUnit(std::u16string_view rStr, FieldUnit eOutUnit) const
{
    double nTempValue;
    // caution: precision loss in double cast
    if (!vcl::TextToValue(rStr, nTempValue, 0, GetDecimalDigits(), ImplGetLocaleDataWrapper(), meUnit))
        nTempValue = static_cast<double>(mnLastValue);
 
    // convert to requested units
    return vcl::ConvertValue(ClipDoubleAgainstMinMax(nTempValue), 0, GetDecimalDigits(), meUnit, eOutUnit);
}
 
sal_Int64 MetricFormatter::GetValueFromString(std::u16string_view rStr) const
{
    return GetValueFromStringUnit(rStr, FieldUnit::NONE);
}
 
sal_Int64 MetricFormatter::GetValue( FieldUnit eOutUnit ) const
{
    return GetField() ? GetValueFromStringUnit(GetField()->GetText(), eOutUnit) : 0;
}
 
void MetricFormatter::SetValue( sal_Int64 nValue )
{
    // Implementation not inline, because it is a virtual Function
    SetValue( nValue, FieldUnit::NONE );
}
 
void MetricFormatter::SetMin( sal_Int64 nNewMin, FieldUnit eInUnit )
{
    // convert to requested units
    NumericFormatter::SetMin(vcl::ConvertValue(nNewMin, 0, GetDecimalDigits(), eInUnit, meUnit));
}
 
sal_Int64 MetricFormatter::GetMin( FieldUnit eOutUnit ) const
{
    // convert to requested units
    return vcl::ConvertValue(NumericFormatter::GetMin(), 0, GetDecimalDigits(), meUnit, eOutUnit);
}
 
void MetricFormatter::SetMax( sal_Int64 nNewMax, FieldUnit eInUnit )
{
    // convert to requested units
    NumericFormatter::SetMax(vcl::ConvertValue(nNewMax, 0, GetDecimalDigits(), eInUnit, meUnit));
}
 
sal_Int64 MetricFormatter::GetMax( FieldUnit eOutUnit ) const
{
    // convert to requested units
    return vcl::ConvertValue(NumericFormatter::GetMax(), 0, GetDecimalDigits(), meUnit, eOutUnit);
}
 
void MetricFormatter::Reformat()
{
    if ( !GetField() )
        return;
 
    OUString aText = GetField()->GetText();
 
    OUString aStr;
    // caution: precision loss in double cast
    double nTemp = static_cast<double>(mnLastValue);
    ImplMetricReformat( aText, nTemp, aStr );
    mnLastValue = static_cast<sal_Int64>(nTemp);
 
    if ( !aStr.isEmpty() )
    {
        ImplSetText( aStr );
    }
    else
        SetValue( mnLastValue );
}
 
sal_Int64 MetricFormatter::GetCorrectedValue( FieldUnit eOutUnit ) const
{
    // convert to requested units
    return vcl::ConvertValue(0/*nCorrectedValue*/, 0, GetDecimalDigits(),
                             meUnit, eOutUnit);
}
 
MetricField::MetricField(vcl::Window* pParent, WinBits nWinStyle)
    : SpinField(pParent, nWinStyle, WindowType::METRICFIELD)
    , MetricFormatter(this)
{
    Reformat();
}
 
void MetricField::dispose()
{
    ClearField();
    SpinField::dispose();
}
 
Size MetricField::CalcMinimumSize() const
{
    return calcMinimumSize(*this, *this);
}
 
bool MetricField::set_property(const OUString &rKey, const OUString &rValue)
{
    if (rKey == "digits")
        SetDecimalDigits(rValue.toInt32());
    else if (rKey == "spin-size")
        SetSpinSize(rValue.toInt32());
    else
        return SpinField::set_property(rKey, rValue);
    return true;
}
 
void MetricField::SetUnit( FieldUnit nNewUnit )
{
    sal_Int64 nRawMax = GetMax( nNewUnit );
    sal_Int64 nMax = Denormalize( nRawMax );
    sal_Int64 nMin = Denormalize( GetMin( nNewUnit ) );
    sal_Int64 nFirst = Denormalize( GetFirst( nNewUnit ) );
    sal_Int64 nLast = Denormalize( GetLast( nNewUnit ) );
 
    MetricFormatter::SetUnit( nNewUnit );
 
    SetMax( Normalize( nMax ), nNewUnit );
    SetMin( Normalize( nMin ), nNewUnit );
    SetFirst( Normalize( nFirst ), nNewUnit );
    SetLast( Normalize( nLast ), nNewUnit );
}
 
void MetricField::SetFirst( sal_Int64 nNewFirst, FieldUnit eInUnit )
{
    // convert
    nNewFirst = vcl::ConvertValue(nNewFirst, 0, GetDecimalDigits(), eInUnit, meUnit);
    mnFirst = nNewFirst;
}
 
sal_Int64 MetricField::GetFirst( FieldUnit eOutUnit ) const
{
    // convert
    return vcl::ConvertValue(mnFirst, 0, GetDecimalDigits(), meUnit, eOutUnit);
}
 
void MetricField::SetLast( sal_Int64 nNewLast, FieldUnit eInUnit )
{
    // convert
    nNewLast = vcl::ConvertValue(nNewLast, 0, GetDecimalDigits(), eInUnit, meUnit);
    mnLast = nNewLast;
}
 
sal_Int64 MetricField::GetLast( FieldUnit eOutUnit ) const
{
    // convert
    return vcl::ConvertValue(mnLast, 0, GetDecimalDigits(), meUnit, eOutUnit);
}
 
bool MetricField::EventNotify( NotifyEvent& rNEvt )
{
    if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
        MarkToBeReformatted( false );
    else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
    {
        if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
            Reformat();
    }
 
    return SpinField::EventNotify( rNEvt );
}
 
void MetricField::DataChanged( const DataChangedEvent& rDCEvt )
{
    SpinField::DataChanged( rDCEvt );
 
    if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
    {
        OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
        OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
        ImplResetLocaleDataWrapper();
        OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
        OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
        ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
        ReformatAll();
    }
}
 
void MetricField::Modify()
{
    MarkToBeReformatted( true );
    SpinField::Modify();
}
 
void MetricField::Up()
{
    FieldUp();
    SpinField::Up();
}
 
void MetricField::Down()
{
    FieldDown();
    SpinField::Down();
}
 
void MetricField::First()
{
    FieldFirst();
    SpinField::First();
}
 
void MetricField::Last()
{
    FieldLast();
    SpinField::Last();
}
 
void MetricField::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
{
    SpinField::DumpAsPropertyTree(rJsonWriter);
    rJsonWriter.put("min", GetMin());
    rJsonWriter.put("max", GetMax());
    rJsonWriter.put("unit", FieldUnitToString(GetUnit()));
    OUString sValue = Application::GetSettings().GetNeutralLocaleDataWrapper().
        getNum(GetValue(), GetDecimalDigits(), false, false);
    rJsonWriter.put("value", sValue);
}
 
FactoryFunction MetricField::GetUITestFactory() const
{
    return MetricFieldUIObject::create;
}
 
MetricBox::MetricBox(vcl::Window* pParent, WinBits nWinStyle)
    : ComboBox(pParent, nWinStyle)
    , MetricFormatter(this)
{
    Reformat();
}
 
void MetricBox::dispose()
{
    ClearField();
    ComboBox::dispose();
}
 
Size MetricBox::CalcMinimumSize() const
{
    Size aRet(calcMinimumSize(*this, *this));
 
    if (IsDropDownBox())
    {
        Size aComboSugg(ComboBox::CalcMinimumSize());
        aRet.setWidth( std::max(aRet.Width(), aComboSugg.Width()) );
        aRet.setHeight( std::max(aRet.Height(), aComboSugg.Height()) );
    }
 
    return aRet;
}
 
bool MetricBox::EventNotify( NotifyEvent& rNEvt )
{
    if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
        MarkToBeReformatted( false );
    else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
    {
        if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
            Reformat();
    }
 
    return ComboBox::EventNotify( rNEvt );
}
 
void MetricBox::DataChanged( const DataChangedEvent& rDCEvt )
{
    ComboBox::DataChanged( rDCEvt );
 
    if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
    {
        OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
        OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
        ImplResetLocaleDataWrapper();
        OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
        OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
        ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
        ReformatAll();
    }
}
 
void MetricBox::Modify()
{
    MarkToBeReformatted( true );
    ComboBox::Modify();
}
 
void MetricBox::ReformatAll()
{
    double nValue;
    OUString aStr;
    SetUpdateMode( false );
    sal_Int32 nEntryCount = GetEntryCount();
    for ( sal_Int32 i=0; i < nEntryCount; i++ )
    {
        ImplMetricReformat( GetEntry( i ), nValue, aStr );
        RemoveEntryAt(i);
        InsertEntry( aStr, i );
    }
    MetricFormatter::Reformat();
    SetUpdateMode( true );
}
 
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(std::u16string_view rStr, OUString& rOutStr)
{
    sal_Int64 nValue;
    if ( !ImplNumericGetValue( rStr, nValue, GetDecimalDigits(), ImplGetLocaleDataWrapper(), true ) )
        return;
    rOutStr = CreateFieldText(ClipAgainstMinMax(nValue));
}
 
CurrencyFormatter::CurrencyFormatter(Edit* pField)
    : NumericFormatter(pField)
{
}
 
CurrencyFormatter::~CurrencyFormatter()
{
}
 
void CurrencyFormatter::SetValue( sal_Int64 nNewValue )
{
    SetUserValue( nNewValue );
    SetEmptyFieldValueData( false );
}
 
OUString CurrencyFormatter::CreateFieldText( sal_Int64 nValue ) const
{
    return ImplGetLocaleDataWrapper().getCurr( nValue, GetDecimalDigits(),
                                               ImplGetLocaleDataWrapper().getCurrSymbol(),
                                               IsUseThousandSep() );
}
 
sal_Int64 CurrencyFormatter::GetValueFromString(std::u16string_view rStr) const
{
    sal_Int64 nTempValue;
    if ( ImplCurrencyGetValue( rStr, nTempValue, GetDecimalDigits(), ImplGetLocaleDataWrapper() ) )
    {
        return ClipAgainstMinMax(nTempValue);
    }
    else
        return mnLastValue;
}
 
void CurrencyFormatter::Reformat()
{
    if ( !GetField() )
        return;
 
    OUString aStr;
    ImplCurrencyReformat( GetField()->GetText(), aStr );
 
    if ( !aStr.isEmpty() )
    {
        ImplSetText( aStr  );
        sal_Int64 nTemp = mnLastValue;
        ImplCurrencyGetValue( aStr, nTemp, GetDecimalDigits(), ImplGetLocaleDataWrapper() );
        mnLastValue = nTemp;
    }
    else
        SetValue( mnLastValue );
}
 
CurrencyField::CurrencyField(vcl::Window* pParent, WinBits nWinStyle)
    : SpinField(pParent, nWinStyle)
    , CurrencyFormatter(this)
{
    Reformat();
}
 
void CurrencyField::dispose()
{
    ClearField();
    SpinField::dispose();
}
 
bool CurrencyField::EventNotify( NotifyEvent& rNEvt )
{
    if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
        MarkToBeReformatted( false );
    else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
    {
        if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
            Reformat();
    }
 
    return SpinField::EventNotify( rNEvt );
}
 
void CurrencyField::DataChanged( const DataChangedEvent& rDCEvt )
{
    SpinField::DataChanged( rDCEvt );
 
    if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
    {
        OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
        OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
        ImplResetLocaleDataWrapper();
        OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
        OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
        ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
        ReformatAll();
    }
}
 
void CurrencyField::Modify()
{
    MarkToBeReformatted( true );
    SpinField::Modify();
}
 
void CurrencyField::Up()
{
    FieldUp();
    SpinField::Up();
}
 
void CurrencyField::Down()
{
    FieldDown();
    SpinField::Down();
}
 
void CurrencyField::First()
{
    FieldFirst();
    SpinField::First();
}
 
void CurrencyField::Last()
{
    FieldLast();
    SpinField::Last();
}
 
CurrencyBox::CurrencyBox(vcl::Window* pParent, WinBits nWinStyle)
    : ComboBox(pParent, nWinStyle)
    , CurrencyFormatter(this)
{
    Reformat();
}
 
void CurrencyBox::dispose()
{
    ClearField();
    ComboBox::dispose();
}
 
bool CurrencyBox::EventNotify( NotifyEvent& rNEvt )
{
    if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
        MarkToBeReformatted( false );
    else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
    {
        if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
            Reformat();
    }
 
    return ComboBox::EventNotify( rNEvt );
}
 
void CurrencyBox::DataChanged( const DataChangedEvent& rDCEvt )
{
    ComboBox::DataChanged( rDCEvt );
 
    if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
    {
        OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
        OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
        ImplResetLocaleDataWrapper();
        OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
        OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
        ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
        ReformatAll();
    }
}
 
void CurrencyBox::Modify()
{
    MarkToBeReformatted( true );
    ComboBox::Modify();
}
 
void CurrencyBox::ReformatAll()
{
    OUString aStr;
    SetUpdateMode( false );
    sal_Int32 nEntryCount = GetEntryCount();
    for ( sal_Int32 i=0; i < nEntryCount; i++ )
    {
        ImplCurrencyReformat( GetEntry( i ), aStr );
        RemoveEntryAt(i);
        InsertEntry( aStr, i );
    }
    CurrencyFormatter::Reformat();
    SetUpdateMode( true );
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'insert' is required to be utilized.

V1053 Calling the 'ReformatAll' virtual function in the constructor may lead to unexpected result at runtime.

V1053 Calling the 'Reformat' virtual function in the constructor may lead to unexpected result at runtime.

V560 A part of conditional expression is always true: !aStr.empty().

V636 The 'nDiv / 2' expression was implicitly cast from 'long' type to 'double' type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;.