1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <tools/debug.hxx>
21 #include <comphelper/processfactory.hxx>
22 #include <comphelper/string.hxx>
23 #include <unotools/localedatawrapper.hxx>
24 #include <vcl/builder.hxx>
25 #include <vcl/event.hxx>
26 #include <vcl/settings.hxx>
27 #include <vcl/commandevent.hxx>
28 #include <svl/zformat.hxx>
29 #include <vcl/fmtfield.hxx>
30 #include <vcl/weld.hxx>
31 #include <i18nlangtag/languagetag.hxx>
32 #include <unotools/syslocale.hxx>
33 #include <map>
34 #include <rtl/math.hxx>
35 #include <rtl/ustrbuf.hxx>
36 #include <sal/log.hxx>
37 #include <osl/diagnose.h>
38 
39 using namespace ::com::sun::star::lang;
40 using namespace ::com::sun::star::util;
41 
42 // hmm. No support for regular expression. Well, I always (not really :) wanted to write a finite automat
43 // so here comes a finite automat ...
44 
45 namespace validation
46 {
47     // the states of our automat.
48     enum State
49     {
50         START,              // at the very start of the string
51         NUM_START,          // the very start of the number
52 
53         DIGIT_PRE_COMMA,    // some pre-comma digits are read, perhaps including some thousand separators
54 
55         DIGIT_POST_COMMA,   // reading digits after the comma
56         EXPONENT_START,     // at the very start of the exponent value
57                             //    (means: not including the "e" which denotes the exponent)
58         EXPONENT_DIGIT,     // currently reading the digits of the exponent
59 
60         END                 // reached the end of the string
61     };
62 
63     // a row in the transition table (means the set of states to be reached from a given state)
64     typedef ::std::map< sal_Unicode, State >        StateTransitions;
65 
66     // a single transition
67     typedef StateTransitions::value_type            Transition;
68 
69     // the complete transition table
70     typedef ::std::map< State, StateTransitions >   TransitionTable;
71 
72     // the validator class
73     class NumberValidator
74     {
75     private:
76         TransitionTable     m_aTransitions;
77 
78     public:
79         NumberValidator( const sal_Unicode _cThSep, const sal_Unicode _cDecSep );
80 
81         bool isValidNumericFragment( const OUString& _rText );
82 
83     private:
84         bool implValidateNormalized( const OUString& _rText );
85     };
86 
lcl_insertStopTransition(StateTransitions & _rRow)87     static void lcl_insertStopTransition( StateTransitions& _rRow )
88     {
89         _rRow.insert( Transition( '_', END ) );
90     }
91 
lcl_insertStartExponentTransition(StateTransitions & _rRow)92     static void lcl_insertStartExponentTransition( StateTransitions& _rRow )
93     {
94         _rRow.insert( Transition( 'e', EXPONENT_START ) );
95     }
96 
lcl_insertSignTransitions(StateTransitions & _rRow,const State eNextState)97     static void lcl_insertSignTransitions( StateTransitions& _rRow, const State eNextState )
98     {
99         _rRow.insert( Transition( '-', eNextState ) );
100         _rRow.insert( Transition( '+', eNextState ) );
101     }
102 
lcl_insertDigitTransitions(StateTransitions & _rRow,const State eNextState)103     static void lcl_insertDigitTransitions( StateTransitions& _rRow, const State eNextState )
104     {
105         for ( sal_Unicode aChar = '0'; aChar <= '9'; ++aChar )
106             _rRow.insert( Transition( aChar, eNextState ) );
107     }
108 
lcl_insertCommonPreCommaTransitions(StateTransitions & _rRow,const sal_Unicode _cThSep,const sal_Unicode _cDecSep)109     static void lcl_insertCommonPreCommaTransitions( StateTransitions& _rRow, const sal_Unicode _cThSep, const sal_Unicode _cDecSep )
110     {
111         // digits are allowed
112         lcl_insertDigitTransitions( _rRow, DIGIT_PRE_COMMA );
113 
114         // the thousand separator is allowed
115         _rRow.insert( Transition( _cThSep, DIGIT_PRE_COMMA ) );
116 
117         // a comma is allowed
118         _rRow.insert( Transition( _cDecSep, DIGIT_POST_COMMA ) );
119     }
120 
NumberValidator(const sal_Unicode _cThSep,const sal_Unicode _cDecSep)121     NumberValidator::NumberValidator( const sal_Unicode _cThSep, const sal_Unicode _cDecSep )
122     {
123         // build up our transition table
124 
125         // how to proceed from START
126         {
127             StateTransitions& rRow = m_aTransitions[ START ];
128             rRow.insert( Transition( '_', NUM_START ) );
129                 // if we encounter the normalizing character, we want to proceed with the number
130         }
131 
132         // how to proceed from NUM_START
133         {
134             StateTransitions& rRow = m_aTransitions[ NUM_START ];
135 
136             // a sign is allowed
137             lcl_insertSignTransitions( rRow, DIGIT_PRE_COMMA );
138 
139             // common transitions for the two pre-comma states
140             lcl_insertCommonPreCommaTransitions( rRow, _cThSep, _cDecSep );
141 
142             // the exponent may start here
143             // (this would mean string like "_+e10_", but this is a valid fragment, though no valid number)
144             lcl_insertStartExponentTransition( rRow );
145         }
146 
147         // how to proceed from DIGIT_PRE_COMMA
148         {
149             StateTransitions& rRow = m_aTransitions[ DIGIT_PRE_COMMA ];
150 
151             // common transitions for the two pre-comma states
152             lcl_insertCommonPreCommaTransitions( rRow, _cThSep, _cDecSep );
153 
154             // the exponent may start here
155             lcl_insertStartExponentTransition( rRow );
156 
157             // the final transition indicating the end of the string
158             // (if there is no comma and no post-comma, then the string may end here)
159             lcl_insertStopTransition( rRow );
160         }
161 
162         // how to proceed from DIGIT_POST_COMMA
163         {
164             StateTransitions& rRow = m_aTransitions[ DIGIT_POST_COMMA ];
165 
166             // there might be digits, which would keep the state at DIGIT_POST_COMMA
167             lcl_insertDigitTransitions( rRow, DIGIT_POST_COMMA );
168 
169             // the exponent may start here
170             lcl_insertStartExponentTransition( rRow );
171 
172             // the string may end here
173             lcl_insertStopTransition( rRow );
174         }
175 
176         // how to proceed from EXPONENT_START
177         {
178             StateTransitions& rRow = m_aTransitions[ EXPONENT_START ];
179 
180             // there may be a sign
181             lcl_insertSignTransitions( rRow, EXPONENT_DIGIT );
182 
183             // there may be digits
184             lcl_insertDigitTransitions( rRow, EXPONENT_DIGIT );
185 
186             // the string may end here
187             lcl_insertStopTransition( rRow );
188         }
189 
190         // how to proceed from EXPONENT_DIGIT
191         {
192             StateTransitions& rRow = m_aTransitions[ EXPONENT_DIGIT ];
193 
194             // there may be digits
195             lcl_insertDigitTransitions( rRow, EXPONENT_DIGIT );
196 
197             // the string may end here
198             lcl_insertStopTransition( rRow );
199         }
200 
201         // how to proceed from END
202         {
203             /*StateTransitions& rRow =*/ m_aTransitions[ EXPONENT_DIGIT ];
204             // no valid transition to leave this state
205             // (note that we, for consistency, nevertheless want to have a row in the table)
206         }
207     }
208 
implValidateNormalized(const OUString & _rText)209     bool NumberValidator::implValidateNormalized( const OUString& _rText )
210     {
211         const sal_Unicode* pCheckPos = _rText.getStr();
212         State eCurrentState = START;
213 
214         while ( END != eCurrentState )
215         {
216             // look up the transition row for the current state
217             TransitionTable::const_iterator aRow = m_aTransitions.find( eCurrentState );
218             DBG_ASSERT( m_aTransitions.end() != aRow,
219                 "NumberValidator::implValidateNormalized: invalid transition table (row not found)!" );
220 
221             if ( m_aTransitions.end() != aRow )
222             {
223                 // look up the current character in this row
224                 StateTransitions::const_iterator aTransition = aRow->second.find( *pCheckPos );
225                 if ( aRow->second.end() != aTransition )
226                 {
227                     // there is a valid transition for this character
228                     eCurrentState = aTransition->second;
229                     ++pCheckPos;
230                     continue;
231                 }
232             }
233 
234             // if we're here, there is no valid transition
235             break;
236         }
237 
238         DBG_ASSERT( ( END != eCurrentState ) || ( 0 == *pCheckPos ),
239             "NumberValidator::implValidateNormalized: inconsistency!" );
240             // if we're at END, then the string should be done, too - the string should be normalized, means ending
241             // a "_" and not containing any other "_" (except at the start), and "_" is the only possibility
242             // to reach the END state
243 
244         // the string is valid if and only if we reached the final state
245         return ( END == eCurrentState );
246     }
247 
isValidNumericFragment(const OUString & _rText)248     bool NumberValidator::isValidNumericFragment( const OUString& _rText )
249     {
250         if ( _rText.isEmpty() )
251             // empty strings are always allowed
252             return true;
253 
254         // normalize the string
255         OUString sNormalized = "_" + _rText + "_";
256 
257         return implValidateNormalized( sNormalized );
258     }
259 }
260 
261 SvNumberFormatter* FormattedField::StaticFormatter::s_cFormatter = nullptr;
262 sal_uLong FormattedField::StaticFormatter::s_nReferences = 0;
263 
GetFormatter()264 SvNumberFormatter* FormattedField::StaticFormatter::GetFormatter()
265 {
266     if (!s_cFormatter)
267     {
268         // get the Office's locale and translate
269         LanguageType eSysLanguage = SvtSysLocale().GetLanguageTag().getLanguageType( false);
270         s_cFormatter = new SvNumberFormatter(
271             ::comphelper::getProcessComponentContext(),
272             eSysLanguage);
273     }
274     return s_cFormatter;
275 }
276 
StaticFormatter()277 FormattedField::StaticFormatter::StaticFormatter()
278 {
279     ++s_nReferences;
280 }
281 
~StaticFormatter()282 FormattedField::StaticFormatter::~StaticFormatter()
283 {
284     if (--s_nReferences == 0)
285     {
286         delete s_cFormatter;
287         s_cFormatter = nullptr;
288     }
289 }
290 
FormattedField(vcl::Window * pParent,WinBits nStyle)291 FormattedField::FormattedField(vcl::Window* pParent, WinBits nStyle)
292     :SpinField(pParent, nStyle)
293     ,m_aLastSelection(0,0)
294     ,m_dMinValue(0)
295     ,m_dMaxValue(0)
296     ,m_bHasMin(false)
297     ,m_bHasMax(false)
298     ,m_bWrapOnLimits(false)
299     ,m_bStrictFormat(true)
300     ,m_bEnableEmptyField(true)
301     ,m_bAutoColor(false)
302     ,m_bEnableNaN(false)
303     ,m_bDisableRemainderFactor(false)
304     ,m_ValueState(valueDirty)
305     ,m_dCurrentValue(0)
306     ,m_dDefaultValue(0)
307     ,m_nFormatKey(0)
308     ,m_pFormatter(nullptr)
309     ,m_dSpinSize(1)
310     ,m_dSpinFirst(-1000000)
311     ,m_dSpinLast(1000000)
312     ,m_bTreatAsNumber(true)
313     ,m_pLastOutputColor(nullptr)
314     ,m_bUseInputStringForFormatting(false)
315 {
316 }
317 
SetText(const OUString & rStr)318 void FormattedField::SetText(const OUString& rStr)
319 {
320 
321     SpinField::SetText(rStr);
322     m_ValueState = valueDirty;
323 }
324 
SetText(const OUString & rStr,const Selection & rNewSelection)325 void FormattedField::SetText( const OUString& rStr, const Selection& rNewSelection )
326 {
327 
328     SpinField::SetText( rStr, rNewSelection );
329     m_ValueState = valueDirty;
330 }
331 
SetTextFormatted(const OUString & rStr)332 void FormattedField::SetTextFormatted(const OUString& rStr)
333 {
334     SAL_INFO_IF(ImplGetFormatter()->IsTextFormat(m_nFormatKey), "svtools",
335         "FormattedField::SetTextFormatted : valid only with text formats !");
336 
337     m_sCurrentTextValue = rStr;
338 
339     OUString sFormatted;
340     double dNumber = 0.0;
341     // IsNumberFormat changes the format key parameter
342     sal_uInt32 nTempFormatKey = static_cast< sal_uInt32 >( m_nFormatKey );
343     if( IsUsingInputStringForFormatting() &&
344         ImplGetFormatter()->IsNumberFormat(m_sCurrentTextValue, nTempFormatKey, dNumber) )
345     {
346         ImplGetFormatter()->GetInputLineString(dNumber, m_nFormatKey, sFormatted);
347     }
348     else
349     {
350         ImplGetFormatter()->GetOutputString(m_sCurrentTextValue,
351                                             m_nFormatKey,
352                                             sFormatted,
353                                             &m_pLastOutputColor);
354     }
355 
356     // calculate the new selection
357     Selection aSel(GetSelection());
358     Selection aNewSel(aSel);
359     aNewSel.Justify();
360     sal_Int32 nNewLen = sFormatted.getLength();
361     sal_Int32 nCurrentLen = GetText().getLength();
362     if ((nNewLen > nCurrentLen) && (aNewSel.Max() == nCurrentLen))
363     {   // the new text is longer and the cursor was behind the last char (of the old text)
364         if (aNewSel.Min() == 0)
365         {   // the whole text was selected -> select the new text on the whole, too
366             aNewSel.Max() = nNewLen;
367             if (!nCurrentLen)
368             {   // there wasn't really a previous selection (as there was no previous text), we're setting a new one -> check the selection options
369                 SelectionOptions nSelOptions = GetSettings().GetStyleSettings().GetSelectionOptions();
370                 if (nSelOptions & SelectionOptions::ShowFirst)
371                 {   // selection should be from right to left -> swap min and max
372                     aNewSel.Min() = aNewSel.Max();
373                     aNewSel.Max() = 0;
374                 }
375             }
376         }
377         else if (aNewSel.Max() == aNewSel.Min())
378         {   // there was no selection -> set the cursor behind the new last char
379             aNewSel.Max() = nNewLen;
380             aNewSel.Min() = nNewLen;
381         }
382     }
383     else if (aNewSel.Max() > nNewLen)
384         aNewSel.Max() = nNewLen;
385     else
386         aNewSel = aSel; // don't use the justified version
387     SpinField::SetText(sFormatted, aNewSel);
388     m_ValueState = valueString;
389 }
390 
GetTextValue() const391 OUString const & FormattedField::GetTextValue() const
392 {
393     if (m_ValueState != valueString )
394     {
395         const_cast<FormattedField*>(this)->m_sCurrentTextValue = GetText();
396         const_cast<FormattedField*>(this)->m_ValueState = valueString;
397     }
398     return m_sCurrentTextValue;
399 }
400 
EnableNotANumber(bool _bEnable)401 void FormattedField::EnableNotANumber( bool _bEnable )
402 {
403     if ( m_bEnableNaN == _bEnable )
404         return;
405 
406     m_bEnableNaN = _bEnable;
407 }
408 
SetAutoColor(bool _bAutomatic)409 void FormattedField::SetAutoColor(bool _bAutomatic)
410 {
411     if (_bAutomatic == m_bAutoColor)
412         return;
413 
414     m_bAutoColor = _bAutomatic;
415     if (m_bAutoColor)
416     {   // if auto color is switched on, adjust the current text color, too
417         if (m_pLastOutputColor)
418             SetControlForeground(*m_pLastOutputColor);
419         else
420             SetControlForeground();
421     }
422 }
423 
impl_Modify(bool makeValueDirty)424 void FormattedField::impl_Modify(bool makeValueDirty)
425 {
426 
427     if (!IsStrictFormat())
428     {
429         if(makeValueDirty)
430             m_ValueState = valueDirty;
431         SpinField::Modify();
432         return;
433     }
434 
435     OUString sCheck = GetText();
436     if (CheckText(sCheck))
437     {
438         m_sLastValidText = sCheck;
439         m_aLastSelection = GetSelection();
440         if(makeValueDirty)
441             m_ValueState = valueDirty;
442     }
443     else
444     {
445         ImplSetTextImpl(m_sLastValidText, &m_aLastSelection);
446     }
447 
448     SpinField::Modify();
449 }
450 
Modify()451 void FormattedField::Modify()
452 {
453 
454     impl_Modify();
455 }
456 
ImplSetTextImpl(const OUString & rNew,Selection const * pNewSel)457 void FormattedField::ImplSetTextImpl(const OUString& rNew, Selection const * pNewSel)
458 {
459 
460     if (m_bAutoColor)
461     {
462         if (m_pLastOutputColor)
463             SetControlForeground(*m_pLastOutputColor);
464         else
465             SetControlForeground();
466     }
467 
468     if (pNewSel)
469         SpinField::SetText(rNew, *pNewSel);
470     else
471     {
472         Selection aSel(GetSelection());
473         aSel.Justify();
474 
475         sal_Int32 nNewLen = rNew.getLength();
476         sal_Int32 nCurrentLen = GetText().getLength();
477 
478         if ((nNewLen > nCurrentLen) && (aSel.Max() == nCurrentLen))
479         {   // new text is longer and the cursor is behind the last char
480             if (aSel.Min() == 0)
481             {
482                 if (!nCurrentLen)
483                 {   // there wasn't really a previous selection (as there was no previous text)
484                     aSel.Max() = 0;
485                 }
486                 else
487                 {   // the whole text was selected -> select the new text on the whole, too
488                     aSel.Max() = nNewLen;
489                 }
490             }
491             else if (aSel.Max() == aSel.Min())
492             {   // there was no selection -> set the cursor behind the new last char
493                 aSel.Max() = nNewLen;
494                 aSel.Min() = nNewLen;
495             }
496         }
497         else if (aSel.Max() > nNewLen)
498             aSel.Max() = nNewLen;
499         SpinField::SetText(rNew, aSel);
500     }
501 
502     m_ValueState = valueDirty; // not always necessary, but better re-evaluate for safety reasons
503 }
504 
PreNotify(NotifyEvent & rNEvt)505 bool FormattedField::PreNotify(NotifyEvent& rNEvt)
506 {
507     if (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT)
508         m_aLastSelection = GetSelection();
509     return SpinField::PreNotify(rNEvt);
510 }
511 
ImplSetFormatKey(sal_uLong nFormatKey)512 void FormattedField::ImplSetFormatKey(sal_uLong nFormatKey)
513 {
514 
515     m_nFormatKey = nFormatKey;
516     bool bNeedFormatter = (m_pFormatter == nullptr) && (nFormatKey != 0);
517     if (bNeedFormatter)
518     {
519         ImplGetFormatter(); // this creates a standard formatter
520 
521         // It might happen that the standard formatter makes no sense here, but it takes a default
522         // format. Thus, it is possible to set one of the other standard keys (which are spanning
523         // across multiple formatters).
524         m_nFormatKey = nFormatKey;
525         // When calling SetFormatKey without a formatter, the key must be one of the standard values
526         // that is available for all formatters (and, thus, also in this new one).
527         DBG_ASSERT(m_pFormatter->GetEntry(nFormatKey) != nullptr, "FormattedField::ImplSetFormatKey : invalid format key !");
528     }
529 }
530 
SetFormatKey(sal_uLong nFormatKey)531 void FormattedField::SetFormatKey(sal_uLong nFormatKey)
532 {
533     bool bNoFormatter = (m_pFormatter == nullptr);
534     ImplSetFormatKey(nFormatKey);
535     FormatChanged((bNoFormatter && (m_pFormatter != nullptr)) ? FORMAT_CHANGE_TYPE::FORMATTER : FORMAT_CHANGE_TYPE::KEYONLY);
536 }
537 
SetFormatter(SvNumberFormatter * pFormatter,bool bResetFormat)538 void FormattedField::SetFormatter(SvNumberFormatter* pFormatter, bool bResetFormat)
539 {
540 
541     if (bResetFormat)
542     {
543         m_pFormatter = pFormatter;
544 
545         // calc the default format key from the Office's UI locale
546         if ( m_pFormatter )
547         {
548             // get the Office's locale and translate
549             LanguageType eSysLanguage = SvtSysLocale().GetLanguageTag().getLanguageType( false);
550             // get the standard numeric format for this language
551             m_nFormatKey = m_pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, eSysLanguage );
552         }
553         else
554             m_nFormatKey = 0;
555     }
556     else
557     {
558         LanguageType aOldLang;
559         OUString sOldFormat = GetFormat(aOldLang);
560 
561         sal_uInt32 nDestKey = pFormatter->TestNewString(sOldFormat);
562         if (nDestKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
563         {
564             // language of the new formatter
565             const SvNumberformat* pDefaultEntry = pFormatter->GetEntry(0);
566             LanguageType aNewLang = pDefaultEntry ? pDefaultEntry->GetLanguage() : LANGUAGE_DONTKNOW;
567 
568             // convert the old format string into the new language
569             sal_Int32 nCheckPos;
570             SvNumFormatType nType;
571             pFormatter->PutandConvertEntry(sOldFormat, nCheckPos, nType, nDestKey, aOldLang, aNewLang, true);
572             m_nFormatKey = nDestKey;
573         }
574         m_pFormatter = pFormatter;
575     }
576 
577     FormatChanged(FORMAT_CHANGE_TYPE::FORMATTER);
578 }
579 
GetFormat(LanguageType & eLang) const580 OUString FormattedField::GetFormat(LanguageType& eLang) const
581 {
582     const SvNumberformat* pFormatEntry = ImplGetFormatter()->GetEntry(m_nFormatKey);
583     DBG_ASSERT(pFormatEntry != nullptr, "FormattedField::GetFormat: no number format for the given format key.");
584     OUString sFormatString = pFormatEntry ? pFormatEntry->GetFormatstring() : OUString();
585     eLang = pFormatEntry ? pFormatEntry->GetLanguage() : LANGUAGE_DONTKNOW;
586 
587     return sFormatString;
588 }
589 
SetFormat(const OUString & rFormatString,LanguageType eLang)590 bool FormattedField::SetFormat(const OUString& rFormatString, LanguageType eLang)
591 {
592     sal_uInt32 nNewKey = ImplGetFormatter()->TestNewString(rFormatString, eLang);
593     if (nNewKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
594     {
595         sal_Int32 nCheckPos;
596         SvNumFormatType nType;
597         OUString rFormat(rFormatString);
598         if (!ImplGetFormatter()->PutEntry(rFormat, nCheckPos, nType, nNewKey, eLang))
599             return false;
600         DBG_ASSERT(nNewKey != NUMBERFORMAT_ENTRY_NOT_FOUND, "FormattedField::SetFormatString : PutEntry returned an invalid key !");
601     }
602 
603     if (nNewKey != m_nFormatKey)
604         SetFormatKey(nNewKey);
605     return true;
606 }
607 
GetThousandsSep() const608 bool FormattedField::GetThousandsSep() const
609 {
610     DBG_ASSERT(!ImplGetFormatter()->IsTextFormat(m_nFormatKey),
611         "FormattedField::GetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
612 
613     bool bThousand, IsRed;
614     sal_uInt16 nPrecision, nLeadingCnt;
615     ImplGetFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
616 
617     return bThousand;
618 }
619 
SetThousandsSep(bool _bUseSeparator)620 void FormattedField::SetThousandsSep(bool _bUseSeparator)
621 {
622     DBG_ASSERT(!ImplGetFormatter()->IsTextFormat(m_nFormatKey),
623         "FormattedField::SetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
624 
625     // get the current settings
626     bool bThousand, IsRed;
627     sal_uInt16 nPrecision, nLeadingCnt;
628     ImplGetFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
629     if (bThousand == _bUseSeparator)
630         return;
631 
632     // we need the language for the following
633     LanguageType eLang;
634     GetFormat(eLang);
635 
636     // generate a new format ...
637     OUString sFmtDescription = ImplGetFormatter()->GenerateFormat(m_nFormatKey, eLang, _bUseSeparator, IsRed, nPrecision, nLeadingCnt);
638     // ... and introduce it to the formatter
639     sal_Int32 nCheckPos = 0;
640     sal_uInt32 nNewKey;
641     SvNumFormatType nType;
642     ImplGetFormatter()->PutEntry(sFmtDescription, nCheckPos, nType, nNewKey, eLang);
643 
644     // set the new key
645     ImplSetFormatKey(nNewKey);
646     FormatChanged(FORMAT_CHANGE_TYPE::THOUSANDSSEP);
647 }
648 
GetDecimalDigits() const649 sal_uInt16 FormattedField::GetDecimalDigits() const
650 {
651     DBG_ASSERT(!ImplGetFormatter()->IsTextFormat(m_nFormatKey),
652         "FormattedField::GetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
653 
654     bool bThousand, IsRed;
655     sal_uInt16 nPrecision, nLeadingCnt;
656     ImplGetFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
657 
658     return nPrecision;
659 }
660 
SetDecimalDigits(sal_uInt16 _nPrecision)661 void FormattedField::SetDecimalDigits(sal_uInt16 _nPrecision)
662 {
663     DBG_ASSERT(!ImplGetFormatter()->IsTextFormat(m_nFormatKey),
664         "FormattedField::SetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
665 
666     // get the current settings
667     bool bThousand, IsRed;
668     sal_uInt16 nPrecision, nLeadingCnt;
669     ImplGetFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
670     if (nPrecision == _nPrecision)
671         return;
672 
673     // we need the language for the following
674     LanguageType eLang;
675     GetFormat(eLang);
676 
677     // generate a new format ...
678     OUString sFmtDescription = ImplGetFormatter()->GenerateFormat(m_nFormatKey, eLang, bThousand, IsRed, _nPrecision, nLeadingCnt);
679     // ... and introduce it to the formatter
680     sal_Int32 nCheckPos = 0;
681     sal_uInt32 nNewKey;
682     SvNumFormatType nType;
683     ImplGetFormatter()->PutEntry(sFmtDescription, nCheckPos, nType, nNewKey, eLang);
684 
685     // set the new key
686     ImplSetFormatKey(nNewKey);
687     FormatChanged(FORMAT_CHANGE_TYPE::PRECISION);
688 }
689 
FormatChanged(FORMAT_CHANGE_TYPE _nWhat)690 void FormattedField::FormatChanged( FORMAT_CHANGE_TYPE _nWhat )
691 {
692     m_pLastOutputColor = nullptr;
693 
694     if ( (_nWhat == FORMAT_CHANGE_TYPE::FORMATTER) && m_pFormatter )
695         m_pFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL );
696 
697     ReFormat();
698 }
699 
Commit()700 void FormattedField::Commit()
701 {
702     // remember the old text
703     OUString sOld( GetText() );
704 
705     // do the reformat
706     ReFormat();
707 
708     // did the text change?
709     if ( GetText() != sOld )
710     {   // consider the field as modified,
711         // but we already have the most recent value;
712         // don't reparse it from the text
713         // (can lead to data loss when the format is lossy,
714         //  as is e.g. our default date format: 2-digit year!)
715         impl_Modify(false);
716     }
717 }
718 
ReFormat()719 void FormattedField::ReFormat()
720 {
721     if (!IsEmptyFieldEnabled() || !GetText().isEmpty())
722     {
723         if (TreatingAsNumber())
724         {
725             double dValue = GetValue();
726             if ( m_bEnableNaN && ::rtl::math::isNan( dValue ) )
727                 return;
728             ImplSetValue( dValue, true );
729         }
730         else
731             SetTextFormatted(GetTextValue());
732     }
733 }
734 
EventNotify(NotifyEvent & rNEvt)735 bool FormattedField::EventNotify(NotifyEvent& rNEvt)
736 {
737 
738     if ((rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !IsReadOnly())
739     {
740         const KeyEvent& rKEvt = *rNEvt.GetKeyEvent();
741         sal_uInt16 nMod = rKEvt.GetKeyCode().GetModifier();
742         switch ( rKEvt.GetKeyCode().GetCode() )
743         {
744             case KEY_UP:
745             case KEY_DOWN:
746             case KEY_PAGEUP:
747             case KEY_PAGEDOWN:
748                 if (!nMod && ImplGetFormatter()->IsTextFormat(m_nFormatKey))
749                 {
750                     // the base class would translate this into calls to Up/Down/First/Last,
751                     // but we don't want this if we are text-formatted
752                     return true;
753                 }
754         }
755     }
756 
757     if ((rNEvt.GetType() == MouseNotifyEvent::COMMAND) && !IsReadOnly())
758     {
759         const CommandEvent* pCommand = rNEvt.GetCommandEvent();
760         if (pCommand->GetCommand() == CommandEventId::Wheel)
761         {
762             const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData();
763             if ((pData->GetMode() == CommandWheelMode::SCROLL) && ImplGetFormatter()->IsTextFormat(m_nFormatKey))
764             {
765                 // same as above : prevent the base class from doing Up/Down-calls
766                 // (normally I should put this test into the Up/Down methods itself, shouldn't I ?)
767                 // FS - 71553 - 19.01.00
768                 return true;
769             }
770         }
771     }
772 
773     if (rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS)
774     {
775         // special treatment for empty texts
776         if (GetText().isEmpty())
777         {
778             if (!IsEmptyFieldEnabled())
779             {
780                 if (TreatingAsNumber())
781                 {
782                     ImplSetValue(m_dCurrentValue, true);
783                     Modify();
784                     m_ValueState = valueDouble;
785                 }
786                 else
787                 {
788                     OUString sNew = GetTextValue();
789                     if (!sNew.isEmpty())
790                         SetTextFormatted(sNew);
791                     else
792                         SetTextFormatted(m_sDefaultText);
793                     m_ValueState = valueString;
794                 }
795             }
796         }
797         else
798         {
799             Commit();
800         }
801     }
802 
803     return SpinField::EventNotify( rNEvt );
804 }
805 
SetMinValue(double dMin)806 void FormattedField::SetMinValue(double dMin)
807 {
808     DBG_ASSERT(m_bTreatAsNumber, "FormattedField::SetMinValue : only to be used in numeric mode !");
809 
810     m_dMinValue = dMin;
811     m_bHasMin = true;
812     // for checking the current value at the new border -> ImplSetValue
813     ReFormat();
814 }
815 
SetMaxValue(double dMax)816 void FormattedField::SetMaxValue(double dMax)
817 {
818     DBG_ASSERT(m_bTreatAsNumber, "FormattedField::SetMaxValue : only to be used in numeric mode !");
819 
820     m_dMaxValue = dMax;
821     m_bHasMax = true;
822     // for checking the current value at the new border -> ImplSetValue
823     ReFormat();
824 }
825 
SetTextValue(const OUString & rText)826 void FormattedField::SetTextValue(const OUString& rText)
827 {
828     SetText(rText);
829     ReFormat();
830 }
831 
EnableEmptyField(bool bEnable)832 void FormattedField::EnableEmptyField(bool bEnable)
833 {
834     if (bEnable == m_bEnableEmptyField)
835         return;
836 
837     m_bEnableEmptyField = bEnable;
838     if (!m_bEnableEmptyField && GetText().isEmpty())
839         ImplSetValue(m_dCurrentValue, true);
840 }
841 
ImplSetValue(double dVal,bool bForce)842 void FormattedField::ImplSetValue(double dVal, bool bForce)
843 {
844     if (m_bHasMin && (dVal<m_dMinValue))
845     {
846         dVal = m_bWrapOnLimits ? fmod(dVal + m_dMaxValue + 1 - m_dMinValue, m_dMaxValue + 1) + m_dMinValue
847                                : m_dMinValue;
848     }
849     if (m_bHasMax && (dVal>m_dMaxValue))
850     {
851         dVal = m_bWrapOnLimits ? fmod(dVal - m_dMinValue, m_dMaxValue + 1) + m_dMinValue
852                                : m_dMaxValue;
853     }
854     if (!bForce && (dVal == GetValue()))
855         return;
856 
857     DBG_ASSERT(ImplGetFormatter() != nullptr, "FormattedField::ImplSetValue : can't set a value without a formatter !");
858 
859     m_ValueState = valueDouble;
860     m_dCurrentValue = dVal;
861 
862     if (!m_aOutputHdl.IsSet() || !m_aOutputHdl.Call(*this))
863     {
864         OUString sNewText;
865         if (ImplGetFormatter()->IsTextFormat(m_nFormatKey))
866         {
867             // first convert the number as string in standard format
868             OUString sTemp;
869             ImplGetFormatter()->GetOutputString(dVal, 0, sTemp, &m_pLastOutputColor);
870             // then encode the string in the corresponding text format
871             ImplGetFormatter()->GetOutputString(sTemp, m_nFormatKey, sNewText, &m_pLastOutputColor);
872         }
873         else
874         {
875             if( IsUsingInputStringForFormatting())
876             {
877                 ImplGetFormatter()->GetInputLineString(dVal, m_nFormatKey, sNewText);
878             }
879             else
880             {
881                 ImplGetFormatter()->GetOutputString(dVal, m_nFormatKey, sNewText, &m_pLastOutputColor);
882             }
883         }
884         ImplSetTextImpl(sNewText, nullptr);
885         DBG_ASSERT(CheckText(sNewText), "FormattedField::ImplSetValue : formatted string doesn't match the criteria !");
886     }
887 
888     m_ValueState = valueDouble;
889 }
890 
ImplGetValue(double & dNewVal)891 bool FormattedField::ImplGetValue(double& dNewVal)
892 {
893     dNewVal = m_dCurrentValue;
894     if (m_ValueState == valueDouble)
895         return true;
896 
897     dNewVal = m_dDefaultValue;
898     OUString sText(GetText());
899     if (sText.isEmpty())
900         return true;
901 
902     bool bUseExternalFormatterValue = false;
903     if (m_aInputHdl.IsSet())
904     {
905         sal_Int64 nResult;
906         auto eState = m_aInputHdl.Call(&nResult);
907         bUseExternalFormatterValue = eState != TRISTATE_INDET;
908         if (bUseExternalFormatterValue)
909         {
910             if (eState == TRISTATE_TRUE)
911             {
912                 dNewVal = nResult;
913                 dNewVal /= weld::SpinButton::Power10(GetDecimalDigits());
914             }
915             else
916                 dNewVal = m_dCurrentValue;
917         }
918     }
919 
920     if (!bUseExternalFormatterValue)
921     {
922         DBG_ASSERT(ImplGetFormatter() != nullptr, "FormattedField::ImplGetValue : can't give you a current value without a formatter !");
923 
924         sal_uInt32 nFormatKey = m_nFormatKey; // IsNumberFormat changes the FormatKey!
925 
926         if (ImplGetFormatter()->IsTextFormat(nFormatKey) && m_bTreatAsNumber)
927             // for detection of values like "1,1" in fields that are formatted as text
928             nFormatKey = 0;
929 
930         // special treatment for percentage formatting
931         if (ImplGetFormatter()->GetType(m_nFormatKey) == SvNumFormatType::PERCENT)
932         {
933             // the language of our format
934             LanguageType eLanguage = m_pFormatter->GetEntry(m_nFormatKey)->GetLanguage();
935             // the default number format for this language
936             sal_uLong nStandardNumericFormat = m_pFormatter->GetStandardFormat(SvNumFormatType::NUMBER, eLanguage);
937 
938             sal_uInt32 nTempFormat = nStandardNumericFormat;
939             double dTemp;
940             if (m_pFormatter->IsNumberFormat(sText, nTempFormat, dTemp) &&
941                 SvNumFormatType::NUMBER == m_pFormatter->GetType(nTempFormat))
942                 // the string is equivalent to a number formatted one (has no % sign) -> append it
943                 sText += "%";
944             // (with this, an input of '3' becomes '3%', which then by the formatter is translated
945             // into 0.03. Without this, the formatter would give us the double 3 for an input '3',
946             // which equals 300 percent.
947         }
948         if (!ImplGetFormatter()->IsNumberFormat(sText, nFormatKey, dNewVal))
949             return false;
950     }
951 
952     if (m_bHasMin && (dNewVal<m_dMinValue))
953         dNewVal = m_dMinValue;
954     if (m_bHasMax && (dNewVal>m_dMaxValue))
955         dNewVal = m_dMaxValue;
956     return true;
957 }
958 
SetValue(double dVal)959 void FormattedField::SetValue(double dVal)
960 {
961     ImplSetValue(dVal, m_ValueState != valueDouble);
962 }
963 
GetValue()964 double FormattedField::GetValue()
965 {
966 
967     if ( !ImplGetValue( m_dCurrentValue ) )
968     {
969         if ( m_bEnableNaN )
970             ::rtl::math::setNan( &m_dCurrentValue );
971         else
972             m_dCurrentValue = m_dDefaultValue;
973     }
974 
975     m_ValueState = valueDouble;
976     return m_dCurrentValue;
977 }
978 
DisableRemainderFactor()979 void FormattedField::DisableRemainderFactor()
980 {
981     m_bDisableRemainderFactor = true;
982 }
983 
set_property(const OString & rKey,const OUString & rValue)984 bool FormattedField::set_property(const OString &rKey, const OUString &rValue)
985 {
986     if (rKey == "digits")
987         SetDecimalDigits(rValue.toInt32());
988     else if (rKey == "wrap")
989         m_bWrapOnLimits = toBool(rValue);
990     else
991         return SpinField::set_property(rKey, rValue);
992     return true;
993 }
994 
Up()995 void FormattedField::Up()
996 {
997     auto nScale = weld::SpinButton::Power10(GetDecimalDigits());
998 
999     sal_Int64 nValue = std::round(GetValue() * nScale);
1000     sal_Int64 nSpinSize = std::round(m_dSpinSize * nScale);
1001     sal_Int64 nRemainder = m_bDisableRemainderFactor ? 0 : nValue % nSpinSize;
1002     if (nValue >= 0)
1003         nValue = (nRemainder == 0) ? nValue + nSpinSize : nValue + nSpinSize - nRemainder;
1004     else
1005         nValue = (nRemainder == 0) ? nValue + nSpinSize : nValue - nRemainder;
1006 
1007     // setValue handles under- and overflows (min/max) automatically
1008     SetValue(static_cast<double>(nValue) / nScale);
1009     SetModifyFlag();
1010     Modify();
1011 
1012     SpinField::Up();
1013 }
1014 
Down()1015 void FormattedField::Down()
1016 {
1017     auto nScale = weld::SpinButton::Power10(GetDecimalDigits());
1018 
1019     sal_Int64 nValue = std::round(GetValue() * nScale);
1020     sal_Int64 nSpinSize = std::round(m_dSpinSize * nScale);
1021     sal_Int64 nRemainder = m_bDisableRemainderFactor ? 0 : nValue % nSpinSize;
1022     if (nValue >= 0)
1023         nValue = (nRemainder == 0) ? nValue - nSpinSize : nValue - nRemainder;
1024     else
1025         nValue = (nRemainder == 0) ? nValue - nSpinSize : nValue - nSpinSize - nRemainder;
1026 
1027     // setValue handles under- and overflows (min/max) automatically
1028     SetValue(static_cast<double>(nValue) / nScale);
1029     SetModifyFlag();
1030     Modify();
1031 
1032     SpinField::Down();
1033 }
1034 
First()1035 void FormattedField::First()
1036 {
1037     if (m_bHasMin)
1038     {
1039         SetValue(m_dMinValue);
1040         SetModifyFlag();
1041         Modify();
1042     }
1043 
1044     SpinField::First();
1045 }
1046 
Last()1047 void FormattedField::Last()
1048 {
1049     if (m_bHasMax)
1050     {
1051         SetValue(m_dMaxValue);
1052         SetModifyFlag();
1053         Modify();
1054     }
1055 
1056     SpinField::Last();
1057 }
1058 
UseInputStringForFormatting()1059 void FormattedField::UseInputStringForFormatting()
1060 {
1061     m_bUseInputStringForFormatting = true;
1062 }
1063 
DoubleNumericField(vcl::Window * pParent,WinBits nStyle)1064 DoubleNumericField::DoubleNumericField(vcl::Window* pParent, WinBits nStyle)
1065     : FormattedField(pParent, nStyle)
1066 {
1067     ResetConformanceTester();
1068 }
1069 
1070 DoubleNumericField::~DoubleNumericField() = default;
1071 
FormatChanged(FORMAT_CHANGE_TYPE nWhat)1072 void DoubleNumericField::FormatChanged(FORMAT_CHANGE_TYPE nWhat)
1073 {
1074     ResetConformanceTester();
1075     FormattedField::FormatChanged(nWhat);
1076 }
1077 
CheckText(const OUString & sText) const1078 bool DoubleNumericField::CheckText(const OUString& sText) const
1079 {
1080     // We'd like to implement this using the NumberFormatter::IsNumberFormat, but unfortunately, this doesn't
1081     // recognize fragments of numbers (like, for instance "1e", which happens during entering e.g. "1e10")
1082     // Thus, the roundabout way via a regular expression
1083     return m_pNumberValidator->isValidNumericFragment( sText );
1084 }
1085 
ResetConformanceTester()1086 void DoubleNumericField::ResetConformanceTester()
1087 {
1088     // the thousands and the decimal separator are language dependent
1089     const SvNumberformat* pFormatEntry = ImplGetFormatter()->GetEntry(m_nFormatKey);
1090 
1091     sal_Unicode cSeparatorThousand = ',';
1092     sal_Unicode cSeparatorDecimal = '.';
1093     if (pFormatEntry)
1094     {
1095         LocaleDataWrapper aLocaleInfo( LanguageTag( pFormatEntry->GetLanguage()) );
1096 
1097         OUString sSeparator = aLocaleInfo.getNumThousandSep();
1098         if (!sSeparator.isEmpty())
1099             cSeparatorThousand = sSeparator[0];
1100 
1101         sSeparator = aLocaleInfo.getNumDecimalSep();
1102         if (!sSeparator.isEmpty())
1103             cSeparatorDecimal = sSeparator[0];
1104     }
1105 
1106     m_pNumberValidator.reset(new validation::NumberValidator( cSeparatorThousand, cSeparatorDecimal ));
1107 }
1108 
DoubleCurrencyField(vcl::Window * pParent,WinBits nStyle)1109 DoubleCurrencyField::DoubleCurrencyField(vcl::Window* pParent, WinBits nStyle)
1110     :FormattedField(pParent, nStyle)
1111     ,m_bChangingFormat(false)
1112 {
1113     m_bPrependCurrSym = false;
1114 
1115     // initialize with a system currency format
1116     m_sCurrencySymbol = SvtSysLocale().GetLocaleData().getCurrSymbol();
1117     UpdateCurrencyFormat();
1118 }
1119 
FormatChanged(FORMAT_CHANGE_TYPE nWhat)1120 void DoubleCurrencyField::FormatChanged(FORMAT_CHANGE_TYPE nWhat)
1121 {
1122     if (m_bChangingFormat)
1123     {
1124         FormattedField::FormatChanged(nWhat);
1125         return;
1126     }
1127 
1128     switch (nWhat)
1129     {
1130         case FORMAT_CHANGE_TYPE::FORMATTER:
1131         case FORMAT_CHANGE_TYPE::PRECISION:
1132         case FORMAT_CHANGE_TYPE::THOUSANDSSEP:
1133             // the aspects which changed don't take our currency settings into account (in fact, they most probably
1134             // destroyed them)
1135             UpdateCurrencyFormat();
1136             break;
1137         case FORMAT_CHANGE_TYPE::KEYONLY:
1138             OSL_FAIL("DoubleCurrencyField::FormatChanged : somebody modified my key !");
1139             // We always build our own format from the settings we get via special methods (setCurrencySymbol etc.).
1140             // Nobody but ourself should modify the format key directly!
1141             break;
1142         default: break;
1143     }
1144 
1145     FormattedField::FormatChanged(nWhat);
1146 }
1147 
setCurrencySymbol(const OUString & rSymbol)1148 void DoubleCurrencyField::setCurrencySymbol(const OUString& rSymbol)
1149 {
1150     if (m_sCurrencySymbol == rSymbol)
1151         return;
1152 
1153     m_sCurrencySymbol = rSymbol;
1154     UpdateCurrencyFormat();
1155     FormatChanged(FORMAT_CHANGE_TYPE::CURRENCY_SYMBOL);
1156 }
1157 
setPrependCurrSym(bool _bPrepend)1158 void DoubleCurrencyField::setPrependCurrSym(bool _bPrepend)
1159 {
1160     if (m_bPrependCurrSym == _bPrepend)
1161          return;
1162 
1163     m_bPrependCurrSym = _bPrepend;
1164     UpdateCurrencyFormat();
1165     FormatChanged(FORMAT_CHANGE_TYPE::CURRSYM_POSITION);
1166 }
1167 
UpdateCurrencyFormat()1168 void DoubleCurrencyField::UpdateCurrencyFormat()
1169 {
1170     // the old settings
1171     LanguageType eLanguage;
1172     GetFormat(eLanguage);
1173     bool bThSep = GetThousandsSep();
1174     sal_uInt16 nDigits = GetDecimalDigits();
1175 
1176     // build a new format string with the base class' and my own settings
1177 
1178     /* Strangely with gcc 4.6.3 this needs a temporary LanguageTag, otherwise
1179      * there's
1180      * error: request for member 'getNumThousandSep' in 'aLocaleInfo', which is
1181      * of non-class type 'LocaleDataWrapper(LanguageTag)' */
1182     LanguageTag aLanguageTag( eLanguage);
1183     LocaleDataWrapper aLocaleInfo( aLanguageTag );
1184 
1185     OUStringBuffer sNewFormat;
1186     if (bThSep)
1187     {
1188         sNewFormat.append('#');
1189         sNewFormat.append(aLocaleInfo.getNumThousandSep());
1190         sNewFormat.append("##0");
1191     }
1192     else
1193         sNewFormat.append('0');
1194 
1195     if (nDigits)
1196     {
1197         sNewFormat.append(aLocaleInfo.getNumDecimalSep());
1198 
1199         OUStringBuffer sTemp;
1200         comphelper::string::padToLength(sTemp, nDigits, '0');
1201         sNewFormat.append(sTemp);
1202     }
1203 
1204     if (getPrependCurrSym())
1205     {
1206         OUString sSymbol = getCurrencySymbol();
1207         sSymbol = comphelper::string::stripStart(sSymbol, ' ');
1208         sSymbol = comphelper::string::stripEnd(sSymbol, ' ');
1209 
1210         OUStringBuffer sTemp("[$");
1211         sTemp.append(sSymbol);
1212         sTemp.append("] ");
1213         sTemp.append(sNewFormat);
1214 
1215         // for negative values : $ -0.00, not -$ 0.00...
1216         // (the real solution would be a possibility to choose a "positive currency format" and a "negative currency format"...
1217         // But not now... (and hey, you could take a formatted field for this...))
1218         // FS - 31.03.00 74642
1219         sTemp.append(";[$");
1220         sTemp.append(sSymbol);
1221         sTemp.append("] -");
1222         sTemp.append(sNewFormat);
1223 
1224         sNewFormat = sTemp;
1225     }
1226     else
1227     {
1228         OUString sTemp = getCurrencySymbol();
1229         sTemp = comphelper::string::stripStart(sTemp, ' ');
1230         sTemp = comphelper::string::stripEnd(sTemp, ' ');
1231 
1232         sNewFormat.append(" [$");
1233         sNewFormat.append(sTemp);
1234         sNewFormat.append(']');
1235     }
1236 
1237     // set this new basic format
1238     m_bChangingFormat = true;
1239     SetFormat(sNewFormat.makeStringAndClear(), eLanguage);
1240     m_bChangingFormat = false;
1241 }
1242 
1243 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1244