1 /////////////////////////////////////////////////////////////////////////////
2 //
3 // Backport from wxWidgets-3.0-rc1
4 //
5 /////////////////////////////////////////////////////////////////////////////
6 // Name: src/common/valnum.cpp
7 // Purpose: Numeric validator classes implementation
8 // Author: Vadim Zeitlin based on the submission of Fulvio Senore
9 // Created: 2010-11-06
10 // Copyright: (c) 2010 wxWidgets team
11 // Licence: wxWindows licence
12 /////////////////////////////////////////////////////////////////////////////
13
14 // ============================================================================
15 // Declarations
16 // ============================================================================
17
18 // ----------------------------------------------------------------------------
19 // headers
20 // ----------------------------------------------------------------------------
21
22
23 #include "valnum.h"
24
25 // For compilers that support precompilation, includes "wx.h".
26 #include <wx/wxprec.h>
27
28 #include <wx/setup.h> // for wxUSE_* macros
29
30 #include "AudacityMessageBox.h"
31 #include "Internat.h"
32
33 #ifdef __BORLANDC__
34 #pragma hdrstop
35 #endif
36
37 #if wxUSE_VALIDATORS && wxUSE_TEXTCTRL
38
39 #ifndef WX_PRECOMP
40 #include <wx/textctrl.h>
41 #include <wx/combobox.h>
42 #endif
43
44 #include <wx/clipbrd.h>
45 #include <wx/dataobj.h>
46
47 #include "numformatter.h"
48
49 // ============================================================================
50 // NumValidatorBase implementation
51 // ============================================================================
52
BEGIN_EVENT_TABLE(NumValidatorBase,wxValidator)53 BEGIN_EVENT_TABLE(NumValidatorBase, wxValidator)
54 EVT_CHAR(NumValidatorBase::OnChar)
55 EVT_TEXT_PASTE(wxID_ANY, NumValidatorBase::OnPaste)
56 EVT_KILL_FOCUS(NumValidatorBase::OnKillFocus)
57 END_EVENT_TABLE()
58
59 int NumValidatorBase::GetFormatFlags() const
60 {
61 int flags = NumberFormatter::Style_None;
62 if ( m_style & NumValidatorStyle::THOUSANDS_SEPARATOR )
63 flags |= NumberFormatter::Style_WithThousandsSep;
64 if ( m_style & NumValidatorStyle::NO_TRAILING_ZEROES )
65 flags |= NumberFormatter::Style_NoTrailingZeroes;
66 if ( m_style & NumValidatorStyle::ONE_TRAILING_ZERO )
67 flags |= NumberFormatter::Style_OneTrailingZero;
68 if ( m_style & NumValidatorStyle::TWO_TRAILING_ZEROES )
69 flags |= NumberFormatter::Style_TwoTrailingZeroes;
70 if ( m_style & NumValidatorStyle::THREE_TRAILING_ZEROES )
71 flags |= NumberFormatter::Style_ThreeTrailingZeroes;
72
73 return flags;
74 }
75
GetTextEntry() const76 wxTextEntry *NumValidatorBase::GetTextEntry() const
77 {
78 #if wxUSE_TEXTCTRL
79 if ( wxTextCtrl *text = wxDynamicCast(m_validatorWindow, wxTextCtrl) )
80 return text;
81 #endif // wxUSE_TEXTCTRL
82
83 #if wxUSE_COMBOBOX
84 if ( wxComboBox *combo = wxDynamicCast(m_validatorWindow, wxComboBox) )
85 return combo;
86 #endif // wxUSE_COMBOBOX
87
88 wxFAIL_MSG(wxT("Can only be used with wxTextCtrl or wxComboBox"));
89
90 return NULL;
91 }
92
Validate(wxWindow * parent)93 bool NumValidatorBase::Validate(wxWindow *parent)
94 {
95 // If window is disabled, simply return
96 if ( !m_validatorWindow->IsEnabled() )
97 return true;
98
99 TranslatableString errmsg;
100 bool res = DoValidateNumber(&errmsg);
101
102 if ( !res )
103 {
104 AudacityMessageBox(
105 errmsg,
106 XO("Validation error"),
107 wxOK | wxICON_ERROR,
108 parent);
109 wxTextEntry *te = GetTextEntry();
110 if ( te )
111 {
112 te->SelectAll();
113 m_validatorWindow->SetFocus();
114 }
115 return false;
116 }
117
118 return true;
119 }
120
121 void
GetCurrentValueAndInsertionPoint(wxString & val,int & pos) const122 NumValidatorBase::GetCurrentValueAndInsertionPoint(wxString& val,
123 int& pos) const
124 {
125 wxTextEntry * const control = GetTextEntry();
126 if ( !control )
127 return;
128
129 val = control->GetValue();
130 pos = control->GetInsertionPoint();
131
132 long selFrom, selTo;
133 control->GetSelection(&selFrom, &selTo);
134
135 const long selLen = selTo - selFrom;
136 if ( selLen )
137 {
138 // Remove selected text because pressing a key would make it disappear.
139 val.erase(selFrom, selLen);
140
141 // And adjust the insertion point to have correct position in the NEW
142 // string.
143 if ( pos > selFrom )
144 {
145 if ( pos >= selTo )
146 pos -= selLen;
147 else
148 pos = selFrom;
149 }
150 }
151 }
152
IsMinusOk(const wxString & val,int pos) const153 bool NumValidatorBase::IsMinusOk(const wxString& val, int pos) const
154 {
155 // Minus is only ever accepted in the beginning of the string.
156 if ( pos != 0 )
157 return false;
158
159 // And then only if there is no existing minus sign there.
160 if ( !val.empty() && val[0] == '-' )
161 return false;
162
163 return true;
164 }
165
OnChar(wxKeyEvent & event)166 void NumValidatorBase::OnChar(wxKeyEvent& event)
167 {
168 // By default we just validate this key so don't prevent the normal
169 // handling from taking place.
170 event.Skip();
171
172 if ( !m_validatorWindow )
173 return;
174
175 #if wxUSE_UNICODE
176 const int ch = event.GetUnicodeKey();
177 const int c = event.GetKeyCode();
178 if ( c > WXK_START )
179 {
180 // It's a character without any Unicode equivalent at all, e.g. cursor
181 // arrow or function key, we never filter those.
182 return;
183 }
184 #else // !wxUSE_UNICODE
185 const int ch = event.GetKeyCode();
186 const int c = ch;
187 if ( ch > WXK_DELETE )
188 {
189 // Not a character neither.
190 return;
191 }
192 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
193
194 // Space is an allowed thousands separator. But we don't allow user to type
195 // it. We will add it at formatting time in OnKillFocus().
196 if ( c < WXK_SPACE || c == WXK_DELETE )
197 {
198 // Allow ASCII control characters and Delete.
199 return;
200 }
201
202 // Check if this character is allowed in the current state.
203 wxString val;
204 int pos;
205 GetCurrentValueAndInsertionPoint(val, pos);
206
207 if ( !IsCharOk(val, pos, ch) )
208 {
209 if ( !wxValidator::IsSilent() )
210 wxBell();
211
212 // Do not skip the event in this case, stop handling it here.
213 event.Skip(false);
214 }
215 }
216
OnPaste(wxClipboardTextEvent & event)217 void NumValidatorBase::OnPaste(wxClipboardTextEvent& event)
218 {
219 event.Skip(false);
220
221 wxTextEntry * const control = GetTextEntry();
222 if ( !control )
223 {
224 return;
225 }
226
227 wxClipboardLocker cb;
228 // if (!wxClipboard::Get()->IsSupported(wxDataFormat(wxDF_UNICODETEXT)))
229 if (!wxClipboard::Get()->IsSupported(wxDF_UNICODETEXT))
230 {
231 return;
232 }
233
234 wxTextDataObject data;
235 if (!wxClipboard::Get()->GetData( data ))
236 {
237 return;
238 }
239
240 wxString toPaste = data.GetText();
241 wxString val;
242 int pos;
243 GetCurrentValueAndInsertionPoint(val, pos);
244
245 for (size_t i = 0, cnt = toPaste.length(); i < cnt; i++)
246 {
247 const wxChar ch = toPaste[i];
248
249 // Check if this character is allowed in the current state.
250 if ( IsCharOk(val, pos, ch) )
251 {
252 val = GetValueAfterInsertingChar(val, pos++, ch);
253 }
254 else if ( !wxValidator::IsSilent() )
255 {
256 wxBell();
257 }
258 }
259
260 // When we change the control value below, its "modified" status is reset
261 // so we need to explicitly keep it marked as modified if it was so in the
262 // first place.
263 //
264 // Notice that only wxTextCtrl (and not wxTextEntry) has
265 // IsModified()/MarkDirty() methods hence the need for dynamic cast.
266 wxTextCtrl * const text = wxDynamicCast(m_validatorWindow, wxTextCtrl);
267 const bool wasModified = text ? text->IsModified() : false;
268
269 // Use SetValue because effect still needs EVT_TEXT (bug 1357)
270 control->SetValue(NormalizeString(val));
271
272 if ( wasModified )
273 {
274 text->MarkDirty();
275 }
276 }
277
OnKillFocus(wxFocusEvent & event)278 void NumValidatorBase::OnKillFocus(wxFocusEvent& event)
279 {
280 wxTextEntry * const control = GetTextEntry();
281 if ( !control )
282 return;
283
284 // When we change the control value below, its "modified" status is reset
285 // so we need to explicitly keep it marked as modified if it was so in the
286 // first place.
287 //
288 // Notice that only wxTextCtrl (and not wxTextEntry) has
289 // IsModified()/MarkDirty() methods hence the need for dynamic cast.
290 wxTextCtrl * const text = wxDynamicCast(m_validatorWindow, wxTextCtrl);
291 const bool wasModified = text ? text->IsModified() : false;
292
293 control->ChangeValue(NormalizeString(control->GetValue()));
294
295 if ( wasModified )
296 text->MarkDirty();
297
298 event.Skip();
299
300 // Validate(text);
301 }
302
303 // ============================================================================
304 // IntegerValidatorBase implementation
305 // ============================================================================
306
ToString(LongestValueType value) const307 wxString IntegerValidatorBase::ToString(LongestValueType value) const
308 {
309 return NumberFormatter::ToString(value, GetFormatFlags());
310 }
311
312 bool
FromString(const wxString & s,LongestValueType * value)313 IntegerValidatorBase::FromString(const wxString& s, LongestValueType *value)
314 {
315 return NumberFormatter::FromString(s, value);
316 }
317
318 bool
IsCharOk(const wxString & val,int pos,wxChar ch) const319 IntegerValidatorBase::IsCharOk(const wxString& val, int pos, wxChar ch) const
320 {
321 // We may accept minus sign if we can represent negative numbers at all.
322 if ( ch == '-' )
323 {
324 // Notice that entering '-' can make our value invalid, for example if
325 // we're limited to -5..15 range and the current value is 12, then the
326 // NEW value would be (invalid) -12. We consider it better to let the
327 // user do this because perhaps he is going to press Delete key next to
328 // make it -2 and forcing him to DELETE 1 first would be unnatural.
329 //
330 // TODO: It would be nice to indicate that the current control contents
331 // is invalid (if it's indeed going to be the case) once
332 // wxValidator supports doing this non-intrusively.
333 return m_min < 0 && IsMinusOk(val, pos);
334 }
335
336 // A separator is accepted if the locale allow it, the other chars must be digits
337 if ( ch < '0' || ch > '9' )
338 {
339 wxChar thousands;
340 if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousands) )
341 {
342 // if (ch != thousands)
343 return false;
344 }
345 else
346 {
347 return false;
348 }
349 }
350
351 return true;
352 }
353
DoValidateNumber(TranslatableString * errMsg) const354 bool IntegerValidatorBase::DoValidateNumber(TranslatableString * errMsg) const
355 {
356 wxTextEntry * const control = GetTextEntry();
357 if ( !control )
358 return false;
359
360 wxString s(control->GetValue());
361 wxChar thousandsSep;
362 if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousandsSep) )
363 s.Replace(wxString(thousandsSep), wxString());
364
365 if ( s.empty() )
366 {
367 // Is blank, but allowed. Stop here
368 if ( HasFlag(NumValidatorStyle::ZERO_AS_BLANK) )
369 {
370 return true;
371 }
372 // We can't do any check with an empty string
373 else
374 {
375 *errMsg = XO("Empty value");
376 return false;
377 }
378 }
379
380 // Can it be converted to a value?
381 LongestValueType value = 0;
382 bool res = FromString(s, &value);
383 if ( !res )
384 *errMsg = XO("Malformed number");
385 else
386 {
387 res = IsInRange(value);
388 if ( !res )
389 *errMsg = XO("Not in range %d to %d")
390 .Format( (int) m_min, (int) m_max );
391 }
392
393 return res;
394 }
395
396 // ============================================================================
397 // FloatingPointValidatorBase implementation
398 // ============================================================================
399
ToString(LongestValueType value) const400 wxString FloatingPointValidatorBase::ToString(LongestValueType value) const
401 {
402 return NumberFormatter::ToString(value, m_precision, GetFormatFlags());
403 }
404
405 bool
FromString(const wxString & s,LongestValueType * value)406 FloatingPointValidatorBase::FromString(const wxString& s,
407 LongestValueType *value)
408 {
409 return NumberFormatter::FromString(s, value);
410 }
411
412 bool
IsCharOk(const wxString & val,int pos,wxChar ch) const413 FloatingPointValidatorBase::IsCharOk(const wxString& val,
414 int pos,
415 wxChar ch) const
416 {
417 if ( ch == '-' )
418 {
419 // We may accept minus sign if we can represent negative numbers at all.
420 if ( pos == 0 )
421 return m_min < 0 && IsMinusOk(val, pos);
422 // or for the exponent definition
423 else if ( val[pos-1] != 'e' && val[pos-1] != 'E' )
424 return false;
425
426 return true;
427 }
428 else if ( ch == '+' )
429 {
430 if ( pos == 0 )
431 return m_max >= 0;
432 else if ( val[pos-1] != 'e' && val[pos-1] != 'E' )
433 return false;
434
435 return true;
436 }
437
438 const wxChar separator = NumberFormatter::GetDecimalSeparator();
439 if ( ch == separator )
440 {
441 if ( val.find(separator) != wxString::npos )
442 {
443 // There is already a decimal separator, can't insert another one.
444 return false;
445 }
446
447 // Prepending a separator before the sign isn't allowed.
448 if ( pos == 0 && !val.empty() && ( val[0] == '-' || val[0] == '+' ) )
449 return false;
450
451 // Otherwise always accept it, adding a decimal separator doesn't
452 // change the number value and, in particular, can't make it invalid.
453 // OTOH the checks below might not pass because strings like "." or
454 // "-." are not valid numbers so parsing them would fail, hence we need
455 // to treat it specially here.
456 return true;
457 }
458
459 // Must be a digit, an exponent or a thousands separator.
460 if( ( ch < '0' || ch > '9' ) && ch != 'E' && ch != 'e' )
461 {
462 wxChar thousands;
463 if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousands) )
464 {
465 // if (ch != thousands)
466 return false;
467 }
468 else
469 {
470 return false;
471 }
472 }
473
474 // Check the number of decimal digits in the final string
475 wxString str(val);
476 str.insert(pos, ch);
477 return ValidatePrecision(str);
478 }
479
DoValidateNumber(TranslatableString * errMsg) const480 bool FloatingPointValidatorBase::DoValidateNumber(TranslatableString * errMsg) const
481 {
482 wxTextEntry * const control = GetTextEntry();
483 if ( !control )
484 return false;
485
486 wxString s(control->GetValue());
487 wxChar thousandsSep;
488 if ( NumberFormatter::GetThousandsSeparatorIfUsed(&thousandsSep) )
489 s.Replace(wxString(thousandsSep), wxString());
490
491 if ( s.empty() )
492 {
493 if ( HasFlag(NumValidatorStyle::ZERO_AS_BLANK) )
494 return true; //Is blank, but allowed. Stop here
495 else
496 {
497 *errMsg = XO("Empty value");
498 return false; //We can't do any checks with an empty string
499 }
500 }
501
502 LongestValueType value = 0;
503 bool res = FromString(s, &value); // Can it be converted to a value?
504 if ( !res )
505 *errMsg = XO("Value overflow");
506 else
507 {
508 res = ValidatePrecision(s);
509 if ( !res )
510 *errMsg = XO("Too many decimal digits");
511 else
512 {
513 res = IsInRange(value);
514 if ( !res )
515 {
516 wxString strMin = wxString::Format(wxT("%f"), m_min);
517 wxString strMax = wxString::Format(wxT("%f"), m_max);
518 NumberFormatter::RemoveTrailingZeroes(strMin);
519 NumberFormatter::RemoveTrailingZeroes(strMax);
520
521 if (m_minSet && m_maxSet)
522 {
523 *errMsg = XO("Value not in range: %s to %s")
524 .Format( strMin, strMax );
525 }
526 else if (m_minSet)
527 {
528 *errMsg = XO("Value must not be less than %s").Format( strMin );
529 }
530 else if (m_maxSet)
531 {
532 *errMsg = XO("Value must not be greater than %s")
533 .Format( strMax );
534 }
535 }
536 }
537 }
538
539 return res;
540 }
541
ValidatePrecision(const wxString & s) const542 bool FloatingPointValidatorBase::ValidatePrecision(const wxString& s) const
543 {
544 size_t posSep = s.find(NumberFormatter::GetDecimalSeparator());
545 if ( posSep == wxString::npos )
546 posSep = s.length();
547
548 // If user typed exponent 'e' the number of decimal digits is not
549 // important at all. But we must know that 'e' position.
550 // PRL: I can't find anything in lconv or std::numpunct that describes
551 // alternatives to e. So just use a plain string literal. Don't trouble
552 // with i18n.
553 size_t posExp = s.Lower().Find("e");
554 if ( posExp == wxString::npos )
555 posExp = s.length();
556
557 // Return true if number has no more decimal digits than allowed
558 return ( (int)(posExp - posSep) - 1 <= (int)m_precision );
559 }
560
RoundValue(int precision,double value)561 double RoundValue(int precision, double value)
562 {
563 return Internat::CompatibleToDouble( Internat::ToString(value, precision) );
564 }
565
566 #endif // wxUSE_VALIDATORS && wxUSE_TEXTCTRL
567