update dev300-m58
[ooovba.git] / svtools / source / control / fmtfield.cxx
blobb51ca251987f9795d2f7a307e4bd1065b2b1261b
1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: fmtfield.cxx,v $
10 * $Revision: 1.40 $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_svtools.hxx"
34 #include <stdio.h>
35 #include <tools/debug.hxx>
36 #include <comphelper/processfactory.hxx>
37 #include <unotools/localedatawrapper.hxx>
38 #include <vcl/svapp.hxx>
39 #include <svtools/zformat.hxx>
40 #include <svtools/fmtfield.hxx>
41 #include <i18npool/mslangid.hxx>
42 #include <com/sun/star/lang/Locale.hpp>
43 #include <com/sun/star/util/SearchOptions.hpp>
44 #include <com/sun/star/util/SearchAlgorithms.hpp>
45 #include <com/sun/star/util/SearchResult.hpp>
46 #include <com/sun/star/util/SearchFlags.hpp>
47 #include <com/sun/star/lang/Locale.hpp>
48 #include <svtools/syslocale.hxx>
50 #ifndef REGEXP_SUPPORT
51 #include <map>
52 #endif
54 #if !defined INCLUDED_RTL_MATH_HXX
55 #include <rtl/math.hxx>
56 #endif
58 using namespace ::com::sun::star::lang;
59 using namespace ::com::sun::star::util;
62 #ifdef REGEXP_SUPPORT
64 //==============================================================================
65 // regular expression to validate complete numbers, plus every fragment which can occur during the input
66 // of a complete number
67 // [+/-][{digit}*.]*{digit}*[,{digit}*][e[+/-]{digit}*]
68 const char __FAR_DATA szNumericInput[] = "_[-+]?([0-9]*\\,)*[0-9]*(\\.[0-9]*)?(e[-+]?[0-9]*)?_";
69 // (the two _ are for normalizing it: With this, we can ensure that a to-be-checked text is always
70 // matched as a _whole_)
71 #else
73 // hmm. No support for regular expression. Well, I always (not really :) wanted to write a finite automat
74 // so here comes a finite automat ...
76 namespace validation
78 // the states of our automat.
79 enum State
81 START, // at the very start of the string
82 NUM_START, // the very start of the number
84 DIGIT_PRE_COMMA, // some pre-comma digits are read, perhaps including some thousand separators
86 DIGIT_POST_COMMA, // reading digits after the comma
87 EXPONENT_START, // at the very start of the exponent value
88 // (means: not including the "e" which denotes the exponent)
89 EXPONENT_DIGIT, // currently reading the digits of the exponent
91 END // reached the end of the string
94 // a row in the transition table (means the set of states to be reached from a given state)
95 typedef ::std::map< sal_Unicode, State > StateTransitions;
97 // a single transition
98 typedef StateTransitions::value_type Transition;
100 // the complete transition table
101 typedef ::std::map< State, StateTransitions > TransitionTable;
103 // the validator class
104 class NumberValidator
106 private:
107 TransitionTable m_aTransitions;
108 const sal_Unicode m_cThSep;
109 const sal_Unicode m_cDecSep;
111 public:
112 NumberValidator( const sal_Unicode _cThSep, const sal_Unicode _cDecSep );
114 sal_Bool isValidNumericFragment( const String& _rText );
116 private:
117 sal_Bool implValidateNormalized( const String& _rText );
120 //--------------------------------------------------------------------------
121 //..........................................................................
122 static void lcl_insertStopTransition( StateTransitions& _rRow )
124 _rRow.insert( Transition( '_', END ) );
127 //..........................................................................
128 static void lcl_insertStartExponentTransition( StateTransitions& _rRow )
130 _rRow.insert( Transition( 'e', EXPONENT_START ) );
133 //..........................................................................
134 static void lcl_insertSignTransitions( StateTransitions& _rRow, const State eNextState )
136 _rRow.insert( Transition( '-', eNextState ) );
137 _rRow.insert( Transition( '+', eNextState ) );
140 //..........................................................................
141 static void lcl_insertDigitTransitions( StateTransitions& _rRow, const State eNextState )
143 for ( sal_Unicode aChar = '0'; aChar <= '9'; ++aChar )
144 _rRow.insert( Transition( aChar, eNextState ) );
147 //..........................................................................
148 static void lcl_insertCommonPreCommaTransitions( StateTransitions& _rRow, const sal_Unicode _cThSep, const sal_Unicode _cDecSep )
150 // digits are allowed
151 lcl_insertDigitTransitions( _rRow, DIGIT_PRE_COMMA );
153 // the thousand separator is allowed
154 _rRow.insert( Transition( _cThSep, DIGIT_PRE_COMMA ) );
156 // a comma is allowed
157 _rRow.insert( Transition( _cDecSep, DIGIT_POST_COMMA ) );
160 //--------------------------------------------------------------------------
161 NumberValidator::NumberValidator( const sal_Unicode _cThSep, const sal_Unicode _cDecSep )
162 :m_cThSep( _cThSep )
163 ,m_cDecSep( _cDecSep )
165 // build up our transition table
167 // how to procede from START
169 StateTransitions& rRow = m_aTransitions[ START ];
170 rRow.insert( Transition( '_', NUM_START ) );
171 // if we encounter the normalizing character, we want to procede with the number
174 // how to procede from NUM_START
176 StateTransitions& rRow = m_aTransitions[ NUM_START ];
178 // a sign is allowed
179 lcl_insertSignTransitions( rRow, DIGIT_PRE_COMMA );
181 // common transitions for the two pre-comma states
182 lcl_insertCommonPreCommaTransitions( rRow, m_cThSep, m_cDecSep );
184 // the exponent may start here
185 // (this would mean string like "_+e10_", but this is a valid fragment, though no valid number)
186 lcl_insertStartExponentTransition( rRow );
189 // how to procede from DIGIT_PRE_COMMA
191 StateTransitions& rRow = m_aTransitions[ DIGIT_PRE_COMMA ];
193 // common transitions for the two pre-comma states
194 lcl_insertCommonPreCommaTransitions( rRow, m_cThSep, m_cDecSep );
196 // the exponent may start here
197 lcl_insertStartExponentTransition( rRow );
199 // the final transition indicating the end of the string
200 // (if there is no comma and no post-comma, then the string may end here)
201 lcl_insertStopTransition( rRow );
204 // how to procede from DIGIT_POST_COMMA
206 StateTransitions& rRow = m_aTransitions[ DIGIT_POST_COMMA ];
208 // there might be digits, which would keep the state at DIGIT_POST_COMMA
209 lcl_insertDigitTransitions( rRow, DIGIT_POST_COMMA );
211 // the exponent may start here
212 lcl_insertStartExponentTransition( rRow );
214 // the string may end here
215 lcl_insertStopTransition( rRow );
218 // how to procede from EXPONENT_START
220 StateTransitions& rRow = m_aTransitions[ EXPONENT_START ];
222 // there may be a sign
223 lcl_insertSignTransitions( rRow, EXPONENT_DIGIT );
225 // there may be digits
226 lcl_insertDigitTransitions( rRow, EXPONENT_DIGIT );
228 // the string may end here
229 lcl_insertStopTransition( rRow );
232 // how to procede from EXPONENT_DIGIT
234 StateTransitions& rRow = m_aTransitions[ EXPONENT_DIGIT ];
236 // there may be digits
237 lcl_insertDigitTransitions( rRow, EXPONENT_DIGIT );
239 // the string may end here
240 lcl_insertStopTransition( rRow );
243 // how to procede from END
245 /*StateTransitions& rRow =*/ m_aTransitions[ EXPONENT_DIGIT ];
246 // no valid transition to leave this state
247 // (note that we, for consistency, nevertheless want to have a row in the table)
251 //--------------------------------------------------------------------------
252 sal_Bool NumberValidator::implValidateNormalized( const String& _rText )
254 const sal_Unicode* pCheckPos = _rText.GetBuffer();
255 State eCurrentState = START;
257 while ( END != eCurrentState )
259 // look up the transition row for the current state
260 TransitionTable::const_iterator aRow = m_aTransitions.find( eCurrentState );
261 DBG_ASSERT( m_aTransitions.end() != aRow,
262 "NumberValidator::implValidateNormalized: invalid transition table (row not found)!" );
264 if ( m_aTransitions.end() != aRow )
266 // look up the current character in this row
267 StateTransitions::const_iterator aTransition = aRow->second.find( *pCheckPos );
268 if ( aRow->second.end() != aTransition )
270 // there is a valid transition for this character
271 eCurrentState = aTransition->second;
272 ++pCheckPos;
273 continue;
277 // if we're here, there is no valid transition
278 break;
281 DBG_ASSERT( ( END != eCurrentState ) || ( 0 == *pCheckPos ),
282 "NumberValidator::implValidateNormalized: inconsistency!" );
283 // if we're at END, then the string should be done, too - the string should be normalized, means ending
284 // a "_" and not containing any other "_" (except at the start), and "_" is the only possibility
285 // to reach the END state
287 // the string is valid if and only if we reached the final state
288 return ( END == eCurrentState );
291 //--------------------------------------------------------------------------
292 sal_Bool NumberValidator::isValidNumericFragment( const String& _rText )
294 if ( !_rText.Len() )
295 // empty strings are always allowed
296 return sal_True;
298 // normalize the string
299 String sNormalized( RTL_CONSTASCII_STRINGPARAM( "_") );
300 sNormalized.Append( _rText );
301 sNormalized.AppendAscii( "_" );
303 return implValidateNormalized( sNormalized );
307 #endif
309 //==============================================================================
310 SvNumberFormatter* FormattedField::StaticFormatter::s_cFormatter = NULL;
311 ULONG FormattedField::StaticFormatter::s_nReferences = 0;
313 //------------------------------------------------------------------------------
314 SvNumberFormatter* FormattedField::StaticFormatter::GetFormatter()
316 if (!s_cFormatter)
318 // get the Office's locale and translate
319 LanguageType eSysLanguage = MsLangId::convertLocaleToLanguage(
320 SvtSysLocale().GetLocaleData().getLocale() );
321 s_cFormatter = new SvNumberFormatter(
322 ::comphelper::getProcessServiceFactory(),
323 eSysLanguage);
325 return s_cFormatter;
328 //------------------------------------------------------------------------------
329 FormattedField::StaticFormatter::StaticFormatter()
331 ++s_nReferences;
334 //------------------------------------------------------------------------------
335 FormattedField::StaticFormatter::~StaticFormatter()
337 if (--s_nReferences == 0)
339 delete s_cFormatter;
340 s_cFormatter = NULL;
344 //==============================================================================
345 DBG_NAME(FormattedField);
347 #define INIT_MEMBERS() \
348 m_aLastSelection(0,0) \
349 ,m_dMinValue(0) \
350 ,m_dMaxValue(0) \
351 ,m_bHasMin(FALSE) \
352 ,m_bHasMax(FALSE) \
353 ,m_bStrictFormat(TRUE) \
354 ,m_bValueDirty(TRUE) \
355 ,m_bEnableEmptyField(TRUE) \
356 ,m_bAutoColor(FALSE) \
357 ,m_bEnableNaN(FALSE) \
358 ,m_dCurrentValue(0) \
359 ,m_dDefaultValue(0) \
360 ,m_nFormatKey(0) \
361 ,m_pFormatter(NULL) \
362 ,m_dSpinSize(1) \
363 ,m_dSpinFirst(-1000000) \
364 ,m_dSpinLast(1000000) \
365 ,m_bTreatAsNumber(TRUE) \
366 ,m_pLastOutputColor(NULL) \
367 ,m_bUseInputStringForFormatting(false)
369 //------------------------------------------------------------------------------
370 FormattedField::FormattedField(Window* pParent, WinBits nStyle, SvNumberFormatter* pInitialFormatter, INT32 nFormatKey)
371 :SpinField(pParent, nStyle)
372 ,INIT_MEMBERS()
374 DBG_CTOR(FormattedField, NULL);
376 if (pInitialFormatter)
378 m_pFormatter = pInitialFormatter;
379 m_nFormatKey = nFormatKey;
383 //------------------------------------------------------------------------------
384 FormattedField::FormattedField(Window* pParent, const ResId& rResId, SvNumberFormatter* pInitialFormatter, INT32 nFormatKey)
385 :SpinField(pParent, rResId)
386 ,INIT_MEMBERS()
388 DBG_CTOR(FormattedField, NULL);
390 if (pInitialFormatter)
392 m_pFormatter = pInitialFormatter;
393 m_nFormatKey = nFormatKey;
397 //------------------------------------------------------------------------------
398 FormattedField::~FormattedField()
400 DBG_DTOR(FormattedField, NULL);
403 //------------------------------------------------------------------------------
404 void FormattedField::SetValidateText(const XubString& rText, const String* pErrorText)
406 DBG_CHKTHIS(FormattedField, NULL);
408 if (CheckText(rText))
409 SetText(rText);
410 else
411 if (pErrorText)
412 ImplSetTextImpl(*pErrorText, NULL);
413 else
414 ImplSetValue(m_dDefaultValue, TRUE);
417 //------------------------------------------------------------------------------
418 void FormattedField::SetText(const XubString& rStr)
420 DBG_CHKTHIS(FormattedField, NULL);
422 SpinField::SetText(rStr);
423 m_bValueDirty = TRUE;
426 //------------------------------------------------------------------------------
427 void FormattedField::SetText( const XubString& rStr, const Selection& rNewSelection )
429 DBG_CHKTHIS(FormattedField, NULL);
431 SpinField::SetText( rStr, rNewSelection );
432 m_bValueDirty = TRUE;
435 //------------------------------------------------------------------------------
436 void FormattedField::SetTextFormatted(const XubString& rStr)
438 DBG_CHKTHIS(FormattedField, NULL);
440 #if defined DBG_UTIL
441 if (ImplGetFormatter()->IsTextFormat(m_nFormatKey))
442 DBG_WARNING("FormattedField::SetTextFormatted : valid only with text formats !");
443 #endif
445 m_sCurrentTextValue = rStr;
447 String sFormatted;
448 double dNumber = 0.0;
449 // IsNumberFormat changes the format key parameter
450 sal_uInt32 nTempFormatKey = static_cast< sal_uInt32 >( m_nFormatKey );
451 if( IsUsingInputStringForFormatting() &&
452 ImplGetFormatter()->IsNumberFormat(m_sCurrentTextValue, nTempFormatKey, dNumber) )
453 ImplGetFormatter()->GetInputLineString(dNumber, m_nFormatKey, sFormatted);
454 else
455 ImplGetFormatter()->GetOutputString(m_sCurrentTextValue, m_nFormatKey, sFormatted, &m_pLastOutputColor);
457 // calculate the new selection
458 Selection aSel(GetSelection());
459 Selection aNewSel(aSel);
460 aNewSel.Justify();
461 USHORT nNewLen = sFormatted.Len();
462 USHORT nCurrentLen = GetText().Len();
463 if ((nNewLen > nCurrentLen) && (aNewSel.Max() == nCurrentLen))
464 { // the new text is longer and the cursor was behind the last char (of the old text)
465 if (aNewSel.Min() == 0)
466 { // the whole text was selected -> select the new text on the whole, too
467 aNewSel.Max() = nNewLen;
468 if (!nCurrentLen)
469 { // there wasn't really a previous selection (as there was no previous text), we're setting a new one -> check the selection options
470 ULONG nSelOptions = GetSettings().GetStyleSettings().GetSelectionOptions();
471 if (nSelOptions & SELECTION_OPTION_SHOWFIRST)
472 { // selection should be from right to left -> swap min and max
473 aNewSel.Min() = aNewSel.Max();
474 aNewSel.Max() = 0;
478 else if (aNewSel.Max() == aNewSel.Min())
479 { // there was no selection -> set the cursor behind the new last char
480 aNewSel.Max() = nNewLen;
481 aNewSel.Min() = nNewLen;
484 else if (aNewSel.Max() > nNewLen)
485 aNewSel.Max() = nNewLen;
486 else
487 aNewSel = aSel; // don't use the justified version
488 SpinField::SetText(sFormatted, aNewSel);
489 m_bValueDirty = FALSE;
492 //------------------------------------------------------------------------------
493 String FormattedField::GetTextValue() const
495 if (m_bValueDirty)
497 ((FormattedField*)this)->m_sCurrentTextValue = GetText();
498 ((FormattedField*)this)->m_bValueDirty = FALSE;
500 return m_sCurrentTextValue;
503 //------------------------------------------------------------------------------
504 void FormattedField::EnableNotANumber( BOOL _bEnable )
506 if ( m_bEnableNaN == _bEnable )
507 return;
509 m_bEnableNaN = _bEnable;
512 //------------------------------------------------------------------------------
513 void FormattedField::SetAutoColor(BOOL _bAutomatic)
515 if (_bAutomatic == m_bAutoColor)
516 return;
518 m_bAutoColor = _bAutomatic;
519 if (m_bAutoColor)
520 { // if auto color is switched on, adjust the current text color, too
521 if (m_pLastOutputColor)
522 SetControlForeground(*m_pLastOutputColor);
523 else
524 SetControlForeground();
528 //------------------------------------------------------------------------------
529 void FormattedField::Modify()
531 DBG_CHKTHIS(FormattedField, NULL);
533 if (!IsStrictFormat())
535 m_bValueDirty = TRUE;
536 SpinField::Modify();
537 return;
540 String sCheck = GetText();
541 if (CheckText(sCheck))
543 m_sLastValidText = sCheck;
544 m_aLastSelection = GetSelection();
545 m_bValueDirty = TRUE;
547 else
549 ImplSetTextImpl(m_sLastValidText, &m_aLastSelection);
552 SpinField::Modify();
555 //------------------------------------------------------------------------------
556 void FormattedField::ImplSetTextImpl(const XubString& rNew, Selection* pNewSel)
558 DBG_CHKTHIS(FormattedField, NULL);
560 if (m_bAutoColor)
562 if (m_pLastOutputColor)
563 SetControlForeground(*m_pLastOutputColor);
564 else
565 SetControlForeground();
568 if (pNewSel)
569 SpinField::SetText(rNew, *pNewSel);
570 else
572 Selection aSel(GetSelection());
573 aSel.Justify();
575 USHORT nNewLen = rNew.Len();
576 USHORT nCurrentLen = GetText().Len();
578 if ((nNewLen > nCurrentLen) && (aSel.Max() == nCurrentLen))
579 { // new new text is longer and the cursor is behind the last char
580 if (aSel.Min() == 0)
581 { // the whole text was selected -> select the new text on the whole, too
582 aSel.Max() = nNewLen;
583 if (!nCurrentLen)
584 { // there wasn't really a previous selection (as there was no previous text), we're setting a new one -> check the selection options
585 ULONG nSelOptions = GetSettings().GetStyleSettings().GetSelectionOptions();
586 if (nSelOptions & SELECTION_OPTION_SHOWFIRST)
587 { // selection should be from right to left -> swap min and max
588 aSel.Min() = aSel.Max();
589 aSel.Max() = 0;
593 else if (aSel.Max() == aSel.Min())
594 { // there was no selection -> set the cursor behind the new last char
595 aSel.Max() = nNewLen;
596 aSel.Min() = nNewLen;
599 else if (aSel.Max() > nNewLen)
600 aSel.Max() = nNewLen;
601 SpinField::SetText(rNew, aSel);
604 m_bValueDirty = TRUE;
605 // muss nicht stimmen, aber sicherheitshalber ...
608 //------------------------------------------------------------------------------
609 long FormattedField::PreNotify(NotifyEvent& rNEvt)
611 DBG_CHKTHIS(FormattedField, NULL);
612 if (rNEvt.GetType() == EVENT_KEYINPUT)
613 m_aLastSelection = GetSelection();
614 return SpinField::PreNotify(rNEvt);
617 //------------------------------------------------------------------------------
618 void FormattedField::ImplSetFormatKey(ULONG nFormatKey)
620 DBG_CHKTHIS(FormattedField, NULL);
622 m_nFormatKey = nFormatKey;
623 BOOL bNeedFormatter = (m_pFormatter == NULL) && (nFormatKey != 0);
624 if (bNeedFormatter)
626 ImplGetFormatter(); // damit wird ein Standard-Formatter angelegt
628 m_nFormatKey = nFormatKey;
629 // kann sein, dass das in dem Standard-Formatter keinen Sinn macht, aber der nimmt dann ein Default-Format an.
630 // Auf diese Weise kann ich einfach einen der - formatteruebergreifended gleichen - Standard-Keys setzen.
631 DBG_ASSERT(m_pFormatter->GetEntry(nFormatKey) != NULL, "FormattedField::ImplSetFormatKey : invalid format key !");
632 // Wenn SetFormatKey aufgerufen wird, ohne dass ein Formatter existiert, muss der Key einer der Standard-Werte
633 // sein, der in allen Formattern (also auch in meinem neu angelegten) vorhanden ist.
637 //------------------------------------------------------------------------------
638 void FormattedField::SetFormatKey(ULONG nFormatKey)
640 DBG_CHKTHIS(FormattedField, NULL);
641 BOOL bNoFormatter = (m_pFormatter == NULL);
642 ImplSetFormatKey(nFormatKey);
643 FormatChanged((bNoFormatter && (m_pFormatter != NULL)) ? FCT_FORMATTER : FCT_KEYONLY);
646 //------------------------------------------------------------------------------
647 void FormattedField::SetFormatter(SvNumberFormatter* pFormatter, BOOL bResetFormat)
649 DBG_CHKTHIS(FormattedField, NULL);
651 if (bResetFormat)
653 m_pFormatter = pFormatter;
655 // calc the default format key from the Office's UI locale
656 if ( m_pFormatter )
658 // get the Office's locale and translate
659 LanguageType eSysLanguage = MsLangId::convertLocaleToLanguage(
660 SvtSysLocale().GetLocaleData().getLocale() );
661 // get the standard numeric format for this language
662 m_nFormatKey = m_pFormatter->GetStandardFormat( NUMBERFORMAT_NUMBER, eSysLanguage );
664 else
665 m_nFormatKey = 0;
667 else
669 XubString sOldFormat;
670 LanguageType aOldLang;
671 GetFormat(sOldFormat, aOldLang);
673 sal_uInt32 nDestKey = pFormatter->TestNewString(sOldFormat);
674 if (nDestKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
676 // die Sprache des neuen Formatters
677 const SvNumberformat* pDefaultEntry = pFormatter->GetEntry(0);
678 LanguageType aNewLang = pDefaultEntry ? pDefaultEntry->GetLanguage() : LANGUAGE_DONTKNOW;
680 // den alten Format-String in die neue Sprache konvertieren
681 USHORT nCheckPos;
682 short nType;
683 pFormatter->PutandConvertEntry(sOldFormat, nCheckPos, nType, nDestKey, aOldLang, aNewLang);
684 m_nFormatKey = nDestKey;
686 m_pFormatter = pFormatter;
689 FormatChanged(FCT_FORMATTER);
692 //------------------------------------------------------------------------------
693 void FormattedField::GetFormat(XubString& rFormatString, LanguageType& eLang) const
695 DBG_CHKTHIS(FormattedField, NULL);
696 const SvNumberformat* pFormatEntry = ImplGetFormatter()->GetEntry(m_nFormatKey);
697 DBG_ASSERT(pFormatEntry != NULL, "FormattedField::GetFormat: no number format for the given format key.");
698 rFormatString = pFormatEntry ? pFormatEntry->GetFormatstring() : XubString();
699 eLang = pFormatEntry ? pFormatEntry->GetLanguage() : LANGUAGE_DONTKNOW;
702 //------------------------------------------------------------------------------
703 BOOL FormattedField::SetFormat(const XubString& rFormatString, LanguageType eLang)
705 DBG_CHKTHIS(FormattedField, NULL);
706 sal_uInt32 nNewKey = ImplGetFormatter()->TestNewString(rFormatString, eLang);
707 if (nNewKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
709 USHORT nCheckPos;
710 short nType;
711 XubString rFormat(rFormatString);
712 if (!ImplGetFormatter()->PutEntry(rFormat, nCheckPos, nType, nNewKey, eLang))
713 return FALSE;
714 DBG_ASSERT(nNewKey != NUMBERFORMAT_ENTRY_NOT_FOUND, "FormattedField::SetFormatString : PutEntry returned an invalid key !");
717 if (nNewKey != m_nFormatKey)
718 SetFormatKey(nNewKey);
719 return TRUE;
722 //------------------------------------------------------------------------------
723 BOOL FormattedField::GetThousandsSep() const
725 DBG_ASSERT(!ImplGetFormatter()->IsTextFormat(m_nFormatKey),
726 "FormattedField::GetThousandsSep : your'e sure what your'e doing when setting the precision of a text format ?");
728 BOOL bThousand, IsRed;
729 USHORT nPrecision, nAnzLeading;
730 ImplGetFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nAnzLeading);
732 return bThousand;
735 //------------------------------------------------------------------------------
736 void FormattedField::SetThousandsSep(BOOL _bUseSeparator)
738 DBG_ASSERT(!ImplGetFormatter()->IsTextFormat(m_nFormatKey),
739 "FormattedField::SetThousandsSep : your'e sure what your'e doing when setting the precision of a text format ?");
741 // get the current settings
742 BOOL bThousand, IsRed;
743 USHORT nPrecision, nAnzLeading;
744 ImplGetFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nAnzLeading);
745 if (bThousand == _bUseSeparator)
746 return;
748 // we need the language for the following
749 LanguageType eLang;
750 String sFmtDescription;
751 GetFormat(sFmtDescription, eLang);
753 // generate a new format ...
754 ImplGetFormatter()->GenerateFormat(sFmtDescription, m_nFormatKey, eLang, _bUseSeparator, IsRed, nPrecision, nAnzLeading);
755 // ... and introduce it to the formatter
756 USHORT nCheckPos;
757 sal_uInt32 nNewKey;
758 short nType;
759 ImplGetFormatter()->PutEntry(sFmtDescription, nCheckPos, nType, nNewKey, eLang);
761 // set the new key
762 ImplSetFormatKey(nNewKey);
763 FormatChanged(FCT_THOUSANDSSEP);
766 //------------------------------------------------------------------------------
767 USHORT FormattedField::GetDecimalDigits() const
769 DBG_ASSERT(!ImplGetFormatter()->IsTextFormat(m_nFormatKey),
770 "FormattedField::GetDecimalDigits : your'e sure what your'e doing when setting the precision of a text format ?");
772 BOOL bThousand, IsRed;
773 USHORT nPrecision, nAnzLeading;
774 ImplGetFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nAnzLeading);
776 return nPrecision;
779 //------------------------------------------------------------------------------
780 void FormattedField::SetDecimalDigits(USHORT _nPrecision)
782 DBG_ASSERT(!ImplGetFormatter()->IsTextFormat(m_nFormatKey),
783 "FormattedField::SetDecimalDigits : your'e sure what your'e doing when setting the precision of a text format ?");
785 // get the current settings
786 BOOL bThousand, IsRed;
787 USHORT nPrecision, nAnzLeading;
788 ImplGetFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nAnzLeading);
789 if (nPrecision == _nPrecision)
790 return;
792 // we need the language for the following
793 LanguageType eLang;
794 String sFmtDescription;
795 GetFormat(sFmtDescription, eLang);
797 // generate a new format ...
798 ImplGetFormatter()->GenerateFormat(sFmtDescription, m_nFormatKey, eLang, bThousand, IsRed, _nPrecision, nAnzLeading);
799 // ... and introduce it to the formatter
800 USHORT nCheckPos;
801 sal_uInt32 nNewKey;
802 short nType;
803 ImplGetFormatter()->PutEntry(sFmtDescription, nCheckPos, nType, nNewKey, eLang);
805 // set the new key
806 ImplSetFormatKey(nNewKey);
807 FormatChanged(FCT_PRECISION);
810 //------------------------------------------------------------------------------
811 void FormattedField::FormatChanged( FORMAT_CHANGE_TYPE _nWhat )
813 DBG_CHKTHIS(FormattedField, NULL);
814 m_pLastOutputColor = NULL;
816 if ( ( 0 != ( _nWhat & FCT_FORMATTER ) ) && m_pFormatter )
817 m_pFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_INTL_FORMAT );
818 // 95845 - 03.04.2002 - fs@openoffice.org
820 ReFormat();
823 //------------------------------------------------------------------------------
824 void FormattedField::Commit()
826 // remember the old text
827 String sOld( GetText() );
829 // do the reformat
830 ReFormat();
832 // did the text change?
833 if ( GetText() != sOld )
834 { // consider the field as modified
835 Modify();
836 // but we have the most recent value now
837 m_bValueDirty = FALSE;
841 //------------------------------------------------------------------------------
842 void FormattedField::ReFormat()
844 if (!IsEmptyFieldEnabled() || GetText().Len())
846 if (TreatingAsNumber())
848 double dValue = GetValue();
849 if ( m_bEnableNaN && ::rtl::math::isNan( dValue ) )
850 return;
851 ImplSetValue( dValue, TRUE );
853 else
854 SetTextFormatted(GetTextValue());
858 //------------------------------------------------------------------------------
859 long FormattedField::Notify(NotifyEvent& rNEvt)
861 DBG_CHKTHIS(FormattedField, NULL);
863 if ((rNEvt.GetType() == EVENT_KEYINPUT) && !IsReadOnly())
865 const KeyEvent& rKEvt = *rNEvt.GetKeyEvent();
866 USHORT nMod = rKEvt.GetKeyCode().GetModifier();
867 switch ( rKEvt.GetKeyCode().GetCode() )
869 case KEY_UP:
870 case KEY_DOWN:
871 case KEY_PAGEUP:
872 case KEY_PAGEDOWN:
873 if (!nMod && ImplGetFormatter()->IsTextFormat(m_nFormatKey))
875 // the base class would translate this into calls to Up/Down/First/Last,
876 // but we don't want this if we are text-formatted
877 return 1;
882 if ((rNEvt.GetType() == EVENT_COMMAND) && !IsReadOnly())
884 const CommandEvent* pCommand = rNEvt.GetCommandEvent();
885 if (pCommand->GetCommand() == COMMAND_WHEEL)
887 const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData();
888 if ((pData->GetMode() == COMMAND_WHEEL_SCROLL) && ImplGetFormatter()->IsTextFormat(m_nFormatKey))
890 // same as above : prevent the base class from doing Up/Down-calls
891 // (normally I should put this test into the Up/Down methods itself, shouldn't I ?)
892 // FS - 71553 - 19.01.00
893 return 1;
898 if (rNEvt.GetType() == EVENT_LOSEFOCUS)
900 // Sonderbehandlung fuer leere Texte
901 if (GetText().Len() == 0)
903 if (!IsEmptyFieldEnabled())
905 if (TreatingAsNumber())
907 ImplSetValue(m_dCurrentValue, TRUE);
908 Modify();
910 else
912 String sNew = GetTextValue();
913 if (sNew.Len())
914 SetTextFormatted(sNew);
915 else
916 SetTextFormatted(m_sDefaultText);
918 m_bValueDirty = FALSE;
921 else
923 Commit();
927 return SpinField::Notify( rNEvt );
930 //------------------------------------------------------------------------------
931 void FormattedField::SetMinValue(double dMin)
933 DBG_CHKTHIS(FormattedField, NULL);
934 DBG_ASSERT(m_bTreatAsNumber, "FormattedField::SetMinValue : only to be used in numeric mode !");
936 m_dMinValue = dMin;
937 m_bHasMin = TRUE;
938 // fuer die Ueberpruefung des aktuellen Wertes an der neuen Grenze -> ImplSetValue
939 ReFormat();
942 //------------------------------------------------------------------------------
943 void FormattedField::SetMaxValue(double dMax)
945 DBG_CHKTHIS(FormattedField, NULL);
946 DBG_ASSERT(m_bTreatAsNumber, "FormattedField::SetMaxValue : only to be used in numeric mode !");
948 m_dMaxValue = dMax;
949 m_bHasMax = TRUE;
950 // fuer die Ueberpruefung des aktuellen Wertes an der neuen Grenze -> ImplSetValue
951 ReFormat();
954 //------------------------------------------------------------------------------
955 void FormattedField::SetTextValue(const XubString& rText)
957 DBG_CHKTHIS(FormattedField, NULL);
958 SetText(rText);
959 ReFormat();
962 //------------------------------------------------------------------------------
963 void FormattedField::EnableEmptyField(BOOL bEnable)
965 DBG_CHKTHIS(FormattedField, NULL);
966 if (bEnable == m_bEnableEmptyField)
967 return;
969 m_bEnableEmptyField = bEnable;
970 if (!m_bEnableEmptyField && GetText().Len()==0)
971 ImplSetValue(m_dCurrentValue, TRUE);
974 //------------------------------------------------------------------------------
975 void FormattedField::ImplSetValue(double dVal, BOOL bForce)
977 DBG_CHKTHIS(FormattedField, NULL);
979 if (m_bHasMin && (dVal<m_dMinValue))
980 dVal = m_dMinValue;
981 if (m_bHasMax && (dVal>m_dMaxValue))
982 dVal = m_dMaxValue;
983 if (!bForce && (dVal == GetValue()))
984 return;
986 DBG_ASSERT(ImplGetFormatter() != NULL, "FormattedField::ImplSetValue : can't set a value without a formatter !");
988 m_bValueDirty = FALSE;
989 m_dCurrentValue = dVal;
991 String sNewText;
992 if (ImplGetFormatter()->IsTextFormat(m_nFormatKey))
994 // zuerst die Zahl als String im Standard-Format
995 String sTemp;
996 ImplGetFormatter()->GetOutputString(dVal, 0, sTemp, &m_pLastOutputColor);
997 // dann den String entsprechend dem Text-Format
998 ImplGetFormatter()->GetOutputString(sTemp, m_nFormatKey, sNewText, &m_pLastOutputColor);
1000 else
1002 if( IsUsingInputStringForFormatting())
1003 ImplGetFormatter()->GetInputLineString(dVal, m_nFormatKey, sNewText);
1004 else
1005 ImplGetFormatter()->GetOutputString(dVal, m_nFormatKey, sNewText, &m_pLastOutputColor);
1008 ImplSetTextImpl(sNewText, NULL);
1009 m_bValueDirty = FALSE;
1010 DBG_ASSERT(CheckText(sNewText), "FormattedField::ImplSetValue : formatted string doesn't match the criteria !");
1013 //------------------------------------------------------------------------------
1014 BOOL FormattedField::ImplGetValue(double& dNewVal)
1016 DBG_CHKTHIS(FormattedField, NULL);
1018 dNewVal = m_dCurrentValue;
1019 if (!m_bValueDirty)
1020 return TRUE;
1022 dNewVal = m_dDefaultValue;
1023 String sText(GetText());
1024 if (!sText.Len())
1025 return TRUE;
1027 DBG_ASSERT(ImplGetFormatter() != NULL, "FormattedField::ImplGetValue : can't give you a current value without a formatter !");
1029 sal_uInt32 nFormatKey = m_nFormatKey; // IsNumberFormat veraendert den FormatKey ...
1031 if (ImplGetFormatter()->IsTextFormat(nFormatKey) && m_bTreatAsNumber)
1032 // damit wir in einem als Text formatierten Feld trotzdem eine Eingabe wie '1,1' erkennen ...
1033 nFormatKey = 0;
1035 // Sonderbehandlung fuer %-Formatierung
1036 if (ImplGetFormatter()->GetType(m_nFormatKey) == NUMBERFORMAT_PERCENT)
1038 // the language of our format
1039 LanguageType eLanguage = m_pFormatter->GetEntry(m_nFormatKey)->GetLanguage();
1040 // the default number format for this language
1041 ULONG nStandardNumericFormat = m_pFormatter->GetStandardFormat(NUMBERFORMAT_NUMBER, eLanguage);
1043 sal_uInt32 nTempFormat = nStandardNumericFormat;
1044 double dTemp;
1045 if (m_pFormatter->IsNumberFormat(sText, nTempFormat, dTemp) &&
1046 NUMBERFORMAT_NUMBER == m_pFormatter->GetType(nTempFormat))
1047 // der String entspricht einer Number-Formatierung, hat also nur kein %
1048 // -> append it
1049 sText += '%';
1050 // (with this, a input of '3' becomes '3%', which then by the formatter is translated
1051 // into 0.03. Without this, the formatter would give us the double 3 for an input '3',
1052 // which equals 300 percent.
1054 if (!ImplGetFormatter()->IsNumberFormat(sText, nFormatKey, dNewVal))
1055 return FALSE;
1058 if (m_bHasMin && (dNewVal<m_dMinValue))
1059 dNewVal = m_dMinValue;
1060 if (m_bHasMax && (dNewVal>m_dMaxValue))
1061 dNewVal = m_dMaxValue;
1062 return TRUE;
1065 //------------------------------------------------------------------------------
1066 void FormattedField::SetValue(double dVal)
1068 DBG_CHKTHIS(FormattedField, NULL);
1069 ImplSetValue(dVal, m_bValueDirty);
1072 //------------------------------------------------------------------------------
1073 double FormattedField::GetValue()
1075 DBG_CHKTHIS(FormattedField, NULL);
1077 if ( !ImplGetValue( m_dCurrentValue ) )
1079 if ( m_bEnableNaN )
1080 ::rtl::math::setNan( &m_dCurrentValue );
1081 else
1082 m_dCurrentValue = m_dDefaultValue;
1085 m_bValueDirty = FALSE;
1086 return m_dCurrentValue;
1089 //------------------------------------------------------------------------------
1090 void FormattedField::Up()
1092 DBG_CHKTHIS(FormattedField, NULL);
1093 SetValue(GetValue() + m_dSpinSize);
1094 // das setValue handelt Bereichsueberschreitungen (min/max) automatisch
1095 SetModifyFlag();
1096 Modify();
1098 SpinField::Up();
1101 //------------------------------------------------------------------------------
1102 void FormattedField::Down()
1104 DBG_CHKTHIS(FormattedField, NULL);
1105 SetValue(GetValue() - m_dSpinSize);
1106 SetModifyFlag();
1107 Modify();
1109 SpinField::Down();
1112 //------------------------------------------------------------------------------
1113 void FormattedField::First()
1115 DBG_CHKTHIS(FormattedField, NULL);
1116 if (m_bHasMin)
1118 SetValue(m_dMinValue);
1119 SetModifyFlag();
1120 Modify();
1123 SpinField::First();
1126 //------------------------------------------------------------------------------
1127 void FormattedField::Last()
1129 DBG_CHKTHIS(FormattedField, NULL);
1130 if (m_bHasMax)
1132 SetValue(m_dMaxValue);
1133 SetModifyFlag();
1134 Modify();
1137 SpinField::Last();
1140 //------------------------------------------------------------------------------
1141 void FormattedField::UseInputStringForFormatting( bool bUseInputStr /* = true */ )
1143 m_bUseInputStringForFormatting = bUseInputStr;
1146 //------------------------------------------------------------------------------
1147 bool FormattedField::IsUsingInputStringForFormatting() const
1149 return m_bUseInputStringForFormatting;
1153 //==============================================================================
1154 //------------------------------------------------------------------------------
1155 DoubleNumericField::~DoubleNumericField()
1157 #ifdef REGEXP_SUPPORT
1158 delete m_pConformanceTester;
1159 #else
1160 delete m_pNumberValidator;
1161 #endif
1164 //------------------------------------------------------------------------------
1165 void DoubleNumericField::FormatChanged(FORMAT_CHANGE_TYPE nWhat)
1167 ResetConformanceTester();
1168 FormattedField::FormatChanged(nWhat);
1171 //------------------------------------------------------------------------------
1172 BOOL DoubleNumericField::CheckText(const XubString& sText) const
1174 // We'd like to implement this using the NumberFormatter::IsNumberFormat, but unfortunately, this doesn't
1175 // recognize fragments of numbers (like, for instance "1e", which happens during entering e.g. "1e10")
1176 // Thus, the roundabout way via a regular expression
1178 #ifdef REGEXP_SUPPORT
1179 if (!sText.Len())
1180 return TRUE;
1182 String sForceComplete = '_';
1183 sForceComplete += sText;
1184 sForceComplete += '_';
1186 USHORT nStart = 0, nEnd = sForceComplete.Len();
1187 BOOL bFound = m_pConformanceTester->SearchFrwrd(sForceComplete, &nStart, &nEnd);
1189 if (bFound && (nStart == 0) && (nEnd == sForceComplete.Len()))
1190 return TRUE;
1192 return FALSE;
1193 #else
1194 return m_pNumberValidator->isValidNumericFragment( sText );
1195 #endif
1198 //------------------------------------------------------------------------------
1199 void DoubleNumericField::ResetConformanceTester()
1201 // the thousands and the decimal separator are language dependent
1202 const SvNumberformat* pFormatEntry = ImplGetFormatter()->GetEntry(m_nFormatKey);
1204 sal_Unicode cSeparatorThousand = ',';
1205 sal_Unicode cSeparatorDecimal = '.';
1206 if (pFormatEntry)
1208 Locale aLocale;
1209 MsLangId::convertLanguageToLocale( pFormatEntry->GetLanguage(), aLocale );
1210 LocaleDataWrapper aLocaleInfo(::comphelper::getProcessServiceFactory(), aLocale);
1212 String sSeparator = aLocaleInfo.getNumThousandSep();
1213 if (sSeparator.Len())
1214 cSeparatorThousand = sSeparator.GetBuffer()[0];
1216 sSeparator = aLocaleInfo.getNumDecimalSep();
1217 if (sSeparator.Len())
1218 cSeparatorDecimal = sSeparator.GetBuffer()[0];
1221 #ifdef REGEXP_SUPPORT
1222 String sDescription = String::CreateFromAscii(szNumericInput);
1224 String sReplaceWith((sal_Unicode)'\\');
1225 sReplaceWith += cSeparatorThousand;
1226 sDescription.SearchAndReplaceAscii("\\,", sReplaceWith);
1228 sReplaceWith = (sal_Unicode)'\\';
1229 sReplaceWith += cSeparatorDecimal;
1230 sDescription.SearchAndReplaceAscii("\\.", sReplaceWith);
1232 delete m_pConformanceTester;
1234 SearchOptions aParam;
1235 aParam.algorithmType = SearchAlgorithms_REGEXP;
1236 aParam.searchFlag = SearchFlags::ALL_IGNORE_CASE;
1237 aParam.searchString = sDescription;
1238 aParam.transliterateFlags = 0;
1240 String sLanguage, sCountry;
1241 ConvertLanguageToIsoNames( pFormatEntry ? pFormatEntry->GetLanguage() : LANGUAGE_ENGLISH_US, sLanguage, sCountry );
1242 aParam.Locale.Language = sLanguage;
1243 aParam.Locale.Country = sCountry;
1245 m_pConformanceTester = new ::utl::TextSearch(aParam);
1246 #else
1247 delete m_pNumberValidator;
1248 m_pNumberValidator = new validation::NumberValidator( cSeparatorThousand, cSeparatorDecimal );
1249 #endif
1253 //==============================================================================
1255 //------------------------------------------------------------------------------
1256 DoubleCurrencyField::DoubleCurrencyField(Window* pParent, WinBits nStyle)
1257 :FormattedField(pParent, nStyle)
1258 ,m_bChangingFormat(FALSE)
1260 m_bPrependCurrSym = FALSE;
1262 // initialize with a system currency format
1263 m_sCurrencySymbol = SvtSysLocale().GetLocaleData().getCurrSymbol();
1264 UpdateCurrencyFormat();
1267 //------------------------------------------------------------------------------
1268 DoubleCurrencyField::DoubleCurrencyField(Window* pParent, const ResId& rResId)
1269 :FormattedField(pParent, rResId)
1270 ,m_bChangingFormat(FALSE)
1272 m_bPrependCurrSym = FALSE;
1274 // initialize with a system currency format
1275 m_sCurrencySymbol = SvtSysLocale().GetLocaleData().getCurrSymbol();
1276 UpdateCurrencyFormat();
1279 //------------------------------------------------------------------------------
1280 void DoubleCurrencyField::FormatChanged(FORMAT_CHANGE_TYPE nWhat)
1282 if (m_bChangingFormat)
1284 FormattedField::FormatChanged(nWhat);
1285 return;
1288 switch (nWhat)
1290 case FCT_FORMATTER:
1291 case FCT_PRECISION:
1292 case FCT_THOUSANDSSEP:
1293 // the aspects which changed don't take our currency settings into account (in fact, they most probably
1294 // destroyed them)
1295 UpdateCurrencyFormat();
1296 break;
1297 case FCT_KEYONLY:
1298 DBG_ERROR("DoubleCurrencyField::FormatChanged : somebody modified my key !");
1299 // We always build our own format from the settings we get via special methods (setCurrencySymbol etc.).
1300 // Nobody but ourself should modifiy the format key directly !
1301 break;
1304 FormattedField::FormatChanged(nWhat);
1307 //------------------------------------------------------------------------------
1308 void DoubleCurrencyField::setCurrencySymbol(const String& _sSymbol)
1310 if (m_sCurrencySymbol == _sSymbol)
1311 return;
1313 m_sCurrencySymbol = _sSymbol;
1314 UpdateCurrencyFormat();
1315 FormatChanged(FCT_CURRENCY_SYMBOL);
1318 //------------------------------------------------------------------------------
1319 void DoubleCurrencyField::setPrependCurrSym(BOOL _bPrepend)
1321 if (m_bPrependCurrSym == _bPrepend)
1322 return;
1324 m_bPrependCurrSym = _bPrepend;
1325 UpdateCurrencyFormat();
1326 FormatChanged(FCT_CURRSYM_POSITION);
1329 //------------------------------------------------------------------------------
1330 void DoubleCurrencyField::UpdateCurrencyFormat()
1332 // the old settings
1333 XubString sOldFormat;
1334 LanguageType eLanguage;
1335 GetFormat(sOldFormat, eLanguage);
1336 BOOL bThSep = GetThousandsSep();
1337 USHORT nDigits = GetDecimalDigits();
1339 // build a new format string with the base class' and my own settings
1340 Locale aLocale;
1341 MsLangId::convertLanguageToLocale( eLanguage, aLocale );
1342 LocaleDataWrapper aLocaleInfo(::comphelper::getProcessServiceFactory(), aLocale);
1344 XubString sNewFormat;
1345 if (bThSep)
1347 sNewFormat = '#';
1348 sNewFormat += aLocaleInfo.getNumThousandSep();
1349 sNewFormat.AppendAscii("##0");
1351 else
1352 sNewFormat = '0';
1354 if (nDigits)
1356 sNewFormat += aLocaleInfo.getNumDecimalSep();
1358 XubString sTemp;
1359 sTemp.Fill(nDigits, '0');
1360 sNewFormat += sTemp;
1363 if (getPrependCurrSym())
1365 XubString sSymbol = getCurrencySymbol();
1366 sSymbol.EraseLeadingChars(' ');
1367 sSymbol.EraseTrailingChars(' ');
1369 XubString sTemp = String::CreateFromAscii("[$");
1370 sTemp += sSymbol;
1371 sTemp.AppendAscii("] ");
1372 sTemp += sNewFormat;
1374 // for negative values : $ -0.00, not -$ 0.00 ...
1375 // (the real solution would be a possibility to choose a "positive currency format" and a "negative currency format" ...
1376 // But not now ... (and hey, you could take a formatted field for this ....))
1377 // FS - 31.03.00 74642
1378 sTemp.AppendAscii(";[$");
1379 sTemp += sSymbol;
1380 sTemp.AppendAscii("] -");
1381 sTemp += sNewFormat;
1383 sNewFormat = sTemp;
1385 else
1387 XubString sTemp = getCurrencySymbol();
1388 sTemp.EraseLeadingChars(' ');
1389 sTemp.EraseTrailingChars(' ');
1391 sNewFormat += String::CreateFromAscii(" [$");
1392 sNewFormat += sTemp;
1393 sNewFormat += ']';
1396 // set this new basic format
1397 m_bChangingFormat = TRUE;
1398 SetFormat(sNewFormat, eLanguage);
1399 m_bChangingFormat = FALSE;