Audacity 3.2.0
valnum.cpp
Go to the documentation of this file.
1
2//
3// Backport from wxWidgets-3.0-rc1
4//
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
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
53BEGIN_EVENT_TABLE(NumValidatorBase, wxValidator)
54 EVT_CHAR(NumValidatorBase::OnChar)
55 EVT_TEXT_PASTE(wxID_ANY, NumValidatorBase::OnPaste)
56 EVT_KILL_FOCUS(NumValidatorBase::OnKillFocus)
58
59int NumValidatorBase::GetFormatFlags() const
60{
62 if ( m_style & NumValidatorStyle::THOUSANDS_SEPARATOR )
64 if ( m_style & NumValidatorStyle::NO_TRAILING_ZEROES )
66 if ( m_style & NumValidatorStyle::ONE_TRAILING_ZERO )
68 if ( m_style & NumValidatorStyle::TWO_TRAILING_ZEROES )
70 if ( m_style & NumValidatorStyle::THREE_TRAILING_ZEROES )
72
73 return flags;
74}
75
76wxTextEntry *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
93bool 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 {
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
121void
122NumValidatorBase::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
153bool 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
166void 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
217void 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
278void 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
307wxString IntegerValidatorBase::ToString(LongestValueType value) const
308{
309 return NumberFormatter::ToString(value, GetFormatFlags());
310}
311
312bool
313IntegerValidatorBase::FromString(const wxString& s, LongestValueType *value)
314{
315 return NumberFormatter::FromString(s, value);
316}
317
318bool
319IntegerValidatorBase::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;
341 {
342// if (ch != thousands)
343 return false;
344 }
345 else
346 {
347 return false;
348 }
349 }
350
351 return true;
352}
353
354bool 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;
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 {
390 *errMsg = XO("Not in range %d to %d")
391 .Format( (int) m_min, (int) m_max );
392
393 if ( value > (int) m_max )
394 control->ChangeValue( wxString::Format( wxT("%d"), (int) m_max ) );
395 else if ( value < (int) m_min )
396 control->ChangeValue( wxString::Format( wxT("%d"), (int) m_min ) );
397 }
398 }
399
400 return res;
401}
402
403// ============================================================================
404// FloatingPointValidatorBase implementation
405// ============================================================================
406
407wxString FloatingPointValidatorBase::ToString(LongestValueType value) const
408{
409 return NumberFormatter::ToString(value, m_precision, GetFormatFlags());
410}
411
412bool
413FloatingPointValidatorBase::FromString(const wxString& s,
414 LongestValueType *value)
415{
416 return NumberFormatter::FromString(s, value);
417}
418
419bool
420FloatingPointValidatorBase::IsCharOk(const wxString& val,
421 int pos,
422 wxChar ch) const
423{
424 if ( ch == '-' )
425 {
426 // We may accept minus sign if we can represent negative numbers at all.
427 if ( pos == 0 )
428 return m_min < 0 && IsMinusOk(val, pos);
429 // or for the exponent definition
430 else if ( val[pos-1] != 'e' && val[pos-1] != 'E' )
431 return false;
432
433 return true;
434 }
435 else if ( ch == '+' )
436 {
437 if ( pos == 0 )
438 return m_max >= 0;
439 else if ( val[pos-1] != 'e' && val[pos-1] != 'E' )
440 return false;
441
442 return true;
443 }
444
445 const wxChar separator = NumberFormatter::GetDecimalSeparator();
446 if ( ch == separator )
447 {
448 if ( val.find(separator) != wxString::npos )
449 {
450 // There is already a decimal separator, can't insert another one.
451 return false;
452 }
453
454 // Prepending a separator before the sign isn't allowed.
455 if ( pos == 0 && !val.empty() && ( val[0] == '-' || val[0] == '+' ) )
456 return false;
457
458 // Otherwise always accept it, adding a decimal separator doesn't
459 // change the number value and, in particular, can't make it invalid.
460 // OTOH the checks below might not pass because strings like "." or
461 // "-." are not valid numbers so parsing them would fail, hence we need
462 // to treat it specially here.
463 return true;
464 }
465
466 // Must be a digit, an exponent or a thousands separator.
467 if( ( ch < '0' || ch > '9' ) && ch != 'E' && ch != 'e' )
468 {
469 wxChar thousands;
471 {
472// if (ch != thousands)
473 return false;
474 }
475 else
476 {
477 return false;
478 }
479 }
480
481 // Check the number of decimal digits in the final string
482 wxString str(val);
483 str.insert(pos, ch);
484 return ValidatePrecision(str);
485}
486
487bool FloatingPointValidatorBase::DoValidateNumber(TranslatableString * errMsg) const
488{
489 wxTextEntry * const control = GetTextEntry();
490 if ( !control )
491 return false;
492
493 wxString s(control->GetValue());
494 wxChar thousandsSep;
496 s.Replace(wxString(thousandsSep), wxString());
497
498 if ( s.empty() )
499 {
500 if ( HasFlag(NumValidatorStyle::ZERO_AS_BLANK) )
501 return true; //Is blank, but allowed. Stop here
502 else
503 {
504 *errMsg = XO("Empty value");
505 return false; //We can't do any checks with an empty string
506 }
507 }
508
509 LongestValueType value = 0;
510 bool res = FromString(s, &value); // Can it be converted to a value?
511 if ( !res )
512 *errMsg = XO("Value overflow");
513 else
514 {
515 res = ValidatePrecision(s);
516 if ( !res )
517 *errMsg = XO("Too many decimal digits");
518 else
519 {
520 res = IsInRange(value);
521 if ( !res )
522 {
523 wxString strMin = wxString::Format(wxT("%f"), m_min);
524 wxString strMax = wxString::Format(wxT("%f"), m_max);
527
528 if (m_minSet && m_maxSet)
529 {
530 *errMsg = XO("Value not in range: %s to %s")
531 .Format( strMin, strMax );
532
533 if ( value > m_max )
534 control->ChangeValue( strMax );
535 else if ( value < m_min )
536 control->ChangeValue( strMin );
537 }
538 else if (m_minSet)
539 {
540 *errMsg = XO("Value must not be less than %s").Format( strMin );
541 }
542 else if (m_maxSet)
543 {
544 *errMsg = XO("Value must not be greater than %s")
545 .Format( strMax );
546 }
547 }
548 }
549 }
550
551 return res;
552}
553
554bool FloatingPointValidatorBase::ValidatePrecision(const wxString& s) const
555{
556 size_t posSep = s.find(NumberFormatter::GetDecimalSeparator());
557 if ( posSep == wxString::npos )
558 posSep = s.length();
559
560 // If user typed exponent 'e' the number of decimal digits is not
561 // important at all. But we must know that 'e' position.
562 // PRL: I can't find anything in lconv or std::numpunct that describes
563 // alternatives to e. So just use a plain string literal. Don't trouble
564 // with i18n.
565 size_t posExp = s.Lower().Find("e");
566 if ( posExp == wxString::npos )
567 posExp = s.length();
568
569 // Return true if number has no more decimal digits than allowed
570 return ( (int)(posExp - posSep) - 1 <= (int)m_precision );
571}
572
573double RoundValue(int precision, double value)
574{
575 return Internat::CompatibleToDouble( Internat::ToString(value, precision) );
576}
577
578#endif // wxUSE_VALIDATORS && wxUSE_TEXTCTRL
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
END_EVENT_TABLE()
#define str(a)
XO("Cut/Copy/Paste")
static wxString ToString(double numberToConvert, int digitsAfterDecimalPoint=-1)
Convert a number to a string, always uses the dot as decimal separator.
Definition: Internat.cpp:126
static bool CompatibleToDouble(const wxString &stringToConvert, double *result)
Convert a string to a number.
Definition: Internat.cpp:109
static bool FromString(const wxString &s, long *val)
static wxChar GetDecimalSeparator()
static void RemoveTrailingZeroes(wxString &s, size_t retain=0)
static bool GetThousandsSeparatorIfUsed(wxChar *sep)
static wxString ToString(long val, int style=Style_WithThousandsSep)
Holds a msgid for the translation catalog; may also bind format arguments.
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:202
auto ToString(const std::optional< TimeSignature > &ts)
constexpr size_t npos(-1)
void OnPaste(const CommandContext &context)
Definition: EditMenus.cpp:546