1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <tools/debug.hxx>
21 #include <boost/property_tree/json_parser.hpp>
22 #include <comphelper/processfactory.hxx>
23 #include <comphelper/string.hxx>
24 #include <unotools/localedatawrapper.hxx>
25 #include <vcl/builder.hxx>
26 #include <vcl/event.hxx>
27 #include <vcl/settings.hxx>
28 #include <vcl/commandevent.hxx>
29 #include <svl/zformat.hxx>
30 #include <vcl/toolkit/fmtfield.hxx>
31 #include <vcl/uitest/uiobject.hxx>
32 #include <vcl/uitest/formattedfielduiobject.hxx>
33 #include <vcl/weld.hxx>
34 #include <i18nlangtag/languagetag.hxx>
35 #include <unotools/syslocale.hxx>
38 #include <rtl/math.hxx>
39 #include <rtl/ustrbuf.hxx>
40 #include <sal/log.hxx>
41 #include <svl/numformat.hxx>
42 #include <osl/diagnose.h>
43 #include <tools/json_writer.hxx>
45 using namespace ::com::sun::star::lang
;
46 using namespace ::com::sun::star::util
;
48 // hmm. No support for regular expression. Well, I always (not really :) wanted to write a finite automat
49 // so here comes a finite automat ...
53 static void lcl_insertStopTransition( StateTransitions
& _rRow
)
55 _rRow
.insert( Transition( '_', END
) );
58 static void lcl_insertStartExponentTransition( StateTransitions
& _rRow
)
60 _rRow
.insert( Transition( 'e', EXPONENT_START
) );
63 static void lcl_insertSignTransitions( StateTransitions
& _rRow
, const State eNextState
)
65 _rRow
.insert( Transition( '-', eNextState
) );
66 _rRow
.insert( Transition( '+', eNextState
) );
69 static void lcl_insertDigitTransitions( StateTransitions
& _rRow
, const State eNextState
)
71 for ( sal_Unicode aChar
= '0'; aChar
<= '9'; ++aChar
)
72 _rRow
.insert( Transition( aChar
, eNextState
) );
75 static void lcl_insertCommonPreCommaTransitions( StateTransitions
& _rRow
, const sal_Unicode _cThSep
, const sal_Unicode _cDecSep
)
78 lcl_insertDigitTransitions( _rRow
, DIGIT_PRE_COMMA
);
80 // the thousand separator is allowed
81 _rRow
.insert( Transition( _cThSep
, DIGIT_PRE_COMMA
) );
84 _rRow
.insert( Transition( _cDecSep
, DIGIT_POST_COMMA
) );
87 NumberValidator::NumberValidator( const sal_Unicode _cThSep
, const sal_Unicode _cDecSep
)
89 // build up our transition table
91 // how to proceed from START
93 StateTransitions
& rRow
= m_aTransitions
[ START
];
94 rRow
.insert( Transition( '_', NUM_START
) );
95 // if we encounter the normalizing character, we want to proceed with the number
98 // how to proceed from NUM_START
100 StateTransitions
& rRow
= m_aTransitions
[ NUM_START
];
103 lcl_insertSignTransitions( rRow
, DIGIT_PRE_COMMA
);
105 // common transitions for the two pre-comma states
106 lcl_insertCommonPreCommaTransitions( rRow
, _cThSep
, _cDecSep
);
108 // the exponent may start here
109 // (this would mean string like "_+e10_", but this is a valid fragment, though no valid number)
110 lcl_insertStartExponentTransition( rRow
);
113 // how to proceed from DIGIT_PRE_COMMA
115 StateTransitions
& rRow
= m_aTransitions
[ DIGIT_PRE_COMMA
];
117 // common transitions for the two pre-comma states
118 lcl_insertCommonPreCommaTransitions( rRow
, _cThSep
, _cDecSep
);
120 // the exponent may start here
121 lcl_insertStartExponentTransition( rRow
);
123 // the final transition indicating the end of the string
124 // (if there is no comma and no post-comma, then the string may end here)
125 lcl_insertStopTransition( rRow
);
128 // how to proceed from DIGIT_POST_COMMA
130 StateTransitions
& rRow
= m_aTransitions
[ DIGIT_POST_COMMA
];
132 // there might be digits, which would keep the state at DIGIT_POST_COMMA
133 lcl_insertDigitTransitions( rRow
, DIGIT_POST_COMMA
);
135 // the exponent may start here
136 lcl_insertStartExponentTransition( rRow
);
138 // the string may end here
139 lcl_insertStopTransition( rRow
);
142 // how to proceed from EXPONENT_START
144 StateTransitions
& rRow
= m_aTransitions
[ EXPONENT_START
];
146 // there may be a sign
147 lcl_insertSignTransitions( rRow
, EXPONENT_DIGIT
);
149 // there may be digits
150 lcl_insertDigitTransitions( rRow
, EXPONENT_DIGIT
);
152 // the string may end here
153 lcl_insertStopTransition( rRow
);
156 // how to proceed from EXPONENT_DIGIT
158 StateTransitions
& rRow
= m_aTransitions
[ EXPONENT_DIGIT
];
160 // there may be digits
161 lcl_insertDigitTransitions( rRow
, EXPONENT_DIGIT
);
163 // the string may end here
164 lcl_insertStopTransition( rRow
);
167 // how to proceed from END
169 /*StateTransitions& rRow =*/ m_aTransitions
[ EXPONENT_DIGIT
];
170 // no valid transition to leave this state
171 // (note that we, for consistency, nevertheless want to have a row in the table)
175 bool NumberValidator::implValidateNormalized( const OUString
& _rText
)
177 const sal_Unicode
* pCheckPos
= _rText
.getStr();
178 State eCurrentState
= START
;
180 while ( END
!= eCurrentState
)
182 // look up the transition row for the current state
183 TransitionTable::const_iterator aRow
= m_aTransitions
.find( eCurrentState
);
184 DBG_ASSERT( m_aTransitions
.end() != aRow
,
185 "NumberValidator::implValidateNormalized: invalid transition table (row not found)!" );
187 if ( m_aTransitions
.end() != aRow
)
189 // look up the current character in this row
190 StateTransitions::const_iterator aTransition
= aRow
->second
.find( *pCheckPos
);
191 if ( aRow
->second
.end() != aTransition
)
193 // there is a valid transition for this character
194 eCurrentState
= aTransition
->second
;
200 // if we're here, there is no valid transition
204 DBG_ASSERT( ( END
!= eCurrentState
) || ( 0 == *pCheckPos
),
205 "NumberValidator::implValidateNormalized: inconsistency!" );
206 // if we're at END, then the string should be done, too - the string should be normalized, means ending
207 // a "_" and not containing any other "_" (except at the start), and "_" is the only possibility
208 // to reach the END state
210 // the string is valid if and only if we reached the final state
211 return ( END
== eCurrentState
);
214 bool NumberValidator::isValidNumericFragment( std::u16string_view _rText
)
216 if ( _rText
.empty() )
217 // empty strings are always allowed
220 // normalize the string
221 OUString sNormalized
= OUString::Concat("_") + _rText
+ "_";
223 return implValidateNormalized( sNormalized
);
227 SvNumberFormatter
* Formatter::StaticFormatter::s_cFormatter
= nullptr;
228 sal_uLong
Formatter::StaticFormatter::s_nReferences
= 0;
230 SvNumberFormatter
* Formatter::StaticFormatter::GetFormatter()
234 // get the Office's locale and translate
235 LanguageType eSysLanguage
= SvtSysLocale().GetLanguageTag().getLanguageType( false);
236 s_cFormatter
= new SvNumberFormatter(
237 ::comphelper::getProcessComponentContext(),
243 Formatter::StaticFormatter::StaticFormatter()
248 Formatter::StaticFormatter::~StaticFormatter()
250 if (--s_nReferences
== 0)
253 s_cFormatter
= nullptr;
257 Formatter::Formatter()
258 :m_aLastSelection(0,0)
263 ,m_bWrapOnLimits(false)
264 ,m_bStrictFormat(true)
265 ,m_bEnableEmptyField(true)
268 ,m_bDisableRemainderFactor(false)
269 ,m_bDefaultValueSet(false)
270 ,m_ValueState(valueDirty
)
274 ,m_pFormatter(nullptr)
276 ,m_dSpinFirst(-1000000)
277 ,m_dSpinLast(1000000)
278 ,m_bTreatAsNumber(true)
279 ,m_pLastOutputColor(nullptr)
280 ,m_bUseInputStringForFormatting(false)
284 Formatter::~Formatter()
288 void Formatter::SetFieldText(const OUString
& rStr
, const Selection
& rNewSelection
)
290 SetEntryText(rStr
, rNewSelection
);
291 m_ValueState
= valueDirty
;
294 void Formatter::SetTextFormatted(const OUString
& rStr
)
296 SAL_INFO_IF(GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
), "svtools",
297 "FormattedField::SetTextFormatted : valid only with text formats !");
299 m_sCurrentTextValue
= rStr
;
302 double dNumber
= 0.0;
303 // IsNumberFormat changes the format key parameter
304 sal_uInt32 nTempFormatKey
= static_cast< sal_uInt32
>( m_nFormatKey
);
305 if( IsUsingInputStringForFormatting() &&
306 GetOrCreateFormatter()->IsNumberFormat(m_sCurrentTextValue
, nTempFormatKey
, dNumber
) )
308 GetOrCreateFormatter()->GetInputLineString(dNumber
, m_nFormatKey
, sFormatted
);
312 GetOrCreateFormatter()->GetOutputString(m_sCurrentTextValue
,
315 &m_pLastOutputColor
);
318 // calculate the new selection
319 Selection
aSel(GetEntrySelection());
320 Selection
aNewSel(aSel
);
322 sal_Int32 nNewLen
= sFormatted
.getLength();
323 sal_Int32 nCurrentLen
= GetEntryText().getLength();
324 if ((nNewLen
> nCurrentLen
) && (aNewSel
.Max() == nCurrentLen
))
325 { // the new text is longer and the cursor was behind the last char (of the old text)
326 if (aNewSel
.Min() == 0)
327 { // the whole text was selected -> select the new text on the whole, too
328 aNewSel
.Max() = nNewLen
;
330 { // there wasn't really a previous selection (as there was no previous text), we're setting a new one -> check the selection options
331 SelectionOptions nSelOptions
= GetEntrySelectionOptions();
332 if (nSelOptions
& SelectionOptions::ShowFirst
)
333 { // selection should be from right to left -> swap min and max
334 aNewSel
.Min() = aNewSel
.Max();
339 else if (aNewSel
.Max() == aNewSel
.Min())
340 { // there was no selection -> set the cursor behind the new last char
341 aNewSel
.Max() = nNewLen
;
342 aNewSel
.Min() = nNewLen
;
345 else if (aNewSel
.Max() > nNewLen
)
346 aNewSel
.Max() = nNewLen
;
348 aNewSel
= aSel
; // don't use the justified version
349 SetEntryText(sFormatted
, aNewSel
);
350 m_ValueState
= valueString
;
353 OUString
const & Formatter::GetTextValue() const
355 if (m_ValueState
!= valueString
)
357 const_cast<Formatter
*>(this)->m_sCurrentTextValue
= GetEntryText();
358 const_cast<Formatter
*>(this)->m_ValueState
= valueString
;
360 return m_sCurrentTextValue
;
363 void Formatter::EnableNotANumber(bool _bEnable
)
365 if ( m_bEnableNaN
== _bEnable
)
368 m_bEnableNaN
= _bEnable
;
371 void Formatter::SetAutoColor(bool _bAutomatic
)
373 if (_bAutomatic
== m_bAutoColor
)
376 m_bAutoColor
= _bAutomatic
;
379 // if auto color is switched on, adjust the current text color, too
380 SetEntryTextColor(m_pLastOutputColor
);
384 void Formatter::Modify(bool makeValueDirty
)
386 if (!IsStrictFormat())
389 m_ValueState
= valueDirty
;
394 OUString sCheck
= GetEntryText();
395 if (CheckText(sCheck
))
397 m_sLastValidText
= sCheck
;
398 m_aLastSelection
= GetEntrySelection();
400 m_ValueState
= valueDirty
;
404 ImplSetTextImpl(m_sLastValidText
, &m_aLastSelection
);
410 void Formatter::ImplSetTextImpl(const OUString
& rNew
, Selection
const * pNewSel
)
413 SetEntryTextColor(m_pLastOutputColor
);
416 SetEntryText(rNew
, *pNewSel
);
419 Selection
aSel(GetEntrySelection());
422 sal_Int32 nNewLen
= rNew
.getLength();
423 sal_Int32 nCurrentLen
= GetEntryText().getLength();
425 if ((nNewLen
> nCurrentLen
) && (aSel
.Max() == nCurrentLen
))
426 { // new text is longer and the cursor is behind the last char
430 { // there wasn't really a previous selection (as there was no previous text)
434 { // the whole text was selected -> select the new text on the whole, too
435 aSel
.Max() = nNewLen
;
438 else if (aSel
.Max() == aSel
.Min())
439 { // there was no selection -> set the cursor behind the new last char
440 aSel
.Max() = nNewLen
;
441 aSel
.Min() = nNewLen
;
444 else if (aSel
.Max() > nNewLen
)
445 aSel
.Max() = nNewLen
;
446 SetEntryText(rNew
, aSel
);
449 m_ValueState
= valueDirty
; // not always necessary, but better re-evaluate for safety reasons
452 void Formatter::ImplSetFormatKey(sal_uLong nFormatKey
)
454 m_nFormatKey
= nFormatKey
;
455 bool bNeedFormatter
= (m_pFormatter
== nullptr) && (nFormatKey
!= 0);
458 GetOrCreateFormatter(); // this creates a standard formatter
460 // It might happen that the standard formatter makes no sense here, but it takes a default
461 // format. Thus, it is possible to set one of the other standard keys (which are spanning
462 // across multiple formatters).
463 m_nFormatKey
= nFormatKey
;
464 // When calling SetFormatKey without a formatter, the key must be one of the standard values
465 // that is available for all formatters (and, thus, also in this new one).
466 DBG_ASSERT(m_pFormatter
->GetEntry(nFormatKey
) != nullptr, "FormattedField::ImplSetFormatKey : invalid format key !");
470 void Formatter::SetFormatKey(sal_uLong nFormatKey
)
472 bool bNoFormatter
= (m_pFormatter
== nullptr);
473 ImplSetFormatKey(nFormatKey
);
474 FormatChanged((bNoFormatter
&& (m_pFormatter
!= nullptr)) ? FORMAT_CHANGE_TYPE::FORMATTER
: FORMAT_CHANGE_TYPE::KEYONLY
);
477 void Formatter::SetFormatter(SvNumberFormatter
* pFormatter
, bool bResetFormat
)
482 m_pFormatter
= pFormatter
;
484 // calc the default format key from the Office's UI locale
487 // get the Office's locale and translate
488 LanguageType eSysLanguage
= SvtSysLocale().GetLanguageTag().getLanguageType( false);
489 // get the standard numeric format for this language
490 m_nFormatKey
= m_pFormatter
->GetStandardFormat( SvNumFormatType::NUMBER
, eSysLanguage
);
497 LanguageType aOldLang
;
498 OUString sOldFormat
= GetFormat(aOldLang
);
500 sal_uInt32 nDestKey
= pFormatter
->TestNewString(sOldFormat
);
501 if (nDestKey
== NUMBERFORMAT_ENTRY_NOT_FOUND
)
503 // language of the new formatter
504 const SvNumberformat
* pDefaultEntry
= pFormatter
->GetEntry(0);
505 LanguageType aNewLang
= pDefaultEntry
? pDefaultEntry
->GetLanguage() : LANGUAGE_DONTKNOW
;
507 // convert the old format string into the new language
509 SvNumFormatType nType
;
510 pFormatter
->PutandConvertEntry(sOldFormat
, nCheckPos
, nType
, nDestKey
, aOldLang
, aNewLang
, true);
511 m_nFormatKey
= nDestKey
;
513 m_pFormatter
= pFormatter
;
516 FormatChanged(FORMAT_CHANGE_TYPE::FORMATTER
);
519 OUString
Formatter::GetFormat(LanguageType
& eLang
) const
521 const SvNumberformat
* pFormatEntry
= GetOrCreateFormatter()->GetEntry(m_nFormatKey
);
522 DBG_ASSERT(pFormatEntry
!= nullptr, "FormattedField::GetFormat: no number format for the given format key.");
523 OUString sFormatString
= pFormatEntry
? pFormatEntry
->GetFormatstring() : OUString();
524 eLang
= pFormatEntry
? pFormatEntry
->GetLanguage() : LANGUAGE_DONTKNOW
;
526 return sFormatString
;
529 bool Formatter::SetFormat(const OUString
& rFormatString
, LanguageType eLang
)
531 sal_uInt32 nNewKey
= GetOrCreateFormatter()->TestNewString(rFormatString
, eLang
);
532 if (nNewKey
== NUMBERFORMAT_ENTRY_NOT_FOUND
)
535 SvNumFormatType nType
;
536 OUString
rFormat(rFormatString
);
537 if (!GetOrCreateFormatter()->PutEntry(rFormat
, nCheckPos
, nType
, nNewKey
, eLang
))
539 DBG_ASSERT(nNewKey
!= NUMBERFORMAT_ENTRY_NOT_FOUND
, "FormattedField::SetFormatString : PutEntry returned an invalid key !");
542 if (nNewKey
!= m_nFormatKey
)
543 SetFormatKey(nNewKey
);
547 bool Formatter::GetThousandsSep() const
549 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
550 "FormattedField::GetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
552 bool bThousand
, IsRed
;
553 sal_uInt16 nPrecision
, nLeadingCnt
;
554 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
559 void Formatter::SetThousandsSep(bool _bUseSeparator
)
561 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
562 "FormattedField::SetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
564 // get the current settings
565 bool bThousand
, IsRed
;
566 sal_uInt16 nPrecision
, nLeadingCnt
;
567 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
568 if (bThousand
== _bUseSeparator
)
571 // we need the language for the following
575 // generate a new format ...
576 OUString sFmtDescription
= GetOrCreateFormatter()->GenerateFormat(m_nFormatKey
, eLang
, _bUseSeparator
, IsRed
, nPrecision
, nLeadingCnt
);
577 // ... and introduce it to the formatter
578 sal_Int32 nCheckPos
= 0;
580 SvNumFormatType nType
;
581 GetOrCreateFormatter()->PutEntry(sFmtDescription
, nCheckPos
, nType
, nNewKey
, eLang
);
584 ImplSetFormatKey(nNewKey
);
585 FormatChanged(FORMAT_CHANGE_TYPE::THOUSANDSSEP
);
588 sal_uInt16
Formatter::GetDecimalDigits() const
590 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
591 "FormattedField::GetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
593 bool bThousand
, IsRed
;
594 sal_uInt16 nPrecision
, nLeadingCnt
;
595 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
600 void Formatter::SetDecimalDigits(sal_uInt16 _nPrecision
)
602 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
603 "FormattedField::SetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
605 // get the current settings
606 bool bThousand
, IsRed
;
607 sal_uInt16 nPrecision
, nLeadingCnt
;
608 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
609 if (nPrecision
== _nPrecision
)
612 // we need the language for the following
616 // generate a new format ...
617 OUString sFmtDescription
= GetOrCreateFormatter()->GenerateFormat(m_nFormatKey
, eLang
, bThousand
, IsRed
, _nPrecision
, nLeadingCnt
);
618 // ... and introduce it to the formatter
619 sal_Int32 nCheckPos
= 0;
621 SvNumFormatType nType
;
622 GetOrCreateFormatter()->PutEntry(sFmtDescription
, nCheckPos
, nType
, nNewKey
, eLang
);
625 ImplSetFormatKey(nNewKey
);
626 FormatChanged(FORMAT_CHANGE_TYPE::PRECISION
);
629 void Formatter::FormatChanged(FORMAT_CHANGE_TYPE _nWhat
)
631 m_pLastOutputColor
= nullptr;
633 if ( (_nWhat
== FORMAT_CHANGE_TYPE::FORMATTER
) && m_pFormatter
)
634 m_pFormatter
->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL
);
639 void Formatter::EntryLostFocus()
641 // special treatment for empty texts
642 if (GetEntryText().isEmpty())
644 if (!IsEmptyFieldEnabled())
646 if (TreatingAsNumber())
648 ImplSetValue(m_dCurrentValue
, true);
650 m_ValueState
= valueDouble
;
654 OUString sNew
= GetTextValue();
656 SetTextFormatted(sNew
);
658 SetTextFormatted(m_sDefaultText
);
659 m_ValueState
= valueString
;
669 void Formatter::Commit()
671 // remember the old text
672 OUString
sOld(GetEntryText());
677 // did the text change?
678 if (GetEntryText() != sOld
)
679 { // consider the field as modified,
680 // but we already have the most recent value;
681 // don't reparse it from the text
682 // (can lead to data loss when the format is lossy,
683 // as is e.g. our default date format: 2-digit year!)
688 void Formatter::ReFormat()
690 if (!IsEmptyFieldEnabled() || !GetEntryText().isEmpty())
692 if (TreatingAsNumber())
694 double dValue
= GetValue();
695 if ( m_bEnableNaN
&& std::isnan( dValue
) )
697 ImplSetValue( dValue
, true );
700 SetTextFormatted(GetTextValue());
704 void Formatter::SetMinValue(double dMin
)
706 DBG_ASSERT(m_bTreatAsNumber
, "FormattedField::SetMinValue : only to be used in numeric mode !");
710 // for checking the current value at the new border -> ImplSetValue
714 void Formatter::SetMaxValue(double dMax
)
716 DBG_ASSERT(m_bTreatAsNumber
, "FormattedField::SetMaxValue : only to be used in numeric mode !");
720 // for checking the current value at the new border -> ImplSetValue
724 void Formatter::SetTextValue(const OUString
& rText
)
726 SetFieldText(rText
, Selection(0, 0));
730 void Formatter::EnableEmptyField(bool bEnable
)
732 if (bEnable
== m_bEnableEmptyField
)
735 m_bEnableEmptyField
= bEnable
;
736 if (!m_bEnableEmptyField
&& GetEntryText().isEmpty())
737 ImplSetValue(m_dCurrentValue
, true);
740 void Formatter::ImplSetValue(double dVal
, bool bForce
)
742 if (m_bHasMin
&& (dVal
<m_dMinValue
))
744 dVal
= m_bWrapOnLimits
? fmod(dVal
+ m_dMaxValue
+ 1 - m_dMinValue
, m_dMaxValue
+ 1) + m_dMinValue
747 if (m_bHasMax
&& (dVal
>m_dMaxValue
))
749 dVal
= m_bWrapOnLimits
? fmod(dVal
- m_dMinValue
, m_dMaxValue
+ 1) + m_dMinValue
752 if (!bForce
&& (dVal
== GetValue()))
755 DBG_ASSERT(GetOrCreateFormatter() != nullptr, "FormattedField::ImplSetValue : can't set a value without a formatter !");
757 m_ValueState
= valueDouble
;
758 UpdateCurrentValue(dVal
);
760 if (!m_aOutputHdl
.IsSet() || !m_aOutputHdl
.Call(nullptr))
763 if (GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
))
765 // first convert the number as string in standard format
767 GetOrCreateFormatter()->GetOutputString(dVal
, 0, sTemp
, &m_pLastOutputColor
);
768 // then encode the string in the corresponding text format
769 GetOrCreateFormatter()->GetOutputString(sTemp
, m_nFormatKey
, sNewText
, &m_pLastOutputColor
);
773 if( IsUsingInputStringForFormatting())
775 GetOrCreateFormatter()->GetInputLineString(dVal
, m_nFormatKey
, sNewText
);
779 GetOrCreateFormatter()->GetOutputString(dVal
, m_nFormatKey
, sNewText
, &m_pLastOutputColor
);
782 ImplSetTextImpl(sNewText
, nullptr);
783 DBG_ASSERT(CheckText(sNewText
), "FormattedField::ImplSetValue : formatted string doesn't match the criteria !");
786 m_ValueState
= valueDouble
;
789 bool Formatter::ImplGetValue(double& dNewVal
)
791 dNewVal
= m_dCurrentValue
;
792 if (m_ValueState
== valueDouble
)
795 // tdf#155241 default to m_dDefaultValue only if explicitly set
796 // otherwise default to m_dCurrentValue
797 if (m_bDefaultValueSet
)
798 dNewVal
= m_dDefaultValue
;
800 OUString
sText(GetEntryText());
804 bool bUseExternalFormatterValue
= false;
805 if (m_aInputHdl
.IsSet())
808 auto eState
= m_aInputHdl
.Call(&nResult
);
809 bUseExternalFormatterValue
= eState
!= TRISTATE_INDET
;
810 if (bUseExternalFormatterValue
)
812 if (eState
== TRISTATE_TRUE
)
815 dNewVal
/= weld::SpinButton::Power10(GetDecimalDigits());
818 dNewVal
= m_dCurrentValue
;
822 if (!bUseExternalFormatterValue
)
824 DBG_ASSERT(GetOrCreateFormatter() != nullptr, "FormattedField::ImplGetValue : can't give you a current value without a formatter !");
826 sal_uInt32 nFormatKey
= m_nFormatKey
; // IsNumberFormat changes the FormatKey!
828 if (GetOrCreateFormatter()->IsTextFormat(nFormatKey
) && m_bTreatAsNumber
)
829 // for detection of values like "1,1" in fields that are formatted as text
832 // special treatment for percentage formatting
833 if (GetOrCreateFormatter()->GetType(m_nFormatKey
) == SvNumFormatType::PERCENT
)
835 // the language of our format
836 LanguageType eLanguage
= m_pFormatter
->GetEntry(m_nFormatKey
)->GetLanguage();
837 // the default number format for this language
838 sal_uLong nStandardNumericFormat
= m_pFormatter
->GetStandardFormat(SvNumFormatType::NUMBER
, eLanguage
);
840 sal_uInt32 nTempFormat
= nStandardNumericFormat
;
842 if (m_pFormatter
->IsNumberFormat(sText
, nTempFormat
, dTemp
) &&
843 SvNumFormatType::NUMBER
== m_pFormatter
->GetType(nTempFormat
))
844 // the string is equivalent to a number formatted one (has no % sign) -> append it
846 // (with this, an input of '3' becomes '3%', which then by the formatter is translated
847 // into 0.03. Without this, the formatter would give us the double 3 for an input '3',
848 // which equals 300 percent.
850 if (!GetOrCreateFormatter()->IsNumberFormat(sText
, nFormatKey
, dNewVal
))
854 if (m_bHasMin
&& (dNewVal
<m_dMinValue
))
855 dNewVal
= m_dMinValue
;
856 if (m_bHasMax
&& (dNewVal
>m_dMaxValue
))
857 dNewVal
= m_dMaxValue
;
861 void Formatter::SetValue(double dVal
)
863 ImplSetValue(dVal
, m_ValueState
!= valueDouble
);
866 double Formatter::GetValue()
868 if ( !ImplGetValue( m_dCurrentValue
) )
869 UpdateCurrentValue(m_bEnableNaN
? std::numeric_limits
<double>::quiet_NaN() : m_dDefaultValue
);
871 m_ValueState
= valueDouble
;
872 return m_dCurrentValue
;
875 void Formatter::DisableRemainderFactor()
877 m_bDisableRemainderFactor
= true;
880 void Formatter::UseInputStringForFormatting()
882 m_bUseInputStringForFormatting
= true;
887 class FieldFormatter
: public Formatter
890 FormattedField
& m_rSpinButton
;
892 FieldFormatter(FormattedField
& rSpinButton
)
893 : m_rSpinButton(rSpinButton
)
897 // Formatter overrides
898 virtual Selection
GetEntrySelection() const override
900 return m_rSpinButton
.GetSelection();
903 virtual OUString
GetEntryText() const override
905 return m_rSpinButton
.GetText();
908 void SetEntryText(const OUString
& rText
, const Selection
& rSel
) override
910 m_rSpinButton
.SpinField::SetText(rText
, rSel
);
913 virtual void SetEntryTextColor(const ::Color
* pColor
) override
916 m_rSpinButton
.SetControlForeground(*pColor
);
918 m_rSpinButton
.SetControlForeground();
921 virtual SelectionOptions
GetEntrySelectionOptions() const override
923 return m_rSpinButton
.GetSettings().GetStyleSettings().GetSelectionOptions();
926 virtual void FieldModified() override
928 m_rSpinButton
.SpinField::Modify();
931 virtual void UpdateCurrentValue(double dCurrentValue
) override
933 Formatter::UpdateCurrentValue(dCurrentValue
);
934 m_rSpinButton
.SetUpperEnabled(!m_bHasMax
|| dCurrentValue
< m_dMaxValue
);
935 m_rSpinButton
.SetLowerEnabled(!m_bHasMin
|| dCurrentValue
> m_dMinValue
);
939 class DoubleNumericFormatter
: public FieldFormatter
942 DoubleNumericField
& m_rNumericSpinButton
;
944 DoubleNumericFormatter(DoubleNumericField
& rNumericSpinButton
)
945 : FieldFormatter(rNumericSpinButton
)
946 , m_rNumericSpinButton(rNumericSpinButton
)
950 virtual bool CheckText(const OUString
& sText
) const override
952 // We'd like to implement this using the NumberFormatter::IsNumberFormat, but unfortunately, this doesn't
953 // recognize fragments of numbers (like, for instance "1e", which happens during entering e.g. "1e10")
954 // Thus, the roundabout way via a regular expression
955 return m_rNumericSpinButton
.GetNumberValidator().isValidNumericFragment(sText
);
958 virtual void FormatChanged(FORMAT_CHANGE_TYPE nWhat
) override
960 m_rNumericSpinButton
.ResetConformanceTester();
961 FieldFormatter::FormatChanged(nWhat
);
965 class DoubleCurrencyFormatter
: public FieldFormatter
968 DoubleCurrencyField
& m_rCurrencySpinButton
;
969 bool m_bChangingFormat
;
971 DoubleCurrencyFormatter(DoubleCurrencyField
& rNumericSpinButton
)
972 : FieldFormatter(rNumericSpinButton
)
973 , m_rCurrencySpinButton(rNumericSpinButton
)
974 , m_bChangingFormat(false)
978 virtual void FormatChanged(FORMAT_CHANGE_TYPE nWhat
) override
980 if (m_bChangingFormat
)
982 FieldFormatter::FormatChanged(nWhat
);
988 case FORMAT_CHANGE_TYPE::FORMATTER
:
989 case FORMAT_CHANGE_TYPE::PRECISION
:
990 case FORMAT_CHANGE_TYPE::THOUSANDSSEP
:
991 // the aspects which changed don't take our currency settings into account (in fact, they most probably
993 m_rCurrencySpinButton
.UpdateCurrencyFormat();
995 case FORMAT_CHANGE_TYPE::KEYONLY
:
996 OSL_FAIL("DoubleCurrencyField::FormatChanged : somebody modified my key !");
997 // We always build our own format from the settings we get via special methods (setCurrencySymbol etc.).
998 // Nobody but ourself should modify the format key directly!
1003 FieldFormatter::FormatChanged(nWhat
);
1006 void GuardSetFormat(const OUString
& rString
, LanguageType eLanguage
)
1008 // set this new basic format
1009 m_bChangingFormat
= true;
1010 SetFormat(rString
, eLanguage
);
1011 m_bChangingFormat
= false;
1017 DoubleNumericField::DoubleNumericField(vcl::Window
* pParent
, WinBits nStyle
)
1018 : FormattedField(pParent
, nStyle
)
1020 m_xOwnFormatter
.reset(new DoubleNumericFormatter(*this));
1021 m_pFormatter
= m_xOwnFormatter
.get();
1022 ResetConformanceTester();
1025 DoubleNumericField::~DoubleNumericField() = default;
1027 void DoubleNumericField::ResetConformanceTester()
1029 // the thousands and the decimal separator are language dependent
1030 Formatter
& rFormatter
= GetFormatter();
1031 const SvNumberformat
* pFormatEntry
= rFormatter
.GetOrCreateFormatter()->GetEntry(rFormatter
.GetFormatKey());
1033 sal_Unicode cSeparatorThousand
= ',';
1034 sal_Unicode cSeparatorDecimal
= '.';
1037 LocaleDataWrapper
aLocaleInfo( LanguageTag( pFormatEntry
->GetLanguage()) );
1039 OUString sSeparator
= aLocaleInfo
.getNumThousandSep();
1040 if (!sSeparator
.isEmpty())
1041 cSeparatorThousand
= sSeparator
[0];
1043 sSeparator
= aLocaleInfo
.getNumDecimalSep();
1044 if (!sSeparator
.isEmpty())
1045 cSeparatorDecimal
= sSeparator
[0];
1048 m_pNumberValidator
.reset(new validation::NumberValidator( cSeparatorThousand
, cSeparatorDecimal
));
1052 DoubleCurrencyField::DoubleCurrencyField(vcl::Window
* pParent
, WinBits nStyle
)
1053 :FormattedField(pParent
, nStyle
)
1055 m_xOwnFormatter
.reset(new DoubleCurrencyFormatter(*this));
1056 m_pFormatter
= m_xOwnFormatter
.get();
1058 m_bPrependCurrSym
= false;
1060 // initialize with a system currency format
1061 m_sCurrencySymbol
= SvtSysLocale().GetLocaleData().getCurrSymbol();
1062 UpdateCurrencyFormat();
1065 void DoubleCurrencyField::setCurrencySymbol(const OUString
& rSymbol
)
1067 if (m_sCurrencySymbol
== rSymbol
)
1070 m_sCurrencySymbol
= rSymbol
;
1071 UpdateCurrencyFormat();
1072 m_pFormatter
->FormatChanged(FORMAT_CHANGE_TYPE::CURRENCY_SYMBOL
);
1075 void DoubleCurrencyField::setPrependCurrSym(bool _bPrepend
)
1077 if (m_bPrependCurrSym
== _bPrepend
)
1080 m_bPrependCurrSym
= _bPrepend
;
1081 UpdateCurrencyFormat();
1082 m_pFormatter
->FormatChanged(FORMAT_CHANGE_TYPE::CURRSYM_POSITION
);
1085 void DoubleCurrencyField::UpdateCurrencyFormat()
1088 LanguageType eLanguage
;
1089 m_pFormatter
->GetFormat(eLanguage
);
1090 bool bThSep
= m_pFormatter
->GetThousandsSep();
1091 sal_uInt16 nDigits
= m_pFormatter
->GetDecimalDigits();
1093 // build a new format string with the base class' and my own settings
1095 /* Strangely with gcc 4.6.3 this needs a temporary LanguageTag, otherwise
1097 * error: request for member 'getNumThousandSep' in 'aLocaleInfo', which is
1098 * of non-class type 'LocaleDataWrapper(LanguageTag)' */
1099 LocaleDataWrapper
aLocaleInfo(( LanguageTag(eLanguage
) ));
1101 OUStringBuffer sNewFormat
;
1104 sNewFormat
.append("#" + aLocaleInfo
.getNumThousandSep() + "##0");
1107 sNewFormat
.append('0');
1111 sNewFormat
.append(aLocaleInfo
.getNumDecimalSep());
1112 comphelper::string::padToLength(sNewFormat
, sNewFormat
.getLength() + nDigits
, '0');
1115 if (getPrependCurrSym())
1117 OUString sSymbol
= getCurrencySymbol();
1118 sSymbol
= comphelper::string::strip(sSymbol
, ' ');
1121 "[$" + sSymbol
+ "] "
1123 // for negative values : $ -0.00, not -$ 0.00...
1124 // (the real solution would be a possibility to choose a "positive currency format" and a "negative currency format"...
1125 // But not now... (and hey, you could take a formatted field for this...))
1126 // FS - 31.03.00 74642
1136 OUString sTemp
= getCurrencySymbol();
1137 sTemp
= comphelper::string::strip(sTemp
, ' ');
1139 sNewFormat
.append(" [$" + sTemp
+ "]");
1142 // set this new basic format
1143 static_cast<DoubleCurrencyFormatter
*>(m_pFormatter
)->GuardSetFormat(sNewFormat
.makeStringAndClear(), eLanguage
);
1146 FormattedField::FormattedField(vcl::Window
* pParent
, WinBits nStyle
)
1147 : SpinField(pParent
, nStyle
, WindowType::FORMATTEDFIELD
)
1148 , m_pFormatter(nullptr)
1152 void FormattedField::dispose()
1154 m_pFormatter
= nullptr;
1155 m_xOwnFormatter
.reset();
1156 SpinField::dispose();
1159 void FormattedField::SetText(const OUString
& rStr
)
1161 GetFormatter().SetFieldText(rStr
, Selection(0, 0));
1164 void FormattedField::SetText(const OUString
& rStr
, const Selection
& rNewSelection
)
1166 GetFormatter().SetFieldText(rStr
, rNewSelection
);
1167 SetSelection(rNewSelection
);
1170 bool FormattedField::set_property(const OUString
&rKey
, const OUString
&rValue
)
1172 if (rKey
== "digits")
1173 GetFormatter().SetDecimalDigits(rValue
.toInt32());
1174 else if (rKey
== "wrap")
1175 GetFormatter().SetWrapOnLimits(toBool(rValue
));
1177 return SpinField::set_property(rKey
, rValue
);
1181 void FormattedField::Up()
1183 Formatter
& rFormatter
= GetFormatter();
1184 auto nScale
= weld::SpinButton::Power10(rFormatter
.GetDecimalDigits());
1186 sal_Int64 nValue
= std::round(rFormatter
.GetValue() * nScale
);
1187 sal_Int64 nSpinSize
= std::round(rFormatter
.GetSpinSize() * nScale
);
1188 assert(nSpinSize
!= 0);
1189 sal_Int64 nRemainder
= rFormatter
.GetDisableRemainderFactor() || nSpinSize
== 0 ? 0 : nValue
% nSpinSize
;
1191 nValue
= (nRemainder
== 0) ? nValue
+ nSpinSize
: nValue
+ nSpinSize
- nRemainder
;
1193 nValue
= (nRemainder
== 0) ? nValue
+ nSpinSize
: nValue
- nRemainder
;
1195 // setValue handles under- and overflows (min/max) automatically
1196 rFormatter
.SetValue(static_cast<double>(nValue
) / nScale
);
1203 void FormattedField::Down()
1205 Formatter
& rFormatter
= GetFormatter();
1206 auto nScale
= weld::SpinButton::Power10(rFormatter
.GetDecimalDigits());
1208 sal_Int64 nValue
= std::round(rFormatter
.GetValue() * nScale
);
1209 sal_Int64 nSpinSize
= std::round(rFormatter
.GetSpinSize() * nScale
);
1210 assert(nSpinSize
!= 0);
1211 sal_Int64 nRemainder
= rFormatter
.GetDisableRemainderFactor() || nSpinSize
== 0 ? 0 : nValue
% nSpinSize
;
1213 nValue
= (nRemainder
== 0) ? nValue
- nSpinSize
: nValue
- nRemainder
;
1215 nValue
= (nRemainder
== 0) ? nValue
- nSpinSize
: nValue
- nSpinSize
- nRemainder
;
1217 // setValue handles under- and overflows (min/max) automatically
1218 rFormatter
.SetValue(static_cast<double>(nValue
) / nScale
);
1225 void FormattedField::First()
1227 Formatter
& rFormatter
= GetFormatter();
1228 if (rFormatter
.HasMinValue())
1230 rFormatter
.SetValue(rFormatter
.GetMinValue());
1238 void FormattedField::Last()
1240 Formatter
& rFormatter
= GetFormatter();
1241 if (rFormatter
.HasMaxValue())
1243 rFormatter
.SetValue(rFormatter
.GetMaxValue());
1251 void FormattedField::Modify()
1253 GetFormatter().Modify();
1256 bool FormattedField::PreNotify(NotifyEvent
& rNEvt
)
1258 if (rNEvt
.GetType() == NotifyEventType::KEYINPUT
)
1259 GetFormatter().SetLastSelection(GetSelection());
1260 return SpinField::PreNotify(rNEvt
);
1263 bool FormattedField::EventNotify(NotifyEvent
& rNEvt
)
1265 if ((rNEvt
.GetType() == NotifyEventType::KEYINPUT
) && !IsReadOnly())
1267 const KeyEvent
& rKEvt
= *rNEvt
.GetKeyEvent();
1268 sal_uInt16 nMod
= rKEvt
.GetKeyCode().GetModifier();
1269 switch ( rKEvt
.GetKeyCode().GetCode() )
1276 Formatter
& rFormatter
= GetFormatter();
1277 if (!nMod
&& rFormatter
.GetOrCreateFormatter()->IsTextFormat(rFormatter
.GetFormatKey()))
1279 // the base class would translate this into calls to Up/Down/First/Last,
1280 // but we don't want this if we are text-formatted
1287 if ((rNEvt
.GetType() == NotifyEventType::COMMAND
) && !IsReadOnly())
1289 const CommandEvent
* pCommand
= rNEvt
.GetCommandEvent();
1290 if (pCommand
->GetCommand() == CommandEventId::Wheel
)
1292 const CommandWheelData
* pData
= rNEvt
.GetCommandEvent()->GetWheelData();
1293 Formatter
& rFormatter
= GetFormatter();
1294 if ((pData
->GetMode() == CommandWheelMode::SCROLL
) &&
1295 rFormatter
.GetOrCreateFormatter()->IsTextFormat(rFormatter
.GetFormatKey()))
1297 // same as above : prevent the base class from doing Up/Down-calls
1298 // (normally I should put this test into the Up/Down methods itself, shouldn't I ?)
1299 // FS - 71553 - 19.01.00
1305 if (rNEvt
.GetType() == NotifyEventType::LOSEFOCUS
&& m_pFormatter
)
1306 m_pFormatter
->EntryLostFocus();
1308 return SpinField::EventNotify( rNEvt
);
1311 Formatter
& FormattedField::GetFormatter()
1315 m_xOwnFormatter
.reset(new FieldFormatter(*this));
1316 m_pFormatter
= m_xOwnFormatter
.get();
1318 return *m_pFormatter
;
1321 void FormattedField::SetFormatter(Formatter
* pFormatter
)
1323 m_xOwnFormatter
.reset();
1324 m_pFormatter
= pFormatter
;
1327 // currently used by online
1328 void FormattedField::SetValueFromString(const OUString
& rStr
)
1331 rtl_math_ConversionStatus eStatus
;
1332 Formatter
& rFormatter
= GetFormatter();
1333 double fValue
= ::rtl::math::stringToDouble(rStr
, '.', rFormatter
.GetDecimalDigits(), &eStatus
, &nEnd
);
1335 if (eStatus
== rtl_math_ConversionStatus_Ok
&&
1336 nEnd
== rStr
.getLength())
1338 rFormatter
.SetValue(fValue
);
1342 // Notify the value has changed
1347 SAL_WARN("vcl", "fail to convert the value: " << rStr
);
1351 void FormattedField::DumpAsPropertyTree(tools::JsonWriter
& rJsonWriter
)
1353 SpinField::DumpAsPropertyTree(rJsonWriter
);
1354 Formatter
& rFormatter
= GetFormatter();
1355 rJsonWriter
.put("min", rFormatter
.GetMinValue());
1356 rJsonWriter
.put("max", rFormatter
.GetMaxValue());
1357 rJsonWriter
.put("value", rFormatter
.GetValue());
1358 rJsonWriter
.put("step", rFormatter
.GetSpinSize());
1361 FactoryFunction
FormattedField::GetUITestFactory() const
1363 return FormattedFieldUIObject::create
;
1366 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */