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