From 08c94d53720637c558344874909a0a0ecf238443 Mon Sep 17 00:00:00 2001 From: "lllucius@gmail.com" Date: Wed, 23 Oct 2013 20:33:17 +0000 Subject: [PATCH] Backported wxWidgets-3.0rc1 number validators The are pretty darn slick. There's an integer one and a floating point one. They support automatic range limiting (ex., you can't even type a number outside of the range), proper number formats (ex., you can't enter a decimal point in an integer field), you can't enter bogus numbers like "0.3-.2", thousands separators are supported, decimal precision may be specified and proper number formatting for string values (or automatic conversion to int, double, float, etc.). --- mac/Audacity.xcodeproj/project.pbxproj | 12 + src/Makefile.in | 2 + src/effects/nyquist/Nyquist.cpp | 63 ++-- src/widgets/numformatter.cpp | 348 ++++++++++++++++++ src/widgets/numformatter.h | 84 +++++ src/widgets/valnum.cpp | 300 ++++++++++++++++ src/widgets/valnum.h | 465 +++++++++++++++++++++++++ win/Projects/Audacity/Audacity.vcproj | 16 + 8 files changed, 1249 insertions(+), 41 deletions(-) create mode 100644 src/widgets/numformatter.cpp create mode 100644 src/widgets/numformatter.h create mode 100644 src/widgets/valnum.cpp create mode 100644 src/widgets/valnum.h diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index 30af7bae5..5041f5d6f 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -1162,6 +1162,8 @@ 28F1D81D0A2D0019005506A7 /* AttachableScrollBar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28F1D8170A2D0018005506A7 /* AttachableScrollBar.cpp */; }; 28F1D81E0A2D0019005506A7 /* ExpandingToolBar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28F1D8190A2D0018005506A7 /* ExpandingToolBar.cpp */; }; 28F1D81F0A2D0019005506A7 /* ImageRoll.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28F1D81B0A2D0019005506A7 /* ImageRoll.cpp */; }; + 28F2CED4181867BB00573D61 /* numformatter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28F2CED0181867BB00573D61 /* numformatter.cpp */; }; + 28F2CED5181867BB00573D61 /* valnum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28F2CED2181867BB00573D61 /* valnum.cpp */; }; 28F5C1110BE5886A00D17341 /* FileDialogPrivate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28F5C10F0BE5886A00D17341 /* FileDialogPrivate.cpp */; }; 28FC1AFB0A47762C00A188AE /* WrappedType.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28FC1AF90A47762C00A188AE /* WrappedType.cpp */; }; 28FE4A080ABF4E960056F5C4 /* mmx_optimized.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28FE4A060ABF4E960056F5C4 /* mmx_optimized.cpp */; }; @@ -3876,6 +3878,10 @@ 28F1D81A0A2D0018005506A7 /* ExpandingToolBar.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = ExpandingToolBar.h; sourceTree = ""; tabWidth = 3; }; 28F1D81B0A2D0019005506A7 /* ImageRoll.cpp */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = ImageRoll.cpp; sourceTree = ""; tabWidth = 3; }; 28F1D81C0A2D0019005506A7 /* ImageRoll.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = ImageRoll.h; sourceTree = ""; tabWidth = 3; }; + 28F2CED0181867BB00573D61 /* numformatter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = numformatter.cpp; sourceTree = ""; }; + 28F2CED1181867BB00573D61 /* numformatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = numformatter.h; sourceTree = ""; }; + 28F2CED2181867BB00573D61 /* valnum.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = valnum.cpp; sourceTree = ""; }; + 28F2CED3181867BB00573D61 /* valnum.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = valnum.h; sourceTree = ""; }; 28F3A3F60E28289500729866 /* algrd_internal.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; name = algrd_internal.h; path = portsmf/algrd_internal.h; sourceTree = ""; tabWidth = 3; }; 28F3A3F70E28289500729866 /* algsmfrd_internal.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; name = algsmfrd_internal.h; path = portsmf/algsmfrd_internal.h; sourceTree = ""; tabWidth = 3; }; 28F3A3F80E28289500729866 /* allegro.cpp */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; name = allegro.cpp; path = portsmf/allegro.cpp; sourceTree = ""; tabWidth = 3; }; @@ -5331,12 +5337,16 @@ 1790B10409883BFD008A330A /* Meter.h */, 1790B10509883BFD008A330A /* MultiDialog.cpp */, 1790B10609883BFD008A330A /* MultiDialog.h */, + 28F2CED0181867BB00573D61 /* numformatter.cpp */, + 28F2CED1181867BB00573D61 /* numformatter.h */, 28530C4A0DF2105200555C94 /* ProgressDialog.cpp */, 28530C4B0DF2105200555C94 /* ProgressDialog.h */, 1790B10709883BFD008A330A /* Ruler.cpp */, 1790B10809883BFD008A330A /* Ruler.h */, 1790B10909883BFD008A330A /* TimeTextCtrl.cpp */, 1790B10A09883BFD008A330A /* TimeTextCtrl.h */, + 28F2CED2181867BB00573D61 /* valnum.cpp */, + 28F2CED3181867BB00573D61 /* valnum.h */, 1790B10B09883BFD008A330A /* Warning.cpp */, 1790B10C09883BFD008A330A /* Warning.h */, ); @@ -8824,6 +8834,8 @@ 284FD04217FC72A50009A025 /* ScienFilter.cpp in Sources */, 284FD04517FC72EE0009A025 /* Biquad.cpp in Sources */, 28C3946D1818356800FDDAC9 /* AudacityLogger.cpp in Sources */, + 28F2CED4181867BB00573D61 /* numformatter.cpp in Sources */, + 28F2CED5181867BB00573D61 /* valnum.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/Makefile.in b/src/Makefile.in index 3888173a8..7c3c1e7f6 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -257,6 +257,8 @@ OBJS = \ widgets/Ruler.o \ widgets/TimeTextCtrl.o \ widgets/Warning.o \ + widgets/numformatter.o \ + widgets/valnum.o \ xml/XMLFileReader.o \ xml/XMLWriter.o \ @OPTOBJS@ diff --git a/src/effects/nyquist/Nyquist.cpp b/src/effects/nyquist/Nyquist.cpp index e02f6d4de..1b824bb9c 100644 --- a/src/effects/nyquist/Nyquist.cpp +++ b/src/effects/nyquist/Nyquist.cpp @@ -53,6 +53,7 @@ effects from this one class. #include "../../LabelTrack.h" #include "../../Internat.h" #include "../../ShuttleGui.h" +#include "../../widgets/valnum.h" #include "Nyquist.h" @@ -1217,8 +1218,16 @@ NyquistDialog::NyquistDialog(wxWindow * parent, wxWindowID id, item = new wxTextCtrl(this, ID_NYQ_TEXT+i, wxT(""), wxDefaultPosition, wxSize(60, -1)); item->SetName(ctrl->name); - wxTextValidator vld(wxFILTER_NUMERIC); - item->SetValidator(vld); + if (ctrl->type == NYQ_CTRL_REAL) { + wxFloatingPointValidator vld(2, &ctrl->val); + vld.SetRange(ctrl->low, ctrl->high); + item->SetValidator(vld); + } + else { + wxIntegerValidator vld(&ctrl->val); + vld.SetRange(ctrl->low, ctrl->high); + item->SetValidator(vld); + } grid->Add(item, 0, wxALIGN_CENTRE | wxALIGN_CENTER_VERTICAL | wxALL, 5); @@ -1298,33 +1307,8 @@ void NyquistDialog::OnSlider(wxCommandEvent & /* event */) newVal /= pow(10.0, precision); ctrl->val = newVal; - } - wxString valStr; - if (ctrl->type == NYQ_CTRL_REAL) { - // If this is a user-typed value, allow unlimited precision - if (ctrl->val != newVal) - { - valStr = Internat::ToDisplayString(ctrl->val); - } - else - { - if (precision == 0) - { - valStr.Printf(wxT("%d"), (int)floor(ctrl->val + 0.5)); - } - else - { - valStr = Internat::ToDisplayString(ctrl->val, precision); - } - } - } - else if (ctrl->type == NYQ_CTRL_INT) { - valStr.Printf(wxT("%d"), (int)floor(ctrl->val + 0.5)); - } - - if (valStr != wxT("")) { - text->SetValue(valStr); + text->GetValidator()->TransferToWindow(); } } @@ -1365,24 +1349,21 @@ void NyquistDialog::OnText(wxCommandEvent &event) wxTextCtrl *text = (wxTextCtrl *)FindWindow(ID_NYQ_TEXT + ctrlId); wxASSERT(text); - ctrl->valStr = text->GetValue(); - if (ctrl->type != NYQ_CTRL_STRING) { + text->GetValidator()->TransferFromWindow(); wxSlider *slider = (wxSlider *)FindWindow(ID_NYQ_SLIDER + ctrlId); wxASSERT(slider); - if (ctrl->valStr.ToDouble(&ctrl->val)) { - int pos = (int)floor((ctrl->val - ctrl->low) / - (ctrl->high - ctrl->low) * ctrl->ticks + 0.5); - if (pos < 0) { - pos = 0; - } - else if (pos > ctrl->ticks) { - pos = ctrl->ticks; - } - - slider->SetValue(pos); + int pos = (int)floor((ctrl->val - ctrl->low) / + (ctrl->high - ctrl->low) * ctrl->ticks + 0.5); + if (pos < 0) { + pos = 0; } + else if (pos > ctrl->ticks) { + pos = ctrl->ticks; + } + + slider->SetValue(pos); } mInHandler = false; diff --git a/src/widgets/numformatter.cpp b/src/widgets/numformatter.cpp new file mode 100644 index 000000000..357e129af --- /dev/null +++ b/src/widgets/numformatter.cpp @@ -0,0 +1,348 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Backport from wxWidgets-3.0-rc1 +// +///////////////////////////////////////////////////////////////////////////// +// Name: src/common/numformatter.cpp +// Purpose: wxNumberFormatter +// Author: Fulvio Senore, Vadim Zeitlin +// Created: 2010-11-06 +// Copyright: (c) 2010 wxWidgets team +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +// ---------------------------------------------------------------------------- +// headers +// ---------------------------------------------------------------------------- + +// For compilers that support precompilation, includes "wx.h". +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif + +#ifdef __WIN32__ + #include "wx/msw/private.h" + +#endif + + +#include "numformatter.h" +#include "wx/intl.h" + +#include // for setlocale and LC_ALL + +// ---------------------------------------------------------------------------- +// local helpers +// ---------------------------------------------------------------------------- + +namespace +{ + +// Contains information about the locale which was used to initialize our +// cached values of the decimal and thousands separators. Notice that it isn't +// enough to store just wxLocale because the user code may call setlocale() +// directly and storing just C locale string is not enough because we can use +// the OS API directly instead of the CRT ones on some platforms. So just store +// both. +class LocaleId +{ +public: + LocaleId() + { +#if wxUSE_INTL + m_wxloc = NULL; +#endif // wxUSE_INTL + m_cloc = NULL; + } + + ~LocaleId() + { + Free(); + } + +#if wxUSE_INTL + // Return true if this is the first time this function is called for this + // object or if the program locale has changed since the last time it was + // called. Otherwise just return false indicating that updating locale- + // dependent information is not necessary. + bool NotInitializedOrHasChanged() + { + wxLocale * const wxloc = wxGetLocale(); + const char * const cloc = setlocale(LC_ALL, NULL); + if ( m_wxloc || m_cloc ) + { + if ( m_wxloc == wxloc && strcmp(m_cloc, cloc) == 0 ) + return false; + + Free(); + } + //else: Not initialized yet. + + m_wxloc = wxloc; + m_cloc = strdup(cloc); + + return true; + } +#endif // wxUSE_INTL + +private: + void Free() + { +#if wxUSE_INTL + free(m_cloc); +#endif // wxUSE_INTL + } + +#if wxUSE_INTL + // Non-owned pointer to wxLocale which was used. + wxLocale *m_wxloc; +#endif // wxUSE_INTL + + // Owned pointer to the C locale string. + char *m_cloc; + +// wxDECLARE_NO_COPY_CLASS(LocaleId); +}; + +} // anonymous namespace + +// ============================================================================ +// wxNumberFormatter implementation +// ============================================================================ + +// ---------------------------------------------------------------------------- +// Locale information accessors +// ---------------------------------------------------------------------------- + +wxChar wxNumberFormatter::GetDecimalSeparator() +{ +#if wxUSE_INTL + // Notice that while using static variable here is not MT-safe, the worst + // that can happen is that we redo the initialization if we're called + // concurrently from more than one thread so it's not a real problem. + static wxChar s_decimalSeparator = 0; + + // Remember the locale which was current when we initialized, we must redo + // the initialization if the locale changed. + static LocaleId s_localeUsedForInit; + + if ( s_localeUsedForInit.NotInitializedOrHasChanged() ) + { + const wxString + s = wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER); + if ( s.empty() ) + { + // We really must have something for decimal separator, so fall + // back to the C locale default. + s_decimalSeparator = '.'; + } + else + { + // To the best of my knowledge there are no locales like this. + wxASSERT_MSG( s.length() == 1, + wxT("Multi-character decimal separator?") ); + + s_decimalSeparator = s[0]; + } + } + + return s_decimalSeparator; +#else // !wxUSE_INTL + return wxT('.'); +#endif // wxUSE_INTL/!wxUSE_INTL +} + +bool wxNumberFormatter::GetThousandsSeparatorIfUsed(wxChar *sep) +{ +#if wxUSE_INTL + static wxChar s_thousandsSeparator = 0; + static LocaleId s_localeUsedForInit; + + if ( s_localeUsedForInit.NotInitializedOrHasChanged() ) + { +#if defined(__WXMSW__) + wxUint32 lcid = LOCALE_USER_DEFAULT; + + if (wxGetLocale()) + { + const wxLanguageInfo *info = wxLocale::GetLanguageInfo(wxGetLocale()->GetLanguage()); + if (info) + { ; + lcid = MAKELCID(MAKELANGID(info->WinLang, info->WinSublang), + SORT_DEFAULT); + } + } + + wxString s; + wxChar buffer[256]; + buffer[0] = wxT('\0'); + size_t count = GetLocaleInfo(lcid, LOCALE_STHOUSAND, buffer, 256); + if (!count) + s << wxT(","); + else + s << buffer; +#else + wxString + s = wxLocale::GetInfo(wxLOCALE_THOUSANDS_SEP, wxLOCALE_CAT_NUMBER); +#endif + if ( !s.empty() ) + { + wxASSERT_MSG( s.length() == 1, + wxT("Multi-character thousands separator?") ); + + s_thousandsSeparator = s[0]; + } + //else: Unlike above it's perfectly fine for the thousands separator to + // be empty if grouping is not used, so just leave it as 0. + } + + if ( !s_thousandsSeparator ) + return false; + + if ( sep ) + *sep = s_thousandsSeparator; + + return true; +#else // !wxUSE_INTL + wxUnusedVar(sep); + return false; +#endif // wxUSE_INTL/!wxUSE_INTL +} + +// ---------------------------------------------------------------------------- +// Conversion to string and helpers +// ---------------------------------------------------------------------------- + +wxString wxNumberFormatter::PostProcessIntString(wxString s, int style) +{ + if ( style & Style_WithThousandsSep ) + AddThousandsSeparators(s); + + wxASSERT_MSG( !(style & Style_NoTrailingZeroes), + wxT("Style_NoTrailingZeroes can't be used with integer values") ); + + return s; +} + +wxString wxNumberFormatter::ToString(long val, int style) +{ + return PostProcessIntString(wxString::Format(wxT("%ld"), val), style); +} + +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +wxString wxNumberFormatter::ToString(wxLongLong_t val, int style) +{ + return PostProcessIntString(wxString::Format(wxT("%") wxLongLongFmtSpec wxT("d"), val), + style); +} + +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +wxString wxNumberFormatter::ToString(double val, int precision, int style) +{ + wxString format; + if ( precision == -1 ) + { + format = wxT("%g"); + } + else // Use fixed precision. + { + format.Printf(wxT("%%.%df"), precision); + } + + wxString s = wxString::Format(format, val); + + if ( style & Style_WithThousandsSep ) + AddThousandsSeparators(s); + + if ( style & Style_NoTrailingZeroes ) + RemoveTrailingZeroes(s); + + return s; +} + +void wxNumberFormatter::AddThousandsSeparators(wxString& s) +{ + wxChar thousandsSep; + if ( !GetThousandsSeparatorIfUsed(&thousandsSep) ) + return; + + size_t pos = s.find(GetDecimalSeparator()); + if ( pos == wxString::npos ) + { + // Start grouping at the end of an integer number. + pos = s.length(); + } + + // End grouping at the beginning of the digits -- there could be at a sign + // before their start. + const size_t start = s.find_first_of(wxT("0123456789")); + + // We currently group digits by 3 independently of the locale. This is not + // the right thing to do and we should use lconv::grouping (under POSIX) + // and GetLocaleInfo(LOCALE_SGROUPING) (under MSW) to get information about + // the correct grouping to use. This is something that needs to be done at + // wxLocale level first and then used here in the future (TODO). + const size_t GROUP_LEN = 3; + + while ( pos > start + GROUP_LEN ) + { + pos -= GROUP_LEN; + s.insert(pos, thousandsSep); + } +} + +void wxNumberFormatter::RemoveTrailingZeroes(wxString& s) +{ + const size_t posDecSep = s.find(GetDecimalSeparator()); + wxCHECK_RET( posDecSep != wxString::npos, + wxString::Format(wxT("No decimal separator in \"%s\""), s.c_str()) ); + wxCHECK_RET( posDecSep, wxT("Can't start with decimal separator" )); + + // Find the last character to keep. + size_t posLastNonZero = s.find_last_not_of(wxT("0")); + + // If it's the decimal separator itself, don't keep it neither. + if ( posLastNonZero == posDecSep ) + posLastNonZero--; + + s.erase(posLastNonZero + 1); +} + +// ---------------------------------------------------------------------------- +// Conversion from strings +// ---------------------------------------------------------------------------- + +void wxNumberFormatter::RemoveThousandsSeparators(wxString& s) +{ + wxChar thousandsSep; + if ( !GetThousandsSeparatorIfUsed(&thousandsSep) ) + return; + + s.Replace(wxString(thousandsSep), wxString()); +} + +bool wxNumberFormatter::FromString(wxString s, long *val) +{ + RemoveThousandsSeparators(s); + return s.ToLong(val); +} + +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +bool wxNumberFormatter::FromString(wxString s, wxLongLong_t *val) +{ + RemoveThousandsSeparators(s); + return s.ToLongLong(val); +} + +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + +bool wxNumberFormatter::FromString(wxString s, double *val) +{ + RemoveThousandsSeparators(s); + return s.ToDouble(val); +} diff --git a/src/widgets/numformatter.h b/src/widgets/numformatter.h new file mode 100644 index 000000000..ab3a68f52 --- /dev/null +++ b/src/widgets/numformatter.h @@ -0,0 +1,84 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Backport from wxWidgets-3.0-rc1 +// +///////////////////////////////////////////////////////////////////////////// +// Name: wx/numformatter.h +// Purpose: wxNumberFormatter class +// Author: Fulvio Senore, Vadim Zeitlin +// Created: 2010-11-06 +// Copyright: (c) 2010 wxWidgets team +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_NUMFORMATTER_H_ +#define _WX_NUMFORMATTER_H_ + +#include "wx/string.h" + +#define wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG 1 + +// Helper class for formatting numbers with thousands separators which also +// supports parsing the numbers formatted by it. +class wxNumberFormatter +{ +public: + // Bit masks for ToString() + enum Style + { + Style_None = 0x00, + Style_WithThousandsSep = 0x01, + Style_NoTrailingZeroes = 0x02 // Only for floating point numbers + }; + + // Format a number as a string. By default, the thousands separator is + // used, specify Style_None to prevent this. For floating point numbers, + // precision can also be specified. + static wxString ToString(long val, + int style = Style_WithThousandsSep); +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + static wxString ToString(wxLongLong_t val, + int style = Style_WithThousandsSep); +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + static wxString ToString(double val, + int precision, + int style = Style_WithThousandsSep); + + // Parse a string representing a number, possibly with thousands separator. + // + // Return true on success and stores the result in the provided location + // which must be a valid non-NULL pointer. + static bool FromString(wxString s, long *val); +#ifdef wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + static bool FromString(wxString s, wxLongLong_t *val); +#endif // wxHAS_LONG_LONG_T_DIFFERENT_FROM_LONG + static bool FromString(wxString s, double *val); + + + // Get the decimal separator for the current locale. It is always defined + // and we fall back to returning '.' in case of an error. + static wxChar GetDecimalSeparator(); + + // Get the thousands separator if grouping of the digits is used by the + // current locale. The value returned in sep should be only used if the + // function returns true. + static bool GetThousandsSeparatorIfUsed(wxChar *sep); + +private: + // Post-process the string representing an integer. + static wxString PostProcessIntString(wxString s, int style); + + // Add the thousands separators to a string representing a number without + // the separators. This is used by ToString(Style_WithThousandsSep). + static void AddThousandsSeparators(wxString& s); + + // Remove trailing zeroes and, if there is nothing left after it, the + // decimal separator itself from a string representing a floating point + // number. Also used by ToString(). + static void RemoveTrailingZeroes(wxString& s); + + // Remove all thousands separators from a string representing a number. + static void RemoveThousandsSeparators(wxString& s); +}; + +#endif // _WX_NUMFORMATTER_H_ diff --git a/src/widgets/valnum.cpp b/src/widgets/valnum.cpp new file mode 100644 index 000000000..d9515b5d9 --- /dev/null +++ b/src/widgets/valnum.cpp @@ -0,0 +1,300 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Backport from wxWidgets-3.0-rc1 +// +///////////////////////////////////////////////////////////////////////////// +// Name: src/common/valnum.cpp +// Purpose: Numeric validator classes implementation +// Author: Vadim Zeitlin based on the submission of Fulvio Senore +// Created: 2010-11-06 +// Copyright: (c) 2010 wxWidgets team +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +// ============================================================================ +// Declarations +// ============================================================================ + +// ---------------------------------------------------------------------------- +// headers +// ---------------------------------------------------------------------------- + +// For compilers that support precompilation, includes "wx.h". +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif + +#if wxUSE_VALIDATORS && wxUSE_TEXTCTRL + +#ifndef WX_PRECOMP + #include "wx/textctrl.h" + #include "wx/combobox.h" +#endif + +#include "valnum.h" +#include "numformatter.h" + +// ============================================================================ +// wxNumValidatorBase implementation +// ============================================================================ + +BEGIN_EVENT_TABLE(wxNumValidatorBase, wxValidator) + EVT_CHAR(wxNumValidatorBase::OnChar) + EVT_KILL_FOCUS(wxNumValidatorBase::OnKillFocus) +END_EVENT_TABLE() + +int wxNumValidatorBase::GetFormatFlags() const +{ + int flags = wxNumberFormatter::Style_None; + if ( m_style & wxNUM_VAL_THOUSANDS_SEPARATOR ) + flags |= wxNumberFormatter::Style_WithThousandsSep; + if ( m_style & wxNUM_VAL_NO_TRAILING_ZEROES ) + flags |= wxNumberFormatter::Style_NoTrailingZeroes; + + return flags; +} + +wxTextEntry *wxNumValidatorBase::GetTextEntry() const +{ +#if wxUSE_TEXTCTRL + if ( wxTextCtrl *text = wxDynamicCast(m_validatorWindow, wxTextCtrl) ) + return text; +#endif // wxUSE_TEXTCTRL + + wxFAIL_MSG(wxT("Can only be used with wxTextCtrl or wxComboBox")); + + return NULL; +} + +void +wxNumValidatorBase::GetCurrentValueAndInsertionPoint(wxString& val, + int& pos) const +{ + wxTextEntry * const control = GetTextEntry(); + if ( !control ) + return; + + val = control->GetValue(); + pos = control->GetInsertionPoint(); + + long selFrom, selTo; + control->GetSelection(&selFrom, &selTo); + + const long selLen = selTo - selFrom; + if ( selLen ) + { + // Remove selected text because pressing a key would make it disappear. + val.erase(selFrom, selLen); + + // And adjust the insertion point to have correct position in the new + // string. + if ( pos > selFrom ) + { + if ( pos >= selTo ) + pos -= selLen; + else + pos = selFrom; + } + } +} + +bool wxNumValidatorBase::IsMinusOk(const wxString& val, int pos) const +{ + // Minus is only ever accepted in the beginning of the string. + if ( pos != 0 ) + return false; + + // And then only if there is no existing minus sign there. + if ( !val.empty() && val[0] == '-' ) + return false; + + return true; +} + +void wxNumValidatorBase::OnChar(wxKeyEvent& event) +{ + // By default we just validate this key so don't prevent the normal + // handling from taking place. + event.Skip(); + + if ( !m_validatorWindow ) + return; + +#if wxUSE_UNICODE + wxKeyEvent & e = event; + const int ch = event.GetUnicodeKey(); + const int c = event.GetKeyCode(); + if ( c > WXK_START ) + { + // It's a character without any Unicode equivalent at all, e.g. cursor + // arrow or function key, we never filter those. + return; + } +#else // !wxUSE_UNICODE + const int ch = event.GetKeyCode(); + if ( ch > WXK_DELETE ) + { + // Not a character neither. + return; + } +#endif // wxUSE_UNICODE/!wxUSE_UNICODE + + if ( c < WXK_SPACE || c == WXK_DELETE ) + { + // Allow ASCII control characters and Delete. + return; + } + + // Check if this character is allowed in the current state. + wxString val; + int pos; + GetCurrentValueAndInsertionPoint(val, pos); + + if ( !IsCharOk(val, pos, ch) ) + { + if ( !wxValidator::IsSilent() ) + wxBell(); + + // Do not skip the event in this case, stop handling it here. + event.Skip(false); + } +} + +void wxNumValidatorBase::OnKillFocus(wxFocusEvent& event) +{ + wxTextEntry * const control = GetTextEntry(); + if ( !control ) + return; + + // When we change the control value below, its "modified" status is reset + // so we need to explicitly keep it marked as modified if it was so in the + // first place. + // + // Notice that only wxTextCtrl (and not wxTextEntry) has + // IsModified()/MarkDirty() methods hence the need for dynamic cast. + wxTextCtrl * const text = wxDynamicCast(m_validatorWindow, wxTextCtrl); + const bool wasModified = text ? text->IsModified() : false; + + control->ChangeValue(NormalizeString(control->GetValue())); + + if ( wasModified ) + text->MarkDirty(); + + event.Skip(); +} + +// ============================================================================ +// wxIntegerValidatorBase implementation +// ============================================================================ + +wxString wxIntegerValidatorBase::ToString(LongestValueType value) const +{ + return wxNumberFormatter::ToString(value, GetFormatFlags()); +} + +bool +wxIntegerValidatorBase::FromString(const wxString& s, LongestValueType *value) +{ + return wxNumberFormatter::FromString(s, value); +} + +bool +wxIntegerValidatorBase::IsCharOk(const wxString& val, int pos, wxChar ch) const +{ + // We may accept minus sign if we can represent negative numbers at all. + if ( ch == '-' ) + { + // Notice that entering '-' can make our value invalid, for example if + // we're limited to -5..15 range and the current value is 12, then the + // new value would be (invalid) -12. We consider it better to let the + // user do this because perhaps he is going to press Delete key next to + // make it -2 and forcing him to delete 1 first would be unnatural. + // + // TODO: It would be nice to indicate that the current control contents + // is invalid (if it's indeed going to be the case) once + // wxValidator supports doing this non-intrusively. + return m_min < 0 && IsMinusOk(val, pos); + } + + // We only accept digits here (remember that '-' is taken care of by the + // base class already). + if ( ch < '0' || ch > '9' ) + return false; + + // And the value after insertion needs to be in the defined range. + LongestValueType value; + if ( !FromString(GetValueAfterInsertingChar(val, pos, ch), &value) ) + return false; + + return IsInRange(value); +} + +// ============================================================================ +// wxFloatingPointValidatorBase implementation +// ============================================================================ + +wxString wxFloatingPointValidatorBase::ToString(LongestValueType value) const +{ + return wxNumberFormatter::ToString(value, m_precision, GetFormatFlags()); +} + +bool +wxFloatingPointValidatorBase::FromString(const wxString& s, + LongestValueType *value) +{ + return wxNumberFormatter::FromString(s, value); +} + +bool +wxFloatingPointValidatorBase::IsCharOk(const wxString& val, + int pos, + wxChar ch) const +{ + // We may accept minus sign if we can represent negative numbers at all. + if ( ch == '-' ) + return m_min < 0 && IsMinusOk(val, pos); + + const wxChar separator = wxNumberFormatter::GetDecimalSeparator(); + if ( ch == separator ) + { + if ( val.find(separator) != wxString::npos ) + { + // There is already a decimal separator, can't insert another one. + return false; + } + + // Prepending a separator before the minus sign isn't allowed. + if ( pos == 0 && !val.empty() && val[0] == '-' ) + return false; + + // Otherwise always accept it, adding a decimal separator doesn't + // change the number value and, in particular, can't make it invalid. + // OTOH the checks below might not pass because strings like "." or + // "-." are not valid numbers so parsing them would fail, hence we need + // to treat it specially here. + return true; + } + + // Must be a digit then. + if ( ch < '0' || ch > '9' ) + return false; + + // Check whether the value we'd obtain if we accepted this key is correct. + const wxString newval(GetValueAfterInsertingChar(val, pos, ch)); + + LongestValueType value; + if ( !FromString(newval, &value) ) + return false; + + // Also check that it doesn't have too many decimal digits. + const size_t posSep = newval.find(separator); + if ( posSep != wxString::npos && newval.length() - posSep - 1 > m_precision ) + return false; + + // Finally check whether it is in the range. + return IsInRange(value); +} + +#endif // wxUSE_VALIDATORS && wxUSE_TEXTCTRL diff --git a/src/widgets/valnum.h b/src/widgets/valnum.h new file mode 100644 index 000000000..2dae214d3 --- /dev/null +++ b/src/widgets/valnum.h @@ -0,0 +1,465 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: wx/valnum.h +// Purpose: Numeric validator classes. +// Author: Vadim Zeitlin based on the submission of Fulvio Senore +// Created: 2010-11-06 +// Copyright: (c) 2010 wxWidgets team +// (c) 2011 Vadim Zeitlin +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_VALNUM_H_ +#define _WX_VALNUM_H_ + +#include "wx/defs.h" + +#if wxUSE_VALIDATORS + +#include "wx/validate.h" + +#include + +#define wxTextEntry wxTextCtrl + +// Bit masks used for numeric validator styles. +enum wxNumValidatorStyle +{ + wxNUM_VAL_DEFAULT = 0x0, + wxNUM_VAL_THOUSANDS_SEPARATOR = 0x1, + wxNUM_VAL_ZERO_AS_BLANK = 0x2, + wxNUM_VAL_NO_TRAILING_ZEROES = 0x4 +}; + +// ---------------------------------------------------------------------------- +// Base class for all numeric validators. +// ---------------------------------------------------------------------------- + +class wxNumValidatorBase : public wxValidator +{ +public: + // Change the validator style. Usually it's specified during construction. + void SetStyle(int style) { m_style = style; } + + + // Override base class method to not do anything but always return success: + // we don't need this as we do our validation on the fly here. + virtual bool Validate(wxWindow * WXUNUSED(parent)) { return true; } + +protected: + wxNumValidatorBase(int style) + { + m_style = style; + } + + wxNumValidatorBase(const wxNumValidatorBase& other) : wxValidator() + { + m_style = other.m_style; + } + + bool HasFlag(wxNumValidatorStyle style) const + { + return (m_style & style) != 0; + } + + // Get the text entry of the associated control. Normally shouldn't ever + // return NULL (and will assert if it does return it) but the caller should + // still test the return value for safety. + wxTextEntry *GetTextEntry() const; + + // Convert wxNUM_VAL_THOUSANDS_SEPARATOR and wxNUM_VAL_NO_TRAILING_ZEROES + // bits of our style to the corresponding wxNumberFormatter::Style values. + int GetFormatFlags() const; + + // Return true if pressing a '-' key is acceptable for the current control + // contents and insertion point. This is meant to be called from the + // derived class IsCharOk() implementation. + bool IsMinusOk(const wxString& val, int pos) const; + + // Return the string which would result from inserting the given character + // at the specified position. + wxString GetValueAfterInsertingChar(wxString val, int pos, wxChar ch) const + { + val.insert(pos, ch); + return val; + } + +private: + // Check whether the specified character can be inserted in the control at + // the given position in the string representing the current controls + // contents. + // + // Notice that the base class checks for '-' itself so it's never passed to + // this function. + virtual bool IsCharOk(const wxString& val, int pos, wxChar ch) const = 0; + + // NormalizeString the contents of the string if it's a valid number, return + // empty string otherwise. + virtual wxString NormalizeString(const wxString& s) const = 0; + + + // Event handlers. + void OnChar(wxKeyEvent& event); + void OnKillFocus(wxFocusEvent& event); + + + // Determine the current insertion point and text in the associated control. + void GetCurrentValueAndInsertionPoint(wxString& val, int& pos) const; + + + // Combination of wxVAL_NUM_XXX values. + int m_style; + + + DECLARE_EVENT_TABLE(); + + DECLARE_NO_ASSIGN_CLASS(wxNumValidatorBase); +}; + +namespace wxPrivate +{ + +// This is a helper class used by wxIntegerValidator and wxFloatingPointValidator +// below that implements Transfer{To,From}Window() adapted to the type of the +// variable. +// +// The template argument B is the name of the base class which must derive from +// wxNumValidatorBase and define LongestValueType type and {To,As}String() +// methods i.e. basically be one of wx{Integer,Number}ValidatorBase classes. +// +// The template argument T is just the type handled by the validator that will +// inherit from this one. +template +class wxNumValidator : public B +{ +public: + typedef B BaseValidator; + typedef T ValueType; + + typedef typename BaseValidator::LongestValueType LongestValueType; + + // FIXME-VC6: This compiler fails to compile the assert below with a + // nonsensical error C2248: "'LongestValueType' : cannot access protected + // typedef declared in class 'wxIntegerValidatorBase'" so just disable the + // check for it. +#ifndef __VISUALC6__ + wxCOMPILE_TIME_ASSERT + ( + sizeof(ValueType) <= sizeof(LongestValueType), + UnsupportedType + ); +#endif // __VISUALC6__ + + void SetMin(ValueType min) + { + this->DoSetMin(min); + } + + void SetMax(ValueType max) + { + this->DoSetMax(max); + } + + void SetRange(ValueType min, ValueType max) + { + SetMin(min); + SetMax(max); + } + + virtual bool TransferToWindow() + { + if ( m_value ) + { + wxTextEntry * const control = BaseValidator::GetTextEntry(); + if ( !control ) + return false; + + control->SetValue(NormalizeValue(*m_value)); + } + + return true; + } + + virtual bool TransferFromWindow() + { + if ( m_value ) + { + wxTextEntry * const control = BaseValidator::GetTextEntry(); + if ( !control ) + return false; + + const wxString s(control->GetValue()); + LongestValueType value; + if ( s.empty() && BaseValidator::HasFlag(wxNUM_VAL_ZERO_AS_BLANK) ) + value = 0; + else if ( !BaseValidator::FromString(s, &value) ) + return false; + + if ( !this->IsInRange(value) ) + return false; + + *m_value = static_cast(value); + } + + return true; + } + +protected: + wxNumValidator(ValueType *value, int style) + : BaseValidator(style), + m_value(value) + { + } + + // Implement wxNumValidatorBase virtual method which is the same for + // both integer and floating point numbers. + virtual wxString NormalizeString(const wxString& s) const + { + LongestValueType value; + return BaseValidator::FromString(s, &value) ? NormalizeValue(value) + : wxString(); + } + +private: + // Just a helper which is a common part of TransferToWindow() and + // NormalizeString(): returns string representation of a number honouring + // wxNUM_VAL_ZERO_AS_BLANK flag. + wxString NormalizeValue(LongestValueType value) const + { + wxString s; + if ( value != 0 || !BaseValidator::HasFlag(wxNUM_VAL_ZERO_AS_BLANK) ) + s = this->ToString(value); + + return s; + } + + + ValueType * const m_value; + + DECLARE_NO_ASSIGN_CLASS(wxNumValidator); +}; + +} // namespace wxPrivate + +// ---------------------------------------------------------------------------- +// Validators for integer numbers. +// ---------------------------------------------------------------------------- + +// Base class for integer numbers validator. This class contains all non +// type-dependent code of wxIntegerValidator<> and always works with values of +// type LongestValueType. It is not meant to be used directly, please use +// wxIntegerValidator<> only instead. +class wxIntegerValidatorBase : public wxNumValidatorBase +{ +protected: + // Define the type we use here, it should be the maximal-sized integer type + // we support to make it possible to base wxIntegerValidator<> for any type + // on it. +#ifdef wxLongLong_t + typedef wxLongLong_t LongestValueType; +#else + typedef long LongestValueType; +#endif + + wxIntegerValidatorBase(int style) + : wxNumValidatorBase(style) + { + wxASSERT_MSG( !(style & wxNUM_VAL_NO_TRAILING_ZEROES), + wxT("This style doesn't make sense for integers.") ); + } + + wxIntegerValidatorBase(const wxIntegerValidatorBase& other) + : wxNumValidatorBase(other) + { + m_min = other.m_min; + m_max = other.m_max; + } + + // Provide methods for wxNumValidator use. + wxString ToString(LongestValueType value) const; + static bool FromString(const wxString& s, LongestValueType *value); + + void DoSetMin(LongestValueType min) { m_min = min; } + void DoSetMax(LongestValueType max) { m_max = max; } + + bool IsInRange(LongestValueType value) const + { + return m_min <= value && value <= m_max; + } + + // Implement wxNumValidatorBase pure virtual method. + virtual bool IsCharOk(const wxString& val, int pos, wxChar ch) const; + +private: + // Minimal and maximal values accepted (inclusive). + LongestValueType m_min, m_max; + + DECLARE_NO_ASSIGN_CLASS(wxIntegerValidatorBase); +}; + +// Validator for integer numbers. It can actually work with any integer type +// (short, int or long and long long if supported) and their unsigned versions +// as well. +template +class wxIntegerValidator + : public wxPrivate::wxNumValidator +{ +public: + typedef T ValueType; + + typedef + wxPrivate::wxNumValidator Base; + + // Ctor for an integer validator. + // + // Sets the range appropriately for the type, including setting 0 as the + // minimal value for the unsigned types. + wxIntegerValidator(ValueType *value = NULL, int style = wxNUM_VAL_DEFAULT) + : Base(value, style) + { + this->DoSetMin(std::numeric_limits::min()); + this->DoSetMax(std::numeric_limits::max()); + } + + virtual wxObject *Clone() const { return new wxIntegerValidator(*this); } + +private: + DECLARE_NO_ASSIGN_CLASS(wxIntegerValidator); +}; + +// Helper function for creating integer validators which allows to avoid +// explicitly specifying the type as it deduces it from its parameter. +template +inline wxIntegerValidator +wxMakeIntegerValidator(T *value, int style = wxNUM_VAL_DEFAULT) +{ + return wxIntegerValidator(value, style); +} + +// ---------------------------------------------------------------------------- +// Validators for floating point numbers. +// ---------------------------------------------------------------------------- + +// Similar to wxIntegerValidatorBase, this class is not meant to be used +// directly, only wxFloatingPointValidator<> should be used in the user code. +class wxFloatingPointValidatorBase : public wxNumValidatorBase +{ +public: + // Set precision i.e. the number of digits shown (and accepted on input) + // after the decimal point. By default this is set to the maximal precision + // supported by the type handled by the validator. + void SetPrecision(unsigned precision) { m_precision = precision; } + +protected: + // Notice that we can't use "long double" here because it's not supported + // by wxNumberFormatter yet, so restrict ourselves to just double (and + // float). + typedef double LongestValueType; + + wxFloatingPointValidatorBase(int style) + : wxNumValidatorBase(style) + { + } + + wxFloatingPointValidatorBase(const wxFloatingPointValidatorBase& other) + : wxNumValidatorBase(other) + { + m_precision = other.m_precision; + + m_min = other.m_min; + m_max = other.m_max; + } + + // Provide methods for wxNumValidator use. + wxString ToString(LongestValueType value) const; + static bool FromString(const wxString& s, LongestValueType *value); + + void DoSetMin(LongestValueType min) { m_min = min; } + void DoSetMax(LongestValueType max) { m_max = max; } + + bool IsInRange(LongestValueType value) const + { + return m_min <= value && value <= m_max; + } + + // Implement wxNumValidatorBase pure virtual method. + virtual bool IsCharOk(const wxString& val, int pos, wxChar ch) const; + +private: + // Maximum number of decimals digits after the decimal separator. + unsigned m_precision; + + // Minimal and maximal values accepted (inclusive). + LongestValueType m_min, m_max; + + DECLARE_NO_ASSIGN_CLASS(wxFloatingPointValidatorBase); +}; + +// Validator for floating point numbers. It can be used with float, double or +// long double values. +template +class wxFloatingPointValidator + : public wxPrivate::wxNumValidator +{ +public: + typedef T ValueType; + typedef wxPrivate::wxNumValidator Base; + + // Ctor using implicit (maximal) precision for this type. + wxFloatingPointValidator(ValueType *value = NULL, + int style = wxNUM_VAL_DEFAULT) + : Base(value, style) + { + DoSetMinMax(); + + this->SetPrecision(std::numeric_limits::digits10); + } + + // Ctor specifying an explicit precision. + wxFloatingPointValidator(int precision, + ValueType *value = NULL, + int style = wxNUM_VAL_DEFAULT) + : Base(value, style) + { + DoSetMinMax(); + + this->SetPrecision(precision); + } + + virtual wxObject *Clone() const + { + return new wxFloatingPointValidator(*this); + } + +private: + void DoSetMinMax() + { + // NB: Do not use min(), it's not the smallest representable value for + // the floating point types but rather the smallest representable + // positive value. + this->DoSetMin(-std::numeric_limits::max()); + this->DoSetMax( std::numeric_limits::max()); + } +}; + +// Helper similar to wxMakeIntValidator(). +// +// NB: Unfortunately we can't just have a wxMakeNumericValidator() which would +// return either wxIntegerValidator<> or wxFloatingPointValidator<> so we +// do need two different functions. +template +inline wxFloatingPointValidator +wxMakeFloatingPointValidator(T *value, int style = wxNUM_VAL_DEFAULT) +{ + return wxFloatingPointValidator(value, style); +} + +template +inline wxFloatingPointValidator +wxMakeFloatingPointValidator(int precision, T *value, int style = wxNUM_VAL_DEFAULT) +{ + return wxFloatingPointValidator(precision, value, style); +} + +#endif // wxUSE_VALIDATORS + +#endif // _WX_VALNUM_H_ diff --git a/win/Projects/Audacity/Audacity.vcproj b/win/Projects/Audacity/Audacity.vcproj index 71b30b35a..f5a5490f3 100644 --- a/win/Projects/Audacity/Audacity.vcproj +++ b/win/Projects/Audacity/Audacity.vcproj @@ -1911,6 +1911,14 @@ RelativePath="..\..\..\src\widgets\MultiDialog.h" > + + + + @@ -1935,6 +1943,14 @@ RelativePath="..\..\..\src\widgets\TimeTextCtrl.h" > + + + +