Even if it's not supposed to do anything, avoid calling wxTextCtrl::ChangeValue() completely if the text contents doesn't actually change. This should be slightly more efficient and avoids any chance of bugs such as the one resulting in the insertion point being still moved to the beginning of the text even if it doesn't change in wxGTK currently (see https://github.com/wxWidgets/wxWidgets/pull/1270).
332 lines
10 KiB
C++
332 lines
10 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// 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 "wx/valnum.h"
|
|
#include "wx/numformatter.h"
|
|
|
|
// ============================================================================
|
|
// wxNumValidatorBase implementation
|
|
// ============================================================================
|
|
|
|
wxBEGIN_EVENT_TABLE(wxNumValidatorBase, wxValidator)
|
|
EVT_CHAR(wxNumValidatorBase::OnChar)
|
|
EVT_KILL_FOCUS(wxNumValidatorBase::OnKillFocus)
|
|
wxEND_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;
|
|
}
|
|
|
|
void wxNumValidatorBase::SetWindow(wxWindow *win)
|
|
{
|
|
wxValidator::SetWindow(win);
|
|
|
|
#if wxUSE_TEXTCTRL
|
|
if ( wxDynamicCast(m_validatorWindow, wxTextCtrl) )
|
|
return;
|
|
#endif // wxUSE_TEXTCTRL
|
|
|
|
#if wxUSE_COMBOBOX
|
|
if ( wxDynamicCast(m_validatorWindow, wxComboBox) )
|
|
return;
|
|
#endif // wxUSE_COMBOBOX
|
|
|
|
wxFAIL_MSG("Can only be used with wxTextCtrl or wxComboBox");
|
|
}
|
|
|
|
wxTextEntry *wxNumValidatorBase::GetTextEntry() const
|
|
{
|
|
#if wxUSE_TEXTCTRL
|
|
if ( wxTextCtrl *text = wxDynamicCast(m_validatorWindow, wxTextCtrl) )
|
|
return text;
|
|
#endif // wxUSE_TEXTCTRL
|
|
|
|
#if wxUSE_COMBOBOX
|
|
if ( wxComboBox *combo = wxDynamicCast(m_validatorWindow, wxComboBox) )
|
|
return combo;
|
|
#endif // wxUSE_COMBOBOX
|
|
|
|
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
|
|
const int ch = event.GetUnicodeKey();
|
|
if ( ch == WXK_NONE )
|
|
{
|
|
// 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 ( ch < WXK_SPACE || ch == 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)
|
|
{
|
|
event.Skip();
|
|
|
|
wxTextEntry * const control = GetTextEntry();
|
|
if ( !control )
|
|
return;
|
|
|
|
const wxString& valueNorm = NormalizeString(control->GetValue());
|
|
if ( control->GetValue() == valueNorm )
|
|
{
|
|
// Don't do anything at all if the value doesn't really change, even if
|
|
// the control optimizes away the calls to ChangeValue() which don't
|
|
// actually change it, it's easier to skip all the complications below
|
|
// if we don't need to do anything.
|
|
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(valueNorm);
|
|
|
|
if ( wasModified )
|
|
text->MarkDirty();
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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_factor,
|
|
m_precision,
|
|
GetFormatFlags());
|
|
}
|
|
|
|
bool
|
|
wxFloatingPointValidatorBase::FromString(const wxString& s,
|
|
LongestValueType *value) const
|
|
{
|
|
if ( !wxNumberFormatter::FromString(s, value) )
|
|
return false;
|
|
|
|
*value /= m_factor;
|
|
|
|
return true;
|
|
}
|
|
|
|
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
|