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