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