]> git.saurik.com Git - wxWidgets.git/blame - src/common/numformatter.cpp
adapting to init pattern
[wxWidgets.git] / src / common / numformatter.cpp
CommitLineData
6686fbad 1/////////////////////////////////////////////////////////////////////////////
80fdcdb9 2// Name: src/common/numformatter.cpp
6686fbad
VZ
3// Purpose: wxNumberFormatter
4// Author: Fulvio Senore, Vadim Zeitlin
5// Created: 2010-11-06
6// Copyright: (c) 2010 wxWidgets team
7// Licence: wxWindows licence
8/////////////////////////////////////////////////////////////////////////////
9
10// ----------------------------------------------------------------------------
11// headers
12// ----------------------------------------------------------------------------
13
14// For compilers that support precompilation, includes "wx.h".
15#include "wx/wxprec.h"
16
17#ifdef __BORLANDC__
18 #pragma hdrstop
19#endif
20
21#include "wx/numformatter.h"
22#include "wx/intl.h"
23
fa5d9d20
DS
24#if !wxUSE_STD_STRING
25 #include <locale.h> // for setlocale and LC_ALL
26#endif
27
d8d9844b
VZ
28// ----------------------------------------------------------------------------
29// local helpers
30// ----------------------------------------------------------------------------
31
32namespace
33{
34
35// Contains information about the locale which was used to initialize our
36// cached values of the decimal and thousands separators. Notice that it isn't
37// enough to store just wxLocale because the user code may call setlocale()
38// directly and storing just C locale string is not enough because we can use
39// the OS API directly instead of the CRT ones on some platforms. So just store
40// both.
41class LocaleId
42{
43public:
44 LocaleId()
45 {
88def163 46#if wxUSE_INTL
d8d9844b 47 m_wxloc = NULL;
88def163 48#endif // wxUSE_INTL
d8d9844b
VZ
49 m_cloc = NULL;
50 }
51
52 ~LocaleId()
53 {
54 Free();
55 }
56
88def163 57#if wxUSE_INTL
d8d9844b
VZ
58 // Return true if this is the first time this function is called for this
59 // object or if the program locale has changed since the last time it was
60 // called. Otherwise just return false indicating that updating locale-
61 // dependent information is not necessary.
62 bool NotInitializedOrHasChanged()
63 {
64 wxLocale * const wxloc = wxGetLocale();
65 const char * const cloc = setlocale(LC_ALL, NULL);
66 if ( m_wxloc || m_cloc )
67 {
68 if ( m_wxloc == wxloc && strcmp(m_cloc, cloc) == 0 )
69 return false;
70
71 Free();
72 }
73 //else: Not initialized yet.
74
75 m_wxloc = wxloc;
76 m_cloc = wxCRT_StrdupA(cloc);
77
78 return true;
79 }
88def163 80#endif // wxUSE_INTL
d8d9844b
VZ
81
82private:
83 void Free()
84 {
88def163 85#if wxUSE_INTL
d8d9844b 86 free(m_cloc);
88def163 87#endif // wxUSE_INTL
d8d9844b
VZ
88 }
89
88def163 90#if wxUSE_INTL
d8d9844b
VZ
91 // Non-owned pointer to wxLocale which was used.
92 wxLocale *m_wxloc;
88def163 93#endif // wxUSE_INTL
d8d9844b
VZ
94
95 // Owned pointer to the C locale string.
96 char *m_cloc;
97
98 wxDECLARE_NO_COPY_CLASS(LocaleId);
99};
100
101} // anonymous namespace
102
6686fbad
VZ
103// ============================================================================
104// wxNumberFormatter implementation
105// ============================================================================
106
107// ----------------------------------------------------------------------------
108// Locale information accessors
109// ----------------------------------------------------------------------------
110
111wxChar wxNumberFormatter::GetDecimalSeparator()
112{
88def163 113#if wxUSE_INTL
6686fbad
VZ
114 // Notice that while using static variable here is not MT-safe, the worst
115 // that can happen is that we redo the initialization if we're called
116 // concurrently from more than one thread so it's not a real problem.
117 static wxChar s_decimalSeparator = 0;
118
0d30c79b
VZ
119 // Remember the locale which was current when we initialized, we must redo
120 // the initialization if the locale changed.
d8d9844b 121 static LocaleId s_localeUsedForInit;
0d30c79b 122
d8d9844b 123 if ( s_localeUsedForInit.NotInitializedOrHasChanged() )
6686fbad
VZ
124 {
125 const wxString
126 s = wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER);
127 if ( s.empty() )
128 {
129 // We really must have something for decimal separator, so fall
130 // back to the C locale default.
131 s_decimalSeparator = '.';
132 }
133 else
134 {
135 // To the best of my knowledge there are no locales like this.
136 wxASSERT_MSG( s.length() == 1,
137 "Multi-character decimal separator?" );
138
139 s_decimalSeparator = s[0];
140 }
141 }
142
143 return s_decimalSeparator;
88def163
DS
144#else // !wxUSE_INTL
145 return wxT('.');
146#endif // wxUSE_INTL/!wxUSE_INTL
6686fbad
VZ
147}
148
149bool wxNumberFormatter::GetThousandsSeparatorIfUsed(wxChar *sep)
150{
88def163 151#if wxUSE_INTL
6686fbad 152 static wxChar s_thousandsSeparator = 0;
d8d9844b 153 static LocaleId s_localeUsedForInit;
6686fbad 154
d8d9844b 155 if ( s_localeUsedForInit.NotInitializedOrHasChanged() )
6686fbad
VZ
156 {
157 const wxString
158 s = wxLocale::GetInfo(wxLOCALE_THOUSANDS_SEP, wxLOCALE_CAT_NUMBER);
159 if ( !s.empty() )
160 {
161 wxASSERT_MSG( s.length() == 1,
162 "Multi-character thousands separator?" );
163
164 s_thousandsSeparator = s[0];
165 }
166 //else: Unlike above it's perfectly fine for the thousands separator to
167 // be empty if grouping is not used, so just leave it as 0.
6686fbad
VZ
168 }
169
170 if ( !s_thousandsSeparator )
171 return false;
172
173 if ( sep )
174 *sep = s_thousandsSeparator;
175
176 return true;
88def163
DS
177#else // !wxUSE_INTL
178 wxUnusedVar(sep);
179 return false;
180#endif // wxUSE_INTL/!wxUSE_INTL
6686fbad
VZ
181}
182
183// ----------------------------------------------------------------------------
184// Conversion to string and helpers
185// ----------------------------------------------------------------------------
186
f2a5052b 187wxString wxNumberFormatter::PostProcessIntString(wxString s, int style)
6686fbad 188{
6686fbad
VZ
189 if ( style & Style_WithThousandsSep )
190 AddThousandsSeparators(s);
191
192 wxASSERT_MSG( !(style & Style_NoTrailingZeroes),
193 "Style_NoTrailingZeroes can't be used with integer values" );
194
195 return s;
196}
197
f2a5052b
VZ
198wxString wxNumberFormatter::ToString(long val, int style)
199{
200 return PostProcessIntString(wxString::Format("%ld", val), style);
201}
202
203#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG
204
205wxString wxNumberFormatter::ToString(wxLongLong_t val, int style)
206{
207 return PostProcessIntString(wxString::Format("%" wxLongLongFmtSpec "d", val),
208 style);
209}
210
211#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG
212
6686fbad
VZ
213wxString wxNumberFormatter::ToString(double val, int precision, int style)
214{
215 const wxString fmt = wxString::Format("%%.%df", precision);
216 wxString s = wxString::Format(fmt, val);
217
218 if ( style & Style_WithThousandsSep )
219 AddThousandsSeparators(s);
220
221 if ( style & Style_NoTrailingZeroes )
222 RemoveTrailingZeroes(s);
223
224 return s;
225}
226
227void wxNumberFormatter::AddThousandsSeparators(wxString& s)
228{
229 wxChar thousandsSep;
230 if ( !GetThousandsSeparatorIfUsed(&thousandsSep) )
231 return;
232
233 size_t pos = s.find(GetDecimalSeparator());
234 if ( pos == wxString::npos )
235 {
236 // Start grouping at the end of an integer number.
237 pos = s.length();
238 }
239
240 // We currently group digits by 3 independently of the locale. This is not
241 // the right thing to do and we should use lconv::grouping (under POSIX)
242 // and GetLocaleInfo(LOCALE_SGROUPING) (under MSW) to get information about
243 // the correct grouping to use. This is something that needs to be done at
244 // wxLocale level first and then used here in the future (TODO).
245 const size_t GROUP_LEN = 3;
246
247 while ( pos > GROUP_LEN )
248 {
249 pos -= GROUP_LEN;
250 s.insert(pos, thousandsSep);
251 }
252}
253
254void wxNumberFormatter::RemoveTrailingZeroes(wxString& s)
255{
256 const size_t posDecSep = s.find(GetDecimalSeparator());
a7d696f1
VZ
257 wxCHECK_RET( posDecSep != wxString::npos,
258 wxString::Format("No decimal separator in \"%s\"", s) );
6686fbad
VZ
259 wxCHECK_RET( posDecSep, "Can't start with decimal separator" );
260
261 // Find the last character to keep.
262 size_t posLastNonZero = s.find_last_not_of("0");
263
264 // If it's the decimal separator itself, don't keep it neither.
265 if ( posLastNonZero == posDecSep )
266 posLastNonZero--;
267
268 s.erase(posLastNonZero + 1);
269}
270
271// ----------------------------------------------------------------------------
272// Conversion from strings
273// ----------------------------------------------------------------------------
274
275void wxNumberFormatter::RemoveThousandsSeparators(wxString& s)
276{
277 wxChar thousandsSep;
278 if ( !GetThousandsSeparatorIfUsed(&thousandsSep) )
279 return;
280
281 s.Replace(wxString(thousandsSep), wxString());
282}
283
284bool wxNumberFormatter::FromString(wxString s, long *val)
285{
286 RemoveThousandsSeparators(s);
287 return s.ToLong(val);
288}
289
f2a5052b
VZ
290#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG
291
292bool wxNumberFormatter::FromString(wxString s, wxLongLong_t *val)
293{
294 RemoveThousandsSeparators(s);
295 return s.ToLongLong(val);
296}
297
298#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG
299
6686fbad
VZ
300bool wxNumberFormatter::FromString(wxString s, double *val)
301{
302 RemoveThousandsSeparators(s);
303 return s.ToDouble(val);
304}