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>
37 #include <rtl/math.hxx>
38 #include <rtl/ustrbuf.hxx>
39 #include <sal/log.hxx>
40 #include <osl/diagnose.h>
41 #include <tools/json_writer.hxx>
43 using namespace ::com::sun::star::lang
;
44 using namespace ::com::sun::star::util
;
46 // hmm. No support for regular expression. Well, I always (not really :) wanted to write a finite automat
47 // so here comes a finite automat ...
51 static void lcl_insertStopTransition( StateTransitions
& _rRow
)
53 _rRow
.insert( Transition( '_', END
) );
56 static void lcl_insertStartExponentTransition( StateTransitions
& _rRow
)
58 _rRow
.insert( Transition( 'e', EXPONENT_START
) );
61 static void lcl_insertSignTransitions( StateTransitions
& _rRow
, const State eNextState
)
63 _rRow
.insert( Transition( '-', eNextState
) );
64 _rRow
.insert( Transition( '+', eNextState
) );
67 static void lcl_insertDigitTransitions( StateTransitions
& _rRow
, const State eNextState
)
69 for ( sal_Unicode aChar
= '0'; aChar
<= '9'; ++aChar
)
70 _rRow
.insert( Transition( aChar
, eNextState
) );
73 static void lcl_insertCommonPreCommaTransitions( StateTransitions
& _rRow
, const sal_Unicode _cThSep
, const sal_Unicode _cDecSep
)
76 lcl_insertDigitTransitions( _rRow
, DIGIT_PRE_COMMA
);
78 // the thousand separator is allowed
79 _rRow
.insert( Transition( _cThSep
, DIGIT_PRE_COMMA
) );
82 _rRow
.insert( Transition( _cDecSep
, DIGIT_POST_COMMA
) );
85 NumberValidator::NumberValidator( const sal_Unicode _cThSep
, const sal_Unicode _cDecSep
)
87 // build up our transition table
89 // how to proceed from START
91 StateTransitions
& rRow
= m_aTransitions
[ START
];
92 rRow
.insert( Transition( '_', NUM_START
) );
93 // if we encounter the normalizing character, we want to proceed with the number
96 // how to proceed from NUM_START
98 StateTransitions
& rRow
= m_aTransitions
[ NUM_START
];
101 lcl_insertSignTransitions( rRow
, DIGIT_PRE_COMMA
);
103 // common transitions for the two pre-comma states
104 lcl_insertCommonPreCommaTransitions( rRow
, _cThSep
, _cDecSep
);
106 // the exponent may start here
107 // (this would mean string like "_+e10_", but this is a valid fragment, though no valid number)
108 lcl_insertStartExponentTransition( rRow
);
111 // how to proceed from DIGIT_PRE_COMMA
113 StateTransitions
& rRow
= m_aTransitions
[ DIGIT_PRE_COMMA
];
115 // common transitions for the two pre-comma states
116 lcl_insertCommonPreCommaTransitions( rRow
, _cThSep
, _cDecSep
);
118 // the exponent may start here
119 lcl_insertStartExponentTransition( rRow
);
121 // the final transition indicating the end of the string
122 // (if there is no comma and no post-comma, then the string may end here)
123 lcl_insertStopTransition( rRow
);
126 // how to proceed from DIGIT_POST_COMMA
128 StateTransitions
& rRow
= m_aTransitions
[ DIGIT_POST_COMMA
];
130 // there might be digits, which would keep the state at DIGIT_POST_COMMA
131 lcl_insertDigitTransitions( rRow
, DIGIT_POST_COMMA
);
133 // the exponent may start here
134 lcl_insertStartExponentTransition( rRow
);
136 // the string may end here
137 lcl_insertStopTransition( rRow
);
140 // how to proceed from EXPONENT_START
142 StateTransitions
& rRow
= m_aTransitions
[ EXPONENT_START
];
144 // there may be a sign
145 lcl_insertSignTransitions( rRow
, EXPONENT_DIGIT
);
147 // there may be digits
148 lcl_insertDigitTransitions( rRow
, EXPONENT_DIGIT
);
150 // the string may end here
151 lcl_insertStopTransition( rRow
);
154 // how to proceed from EXPONENT_DIGIT
156 StateTransitions
& rRow
= m_aTransitions
[ EXPONENT_DIGIT
];
158 // there may be digits
159 lcl_insertDigitTransitions( rRow
, EXPONENT_DIGIT
);
161 // the string may end here
162 lcl_insertStopTransition( rRow
);
165 // how to proceed from END
167 /*StateTransitions& rRow =*/ m_aTransitions
[ EXPONENT_DIGIT
];
168 // no valid transition to leave this state
169 // (note that we, for consistency, nevertheless want to have a row in the table)
173 bool NumberValidator::implValidateNormalized( const OUString
& _rText
)
175 const sal_Unicode
* pCheckPos
= _rText
.getStr();
176 State eCurrentState
= START
;
178 while ( END
!= eCurrentState
)
180 // look up the transition row for the current state
181 TransitionTable::const_iterator aRow
= m_aTransitions
.find( eCurrentState
);
182 DBG_ASSERT( m_aTransitions
.end() != aRow
,
183 "NumberValidator::implValidateNormalized: invalid transition table (row not found)!" );
185 if ( m_aTransitions
.end() != aRow
)
187 // look up the current character in this row
188 StateTransitions::const_iterator aTransition
= aRow
->second
.find( *pCheckPos
);
189 if ( aRow
->second
.end() != aTransition
)
191 // there is a valid transition for this character
192 eCurrentState
= aTransition
->second
;
198 // if we're here, there is no valid transition
202 DBG_ASSERT( ( END
!= eCurrentState
) || ( 0 == *pCheckPos
),
203 "NumberValidator::implValidateNormalized: inconsistency!" );
204 // if we're at END, then the string should be done, too - the string should be normalized, means ending
205 // a "_" and not containing any other "_" (except at the start), and "_" is the only possibility
206 // to reach the END state
208 // the string is valid if and only if we reached the final state
209 return ( END
== eCurrentState
);
212 bool NumberValidator::isValidNumericFragment( const OUString
& _rText
)
214 if ( _rText
.isEmpty() )
215 // empty strings are always allowed
218 // normalize the string
219 OUString sNormalized
= "_" + _rText
+ "_";
221 return implValidateNormalized( sNormalized
);
225 SvNumberFormatter
* Formatter::StaticFormatter::s_cFormatter
= nullptr;
226 sal_uLong
Formatter::StaticFormatter::s_nReferences
= 0;
228 SvNumberFormatter
* Formatter::StaticFormatter::GetFormatter()
232 // get the Office's locale and translate
233 LanguageType eSysLanguage
= SvtSysLocale().GetLanguageTag().getLanguageType( false);
234 s_cFormatter
= new SvNumberFormatter(
235 ::comphelper::getProcessComponentContext(),
241 Formatter::StaticFormatter::StaticFormatter()
246 Formatter::StaticFormatter::~StaticFormatter()
248 if (--s_nReferences
== 0)
251 s_cFormatter
= nullptr;
255 Formatter::Formatter()
256 :m_aLastSelection(0,0)
261 ,m_bWrapOnLimits(false)
262 ,m_bStrictFormat(true)
263 ,m_bEnableEmptyField(true)
266 ,m_bDisableRemainderFactor(false)
267 ,m_ValueState(valueDirty
)
271 ,m_pFormatter(nullptr)
273 ,m_dSpinFirst(-1000000)
274 ,m_dSpinLast(1000000)
275 ,m_bTreatAsNumber(true)
276 ,m_pLastOutputColor(nullptr)
277 ,m_bUseInputStringForFormatting(false)
281 Formatter::~Formatter()
285 void Formatter::SetFieldText(const OUString
& rStr
, const Selection
& rNewSelection
)
287 SetEntryText(rStr
, rNewSelection
);
288 m_ValueState
= valueDirty
;
291 void Formatter::SetTextFormatted(const OUString
& rStr
)
293 SAL_INFO_IF(GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
), "svtools",
294 "FormattedField::SetTextFormatted : valid only with text formats !");
296 m_sCurrentTextValue
= rStr
;
299 double dNumber
= 0.0;
300 // IsNumberFormat changes the format key parameter
301 sal_uInt32 nTempFormatKey
= static_cast< sal_uInt32
>( m_nFormatKey
);
302 if( IsUsingInputStringForFormatting() &&
303 GetOrCreateFormatter()->IsNumberFormat(m_sCurrentTextValue
, nTempFormatKey
, dNumber
) )
305 GetOrCreateFormatter()->GetInputLineString(dNumber
, m_nFormatKey
, sFormatted
);
309 GetOrCreateFormatter()->GetOutputString(m_sCurrentTextValue
,
312 &m_pLastOutputColor
);
315 // calculate the new selection
316 Selection
aSel(GetEntrySelection());
317 Selection
aNewSel(aSel
);
319 sal_Int32 nNewLen
= sFormatted
.getLength();
320 sal_Int32 nCurrentLen
= GetEntryText().getLength();
321 if ((nNewLen
> nCurrentLen
) && (aNewSel
.Max() == nCurrentLen
))
322 { // the new text is longer and the cursor was behind the last char (of the old text)
323 if (aNewSel
.Min() == 0)
324 { // the whole text was selected -> select the new text on the whole, too
325 aNewSel
.Max() = nNewLen
;
327 { // there wasn't really a previous selection (as there was no previous text), we're setting a new one -> check the selection options
328 SelectionOptions nSelOptions
= GetEntrySelectionOptions();
329 if (nSelOptions
& SelectionOptions::ShowFirst
)
330 { // selection should be from right to left -> swap min and max
331 aNewSel
.Min() = aNewSel
.Max();
336 else if (aNewSel
.Max() == aNewSel
.Min())
337 { // there was no selection -> set the cursor behind the new last char
338 aNewSel
.Max() = nNewLen
;
339 aNewSel
.Min() = nNewLen
;
342 else if (aNewSel
.Max() > nNewLen
)
343 aNewSel
.Max() = nNewLen
;
345 aNewSel
= aSel
; // don't use the justified version
346 SetEntryText(sFormatted
, aNewSel
);
347 m_ValueState
= valueString
;
350 OUString
const & Formatter::GetTextValue() const
352 if (m_ValueState
!= valueString
)
354 const_cast<Formatter
*>(this)->m_sCurrentTextValue
= GetEntryText();
355 const_cast<Formatter
*>(this)->m_ValueState
= valueString
;
357 return m_sCurrentTextValue
;
360 void Formatter::EnableNotANumber(bool _bEnable
)
362 if ( m_bEnableNaN
== _bEnable
)
365 m_bEnableNaN
= _bEnable
;
368 void Formatter::SetAutoColor(bool _bAutomatic
)
370 if (_bAutomatic
== m_bAutoColor
)
373 m_bAutoColor
= _bAutomatic
;
376 // if auto color is switched on, adjust the current text color, too
377 SetEntryTextColor(m_pLastOutputColor
);
381 void Formatter::Modify(bool makeValueDirty
)
383 if (!IsStrictFormat())
386 m_ValueState
= valueDirty
;
391 OUString sCheck
= GetEntryText();
392 if (CheckText(sCheck
))
394 m_sLastValidText
= sCheck
;
395 m_aLastSelection
= GetEntrySelection();
397 m_ValueState
= valueDirty
;
401 ImplSetTextImpl(m_sLastValidText
, &m_aLastSelection
);
407 void Formatter::ImplSetTextImpl(const OUString
& rNew
, Selection
const * pNewSel
)
410 SetEntryTextColor(m_pLastOutputColor
);
413 SetEntryText(rNew
, *pNewSel
);
416 Selection
aSel(GetEntrySelection());
419 sal_Int32 nNewLen
= rNew
.getLength();
420 sal_Int32 nCurrentLen
= GetEntryText().getLength();
422 if ((nNewLen
> nCurrentLen
) && (aSel
.Max() == nCurrentLen
))
423 { // new text is longer and the cursor is behind the last char
427 { // there wasn't really a previous selection (as there was no previous text)
431 { // the whole text was selected -> select the new text on the whole, too
432 aSel
.Max() = nNewLen
;
435 else if (aSel
.Max() == aSel
.Min())
436 { // there was no selection -> set the cursor behind the new last char
437 aSel
.Max() = nNewLen
;
438 aSel
.Min() = nNewLen
;
441 else if (aSel
.Max() > nNewLen
)
442 aSel
.Max() = nNewLen
;
443 SetEntryText(rNew
, aSel
);
446 m_ValueState
= valueDirty
; // not always necessary, but better re-evaluate for safety reasons
449 void Formatter::ImplSetFormatKey(sal_uLong nFormatKey
)
451 m_nFormatKey
= nFormatKey
;
452 bool bNeedFormatter
= (m_pFormatter
== nullptr) && (nFormatKey
!= 0);
455 GetOrCreateFormatter(); // this creates a standard formatter
457 // It might happen that the standard formatter makes no sense here, but it takes a default
458 // format. Thus, it is possible to set one of the other standard keys (which are spanning
459 // across multiple formatters).
460 m_nFormatKey
= nFormatKey
;
461 // When calling SetFormatKey without a formatter, the key must be one of the standard values
462 // that is available for all formatters (and, thus, also in this new one).
463 DBG_ASSERT(m_pFormatter
->GetEntry(nFormatKey
) != nullptr, "FormattedField::ImplSetFormatKey : invalid format key !");
467 void Formatter::SetFormatKey(sal_uLong nFormatKey
)
469 bool bNoFormatter
= (m_pFormatter
== nullptr);
470 ImplSetFormatKey(nFormatKey
);
471 FormatChanged((bNoFormatter
&& (m_pFormatter
!= nullptr)) ? FORMAT_CHANGE_TYPE::FORMATTER
: FORMAT_CHANGE_TYPE::KEYONLY
);
474 void Formatter::SetFormatter(SvNumberFormatter
* pFormatter
, bool bResetFormat
)
479 m_pFormatter
= pFormatter
;
481 // calc the default format key from the Office's UI locale
484 // get the Office's locale and translate
485 LanguageType eSysLanguage
= SvtSysLocale().GetLanguageTag().getLanguageType( false);
486 // get the standard numeric format for this language
487 m_nFormatKey
= m_pFormatter
->GetStandardFormat( SvNumFormatType::NUMBER
, eSysLanguage
);
494 LanguageType aOldLang
;
495 OUString sOldFormat
= GetFormat(aOldLang
);
497 sal_uInt32 nDestKey
= pFormatter
->TestNewString(sOldFormat
);
498 if (nDestKey
== NUMBERFORMAT_ENTRY_NOT_FOUND
)
500 // language of the new formatter
501 const SvNumberformat
* pDefaultEntry
= pFormatter
->GetEntry(0);
502 LanguageType aNewLang
= pDefaultEntry
? pDefaultEntry
->GetLanguage() : LANGUAGE_DONTKNOW
;
504 // convert the old format string into the new language
506 SvNumFormatType nType
;
507 pFormatter
->PutandConvertEntry(sOldFormat
, nCheckPos
, nType
, nDestKey
, aOldLang
, aNewLang
, true);
508 m_nFormatKey
= nDestKey
;
510 m_pFormatter
= pFormatter
;
513 FormatChanged(FORMAT_CHANGE_TYPE::FORMATTER
);
516 OUString
Formatter::GetFormat(LanguageType
& eLang
) const
518 const SvNumberformat
* pFormatEntry
= GetOrCreateFormatter()->GetEntry(m_nFormatKey
);
519 DBG_ASSERT(pFormatEntry
!= nullptr, "FormattedField::GetFormat: no number format for the given format key.");
520 OUString sFormatString
= pFormatEntry
? pFormatEntry
->GetFormatstring() : OUString();
521 eLang
= pFormatEntry
? pFormatEntry
->GetLanguage() : LANGUAGE_DONTKNOW
;
523 return sFormatString
;
526 bool Formatter::SetFormat(const OUString
& rFormatString
, LanguageType eLang
)
528 sal_uInt32 nNewKey
= GetOrCreateFormatter()->TestNewString(rFormatString
, eLang
);
529 if (nNewKey
== NUMBERFORMAT_ENTRY_NOT_FOUND
)
532 SvNumFormatType nType
;
533 OUString
rFormat(rFormatString
);
534 if (!GetOrCreateFormatter()->PutEntry(rFormat
, nCheckPos
, nType
, nNewKey
, eLang
))
536 DBG_ASSERT(nNewKey
!= NUMBERFORMAT_ENTRY_NOT_FOUND
, "FormattedField::SetFormatString : PutEntry returned an invalid key !");
539 if (nNewKey
!= m_nFormatKey
)
540 SetFormatKey(nNewKey
);
544 bool Formatter::GetThousandsSep() const
546 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
547 "FormattedField::GetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
549 bool bThousand
, IsRed
;
550 sal_uInt16 nPrecision
, nLeadingCnt
;
551 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
556 void Formatter::SetThousandsSep(bool _bUseSeparator
)
558 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
559 "FormattedField::SetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
561 // get the current settings
562 bool bThousand
, IsRed
;
563 sal_uInt16 nPrecision
, nLeadingCnt
;
564 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
565 if (bThousand
== _bUseSeparator
)
568 // we need the language for the following
572 // generate a new format ...
573 OUString sFmtDescription
= GetOrCreateFormatter()->GenerateFormat(m_nFormatKey
, eLang
, _bUseSeparator
, IsRed
, nPrecision
, nLeadingCnt
);
574 // ... and introduce it to the formatter
575 sal_Int32 nCheckPos
= 0;
577 SvNumFormatType nType
;
578 GetOrCreateFormatter()->PutEntry(sFmtDescription
, nCheckPos
, nType
, nNewKey
, eLang
);
581 ImplSetFormatKey(nNewKey
);
582 FormatChanged(FORMAT_CHANGE_TYPE::THOUSANDSSEP
);
585 sal_uInt16
Formatter::GetDecimalDigits() const
587 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
588 "FormattedField::GetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
590 bool bThousand
, IsRed
;
591 sal_uInt16 nPrecision
, nLeadingCnt
;
592 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
597 void Formatter::SetDecimalDigits(sal_uInt16 _nPrecision
)
599 DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
),
600 "FormattedField::SetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
602 // get the current settings
603 bool bThousand
, IsRed
;
604 sal_uInt16 nPrecision
, nLeadingCnt
;
605 GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey
, bThousand
, IsRed
, nPrecision
, nLeadingCnt
);
606 if (nPrecision
== _nPrecision
)
609 // we need the language for the following
613 // generate a new format ...
614 OUString sFmtDescription
= GetOrCreateFormatter()->GenerateFormat(m_nFormatKey
, eLang
, bThousand
, IsRed
, _nPrecision
, nLeadingCnt
);
615 // ... and introduce it to the formatter
616 sal_Int32 nCheckPos
= 0;
618 SvNumFormatType nType
;
619 GetOrCreateFormatter()->PutEntry(sFmtDescription
, nCheckPos
, nType
, nNewKey
, eLang
);
622 ImplSetFormatKey(nNewKey
);
623 FormatChanged(FORMAT_CHANGE_TYPE::PRECISION
);
626 void Formatter::FormatChanged(FORMAT_CHANGE_TYPE _nWhat
)
628 m_pLastOutputColor
= nullptr;
630 if ( (_nWhat
== FORMAT_CHANGE_TYPE::FORMATTER
) && m_pFormatter
)
631 m_pFormatter
->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL
);
636 void Formatter::EntryLostFocus()
638 // special treatment for empty texts
639 if (GetEntryText().isEmpty())
641 if (!IsEmptyFieldEnabled())
643 if (TreatingAsNumber())
645 ImplSetValue(m_dCurrentValue
, true);
647 m_ValueState
= valueDouble
;
651 OUString sNew
= GetTextValue();
653 SetTextFormatted(sNew
);
655 SetTextFormatted(m_sDefaultText
);
656 m_ValueState
= valueString
;
666 void Formatter::Commit()
668 // remember the old text
669 OUString
sOld(GetEntryText());
674 // did the text change?
675 if (GetEntryText() != sOld
)
676 { // consider the field as modified,
677 // but we already have the most recent value;
678 // don't reparse it from the text
679 // (can lead to data loss when the format is lossy,
680 // as is e.g. our default date format: 2-digit year!)
685 void Formatter::ReFormat()
687 if (!IsEmptyFieldEnabled() || !GetEntryText().isEmpty())
689 if (TreatingAsNumber())
691 double dValue
= GetValue();
692 if ( m_bEnableNaN
&& std::isnan( dValue
) )
694 ImplSetValue( dValue
, true );
697 SetTextFormatted(GetTextValue());
701 void Formatter::SetMinValue(double dMin
)
703 DBG_ASSERT(m_bTreatAsNumber
, "FormattedField::SetMinValue : only to be used in numeric mode !");
707 // for checking the current value at the new border -> ImplSetValue
711 void Formatter::SetMaxValue(double dMax
)
713 DBG_ASSERT(m_bTreatAsNumber
, "FormattedField::SetMaxValue : only to be used in numeric mode !");
717 // for checking the current value at the new border -> ImplSetValue
721 void Formatter::SetTextValue(const OUString
& rText
)
723 SetFieldText(rText
, Selection(0, 0));
727 void Formatter::EnableEmptyField(bool bEnable
)
729 if (bEnable
== m_bEnableEmptyField
)
732 m_bEnableEmptyField
= bEnable
;
733 if (!m_bEnableEmptyField
&& GetEntryText().isEmpty())
734 ImplSetValue(m_dCurrentValue
, true);
737 void Formatter::ImplSetValue(double dVal
, bool bForce
)
739 if (m_bHasMin
&& (dVal
<m_dMinValue
))
741 dVal
= m_bWrapOnLimits
? fmod(dVal
+ m_dMaxValue
+ 1 - m_dMinValue
, m_dMaxValue
+ 1) + m_dMinValue
744 if (m_bHasMax
&& (dVal
>m_dMaxValue
))
746 dVal
= m_bWrapOnLimits
? fmod(dVal
- m_dMinValue
, m_dMaxValue
+ 1) + m_dMinValue
749 if (!bForce
&& (dVal
== GetValue()))
752 DBG_ASSERT(GetOrCreateFormatter() != nullptr, "FormattedField::ImplSetValue : can't set a value without a formatter !");
754 m_ValueState
= valueDouble
;
755 UpdateCurrentValue(dVal
);
757 if (!m_aOutputHdl
.IsSet() || !m_aOutputHdl
.Call(nullptr))
760 if (GetOrCreateFormatter()->IsTextFormat(m_nFormatKey
))
762 // first convert the number as string in standard format
764 GetOrCreateFormatter()->GetOutputString(dVal
, 0, sTemp
, &m_pLastOutputColor
);
765 // then encode the string in the corresponding text format
766 GetOrCreateFormatter()->GetOutputString(sTemp
, m_nFormatKey
, sNewText
, &m_pLastOutputColor
);
770 if( IsUsingInputStringForFormatting())
772 GetOrCreateFormatter()->GetInputLineString(dVal
, m_nFormatKey
, sNewText
);
776 GetOrCreateFormatter()->GetOutputString(dVal
, m_nFormatKey
, sNewText
, &m_pLastOutputColor
);
779 ImplSetTextImpl(sNewText
, nullptr);
780 DBG_ASSERT(CheckText(sNewText
), "FormattedField::ImplSetValue : formatted string doesn't match the criteria !");
783 m_ValueState
= valueDouble
;
786 bool Formatter::ImplGetValue(double& dNewVal
)
788 dNewVal
= m_dCurrentValue
;
789 if (m_ValueState
== valueDouble
)
792 dNewVal
= m_dDefaultValue
;
793 OUString
sText(GetEntryText());
797 bool bUseExternalFormatterValue
= false;
798 if (m_aInputHdl
.IsSet())
801 auto eState
= m_aInputHdl
.Call(&nResult
);
802 bUseExternalFormatterValue
= eState
!= TRISTATE_INDET
;
803 if (bUseExternalFormatterValue
)
805 if (eState
== TRISTATE_TRUE
)
808 dNewVal
/= weld::SpinButton::Power10(GetDecimalDigits());
811 dNewVal
= m_dCurrentValue
;
815 if (!bUseExternalFormatterValue
)
817 DBG_ASSERT(GetOrCreateFormatter() != nullptr, "FormattedField::ImplGetValue : can't give you a current value without a formatter !");
819 sal_uInt32 nFormatKey
= m_nFormatKey
; // IsNumberFormat changes the FormatKey!
821 if (GetOrCreateFormatter()->IsTextFormat(nFormatKey
) && m_bTreatAsNumber
)
822 // for detection of values like "1,1" in fields that are formatted as text
825 // special treatment for percentage formatting
826 if (GetOrCreateFormatter()->GetType(m_nFormatKey
) == SvNumFormatType::PERCENT
)
828 // the language of our format
829 LanguageType eLanguage
= m_pFormatter
->GetEntry(m_nFormatKey
)->GetLanguage();
830 // the default number format for this language
831 sal_uLong nStandardNumericFormat
= m_pFormatter
->GetStandardFormat(SvNumFormatType::NUMBER
, eLanguage
);
833 sal_uInt32 nTempFormat
= nStandardNumericFormat
;
835 if (m_pFormatter
->IsNumberFormat(sText
, nTempFormat
, dTemp
) &&
836 SvNumFormatType::NUMBER
== m_pFormatter
->GetType(nTempFormat
))
837 // the string is equivalent to a number formatted one (has no % sign) -> append it
839 // (with this, an input of '3' becomes '3%', which then by the formatter is translated
840 // into 0.03. Without this, the formatter would give us the double 3 for an input '3',
841 // which equals 300 percent.
843 if (!GetOrCreateFormatter()->IsNumberFormat(sText
, nFormatKey
, dNewVal
))
847 if (m_bHasMin
&& (dNewVal
<m_dMinValue
))
848 dNewVal
= m_dMinValue
;
849 if (m_bHasMax
&& (dNewVal
>m_dMaxValue
))
850 dNewVal
= m_dMaxValue
;
854 void Formatter::SetValue(double dVal
)
856 ImplSetValue(dVal
, m_ValueState
!= valueDouble
);
859 double Formatter::GetValue()
861 if ( !ImplGetValue( m_dCurrentValue
) )
865 ::rtl::math::setNan(&dValue
);
867 dValue
= m_dDefaultValue
;
868 UpdateCurrentValue(dValue
);
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();
932 class DoubleNumericFormatter
: public FieldFormatter
935 DoubleNumericField
& m_rNumericSpinButton
;
937 DoubleNumericFormatter(DoubleNumericField
& rNumericSpinButton
)
938 : FieldFormatter(rNumericSpinButton
)
939 , m_rNumericSpinButton(rNumericSpinButton
)
943 virtual bool CheckText(const OUString
& sText
) const override
945 // We'd like to implement this using the NumberFormatter::IsNumberFormat, but unfortunately, this doesn't
946 // recognize fragments of numbers (like, for instance "1e", which happens during entering e.g. "1e10")
947 // Thus, the roundabout way via a regular expression
948 return m_rNumericSpinButton
.GetNumberValidator().isValidNumericFragment(sText
);
951 virtual void FormatChanged(FORMAT_CHANGE_TYPE nWhat
) override
953 m_rNumericSpinButton
.ResetConformanceTester();
954 FieldFormatter::FormatChanged(nWhat
);
958 class DoubleCurrencyFormatter
: public FieldFormatter
961 DoubleCurrencyField
& m_rCurrencySpinButton
;
962 bool m_bChangingFormat
;
964 DoubleCurrencyFormatter(DoubleCurrencyField
& rNumericSpinButton
)
965 : FieldFormatter(rNumericSpinButton
)
966 , m_rCurrencySpinButton(rNumericSpinButton
)
967 , m_bChangingFormat(false)
971 virtual void FormatChanged(FORMAT_CHANGE_TYPE nWhat
) override
973 if (m_bChangingFormat
)
975 FieldFormatter::FormatChanged(nWhat
);
981 case FORMAT_CHANGE_TYPE::FORMATTER
:
982 case FORMAT_CHANGE_TYPE::PRECISION
:
983 case FORMAT_CHANGE_TYPE::THOUSANDSSEP
:
984 // the aspects which changed don't take our currency settings into account (in fact, they most probably
986 m_rCurrencySpinButton
.UpdateCurrencyFormat();
988 case FORMAT_CHANGE_TYPE::KEYONLY
:
989 OSL_FAIL("DoubleCurrencyField::FormatChanged : somebody modified my key !");
990 // We always build our own format from the settings we get via special methods (setCurrencySymbol etc.).
991 // Nobody but ourself should modify the format key directly!
996 FieldFormatter::FormatChanged(nWhat
);
999 void GuardSetFormat(const OUString
& rString
, LanguageType eLanguage
)
1001 // set this new basic format
1002 m_bChangingFormat
= true;
1003 SetFormat(rString
, eLanguage
);
1004 m_bChangingFormat
= false;
1010 DoubleNumericField::DoubleNumericField(vcl::Window
* pParent
, WinBits nStyle
)
1011 : FormattedField(pParent
, nStyle
)
1013 m_xOwnFormatter
.reset(new DoubleNumericFormatter(*this));
1014 m_pFormatter
= m_xOwnFormatter
.get();
1015 ResetConformanceTester();
1018 DoubleNumericField::~DoubleNumericField() = default;
1020 void DoubleNumericField::ResetConformanceTester()
1022 // the thousands and the decimal separator are language dependent
1023 Formatter
& rFormatter
= GetFormatter();
1024 const SvNumberformat
* pFormatEntry
= rFormatter
.GetOrCreateFormatter()->GetEntry(rFormatter
.GetFormatKey());
1026 sal_Unicode cSeparatorThousand
= ',';
1027 sal_Unicode cSeparatorDecimal
= '.';
1030 LocaleDataWrapper
aLocaleInfo( LanguageTag( pFormatEntry
->GetLanguage()) );
1032 OUString sSeparator
= aLocaleInfo
.getNumThousandSep();
1033 if (!sSeparator
.isEmpty())
1034 cSeparatorThousand
= sSeparator
[0];
1036 sSeparator
= aLocaleInfo
.getNumDecimalSep();
1037 if (!sSeparator
.isEmpty())
1038 cSeparatorDecimal
= sSeparator
[0];
1041 m_pNumberValidator
.reset(new validation::NumberValidator( cSeparatorThousand
, cSeparatorDecimal
));
1045 DoubleCurrencyField::DoubleCurrencyField(vcl::Window
* pParent
, WinBits nStyle
)
1046 :FormattedField(pParent
, nStyle
)
1048 m_xOwnFormatter
.reset(new DoubleCurrencyFormatter(*this));
1049 m_pFormatter
= m_xOwnFormatter
.get();
1051 m_bPrependCurrSym
= false;
1053 // initialize with a system currency format
1054 m_sCurrencySymbol
= SvtSysLocale().GetLocaleData().getCurrSymbol();
1055 UpdateCurrencyFormat();
1058 void DoubleCurrencyField::setCurrencySymbol(const OUString
& rSymbol
)
1060 if (m_sCurrencySymbol
== rSymbol
)
1063 m_sCurrencySymbol
= rSymbol
;
1064 UpdateCurrencyFormat();
1065 m_pFormatter
->FormatChanged(FORMAT_CHANGE_TYPE::CURRENCY_SYMBOL
);
1068 void DoubleCurrencyField::setPrependCurrSym(bool _bPrepend
)
1070 if (m_bPrependCurrSym
== _bPrepend
)
1073 m_bPrependCurrSym
= _bPrepend
;
1074 UpdateCurrencyFormat();
1075 m_pFormatter
->FormatChanged(FORMAT_CHANGE_TYPE::CURRSYM_POSITION
);
1078 void DoubleCurrencyField::UpdateCurrencyFormat()
1081 LanguageType eLanguage
;
1082 m_pFormatter
->GetFormat(eLanguage
);
1083 bool bThSep
= m_pFormatter
->GetThousandsSep();
1084 sal_uInt16 nDigits
= m_pFormatter
->GetDecimalDigits();
1086 // build a new format string with the base class' and my own settings
1088 /* Strangely with gcc 4.6.3 this needs a temporary LanguageTag, otherwise
1090 * error: request for member 'getNumThousandSep' in 'aLocaleInfo', which is
1091 * of non-class type 'LocaleDataWrapper(LanguageTag)' */
1092 LanguageTag
aLanguageTag( eLanguage
);
1093 LocaleDataWrapper
aLocaleInfo( aLanguageTag
);
1095 OUStringBuffer sNewFormat
;
1098 sNewFormat
.append('#');
1099 sNewFormat
.append(aLocaleInfo
.getNumThousandSep());
1100 sNewFormat
.append("##0");
1103 sNewFormat
.append('0');
1107 sNewFormat
.append(aLocaleInfo
.getNumDecimalSep());
1109 OUStringBuffer sTemp
;
1110 comphelper::string::padToLength(sTemp
, nDigits
, '0');
1111 sNewFormat
.append(sTemp
);
1114 if (getPrependCurrSym())
1116 OUString sSymbol
= getCurrencySymbol();
1117 sSymbol
= comphelper::string::stripStart(sSymbol
, ' ');
1118 sSymbol
= comphelper::string::stripEnd(sSymbol
, ' ');
1120 OUStringBuffer
sTemp("[$");
1121 sTemp
.append(sSymbol
);
1123 sTemp
.append(sNewFormat
);
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
1129 sTemp
.append(";[$");
1130 sTemp
.append(sSymbol
);
1131 sTemp
.append("] -");
1132 sTemp
.append(sNewFormat
);
1138 OUString sTemp
= getCurrencySymbol();
1139 sTemp
= comphelper::string::stripStart(sTemp
, ' ');
1140 sTemp
= comphelper::string::stripEnd(sTemp
, ' ');
1142 sNewFormat
.append(" [$");
1143 sNewFormat
.append(sTemp
);
1144 sNewFormat
.append(']');
1147 // set this new basic format
1148 static_cast<DoubleCurrencyFormatter
*>(m_pFormatter
)->GuardSetFormat(sNewFormat
.makeStringAndClear(), eLanguage
);
1151 FormattedField::FormattedField(vcl::Window
* pParent
, WinBits nStyle
)
1152 : SpinField(pParent
, nStyle
, WindowType::FORMATTEDFIELD
)
1153 , m_pFormatter(nullptr)
1157 void FormattedField::dispose()
1159 m_pFormatter
= nullptr;
1160 m_xOwnFormatter
.reset();
1161 SpinField::dispose();
1164 void FormattedField::SetText(const OUString
& rStr
)
1166 GetFormatter().SetFieldText(rStr
, Selection(0, 0));
1169 void FormattedField::SetText(const OUString
& rStr
, const Selection
& rNewSelection
)
1171 GetFormatter().SetFieldText(rStr
, rNewSelection
);
1172 SetSelection(rNewSelection
);
1175 bool FormattedField::set_property(const OString
&rKey
, const OUString
&rValue
)
1177 if (rKey
== "digits")
1178 GetFormatter().SetDecimalDigits(rValue
.toInt32());
1179 else if (rKey
== "wrap")
1180 GetFormatter().SetWrapOnLimits(toBool(rValue
));
1182 return SpinField::set_property(rKey
, rValue
);
1186 void FormattedField::Up()
1188 Formatter
& rFormatter
= GetFormatter();
1189 auto nScale
= weld::SpinButton::Power10(rFormatter
.GetDecimalDigits());
1191 sal_Int64 nValue
= std::round(rFormatter
.GetValue() * nScale
);
1192 sal_Int64 nSpinSize
= std::round(rFormatter
.GetSpinSize() * nScale
);
1193 sal_Int64 nRemainder
= rFormatter
.GetDisableRemainderFactor() ? 0 : nValue
% nSpinSize
;
1195 nValue
= (nRemainder
== 0) ? nValue
+ nSpinSize
: nValue
+ nSpinSize
- nRemainder
;
1197 nValue
= (nRemainder
== 0) ? nValue
+ nSpinSize
: nValue
- nRemainder
;
1199 // setValue handles under- and overflows (min/max) automatically
1200 rFormatter
.SetValue(static_cast<double>(nValue
) / nScale
);
1207 void FormattedField::Down()
1209 Formatter
& rFormatter
= GetFormatter();
1210 auto nScale
= weld::SpinButton::Power10(rFormatter
.GetDecimalDigits());
1212 sal_Int64 nValue
= std::round(rFormatter
.GetValue() * nScale
);
1213 sal_Int64 nSpinSize
= std::round(rFormatter
.GetSpinSize() * nScale
);
1214 sal_Int64 nRemainder
= rFormatter
.GetDisableRemainderFactor() ? 0 : nValue
% nSpinSize
;
1216 nValue
= (nRemainder
== 0) ? nValue
- nSpinSize
: nValue
- nRemainder
;
1218 nValue
= (nRemainder
== 0) ? nValue
- nSpinSize
: nValue
- nSpinSize
- nRemainder
;
1220 // setValue handles under- and overflows (min/max) automatically
1221 rFormatter
.SetValue(static_cast<double>(nValue
) / nScale
);
1228 void FormattedField::First()
1230 Formatter
& rFormatter
= GetFormatter();
1231 if (rFormatter
.HasMinValue())
1233 rFormatter
.SetValue(rFormatter
.GetMinValue());
1241 void FormattedField::Last()
1243 Formatter
& rFormatter
= GetFormatter();
1244 if (rFormatter
.HasMaxValue())
1246 rFormatter
.SetValue(rFormatter
.GetMaxValue());
1254 void FormattedField::Modify()
1256 GetFormatter().Modify();
1259 bool FormattedField::PreNotify(NotifyEvent
& rNEvt
)
1261 if (rNEvt
.GetType() == MouseNotifyEvent::KEYINPUT
)
1262 GetFormatter().SetLastSelection(GetSelection());
1263 return SpinField::PreNotify(rNEvt
);
1266 bool FormattedField::EventNotify(NotifyEvent
& rNEvt
)
1268 if ((rNEvt
.GetType() == MouseNotifyEvent::KEYINPUT
) && !IsReadOnly())
1270 const KeyEvent
& rKEvt
= *rNEvt
.GetKeyEvent();
1271 sal_uInt16 nMod
= rKEvt
.GetKeyCode().GetModifier();
1272 switch ( rKEvt
.GetKeyCode().GetCode() )
1279 Formatter
& rFormatter
= GetFormatter();
1280 if (!nMod
&& rFormatter
.GetOrCreateFormatter()->IsTextFormat(rFormatter
.GetFormatKey()))
1282 // the base class would translate this into calls to Up/Down/First/Last,
1283 // but we don't want this if we are text-formatted
1290 if ((rNEvt
.GetType() == MouseNotifyEvent::COMMAND
) && !IsReadOnly())
1292 const CommandEvent
* pCommand
= rNEvt
.GetCommandEvent();
1293 if (pCommand
->GetCommand() == CommandEventId::Wheel
)
1295 const CommandWheelData
* pData
= rNEvt
.GetCommandEvent()->GetWheelData();
1296 Formatter
& rFormatter
= GetFormatter();
1297 if ((pData
->GetMode() == CommandWheelMode::SCROLL
) &&
1298 rFormatter
.GetOrCreateFormatter()->IsTextFormat(rFormatter
.GetFormatKey()))
1300 // same as above : prevent the base class from doing Up/Down-calls
1301 // (normally I should put this test into the Up/Down methods itself, shouldn't I ?)
1302 // FS - 71553 - 19.01.00
1308 if (rNEvt
.GetType() == MouseNotifyEvent::LOSEFOCUS
&& m_pFormatter
)
1309 m_pFormatter
->EntryLostFocus();
1311 return SpinField::EventNotify( rNEvt
);
1314 Formatter
& FormattedField::GetFormatter()
1318 m_xOwnFormatter
.reset(new FieldFormatter(*this));
1319 m_pFormatter
= m_xOwnFormatter
.get();
1321 return *m_pFormatter
;
1324 void FormattedField::SetFormatter(Formatter
* pFormatter
)
1326 m_xOwnFormatter
.reset();
1327 m_pFormatter
= pFormatter
;
1330 // currently used by online
1331 void FormattedField::SetValueFromString(const OUString
& rStr
)
1334 rtl_math_ConversionStatus eStatus
;
1335 Formatter
& rFormatter
= GetFormatter();
1336 double fValue
= ::rtl::math::stringToDouble(rStr
, '.', rFormatter
.GetDecimalDigits(), &eStatus
, &nEnd
);
1338 if (eStatus
== rtl_math_ConversionStatus_Ok
&&
1339 nEnd
== rStr
.getLength())
1341 rFormatter
.SetValue(fValue
);
1345 // Notify the value has changed
1350 SAL_WARN("vcl", "fail to convert the value: " << rStr
);
1354 void FormattedField::DumpAsPropertyTree(tools::JsonWriter
& rJsonWriter
)
1356 SpinField::DumpAsPropertyTree(rJsonWriter
);
1357 Formatter
& rFormatter
= GetFormatter();
1358 rJsonWriter
.put("min", rFormatter
.GetMinValue());
1359 rJsonWriter
.put("max", rFormatter
.GetMaxValue());
1360 rJsonWriter
.put("value", rFormatter
.GetValue());
1363 FactoryFunction
FormattedField::GetUITestFactory() const
1365 return FormattedFieldUIObject::create
;
1368 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */