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
