LibreOffice Module vcl (master)  1
longcurr.cxx
Go to the documentation of this file.
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 <sal/config.h>
21 
22 #include <string_view>
23 
24 #include <comphelper/string.hxx>
25 #include <tools/bigint.hxx>
26 #include <sal/log.hxx>
27 
28 #include <vcl/event.hxx>
29 #include <vcl/svapp.hxx>
30 #include <vcl/toolkit/longcurr.hxx>
31 #include <vcl/weldutils.hxx>
32 
34 
35 using namespace ::comphelper;
36 
37 namespace
38 {
39 
40 BigInt ImplPower10( sal_uInt16 n )
41 {
42  sal_uInt16 i;
43  BigInt nValue = 1;
44 
45  for ( i=0; i < n; i++ )
46  nValue *= 10;
47 
48  return nValue;
49 }
50 
51 OUString ImplGetCurr( const LocaleDataWrapper& rLocaleDataWrapper, const BigInt &rNumber, sal_uInt16 nDigits, const OUString& rCurrSymbol, bool bShowThousandSep )
52 {
53  SAL_WARN_IF( nDigits >= 10, "vcl", "LongCurrency may only have 9 decimal places" );
54 
55  if ( rNumber.IsZero() || static_cast<long>(rNumber) )
56  return rLocaleDataWrapper.getCurr( static_cast<long>(rNumber), nDigits, rCurrSymbol, bShowThousandSep );
57 
58  BigInt aTmp( ImplPower10( nDigits ) );
59  BigInt aInteger( rNumber );
60  aInteger.Abs();
61  aInteger /= aTmp;
62  BigInt aFraction( rNumber );
63  aFraction.Abs();
64  aFraction %= aTmp;
65  if ( !aInteger.IsZero() )
66  {
67  aFraction += aTmp;
68  aTmp = 1000000000;
69  }
70  if ( rNumber.IsNeg() )
71  aFraction *= -1;
72 
73  OUStringBuffer aTemplate = rLocaleDataWrapper.getCurr( static_cast<long>(aFraction), nDigits, rCurrSymbol, bShowThousandSep );
74  while( !aInteger.IsZero() )
75  {
76  aFraction = aInteger;
77  aFraction %= aTmp;
78  aInteger /= aTmp;
79  if( !aInteger.IsZero() )
80  aFraction += aTmp;
81 
82  OUString aFractionStr = rLocaleDataWrapper.getNum( static_cast<long>(aFraction), 0 );
83 
84  sal_Int32 nSPos = aTemplate.indexOf( '1' );
85  if (nSPos == -1)
86  break;
87  if ( aFractionStr.getLength() == 1 )
88  aTemplate[ nSPos ] = aFractionStr[0];
89  else
90  {
91  aTemplate.remove( nSPos, 1 );
92  aTemplate.insert( nSPos, aFractionStr );
93  }
94  }
95 
96  return aTemplate.makeStringAndClear();
97 }
98 
99 bool ImplCurrencyGetValue( const OUString& rStr, BigInt& rValue,
100  sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper )
101 {
102  OUString aStr = rStr;
103  OUStringBuffer aStr1;
104  OUStringBuffer aStr2;
105  sal_Int32 nDecPos;
106  bool bNegative = false;
107 
108  // On empty string
109  if ( rStr.isEmpty() )
110  return false;
111 
112  // Trim leading and trailing spaces
113  aStr = string::strip(aStr, ' ');
114 
115  // Find decimal sign's position
116  nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSep() );
117  if (nDecPos < 0 && !rLocaleDataWrapper.getNumDecimalSepAlt().isEmpty())
118  nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSepAlt() );
119 
120  if ( nDecPos != -1 )
121  {
122  aStr1 = aStr.copy( 0, nDecPos );
123  aStr2.append(std::u16string_view(aStr).substr(nDecPos+1));
124  }
125  else
126  aStr1 = aStr;
127 
128  // Negative?
129  if ( (aStr[ 0 ] == '(') && (aStr[ aStr.getLength()-1 ] == ')') )
130  bNegative = true;
131  if ( !bNegative )
132  {
133  for (sal_Int32 i=0; i < aStr.getLength(); i++ )
134  {
135  if ( (aStr[ i ] >= '0') && (aStr[ i ] <= '9') )
136  break;
137  else if ( aStr[ i ] == '-' )
138  {
139  bNegative = true;
140  break;
141  }
142  }
143  }
144  if ( !bNegative && !aStr.isEmpty() )
145  {
146  sal_uInt16 nFormat = rLocaleDataWrapper.getCurrNegativeFormat();
147  if ( (nFormat == 3) || (nFormat == 6) ||
148  (nFormat == 7) || (nFormat == 10) )
149  {
150  for (sal_Int32 i = aStr.getLength()-1; i > 0; i++ )
151  {
152  if ( (aStr[ i ] >= '0') && (aStr[ i ] <= '9') )
153  break;
154  else if ( aStr[ i ] == '-' )
155  {
156  bNegative = true;
157  break;
158  }
159  }
160  }
161  }
162 
163  // delete unwanted characters
164  for (sal_Int32 i=0; i < aStr1.getLength(); )
165  {
166  if ( (aStr1[ i ] >= '0') && (aStr1[ i ] <= '9') )
167  i++;
168  else
169  aStr1.remove( i, 1 );
170  }
171  for (sal_Int32 i=0; i < aStr2.getLength(); )
172  {
173  if ((aStr2[i] >= '0') && (aStr2[i] <= '9'))
174  ++i;
175  else
176  aStr2.remove(i, 1);
177  }
178 
179  if ( aStr1.isEmpty() && aStr2.isEmpty())
180  return false;
181 
182  if ( aStr1.isEmpty() )
183  aStr1 = "0";
184  if ( bNegative )
185  aStr1.insert( 0, '-');
186 
187  // Cut down decimal part and round while doing so
188  bool bRound = false;
189  if (aStr2.getLength() > nDecDigits)
190  {
191  if (aStr2[nDecDigits] >= '5')
192  bRound = true;
193  string::truncateToLength(aStr2, nDecDigits);
194  }
195  if (aStr2.getLength() < nDecDigits)
196  string::padToLength(aStr2, nDecDigits, '0');
197 
198  aStr1.append(aStr2);
199  aStr = aStr1.makeStringAndClear();
200 
201  // check range
202  BigInt nValue( aStr );
203  if ( bRound )
204  {
205  if ( !bNegative )
206  nValue+=1;
207  else
208  nValue-=1;
209  }
210 
211  rValue = nValue;
212 
213  return true;
214 }
215 
216 } // namespace
217 
218 static bool ImplLongCurrencyGetValue( const OUString& rStr, BigInt& rValue,
219  sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper )
220 {
221  return ImplCurrencyGetValue( rStr, rValue, nDecDigits, rLocaleDataWrapper );
222 }
223 
224 namespace weld
225 {
227  {
228  const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
229  const OUString& rCurrencySymbol = !m_aCurrencySymbol.isEmpty() ? m_aCurrencySymbol : rLocaleDataWrapper.getCurrSymbol();
230  double fValue = GetValue() * weld::SpinButton::Power10(GetDecimalDigits());
231  OUString aText = ImplGetCurr(rLocaleDataWrapper, fValue, GetDecimalDigits(), rCurrencySymbol, m_bThousandSep);
232  ImplSetTextImpl(aText, nullptr);
233  return true;
234  }
235 
236  IMPL_LINK(LongCurrencyFormatter, ParseInputHdl, sal_Int64*, result, TriState)
237  {
238  const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
239 
240  BigInt value;
241  bool bRet = ImplLongCurrencyGetValue(GetEntryText(), value, GetDecimalDigits(), rLocaleDataWrapper);
242 
243  if (bRet)
244  *result = double(value);
245 
246  return bRet ? TRISTATE_TRUE : TRISTATE_FALSE;
247  }
248 }
249 
250 bool ImplLongCurrencyReformat( const OUString& rStr, BigInt const & nMin, BigInt const & nMax,
251  sal_uInt16 nDecDigits,
252  const LocaleDataWrapper& rLocaleDataWrapper, OUString& rOutStr,
253  LongCurrencyFormatter const & rFormatter )
254 {
255  BigInt nValue;
256  if ( !ImplCurrencyGetValue( rStr, nValue, nDecDigits, rLocaleDataWrapper ) )
257  return true;
258  else
259  {
260  BigInt nTempVal = nValue;
261  if ( nTempVal > nMax )
262  nTempVal = nMax;
263  else if ( nTempVal < nMin )
264  nTempVal = nMin;
265 
266  rOutStr = ImplGetCurr( rLocaleDataWrapper, nTempVal, nDecDigits, rFormatter.GetCurrencySymbol(), /*IsUseThousandSep*/true );
267  return true;
268  }
269 }
270 
271 void LongCurrencyFormatter::ImpInit()
272 {
273  mnLastValue = 0;
274  mnMin = 0;
275  mnMax = 0x7FFFFFFF;
276  mnMax *= 0x7FFFFFFF;
277  mnDecimalDigits = 0;
278  SetDecimalDigits( 0 );
279 }
280 
281 LongCurrencyFormatter::LongCurrencyFormatter(Edit* pEdit)
282  : FormatterBase(pEdit)
283 {
284  ImpInit();
285 }
286 
287 LongCurrencyFormatter::~LongCurrencyFormatter()
288 {
289 }
290 
291 OUString const & LongCurrencyFormatter::GetCurrencySymbol() const
292 {
294 }
295 
296 void LongCurrencyFormatter::SetValue(const BigInt& rNewValue)
297 {
298  SetUserValue(rNewValue);
299  SetEmptyFieldValueData( false );
300 }
301 
302 void LongCurrencyFormatter::SetUserValue( BigInt nNewValue )
303 {
304  if ( nNewValue > mnMax )
305  nNewValue = mnMax;
306  else if ( nNewValue < mnMin )
307  nNewValue = mnMin;
308  mnLastValue = nNewValue;
309 
310  if ( !GetField() )
311  return;
312 
313  OUString aStr = ImplGetCurr( GetLocaleDataWrapper(), nNewValue, GetDecimalDigits(), GetCurrencySymbol(), /*UseThousandSep*/true );
314  if ( GetField()->HasFocus() )
315  {
316  Selection aSelection = GetField()->GetSelection();
317  GetField()->SetText( aStr );
318  GetField()->SetSelection( aSelection );
319  }
320  else
321  GetField()->SetText( aStr );
322  MarkToBeReformatted( false );
323 }
324 
325 BigInt LongCurrencyFormatter::GetValue() const
326 {
327  if ( !GetField() )
328  return 0;
329 
330  BigInt nTempValue;
331  if ( ImplLongCurrencyGetValue( GetField()->GetText(), nTempValue, GetDecimalDigits(), GetLocaleDataWrapper() ) )
332  {
333  if ( nTempValue > mnMax )
334  nTempValue = mnMax;
335  else if ( nTempValue < mnMin )
336  nTempValue = mnMin;
337  return nTempValue;
338  }
339  else
340  return mnLastValue;
341 }
342 
343 void LongCurrencyFormatter::Reformat()
344 {
345  if ( !GetField() )
346  return;
347 
348  if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
349  return;
350 
351  OUString aStr;
352  bool bOK = ImplLongCurrencyReformat( GetField()->GetText(), mnMin, mnMax,
353  GetDecimalDigits(), GetLocaleDataWrapper(), aStr, *this );
354  if ( !bOK )
355  return;
356 
357  if ( !aStr.isEmpty() )
358  {
359  GetField()->SetText( aStr );
360  MarkToBeReformatted( false );
361  ImplLongCurrencyGetValue( aStr, mnLastValue, GetDecimalDigits(), GetLocaleDataWrapper() );
362  }
363  else
364  SetValue( mnLastValue );
365 }
366 
367 void LongCurrencyFormatter::ReformatAll()
368 {
369  Reformat();
370 }
371 
372 void LongCurrencyFormatter::SetDecimalDigits( sal_uInt16 nDigits )
373 {
374  if ( nDigits > 9 )
375  nDigits = 9;
376 
377  mnDecimalDigits = nDigits;
378  ReformatAll();
379 }
380 
381 
382 
383 LongCurrencyBox::LongCurrencyBox(vcl::Window* pParent, WinBits nWinStyle)
384  : ComboBox(pParent, nWinStyle)
385  , LongCurrencyFormatter(this)
386 {
387  Reformat();
388 }
389 
390 bool LongCurrencyBox::EventNotify( NotifyEvent& rNEvt )
391 {
392  if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS )
393  {
394  MarkToBeReformatted( false );
395  }
396  else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS )
397  {
398  if ( MustBeReformatted() )
399  {
400  Reformat();
402  }
403  }
404  return ComboBox::EventNotify( rNEvt );
405 }
406 
407 void LongCurrencyBox::Modify()
408 {
409  MarkToBeReformatted( true );
411 }
412 
413 void LongCurrencyBox::ReformatAll()
414 {
415  OUString aStr;
416  SetUpdateMode( false );
417  const sal_Int32 nEntryCount = GetEntryCount();
418  for ( sal_Int32 i=0; i < nEntryCount; ++i )
419  {
420  ImplLongCurrencyReformat( GetEntry( i ), mnMin, mnMax,
421  GetDecimalDigits(), GetLocaleDataWrapper(),
422  aStr, *this );
423  RemoveEntryAt(i);
424  InsertEntry( aStr, i );
425  }
426  LongCurrencyFormatter::Reformat();
427  SetUpdateMode( true );
428 }
429 
430 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_uInt16 getCurrNegativeFormat() const
const LocaleDataWrapper & GetLocaleDataWrapper() const
std::string GetValue
static const AllSettings & GetSettings()
Gets the application's settings.
Definition: svapp.cxx:706
TRISTATE_TRUE
sal_Int64 WinBits
IMPL_LINK(CustomWeld, DoResize, const Size &, rSize, void)
Definition: customweld.cxx:44
Definition: edit.hxx:55
static bool ImplCurrencyGetValue(const OUString &rStr, sal_Int64 &rValue, sal_uInt16 nDecDigits, const LocaleDataWrapper &rWrapper)
Definition: field.cxx:1717
bool ImplLongCurrencyReformat(const OUString &rStr, BigInt const &nMin, BigInt const &nMax, sal_uInt16 nDecDigits, const LocaleDataWrapper &rLocaleDataWrapper, OUString &rOutStr, LongCurrencyFormatter const &rFormatter)
Definition: longcurr.cxx:250
const OUString & getNumDecimalSep() const
int i
virtual bool EventNotify(NotifyEvent &rNEvt) override
Definition: combobox.cxx:714
TRISTATE_FALSE
bool IsZero() const
MouseNotifyEvent GetType() const
Definition: event.hxx:303
OUString getCurr(sal_Int64 nNumber, sal_uInt16 nDecimals, const OUString &rCurrencySymbol, bool bUseThousandSep=true) const
const LocaleDataWrapper & GetLocaleDataWrapper(LanguageType nLang)
bool IsNeg() const
const OUString & getCurrSymbol() const
const OUString & getNumDecimalSepAlt() const
IMPL_LINK_NOARG(HexColorControl, OnAsyncModifyHdl, void *, void)
#define SAL_WARN_IF(condition, area, stream)
Any value
A widget used to choose from a list of items and which has an entry.
Definition: combobox.hxx:38
Any result
OUString getNum(sal_Int64 nNumber, sal_uInt16 nDecimals, bool bUseThousandSep=true, bool bTrailingZeros=true) const
TriState
static unsigned int Power10(unsigned int n)
Definition: builder.cxx:249
aStr
static bool ImplLongCurrencyGetValue(const OUString &rStr, BigInt &rValue, sal_uInt16 nDecDigits, const LocaleDataWrapper &rLocaleDataWrapper)
Definition: longcurr.cxx:218
sal_Int16 nValue
virtual void Modify() override
Definition: combobox.cxx:819