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