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.).
This commit is contained in:
lllucius@gmail.com 2013-10-23 20:33:17 +00:00
parent dd8e0a5edd
commit 08c94d5372
8 changed files with 1249 additions and 41 deletions

View File

@ -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 = "<group>"; tabWidth = 3; };
28F1D81B0A2D0019005506A7 /* ImageRoll.cpp */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = ImageRoll.cpp; sourceTree = "<group>"; tabWidth = 3; };
28F1D81C0A2D0019005506A7 /* ImageRoll.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = ImageRoll.h; sourceTree = "<group>"; tabWidth = 3; };
28F2CED0181867BB00573D61 /* numformatter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = numformatter.cpp; sourceTree = "<group>"; };
28F2CED1181867BB00573D61 /* numformatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = numformatter.h; sourceTree = "<group>"; };
28F2CED2181867BB00573D61 /* valnum.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = valnum.cpp; sourceTree = "<group>"; };
28F2CED3181867BB00573D61 /* valnum.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = valnum.h; sourceTree = "<group>"; };
28F3A3F60E28289500729866 /* algrd_internal.h */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.c.h; name = algrd_internal.h; path = portsmf/algrd_internal.h; sourceTree = "<group>"; 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 = "<group>"; tabWidth = 3; };
28F3A3F80E28289500729866 /* allegro.cpp */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; name = allegro.cpp; path = portsmf/allegro.cpp; sourceTree = "<group>"; 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;
};

View File

@ -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@

View File

@ -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<double> vld(2, &ctrl->val);
vld.SetRange(ctrl->low, ctrl->high);
item->SetValidator(vld);
}
else {
wxIntegerValidator<double> 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;

View File

@ -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 <locale.h> // 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);
}

View File

@ -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_

300
src/widgets/valnum.cpp Normal file
View File

@ -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

465
src/widgets/valnum.h Normal file
View File

@ -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 <vadim@wxwidgets.org>
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
#ifndef _WX_VALNUM_H_
#define _WX_VALNUM_H_
#include "wx/defs.h"
#if wxUSE_VALIDATORS
#include "wx/validate.h"
#include <limits>
#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 B, typename T>
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<ValueType>(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 <typename T>
class wxIntegerValidator
: public wxPrivate::wxNumValidator<wxIntegerValidatorBase, T>
{
public:
typedef T ValueType;
typedef
wxPrivate::wxNumValidator<wxIntegerValidatorBase, T> 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<ValueType>::min());
this->DoSetMax(std::numeric_limits<ValueType>::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 <typename T>
inline wxIntegerValidator<T>
wxMakeIntegerValidator(T *value, int style = wxNUM_VAL_DEFAULT)
{
return wxIntegerValidator<T>(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 <typename T>
class wxFloatingPointValidator
: public wxPrivate::wxNumValidator<wxFloatingPointValidatorBase, T>
{
public:
typedef T ValueType;
typedef wxPrivate::wxNumValidator<wxFloatingPointValidatorBase, T> 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<ValueType>::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<ValueType>::max());
this->DoSetMax( std::numeric_limits<ValueType>::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 <typename T>
inline wxFloatingPointValidator<T>
wxMakeFloatingPointValidator(T *value, int style = wxNUM_VAL_DEFAULT)
{
return wxFloatingPointValidator<T>(value, style);
}
template <typename T>
inline wxFloatingPointValidator<T>
wxMakeFloatingPointValidator(int precision, T *value, int style = wxNUM_VAL_DEFAULT)
{
return wxFloatingPointValidator<T>(precision, value, style);
}
#endif // wxUSE_VALIDATORS
#endif // _WX_VALNUM_H_

View File

@ -1911,6 +1911,14 @@
RelativePath="..\..\..\src\widgets\MultiDialog.h"
>
</File>
<File
RelativePath="..\..\..\src\widgets\numformatter.cpp"
>
</File>
<File
RelativePath="..\..\..\src\widgets\numformatter.h"
>
</File>
<File
RelativePath="..\..\..\src\widgets\ProgressDialog.cpp"
>
@ -1935,6 +1943,14 @@
RelativePath="..\..\..\src\widgets\TimeTextCtrl.h"
>
</File>
<File
RelativePath="..\..\..\src\widgets\valnum.cpp"
>
</File>
<File
RelativePath="..\..\..\src\widgets\valnum.h"
>
</File>
<File
RelativePath="..\..\..\src\widgets\Warning.cpp"
>