diff options
Diffstat (limited to 'vcl/source/control/field.cxx')
-rw-r--r-- | vcl/source/control/field.cxx | 1882 |
1 files changed, 1882 insertions, 0 deletions
diff --git a/vcl/source/control/field.cxx b/vcl/source/control/field.cxx new file mode 100644 index 0000000000..d85b235b0e --- /dev/null +++ b/vcl/source/control/field.cxx @@ -0,0 +1,1882 @@ +/* -*- 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 <cmath> +#include <string_view> + +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> + +#include <comphelper/string.hxx> +#include <tools/UnitConversion.hxx> + +#include <vcl/builder.hxx> +#include <vcl/fieldvalues.hxx> +#include <vcl/toolkit/field.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/uitest/uiobject.hxx> +#include <vcl/uitest/metricfielduiobject.hxx> + +#include <svdata.hxx> + +#include <i18nutil/unicode.hxx> + +#include <rtl/math.hxx> + +#include <unotools/localedatawrapper.hxx> +#include <boost/property_tree/ptree.hpp> +#include <tools/json_writer.hxx> + +using namespace ::com::sun::star; +using namespace ::comphelper; + +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"; + } + + return ""; +} + +sal_Int64 ImplPower10( sal_uInt16 n ) +{ + sal_uInt16 i; + sal_Int64 nValue = 1; + + for ( 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 == '-')); + } +} + +bool ImplNumericGetValue( const OUString& rStr, sal_Int64& rValue, + sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper, + bool bCurrency = false ) +{ + OUString aStr = rStr; + 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(); + + + // 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( '/' ); + + // parse fractional strings + if (nFracDivPos > 0) + { + bFrac = true; + sal_Int32 nFracNumPos = aStr.lastIndexOf(' ', nFracDivPos); + + // If in "a b/c" format. + if(nFracNumPos != -1 ) + { + aStr1.append(aStr.subView(0, nFracNumPos)); + aStrNum.append(aStr.subView(nFracNumPos+1, nFracDivPos-nFracNumPos-1)); + aStrDenom.append(aStr.subView(nFracDivPos+1)); + } + // "a/b" format, or not a fraction at all + else + { + aStrNum.append(aStr.subView(0, nFracDivPos)); + aStrDenom.append(aStr.subView(nFracDivPos+1)); + } + + } + // parse decimal strings + else if ( nDecPos >= 0) + { + aStr1.append(aStr.subView(0, nDecPos)); + aStr2.append(aStr.subView(nDecPos+1)); + } + else + aStr1 = aStr; + + // negative? + if ( bCurrency ) + { + if ( aStr.startsWith("(") && aStr.endsWith(")") ) + bNegative = true; + if ( !bNegative ) + { + for (sal_Int32 i=0; i < aStr.getLength(); i++ ) + { + if ( (aStr[i] >= '0') && (aStr[i] <= '9') ) + break; + else if ( aStr[i] == '-' ) + { + bNegative = true; + break; + } + } + } + if (!bNegative && !aStr.isEmpty()) + { + 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 ) + { + 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 false; + else if ( bFrac && aStr1.isEmpty() && (aStrNum.isEmpty() || aStrDenom.isEmpty()) ) + return false; + + 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 false; // 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) + { + aStr1.append(aStrFrac.getStr(), nDecPos); + aStr2.append(aStrFrac.getStr()+nDecPos+1); + } + else + aStr1 = aStrFrac; + } + + // prune and round fraction + bool bRound = false; + if (aStr2.getLength() > nDecDigits) + { + if (aStr2[nDecDigits] >= '5') + bRound = true; + string::truncateToLength(aStr2, nDecDigits); + } + if (aStr2.getLength() < nDecDigits) + string::padToLength(aStr2, nDecDigits, '0'); + + aStr = aStr1 + aStr2; + + // check range + nValue = aStr.toInt64(); + if( nValue == 0 ) + { + // check if string is equivalent to zero + sal_Int32 nIndex = bNegative ? 1 : 0; + while (nIndex < aStr.getLength() && aStr[nIndex] == '0') + ++nIndex; + if( nIndex < aStr.getLength() ) + { + rValue = bNegative ? SAL_MIN_INT64 : SAL_MAX_INT64; + return true; + } + } + if (bRound) + { + if ( !bNegative ) + nValue++; + else + nValue--; + } + + rValue = nValue; + + 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 ); +} + +} // 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 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(const OUString& 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 +{ + if (nValue > mnMax) + nValue = mnMax; + else if (nValue < mnMin) + nValue = mnMin; + return nValue; +} + +namespace +{ + Size calcMinimumSize(const Edit &rSpinField, const NumericFormatter &rFormatter) + { + OUStringBuffer aBuf; + sal_Int32 nTextLen; + + nTextLen = std::u16string_view(OUString::number(rFormatter.GetMin())).size(); + string::padToLength(aBuf, nTextLen, '9'); + Size aMinTextSize = rSpinField.CalcMinimumSizeForText( + rFormatter.CreateFieldText(OUString::unacquired(aBuf).toInt64())); + aBuf.setLength(0); + + nTextLen = std::u16string_view(OUString::number(rFormatter.GetMax())).size(); + 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())); + + OUStringBuffer sBuf("999999999"); + sal_uInt16 nDigits = rFormatter.GetDecimalDigits(); + if (nDigits) + { + sBuf.append('.'); + string::padToLength(aBuf, aBuf.getLength() + nDigits, '9'); + } + aMaxTextSize = rSpinField.CalcMinimumSizeForText(sBuf.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( const OUString& 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 bool ImplMetricProcessKeyInput( const KeyEvent& rKEvt, + bool bUseThousandSep, const LocaleDataWrapper& rWrapper ) +{ + // no meaningful strict format; therefore allow all characters + return ImplNumericProcessKeyInput( rKEvt, false, bUseThousandSep, rWrapper ); +} + +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; +} + +namespace vcl +{ + sal_Int64 ConvertValue(sal_Int64 nValue, sal_Int64 mnBaseValue, sal_uInt16 nDecDigits, + FieldUnit eInUnit, FieldUnit eOutUnit) + { + double nDouble = nonValueDoubleToValueDouble(vcl::ConvertDoubleValue( + static_cast<double>(nValue), mnBaseValue, nDecDigits, eInUnit, eOutUnit)); + sal_Int64 nLong ; + + // caution: precision loss in double cast + if ( nDouble <= double(SAL_MIN_INT64) ) + nLong = SAL_MIN_INT64; + else if ( nDouble >= double(SAL_MAX_INT64) ) + nLong = SAL_MAX_INT64; + else + nLong = static_cast<sal_Int64>( std::round(nDouble) ); + + return nLong; + } +} + +namespace { + +bool checkConversionUnits(MapUnit eInUnit, FieldUnit eOutUnit) +{ + return eOutUnit != FieldUnit::PERCENT + && eOutUnit != FieldUnit::CUSTOM + && eOutUnit != FieldUnit::NONE + && eInUnit != MapUnit::MapPixel + && eInUnit != MapUnit::MapSysFont + && eInUnit != MapUnit::MapAppFont + && eInUnit != MapUnit::MapRelative; +} + +double convertValue( double nValue, tools::Long nDigits, FieldUnit eInUnit, FieldUnit eOutUnit ) +{ + if ( nDigits < 0 ) + { + while ( nDigits ) + { + nValue += 5; + nValue /= 10; + 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 +{ + sal_Int64 ConvertValue( sal_Int64 nValue, sal_uInt16 nDigits, + MapUnit eInUnit, FieldUnit eOutUnit ) + { + if ( !checkConversionUnits(eInUnit, eOutUnit) ) + { + OSL_FAIL( "invalid parameters" ); + return nValue; + } + + tools::Long nDecDigits = nDigits; + FieldUnit eFieldUnit = ImplMap2FieldUnit( eInUnit, nDecDigits ); + + // Avoid sal_Int64 <-> double conversion issues if possible: + if (eFieldUnit == eOutUnit && nDigits == 0) + { + return nValue; + } + + return static_cast<sal_Int64>( + nonValueDoubleToValueDouble( + convertValue( nValue, nDecDigits, eFieldUnit, eOutUnit ) ) ); + } + + 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; + } + + double ConvertDoubleValue(double nValue, sal_uInt16 nDigits, + MapUnit eInUnit, FieldUnit eOutUnit) + { + if ( !checkConversionUnits(eInUnit, eOutUnit) ) + { + OSL_FAIL( "invalid parameters" ); + return nValue; + } + + tools::Long nDecDigits = nDigits; + FieldUnit eFieldUnit = ImplMap2FieldUnit( eInUnit, nDecDigits ); + + return convertValue(nValue, nDecDigits, eFieldUnit, eOutUnit); + } + + double ConvertDoubleValue(double nValue, sal_uInt16 nDigits, + FieldUnit eInUnit, MapUnit eOutUnit) + { + if ( eInUnit == FieldUnit::PERCENT || + eInUnit == FieldUnit::CUSTOM || + eInUnit == FieldUnit::NONE || + eInUnit == FieldUnit::DEGREE || + eInUnit == FieldUnit::SECOND || + eInUnit == FieldUnit::MILLISECOND || + eInUnit == FieldUnit::PIXEL || + eOutUnit == MapUnit::MapPixel || + eOutUnit == MapUnit::MapSysFont || + eOutUnit == MapUnit::MapAppFont || + eOutUnit == MapUnit::MapRelative ) + { + OSL_FAIL( "invalid parameters" ); + return nValue; + } + + tools::Long nDecDigits = nDigits; + FieldUnit eFieldUnit = ImplMap2FieldUnit( eOutUnit, nDecDigits ); + + if ( nDecDigits < 0 ) + { + nValue *= ImplPower10(-nDecDigits); + } + else + { + nValue /= ImplPower10(nDecDigits); + } + + if ( eFieldUnit != eInUnit ) + { + const o3tl::Length eFrom = FieldToO3tlLength(eInUnit, o3tl::Length::invalid); + const o3tl::Length eTo = FieldToO3tlLength(eFieldUnit, o3tl::Length::invalid); + if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid) + nValue = o3tl::convert(nValue, eFrom, eTo); + } + return nValue; + } +} + +namespace vcl +{ + bool TextToValue(const OUString& 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 ) ) + 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); + + return true; + } +} + +void MetricFormatter::ImplMetricReformat( const OUString& rStr, double& rValue, OUString& rOutStr ) +{ + if (!vcl::TextToValue(rStr, rValue, 0, GetDecimalDigits(), ImplGetLocaleDataWrapper(), meUnit)) + return; + + double nTempVal = rValue; + // caution: precision loss in double cast + if ( nTempVal > GetMax() ) + nTempVal = static_cast<double>(GetMax()); + else if ( nTempVal < GetMin()) + nTempVal = static_cast<double>(GetMin()); + rOutStr = CreateFieldText( static_cast<sal_Int64>(nTempVal) ); +} + +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(const OUString& 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); + + // caution: precision loss in double cast + if (nTempValue > mnMax) + nTempValue = static_cast<double>(mnMax); + else if (nTempValue < mnMin) + nTempValue = static_cast<double>(mnMin); + + // convert to requested units + return vcl::ConvertValue(static_cast<sal_Int64>(nTempValue), 0, GetDecimalDigits(), meUnit, eOutUnit); +} + +sal_Int64 MetricFormatter::GetValueFromString(const OUString& 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::PreNotify( NotifyEvent& rNEvt ) +{ + if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() ) + { + if ( ImplMetricProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) ) + return true; + } + + return SpinField::PreNotify( rNEvt ); +} + +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::PreNotify( NotifyEvent& rNEvt ) +{ + if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() ) + { + if ( ImplMetricProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) ) + return true; + } + + return ComboBox::PreNotify( rNEvt ); +} + +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 ImplCurrencyProcessKeyInput( const KeyEvent& rKEvt, + bool bUseThousandSep, const LocaleDataWrapper& rWrapper ) +{ + // no strict format set; therefore allow all characters + return ImplNumericProcessKeyInput( rKEvt, false, bUseThousandSep, rWrapper ); +} + +static bool ImplCurrencyGetValue( const OUString& 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 ) +{ + sal_Int64 nValue; + if ( !ImplNumericGetValue( rStr, nValue, GetDecimalDigits(), ImplGetLocaleDataWrapper(), true ) ) + return; + + sal_Int64 nTempVal = nValue; + if ( nTempVal > GetMax() ) + nTempVal = GetMax(); + else if ( nTempVal < GetMin()) + nTempVal = GetMin(); + rOutStr = CreateFieldText( nTempVal ); +} + +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(const OUString& 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::PreNotify( NotifyEvent& rNEvt ) +{ + if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() ) + { + if ( ImplCurrencyProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) ) + return true; + } + + return SpinField::PreNotify( rNEvt ); +} + +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::PreNotify( NotifyEvent& rNEvt ) +{ + if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() ) + { + if ( ImplCurrencyProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) ) + return true; + } + + return ComboBox::PreNotify( rNEvt ); +} + +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: */ |