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 *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
400wxString FloatingPointValidatorBase::ToString(LongestValueType value) const
401{
402 return NumberFormatter::ToString(value, m_precision, GetFormatFlags());
403}
404
405bool
406FloatingPointValidatorBase::FromString(const wxString& s,
407 LongestValueType *value)
408{
409 return NumberFormatter::FromString(s, value);
410}
411
412bool
413FloatingPointValidatorBase::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;
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
480bool 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;
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);
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
542bool 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
561double RoundValue(int precision, double value)
562{
563 return Internat::CompatibleToDouble( Internat::ToString(value, precision) );
564}
565
566#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:196
auto ToString(const std::optional< TimeSignature > &ts)
constexpr size_t npos(-1)
void OnPaste(const CommandContext &context)
Definition: EditMenus.cpp:526