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/field.hxx>
31 #include <vcl/toolkit/fmtfield.hxx>
32 #include <vcl/uitest/uiobject.hxx>
33 #include <vcl/uitest/formattedfielduiobject.hxx>
34 #include <vcl/weld.hxx>
35 #include <vcl/weldutils.hxx>
36 #include <i18nlangtag/languagetag.hxx>
37 #include <unotools/syslocale.hxx>
40 #include <rtl/math.hxx>
41 #include <rtl/ustrbuf.hxx>
42 #include <sal/log.hxx>
43 #include <svl/numformat.hxx>
44 #include <osl/diagnose.h>
45 #include <tools/json_writer.hxx>
47 using namespace ::com::sun::star::lang
;
49 // hmm. No support for regular expression. Well, I always (not really :) wanted to write a finite automat
50 // so here comes a finite automat ...
54 static void lcl_insertStopTransition( StateTransitions
& _rRow
)
56 _rRow
.insert( Transition( '_', END
) );
59 static void lcl_insertStartExponentTransition( StateTransitions
& _rRow
)
61 _rRow
.insert( Transition( 'e', EXPONENT_START
) );
64 static void lcl_insertSignTransitions( StateTransitions
& _rRow
, const State eNextState
)
66 _rRow
.insert( Transition( '-', eNextState
) );
67 _rRow
.insert( Transition( '+', eNextState
) );
70 static void lcl_insertDigitTransitions( StateTransitions
& _rRow
, const State eNextState
)
72 for ( sal_Unicode aChar
= '0'; aChar
<= '9'; ++aChar
)
73 _rRow
.insert( Transition( aChar
, eNextState
) );
76 static void lcl_insertCommonPreCommaTransitions( StateTransitions
& _rRow
, const sal_Unicode _cThSep
, const sal_Unicode _cDecSep
)
79 lcl_insertDigitTransitions( _rRow
, DIGIT_PRE_COMMA
);
81 // the thousand separator is allowed
82 _rRow
.insert( Transition( _cThSep
, DIGIT_PRE_COMMA
) );
85 _rRow
.insert( Transition( _cDecSep
, DIGIT_POST_COMMA
) );
88 NumberValidator::NumberValidator( const sal_Unicode _cThSep
, const sal_Unicode _cDecSep
)
90 // build up our transition table
92 // how to proceed from START
94 StateTransitions
& rRow
= m_aTransitions
[ START
];
95 rRow
.insert( Transition( '_', NUM_START
) );
96 // if we encounter the normalizing character, we want to proceed with the number
99 // how to proceed from NUM_START
101 StateTransitions
& rRow
= m_aTransitions
[ NUM_START
];
104 lcl_insertSignTransitions( rRow
, DIGIT_PRE_COMMA
);
106 // common transitions for the two pre-comma states
107 lcl_insertCommonPreCommaTransitions( rRow
, _cThSep
, _cDecSep
);
109 // the exponent may start here
110 // (this would mean string like "_+e10_", but this is a valid fragment, though no valid number)
111 lcl_insertStartExponentTransition( rRow
);
114 // how to proceed from DIGIT_PRE_COMMA
116 StateTransitions
& rRow
= m_aTransitions
[ DIGIT_PRE_COMMA
];
118 // common transitions for the two pre-comma states
119 lcl_insertCommonPreCommaTransitions( rRow
, _cThSep
, _cDecSep
);
121 // the exponent may start here
122 lcl_insertStartExponentTransition( rRow
);
124 // the final transition indicating the end of the string
125 // (if there is no comma and no post-comma, then the string may end here)
126 lcl_insertStopTransition( rRow
);
129 // how to proceed from DIGIT_POST_COMMA
131 StateTransitions
& rRow
= m_aTransitions
[ DIGIT_POST_COMMA
];
133 // there might be digits, which would keep the state at DIGIT_POST_COMMA
134 lcl_insertDigitTransitions( rRow
, DIGIT_POST_COMMA
);
136 // the exponent may start here
137 lcl_insertStartExponentTransition( rRow
);
139 // the string may end here
140 lcl_insertStopTransition( rRow
);
143 // how to proceed from EXPONENT_START
145 StateTransitions
& rRow
= m_aTransitions
[ EXPONENT_START
];
147 // there may be a sign
148 lcl_insertSignTransitions( rRow
, EXPONENT_DIGIT
);
150 // there may be digits
151 lcl_insertDigitTransitions( rRow
, EXPONENT_DIGIT
);
153 // the string may end here
154 lcl_insertStopTransition( rRow
);
157 // how to proceed from EXPONENT_DIGIT
159 StateTransitions
& rRow
= m_aTransitions
[ EXPONENT_DIGIT
];
161 // there may be digits
162 lcl_insertDigitTransitions( rRow
, EXPONENT_DIGIT
);
164 // the string may end here
165 lcl_insertStopTransition( rRow
);
168 // how to proceed from END
170 /*StateTransitions& rRow =*/ m_aTransitions
[ EXPONENT_DIGIT
];
171 // no valid transition to leave this state
172 // (note that we, for consistency, nevertheless want to have a row in the table)
176 bool NumberValidator::implValidateNormalized( const OUString
& _rText
)
178 const sal_Unicode
* pCheckPos
= _rText
.getStr();
179 State eCurrentState
= START
;
181 while ( END
!= eCurrentState
)
183 // look up the transition row for the current state
184 TransitionTable::const_iterator aRow
= m_aTransitions
.find( eCurrentState
);
185 DBG_ASSERT( m_aTransitions
.end() != aRow
,
186 "NumberValidator::implValidateNormalized: invalid transition table (row not found)!" );
188 if ( m_aTransitions
.end() != aRow
)
190 // look up the current character in this row
191 StateTransitions::const_iterator aTransition
= aRow
->second
.find( *pCheckPos
);
192 if ( aRow
->second
.end() != aTransition
)
194 // there is a valid transition for this character
195 eCurrentState
= aTransition
->second
;
201 // if we're here, there is no valid transition
205 DBG_ASSERT( ( END
!= eCurrentState
) || ( 0 == *pCheckPos
),
206 "NumberValidator::implValidateNormalized: inconsistency!" );
207 // if we're at END, then the string should be done, too - the string should be normalized, means ending
208 // a "_" and not containing any other "_" (except at the start), and "_" is the only possibility
209 // to reach the END state
211 // the string is valid if and only if we reached the final state
212 return ( END
== eCurrentState
);
215 bool NumberValidator::isValidNumericFragment( std::u16string_view _rText
)
217 if ( _rText
.empty() )
218 // empty strings are always allowed
221 // normalize the string
222 OUString sNormalized
= OUString::Concat("_") + _rText
+ "_";
224 return implValidateNormalized( sNormalized
);
228 SvNumberFormatter
* Formatter::StaticFormatter::s_cFormatter
= nullptr;
229 sal_uLong
Formatter::StaticFormatter::s_nReferences
= 0;
231 SvNumberFormatter
* Formatter::StaticFormatter::GetFormatter()
235 // get the Office's locale and translate
236 LanguageType eSysLanguage
= SvtSysLocale().GetLanguageTag().getLanguageType( false);
237 s_cFormatter
= new SvNumberFormatter(
238 ::comphelper::getProcessComponentContext(),
244 Formatter::StaticFormatter::StaticFormatter()
249 Formatter::StaticFormatter::~StaticFormatter()
251 if (--s_nReferences
== 0)
254 s_cFormatter
= nullptr;
258 Formatter::Formatter()
259 :m_aLastSelection(0,0)
264 ,m_bWrapOnLimits(false)
265 ,m_bStrictFormat(true)
266 ,m_bEnableEmptyField(true)
269 ,m_bDisableRemainderFactor(false)
270 ,m_bDefaultValueSet(false)
271 ,m_ValueState(valueDirty
)
275 ,m_pFormatter(nullptr)
277 ,m_dSpinFirst(-1000000)
278 ,m_dSpinLast(1000000)
279 ,m_bTreatAsNumber(true)
280 ,m_pLastOutputColor(nullptr)
281 ,m_bUseInputStringForFormatting(false)
285 Formatter::~Formatter()
289 void Formatter::SetFieldText(const OUString
& rStr
, const Selection
& rNewSelection
)
291 SetEntryText(rStr
, rNewSelection
);
292 m_ValueState
= valueDirty
;
295 void Formatter::SetTextFormatted(const OUString
& rStr
)
297 SAL_INFO_IF(GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
), "svtools",
298 "FormattedField::SetTextFormatted : valid only with text formats !");
300 m_sCurrentTextValue
= rStr
;
303 double dNumber
= 0.0;
304 // IsNumberFormat changes the format key parameter
305 sal_uInt32 nTempFormatKey
= static_cast< sal_uInt32
>( m_nFormatKey
);
306 if( IsUsingInputStringForFormatting() &&
307 GetOrCreateFormatter()->IsNumberFormat(m_sCurrentTextValue
, nTempFormatKey
, dNumber
) )
309 sFormatted
= GetOrCreateFormatter()->GetInputLineString(dNumber
, m_nFormatKey
);
313 GetOrCreateFormatter()->GetOutputString(m_sCurrentTextValue
,
316 &m_pLastOutputColor
);
319 // calculate the new selection
320 Selection
aSel(GetEntrySelection());
321 Selection
aNewSel(aSel
);
323 sal_Int32 nNewLen
= sFormatted
.getLength();
324 sal_Int32 nCurrentLen
= GetEntryText().getLength();
325 if ((nNewLen
> nCurrentLen
) && (aNewSel
.Max() == nCurrentLen
))
326 { // the new text is longer and the cursor was behind the last char (of the old text)
327 if (aNewSel
.Min() == 0)
328 { // the whole text was selected -> select the new text on the whole, too
329 aNewSel
.Max() = nNewLen
;
331 { // there wasn't really a previous selection (as there was no previous text), we're setting a new one -> check the selection options
332 SelectionOptions nSelOptions
= GetEntrySelectionOptions();
333 if (nSelOptions
& SelectionOptions::ShowFirst
)
334 { // selection should be from right to left -> swap min and max
335 aNewSel
.Min() = aNewSel
.Max();
340 else if (aNewSel
.Max() == aNewSel
.Min())
341 { // there was no selection -> set the cursor behind the new last char
342 aNewSel
.Max() = nNewLen
;
343 aNewSel
.Min() = nNewLen
;
346 else if (aNewSel
.Max() > nNewLen
)
347 aNewSel
.Max() = nNewLen
;
349 aNewSel
= aSel
; // don't use the justified version
350 SetEntryText(sFormatted
, aNewSel
);
351 m_ValueState
= valueString
;
354 OUString
const & Formatter::GetTextValue() const
356 if (m_ValueState
!= valueString
)
358 const_cast<Formatter
*>(this)->m_sCurrentTextValue
= GetEntryText();
359 const_cast<Formatter
*>(this)->m_ValueState
= valueString
;
361 return m_sCurrentTextValue
;
364 void Formatter::EnableNotANumber(bool _bEnable
)
366 if ( m_bEnableNaN
== _bEnable
)
369 m_bEnableNaN
= _bEnable
;
372 void Formatter::SetAutoColor(bool _bAutomatic
)
374 if (_bAutomatic
== m_bAutoColor
)
377 m_bAutoColor
= _bAutomatic
;
380 // if auto color is switched on, adjust the current text color, too
381 SetEntryTextColor(m_pLastOutputColor
);
385 void Formatter::Modify(bool makeValueDirty
)
387 if (!IsStrictFormat())
390 m_ValueState
= valueDirty
;
395 OUString sCheck
= GetEntryText();
396 if (CheckText(sCheck
))
398 m_sLastValidText
= sCheck
;
399 m_aLastSelection
= GetEntrySelection();
401 m_ValueState
= valueDirty
;
405 ImplSetTextImpl(m_sLastValidText
, &m_aLastSelection
);
411 void Formatter::ImplSetTextImpl(const OUString
& rNew
, Selection
const * pNewSel
)
414 SetEntryTextColor(m_pLastOutputColor
);
417 SetEntryText(rNew
, *pNewSel
);
420 Selection
aSel(GetEntrySelection());
423 sal_Int32 nNewLen
= rNew
.getLength();
424 sal_Int32 nCurrentLen
= GetEntryText().getLength();
426 if ((nNewLen
> nCurrentLen
) && (aSel
.Max() == nCurrentLen
))
427 { // new text is longer and the cursor is behind the last char
431 { // there wasn't really a previous selection (as there was no previous text)
435 { // the whole text was selected -> select the new text on the whole, too
436 aSel
.Max() = nNewLen
;
439 else if (aSel
.Max() == aSel
.Min())
440 { // there was no selection -> set the cursor behind the new last char
441 aSel
.Max() = nNewLen
;
442 aSel
.Min() = nNewLen
;
445 else if (aSel
.Max() > nNewLen
)
446 aSel
.Max() = nNewLen
;
447 SetEntryText(rNew
, aSel
);
450 m_ValueState
= valueDirty
; // not always necessary, but better re-evaluate for safety reasons
453 void Formatter::ImplSetFormatKey(sal_uLong nFormatKey
)
455 m_nFormatKey
= nFormatKey
;
456 bool bNeedFormatter
= (m_pFormatter
== nullptr) && (nFormatKey
!= 0);
459 GetOrCreateFormatter(); // this creates a standard formatter
460 assert(m_pFormatter
);
462 // It might happen that the standard formatter makes no sense here, but it takes a default
463 // format. Thus, it is possible to set one of the other standard keys (which are spanning
464 // across multiple formatters).
465 m_nFormatKey
= nFormatKey
;
466 // When calling SetFormatKey without a formatter, the key must be one of the standard values
467 // that is available for all formatters (and, thus, also in this new one).
468 DBG_ASSERT(m_pFormatter
->GetEntry(nFormatKey
) != nullptr, "FormattedField::ImplSetFormatKey : invalid format key !");
472 void Formatter::SetFormatKey(sal_uLong nFormatKey
)
474 bool bNoFormatter
= (m_pFormatter
== nullptr);
475 ImplSetFormatKey(nFormatKey
);
476 FormatChanged((bNoFormatter
&& (m_pFormatter
!= nullptr)) ? FORMAT_CHANGE_TYPE::FORMATTER
: FORMAT_CHANGE_TYPE::KEYONLY
);
479 void Formatter::SetFormatter(SvNumberFormatter
* pFormatter
, bool bResetFormat
)
484 m_pFormatter
= pFormatter
;
486 // calc the default format key from the Office's UI locale
489 // get the Office's locale and translate
490 LanguageType eSysLanguage
= SvtSysLocale().GetLanguageTag().getLanguageType( false);
491 // get the standard numeric format for this language
492 m_nFormatKey
= m_pFormatter
->GetStandardFormat( SvNumFormatType::NUMBER
, eSysLanguage
);
499 LanguageType aOldLang
;
500 OUString sOldFormat
= GetFormat(aOldLang
);
502 sal_uInt32 nDestKey
= pFormatter
->TestNewString(sOldFormat
);
503 if (nDestKey
== NUMBERFORMAT_ENTRY_NOT_FOUND
)
505 // language of the new formatter
506 const SvNumberformat
* pDefaultEntry
= pFormatter
->GetEntry(0);
507 LanguageType aNewLang
= pDefaultEntry
? pDefaultEntry
->GetLanguage() : LANGUAGE_DONTKNOW
;
509 // convert the old format string into the new language
511 SvNumFormatType nType
;
512 pFormatter
->PutandConvertEntry(sOldFormat
, nCheckPos
, nType
, nDestKey
, aOldLang
, aNewLang
, true);
513 m_nFormatKey
= nDestKey
;
515 m_pFormatter
= pFormatter
;
518 FormatChanged(FORMAT_CHANGE_TYPE::FORMATTER
);
521 OUString
Formatter::GetFormat(LanguageType
& eLang
) const
523 const SvNumberformat
* pFormatEntry
= GetOrCreateFormatter()->GetEntry(m_nFormatKey
);
524 DBG_ASSERT(pFormatEntry
!= nullptr, "FormattedField::GetFormat: no number format for the given format key.");
525 OUString sFormatString
= pFormatEntry
? pFormatEntry
->GetFormatstring() : OUString();
526 eLang
= pFormatEntry
? pFormatEntry
->GetLanguage() : LANGUAGE_DONTKNOW
;
528 return sFormatString
;
531 bool Formatter::SetFormat(const OUString
& rFormatString
, LanguageType eLang
)
533 sal_uInt32 nNewKey
= GetOrCreateFormatter()->TestNewString(rFormatString
, eLang
);
534 if (nNewKey
== NUMBERFORMAT_ENTRY_NOT_FOUND
)
537 SvNumFormatType nType
;
538 OUString
rFormat(rFormatString
);
539 if (!GetOrCreateFormatter()->PutEntry(rFormat
, nCheckPos
, nType
, nNewKey
, eLang
))
541 DBG_ASSERT(nNewKey
!= NUMBERFORMAT_ENTRY_NOT_FOUND
, "FormattedField::SetFormatString : PutEntry returned an invalid key !");
544 if (nNewKey
!= m_nFormatKey
)
545 SetFormatKey(nNewKey
);
549 bool Formatter::GetThousandsSep() const
551 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
552 "FormattedField::GetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
554 bool bThousand
, IsRed
;
555 sal_uInt16 nPrecision
, nLeadingCnt
;
556 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
561 void Formatter::SetThousandsSep(bool _bUseSeparator
)
563 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
564 "FormattedField::SetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
566 // get the current settings
567 bool bThousand
, IsRed
;
568 sal_uInt16 nPrecision
, nLeadingCnt
;
569 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
570 if (bThousand
== _bUseSeparator
)
573 // we need the language for the following
577 // generate a new format ...
578 OUString sFmtDescription
= GetOrCreateFormatter()->GenerateFormat(m_nFormatKey
, eLang
, _bUseSeparator
, IsRed
, nPrecision
, nLeadingCnt
);
579 // ... and introduce it to the formatter
580 sal_Int32 nCheckPos
= 0;
582 SvNumFormatType nType
;
583 GetOrCreateFormatter()->PutEntry(sFmtDescription
, nCheckPos
, nType
, nNewKey
, eLang
);
586 ImplSetFormatKey(nNewKey
);
587 FormatChanged(FORMAT_CHANGE_TYPE::THOUSANDSSEP
);
590 sal_uInt16
Formatter::GetDecimalDigits() const
592 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
593 "FormattedField::GetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
595 bool bThousand
, IsRed
;
596 sal_uInt16 nPrecision
, nLeadingCnt
;
597 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
602 void Formatter::SetDecimalDigits(sal_uInt16 _nPrecision
)
604 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
605 "FormattedField::SetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
607 // get the current settings
608 bool bThousand
, IsRed
;
609 sal_uInt16 nPrecision
, nLeadingCnt
;
610 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
611 if (nPrecision
== _nPrecision
)
614 // we need the language for the following
618 // generate a new format ...
619 OUString sFmtDescription
= GetOrCreateFormatter()->GenerateFormat(m_nFormatKey
, eLang
, bThousand
, IsRed
, _nPrecision
, nLeadingCnt
);
620 // ... and introduce it to the formatter
621 sal_Int32 nCheckPos
= 0;
623 SvNumFormatType nType
;
624 GetOrCreateFormatter()->PutEntry(sFmtDescription
, nCheckPos
, nType
, nNewKey
, eLang
);
627 ImplSetFormatKey(nNewKey
);
628 FormatChanged(FORMAT_CHANGE_TYPE::PRECISION
);
631 void Formatter::FormatChanged(FORMAT_CHANGE_TYPE _nWhat
)
633 m_pLastOutputColor
= nullptr;
635 if ( (_nWhat
== FORMAT_CHANGE_TYPE::FORMATTER
) && m_pFormatter
)
636 m_pFormatter
->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL
);
641 void Formatter::EntryLostFocus()
643 // special treatment for empty texts
644 if (GetEntryText().isEmpty())
646 if (!IsEmptyFieldEnabled())
648 if (TreatingAsNumber())
650 ImplSetValue(m_dCurrentValue
, true);
652 m_ValueState
= valueDouble
;
656 OUString sNew
= GetTextValue();
658 SetTextFormatted(sNew
);
660 SetTextFormatted(m_sDefaultText
);
661 m_ValueState
= valueString
;
671 void Formatter::Commit()
673 // remember the old text
674 OUString
sOld(GetEntryText());
679 // did the text change?
680 if (GetEntryText() != sOld
)
681 { // consider the field as modified,
682 // but we already have the most recent value;
683 // don't reparse it from the text
684 // (can lead to data loss when the format is lossy,
685 // as is e.g. our default date format: 2-digit year!)
690 void Formatter::ReFormat()
692 if (!IsEmptyFieldEnabled() || !GetEntryText().isEmpty())
694 if (TreatingAsNumber())
696 double dValue
= GetValue();
697 if ( m_bEnableNaN
&& std::isnan( dValue
) )
699 ImplSetValue( dValue
, true );
702 SetTextFormatted(GetTextValue());
706 void Formatter::SetMinValue(double dMin
)
708 DBG_ASSERT(m_bTreatAsNumber
, "FormattedField::SetMinValue : only to be used in numeric mode !");
712 // for checking the current value at the new border -> ImplSetValue
716 void Formatter::SetMaxValue(double dMax
)
718 DBG_ASSERT(m_bTreatAsNumber
, "FormattedField::SetMaxValue : only to be used in numeric mode !");
722 // for checking the current value at the new border -> ImplSetValue
726 void Formatter::SetTextValue(const OUString
& rText
)
728 SetFieldText(rText
, Selection(0, 0));
732 void Formatter::EnableEmptyField(bool bEnable
)
734 if (bEnable
== m_bEnableEmptyField
)
737 m_bEnableEmptyField
= bEnable
;
738 if (!m_bEnableEmptyField
&& GetEntryText().isEmpty())
739 ImplSetValue(m_dCurrentValue
, true);
742 void Formatter::ImplSetValue(double dVal
, bool bForce
)
744 if (m_bHasMin
&& (dVal
<m_dMinValue
))
746 dVal
= m_bWrapOnLimits
? fmod(dVal
+ m_dMaxValue
+ 1 - m_dMinValue
, m_dMaxValue
+ 1) + m_dMinValue
749 if (m_bHasMax
&& (dVal
>m_dMaxValue
))
751 dVal
= m_bWrapOnLimits
? fmod(dVal
- m_dMinValue
, m_dMaxValue
+ 1) + m_dMinValue
754 if (!bForce
&& (dVal
== GetValue()))
757 DBG_ASSERT(GetOrCreateFormatter() != nullptr, "FormattedField::ImplSetValue : can't set a value without a formatter !");
759 m_ValueState
= valueDouble
;
760 UpdateCurrentValue(dVal
);
762 if (!m_aOutputHdl
.IsSet() || !m_aOutputHdl
.Call(nullptr))
765 if (GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
))
767 // first convert the number as string in standard format
769 GetOrCreateFormatter()->GetOutputString(dVal
, 0, sTemp
, &m_pLastOutputColor
);
770 // then encode the string in the corresponding text format
771 GetOrCreateFormatter()->GetOutputString(sTemp
, m_nFormatKey
, sNewText
, &m_pLastOutputColor
);
775 if( IsUsingInputStringForFormatting())
777 sNewText
= GetOrCreateFormatter()->GetInputLineString(dVal
, m_nFormatKey
);
781 GetOrCreateFormatter()->GetOutputString(dVal
, m_nFormatKey
, sNewText
, &m_pLastOutputColor
);
784 ImplSetTextImpl(sNewText
, nullptr);
785 DBG_ASSERT(CheckText(sNewText
), "FormattedField::ImplSetValue : formatted string doesn't match the criteria !");
788 m_ValueState
= valueDouble
;
791 bool Formatter::ImplGetValue(double& dNewVal
)
793 dNewVal
= m_dCurrentValue
;
794 if (m_ValueState
== valueDouble
)
797 // tdf#155241 default to m_dDefaultValue only if explicitly set
798 // otherwise default to m_dCurrentValue
799 if (m_bDefaultValueSet
)
800 dNewVal
= m_dDefaultValue
;
802 OUString
sText(GetEntryText());
806 bool bUseExternalFormatterValue
= false;
807 if (m_aInputHdl
.IsSet())
810 auto eState
= m_aInputHdl
.Call(&nResult
);
811 bUseExternalFormatterValue
= eState
!= TRISTATE_INDET
;
812 if (bUseExternalFormatterValue
)
814 if (eState
== TRISTATE_TRUE
)
817 dNewVal
/= weld::SpinButton::Power10(GetDecimalDigits());
820 dNewVal
= m_dCurrentValue
;
824 if (!bUseExternalFormatterValue
)
826 DBG_ASSERT(GetOrCreateFormatter() != nullptr, "FormattedField::ImplGetValue : can't give you a current value without a formatter !");
828 sal_uInt32 nFormatKey
= m_nFormatKey
; // IsNumberFormat changes the FormatKey!
830 if (GetOrCreateFormatter()->IsTextFormat(nFormatKey
) && m_bTreatAsNumber
)
831 // for detection of values like "1,1" in fields that are formatted as text
834 // special treatment for percentage formatting
835 if (GetOrCreateFormatter()->GetType(m_nFormatKey
) == SvNumFormatType::PERCENT
)
837 // the language of our format
838 LanguageType eLanguage
= m_pFormatter
->GetEntry(m_nFormatKey
)->GetLanguage();
839 // the default number format for this language
840 sal_uLong nStandardNumericFormat
= m_pFormatter
->GetStandardFormat(SvNumFormatType::NUMBER
, eLanguage
);
842 sal_uInt32 nTempFormat
= nStandardNumericFormat
;
844 if (m_pFormatter
->IsNumberFormat(sText
, nTempFormat
, dTemp
) &&
845 SvNumFormatType::NUMBER
== m_pFormatter
->GetType(nTempFormat
))
846 // the string is equivalent to a number formatted one (has no % sign) -> append it
848 // (with this, an input of '3' becomes '3%', which then by the formatter is translated
849 // into 0.03. Without this, the formatter would give us the double 3 for an input '3',
850 // which equals 300 percent.
852 if (!GetOrCreateFormatter()->IsNumberFormat(sText
, nFormatKey
, dNewVal
))
856 if (m_bHasMin
&& (dNewVal
<m_dMinValue
))
857 dNewVal
= m_dMinValue
;
858 if (m_bHasMax
&& (dNewVal
>m_dMaxValue
))
859 dNewVal
= m_dMaxValue
;
863 void Formatter::SetValue(double dVal
)
865 ImplSetValue(dVal
, m_ValueState
!= valueDouble
);
868 double Formatter::GetValue()
870 if ( !ImplGetValue( m_dCurrentValue
) )
871 UpdateCurrentValue(m_bEnableNaN
? std::numeric_limits
<double>::quiet_NaN() : m_dDefaultValue
);
873 m_ValueState
= valueDouble
;
874 return m_dCurrentValue
;
877 void Formatter::DisableRemainderFactor()
879 m_bDisableRemainderFactor
= true;
882 void Formatter::UseInputStringForFormatting()
884 m_bUseInputStringForFormatting
= true;
889 class FieldFormatter
: public Formatter
892 FormattedField
& m_rSpinButton
;
894 FieldFormatter(FormattedField
& rSpinButton
)
895 : m_rSpinButton(rSpinButton
)
899 // Formatter overrides
900 virtual Selection
GetEntrySelection() const override
902 return m_rSpinButton
.GetSelection();
905 virtual OUString
GetEntryText() const override
907 return m_rSpinButton
.GetText();
910 void SetEntryText(const OUString
& rText
, const Selection
& rSel
) override
912 m_rSpinButton
.SpinField::SetText(rText
, rSel
);
915 virtual void SetEntryTextColor(const ::Color
* pColor
) override
918 m_rSpinButton
.SetControlForeground(*pColor
);
920 m_rSpinButton
.SetControlForeground();
923 virtual SelectionOptions
GetEntrySelectionOptions() const override
925 return m_rSpinButton
.GetSettings().GetStyleSettings().GetSelectionOptions();
928 virtual void FieldModified() override
930 m_rSpinButton
.SpinField::Modify();
933 virtual void UpdateCurrentValue(double dCurrentValue
) override
935 Formatter::UpdateCurrentValue(dCurrentValue
);
936 m_rSpinButton
.SetUpperEnabled(!m_bHasMax
|| dCurrentValue
< m_dMaxValue
);
937 m_rSpinButton
.SetLowerEnabled(!m_bHasMin
|| dCurrentValue
> m_dMinValue
);
941 class DoubleNumericFormatter
: public FieldFormatter
944 DoubleNumericField
& m_rNumericSpinButton
;
946 DoubleNumericFormatter(DoubleNumericField
& rNumericSpinButton
)
947 : FieldFormatter(rNumericSpinButton
)
948 , m_rNumericSpinButton(rNumericSpinButton
)
952 virtual bool CheckText(const OUString
& sText
) const override
954 // We'd like to implement this using the NumberFormatter::IsNumberFormat, but unfortunately, this doesn't
955 // recognize fragments of numbers (like, for instance "1e", which happens during entering e.g. "1e10")
956 // Thus, the roundabout way via a regular expression
957 return m_rNumericSpinButton
.GetNumberValidator().isValidNumericFragment(sText
);
960 virtual void FormatChanged(FORMAT_CHANGE_TYPE nWhat
) override
962 m_rNumericSpinButton
.ResetConformanceTester();
963 FieldFormatter::FormatChanged(nWhat
);
967 class DoubleCurrencyFormatter
: public FieldFormatter
970 DoubleCurrencyField
& m_rCurrencySpinButton
;
971 bool m_bChangingFormat
;
973 DoubleCurrencyFormatter(DoubleCurrencyField
& rNumericSpinButton
)
974 : FieldFormatter(rNumericSpinButton
)
975 , m_rCurrencySpinButton(rNumericSpinButton
)
976 , m_bChangingFormat(false)
980 virtual void FormatChanged(FORMAT_CHANGE_TYPE nWhat
) override
982 if (m_bChangingFormat
)
984 FieldFormatter::FormatChanged(nWhat
);
990 case FORMAT_CHANGE_TYPE::FORMATTER
:
991 case FORMAT_CHANGE_TYPE::PRECISION
:
992 case FORMAT_CHANGE_TYPE::THOUSANDSSEP
:
993 // the aspects which changed don't take our currency settings into account (in fact, they most probably
995 m_rCurrencySpinButton
.UpdateCurrencyFormat();
997 case FORMAT_CHANGE_TYPE::KEYONLY
:
998 OSL_FAIL("DoubleCurrencyField::FormatChanged : somebody modified my key !");
999 // We always build our own format from the settings we get via special methods (setCurrencySymbol etc.).
1000 // Nobody but ourself should modify the format key directly!
1005 FieldFormatter::FormatChanged(nWhat
);
1008 void GuardSetFormat(const OUString
& rString
, LanguageType eLanguage
)
1010 // set this new basic format
1011 m_bChangingFormat
= true;
1012 SetFormat(rString
, eLanguage
);
1013 m_bChangingFormat
= false;
1019 DoubleNumericField::DoubleNumericField(vcl::Window
* pParent
, WinBits nStyle
)
1020 : FormattedField(pParent
, nStyle
)
1022 m_xOwnFormatter
.reset(new DoubleNumericFormatter(*this));
1023 m_pFormatter
= m_xOwnFormatter
.get();
1024 ResetConformanceTester();
1027 DoubleNumericField::~DoubleNumericField() = default;
1029 void DoubleNumericField::ResetConformanceTester()
1031 // the thousands and the decimal separator are language dependent
1032 Formatter
& rFormatter
= GetFormatter();
1033 const SvNumberformat
* pFormatEntry
= rFormatter
.GetOrCreateFormatter()->GetEntry(rFormatter
.GetFormatKey());
1035 sal_Unicode cSeparatorThousand
= ',';
1036 sal_Unicode cSeparatorDecimal
= '.';
1039 LocaleDataWrapper
aLocaleInfo( LanguageTag( pFormatEntry
->GetLanguage()) );
1041 OUString sSeparator
= aLocaleInfo
.getNumThousandSep();
1042 if (!sSeparator
.isEmpty())
1043 cSeparatorThousand
= sSeparator
[0];
1045 sSeparator
= aLocaleInfo
.getNumDecimalSep();
1046 if (!sSeparator
.isEmpty())
1047 cSeparatorDecimal
= sSeparator
[0];
1050 m_pNumberValidator
.reset(new validation::NumberValidator( cSeparatorThousand
, cSeparatorDecimal
));
1054 DoubleCurrencyField::DoubleCurrencyField(vcl::Window
* pParent
, WinBits nStyle
)
1055 :FormattedField(pParent
, nStyle
)
1057 m_xOwnFormatter
.reset(new DoubleCurrencyFormatter(*this));
1058 m_pFormatter
= m_xOwnFormatter
.get();
1060 m_bPrependCurrSym
= false;
1062 // initialize with a system currency format
1063 m_sCurrencySymbol
= SvtSysLocale().GetLocaleData().getCurrSymbol();
1064 UpdateCurrencyFormat();
1067 void DoubleCurrencyField::setCurrencySymbol(const OUString
& rSymbol
)
1069 if (m_sCurrencySymbol
== rSymbol
)
1072 m_sCurrencySymbol
= rSymbol
;
1073 UpdateCurrencyFormat();
1074 m_pFormatter
->FormatChanged(FORMAT_CHANGE_TYPE::CURRENCY_SYMBOL
);
1077 void DoubleCurrencyField::setPrependCurrSym(bool _bPrepend
)
1079 if (m_bPrependCurrSym
== _bPrepend
)
1082 m_bPrependCurrSym
= _bPrepend
;
1083 UpdateCurrencyFormat();
1084 m_pFormatter
->FormatChanged(FORMAT_CHANGE_TYPE::CURRSYM_POSITION
);
1087 void DoubleCurrencyField::UpdateCurrencyFormat()
1090 LanguageType eLanguage
;
1091 m_pFormatter
->GetFormat(eLanguage
);
1092 bool bThSep
= m_pFormatter
->GetThousandsSep();
1093 sal_uInt16 nDigits
= m_pFormatter
->GetDecimalDigits();
1095 // build a new format string with the base class' and my own settings
1097 /* Strangely with gcc 4.6.3 this needs a temporary LanguageTag, otherwise
1099 * error: request for member 'getNumThousandSep' in 'aLocaleInfo', which is
1100 * of non-class type 'LocaleDataWrapper(LanguageTag)' */
1101 LocaleDataWrapper
aLocaleInfo(( LanguageTag(eLanguage
) ));
1103 OUStringBuffer sNewFormat
;
1106 sNewFormat
.append("#" + aLocaleInfo
.getNumThousandSep() + "##0");
1109 sNewFormat
.append('0');
1113 sNewFormat
.append(aLocaleInfo
.getNumDecimalSep());
1114 comphelper::string::padToLength(sNewFormat
, sNewFormat
.getLength() + nDigits
, '0');
1117 if (getPrependCurrSym())
1119 OUString sSymbol
= getCurrencySymbol();
1120 sSymbol
= comphelper::string::strip(sSymbol
, ' ');
1123 "[$" + sSymbol
+ "] "
1125 // for negative values : $ -0.00, not -$ 0.00...
1126 // (the real solution would be a possibility to choose a "positive currency format" and a "negative currency format"...
1127 // But not now... (and hey, you could take a formatted field for this...))
1128 // FS - 31.03.00 74642
1138 OUString sTemp
= getCurrencySymbol();
1139 sTemp
= comphelper::string::strip(sTemp
, ' ');
1141 sNewFormat
.append(" [$" + sTemp
+ "]");
1144 // set this new basic format
1145 static_cast<DoubleCurrencyFormatter
*>(m_pFormatter
)->GuardSetFormat(sNewFormat
.makeStringAndClear(), eLanguage
);
1148 FormattedField::FormattedField(vcl::Window
* pParent
, WinBits nStyle
)
1149 : SpinField(pParent
, nStyle
, WindowType::FORMATTEDFIELD
)
1150 , m_pFormatter(nullptr)
1154 void FormattedField::dispose()
1156 m_pFormatter
= nullptr;
1157 m_xOwnFormatter
.reset();
1158 SpinField::dispose();
1161 void FormattedField::SetText(const OUString
& rStr
)
1163 GetFormatter().SetFieldText(rStr
, Selection(0, 0));
1166 void FormattedField::SetText(const OUString
& rStr
, const Selection
& rNewSelection
)
1168 GetFormatter().SetFieldText(rStr
, rNewSelection
);
1169 SetSelection(rNewSelection
);
1172 bool FormattedField::set_property(const OUString
&rKey
, const OUString
&rValue
)
1174 if (rKey
== "digits")
1175 GetFormatter().SetDecimalDigits(rValue
.toInt32());
1176 else if (rKey
== "wrap")
1177 GetFormatter().SetWrapOnLimits(toBool(rValue
));
1179 return SpinField::set_property(rKey
, rValue
);
1183 void FormattedField::Up()
1185 Formatter
& rFormatter
= GetFormatter();
1186 auto nScale
= weld::SpinButton::Power10(rFormatter
.GetDecimalDigits());
1188 sal_Int64 nValue
= std::round(rFormatter
.GetValue() * nScale
);
1189 sal_Int64 nSpinSize
= std::round(rFormatter
.GetSpinSize() * nScale
);
1190 assert(nSpinSize
!= 0);
1191 sal_Int64 nRemainder
= rFormatter
.GetDisableRemainderFactor() || nSpinSize
== 0 ? 0 : nValue
% nSpinSize
;
1193 nValue
= (nRemainder
== 0) ? nValue
+ nSpinSize
: nValue
+ nSpinSize
- nRemainder
;
1195 nValue
= (nRemainder
== 0) ? nValue
+ nSpinSize
: nValue
- nRemainder
;
1197 // setValue handles under- and overflows (min/max) automatically
1198 rFormatter
.SetValue(static_cast<double>(nValue
) / nScale
);
1205 void FormattedField::Down()
1207 Formatter
& rFormatter
= GetFormatter();
1208 auto nScale
= weld::SpinButton::Power10(rFormatter
.GetDecimalDigits());
1210 sal_Int64 nValue
= std::round(rFormatter
.GetValue() * nScale
);
1211 sal_Int64 nSpinSize
= std::round(rFormatter
.GetSpinSize() * nScale
);
1212 assert(nSpinSize
!= 0);
1213 sal_Int64 nRemainder
= rFormatter
.GetDisableRemainderFactor() || nSpinSize
== 0 ? 0 : nValue
% nSpinSize
;
1215 nValue
= (nRemainder
== 0) ? nValue
- nSpinSize
: nValue
- nRemainder
;
1217 nValue
= (nRemainder
== 0) ? nValue
- nSpinSize
: nValue
- nSpinSize
- nRemainder
;
1219 // setValue handles under- and overflows (min/max) automatically
1220 rFormatter
.SetValue(static_cast<double>(nValue
) / nScale
);
1227 void FormattedField::First()
1229 Formatter
& rFormatter
= GetFormatter();
1230 if (rFormatter
.HasMinValue())
1232 rFormatter
.SetValue(rFormatter
.GetMinValue());
1240 void FormattedField::Last()
1242 Formatter
& rFormatter
= GetFormatter();
1243 if (rFormatter
.HasMaxValue())
1245 rFormatter
.SetValue(rFormatter
.GetMaxValue());
1253 void FormattedField::Modify()
1255 GetFormatter().Modify();
1258 bool FormattedField::PreNotify(NotifyEvent
& rNEvt
)
1260 if (rNEvt
.GetType() == NotifyEventType::KEYINPUT
)
1261 GetFormatter().SetLastSelection(GetSelection());
1262 return SpinField::PreNotify(rNEvt
);
1265 bool FormattedField::EventNotify(NotifyEvent
& rNEvt
)
1267 if ((rNEvt
.GetType() == NotifyEventType::KEYINPUT
) && !IsReadOnly())
1269 const KeyEvent
& rKEvt
= *rNEvt
.GetKeyEvent();
1270 sal_uInt16 nMod
= rKEvt
.GetKeyCode().GetModifier();
1271 switch ( rKEvt
.GetKeyCode().GetCode() )
1278 Formatter
& rFormatter
= GetFormatter();
1279 if (!nMod
&& rFormatter
.GetOrCreateFormatter()->IsTextFormat(rFormatter
.GetFormatKey()))
1281 // the base class would translate this into calls to Up/Down/First/Last,
1282 // but we don't want this if we are text-formatted
1289 if ((rNEvt
.GetType() == NotifyEventType::COMMAND
) && !IsReadOnly())
1291 const CommandEvent
* pCommand
= rNEvt
.GetCommandEvent();
1292 if (pCommand
->GetCommand() == CommandEventId::Wheel
)
1294 const CommandWheelData
* pData
= rNEvt
.GetCommandEvent()->GetWheelData();
1295 Formatter
& rFormatter
= GetFormatter();
1296 if ((pData
->GetMode() == CommandWheelMode::SCROLL
) &&
1297 rFormatter
.GetOrCreateFormatter()->IsTextFormat(rFormatter
.GetFormatKey()))
1299 // same as above : prevent the base class from doing Up/Down-calls
1300 // (normally I should put this test into the Up/Down methods itself, shouldn't I ?)
1301 // FS - 71553 - 19.01.00
1307 if (rNEvt
.GetType() == NotifyEventType::LOSEFOCUS
&& m_pFormatter
)
1308 m_pFormatter
->EntryLostFocus();
1310 return SpinField::EventNotify( rNEvt
);
1313 Formatter
& FormattedField::GetFormatter()
1317 m_xOwnFormatter
.reset(new FieldFormatter(*this));
1318 m_pFormatter
= m_xOwnFormatter
.get();
1320 return *m_pFormatter
;
1323 void FormattedField::SetFormatter(Formatter
* pFormatter
)
1325 m_xOwnFormatter
.reset();
1326 m_pFormatter
= pFormatter
;
1329 // currently used by online
1330 void FormattedField::SetValueFromString(const OUString
& rStr
)
1333 rtl_math_ConversionStatus eStatus
;
1334 Formatter
& rFormatter
= GetFormatter();
1335 double fValue
= ::rtl::math::stringToDouble(rStr
, '.', rFormatter
.GetDecimalDigits(), &eStatus
, &nEnd
);
1337 if (eStatus
== rtl_math_ConversionStatus_Ok
&&
1338 nEnd
== rStr
.getLength())
1340 rFormatter
.SetValue(fValue
);
1344 // Notify the value has changed
1349 SAL_WARN("vcl", "fail to convert the value: " << rStr
);
1353 void FormattedField::DumpAsPropertyTree(tools::JsonWriter
& rJsonWriter
)
1355 SpinField::DumpAsPropertyTree(rJsonWriter
);
1356 Formatter
& rFormatter
= GetFormatter();
1358 if (dynamic_cast<weld::TimeFormatter
*>(&rFormatter
))
1360 // weld::TimeFormatter uses h24 format
1361 rJsonWriter
.put("type", "time");
1363 rJsonWriter
.put("min", rFormatter
.GetMinValue());
1364 rJsonWriter
.put("max", rFormatter
.GetMaxValue());
1365 rJsonWriter
.put("value", rFormatter
.GetValue());
1366 rJsonWriter
.put("step", rFormatter
.GetSpinSize());
1369 FactoryFunction
FormattedField::GetUITestFactory() const
1371 return FormattedFieldUIObject::create
;
1374 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */