Rewrite NumValidator

Split int validating and double validating into two separate classes.

Make double parsing, validating and stringifying locale-aware. This is
far more complicated than it needs to be due to that Aegisub's locale
handling is a total mess.

Use DoubleValidator rather than wxFloatingPointValidator, because the
latter doesn't work with Aegisub's locale mess (on OS X it uses the C
locale for some things, and the locale reported by CoreFoundation for
others).

Closes #1568.
This commit is contained in:
Thomas Goyne 2013-10-14 12:11:50 -07:00
parent 3691849bac
commit ebd01c50c9
8 changed files with 172 additions and 262 deletions

View file

@ -328,8 +328,7 @@ namespace Automation4 {
return scd;
}
wxFloatingPointValidator<double> val(4, &value, wxNUM_VAL_NO_TRAILING_ZEROES);
val.SetRange(min, max);
::DoubleValidator val(&value, min, max);
cw = new wxTextCtrl(parent, -1, "", wxDefaultPosition, wxDefaultSize, 0, val);
cw->SetToolTip(to_wx(hint));

View file

@ -39,6 +39,7 @@
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "options.h"
#include "validators.h"
#include "video_provider_dummy.h"
namespace {
@ -68,9 +69,7 @@ wxSpinCtrl *spin_ctrl(wxWindow *parent, int min, int max, int *value) {
}
wxControl *spin_ctrl(wxWindow *parent, double min, double max, double *value) {
wxFloatingPointValidator<double> val(4, value);
val.SetRange(min, max);
return new wxTextCtrl(parent, -1, "", wxDefaultPosition, wxSize(50, -1), 0, val);
return new wxTextCtrl(parent, -1, "", wxDefaultPosition, wxSize(50, -1), 0, DoubleValidator(value, min, max));
}
wxComboBox *resolution_shortcuts(wxWindow *parent, int width, int height) {

View file

@ -58,7 +58,7 @@ DialogJumpTo::DialogJumpTo(agi::Context *c)
auto LabelFrame = new wxStaticText(this, -1, _("Frame: "));
auto LabelTime = new wxStaticText(this, -1, _("Time: "));
JumpFrame = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(-1,-1),wxTE_PROCESS_ENTER, NumValidator((int)jumpframe));
JumpFrame = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(-1,-1),wxTE_PROCESS_ENTER, IntValidator((int)jumpframe));
JumpFrame->SetMaxLength(std::to_string(c->videoController->GetLength() - 1).size());
JumpTime = new TimeEdit(this, -1, c, AssTime(c->videoController->TimeAtFrame(jumpframe)).GetAssFormated(), wxSize(-1,-1));

View file

@ -78,8 +78,8 @@ DialogProperties::DialogProperties(agi::Context *c)
// Resolution box
wxSizer *ResSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Resolution"));
ResX = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(50,20),0,NumValidator(to_wx(c->ass->GetScriptInfo("PlayResX"))));
ResY = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(50,20),0,NumValidator(to_wx(c->ass->GetScriptInfo("PlayResY"))));
ResX = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(50,20),0,IntValidator(c->ass->GetScriptInfo("PlayResX")));
ResY = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(50,20),0,IntValidator(c->ass->GetScriptInfo("PlayResY")));
wxStaticText *ResText = new wxStaticText(this,-1,"x");
wxButton *FromVideo = new wxButton(this,-1,_("From &video"));

View file

@ -135,8 +135,8 @@ static wxSpinCtrl *spin_ctrl(wxWindow *parent, float value, int max_value) {
return new wxSpinCtrl(parent, -1, wxString::Format("%g", value), wxDefaultPosition, wxSize(60, -1), wxSP_ARROW_KEYS, 0, max_value, value);
}
static wxTextCtrl *num_text_ctrl(wxWindow *parent, double value, bool allow_negative, wxSize size = wxSize(70, 20)) {
return new wxTextCtrl(parent, -1, "", wxDefaultPosition, size, 0, NumValidator(value, allow_negative));
static wxTextCtrl *num_text_ctrl(wxWindow *parent, double *value, bool allow_negative, wxSize size = wxSize(70, 20)) {
return new wxTextCtrl(parent, -1, "", wxDefaultPosition, size, 0, DoubleValidator(value, allow_negative));
}
DialogStyleEditor::DialogStyleEditor(wxWindow *parent, AssStyle *style, agi::Context *c, AssStyleStorage *store, std::string const& new_name, wxArrayString const& font_list)
@ -180,7 +180,7 @@ DialogStyleEditor::DialogStyleEditor(wxWindow *parent, AssStyle *style, agi::Con
// Create controls
StyleName = new wxTextCtrl(this, -1, to_wx(style->name));
FontName = new wxComboBox(this, -1, to_wx(style->font), wxDefaultPosition, wxSize(150, -1), 0, 0, wxCB_DROPDOWN);
FontSize = num_text_ctrl(this, style->fontsize, false, wxSize(50, -1));
FontSize = num_text_ctrl(this, &work->fontsize, false, wxSize(50, -1));
BoxBold = new wxCheckBox(this, -1, _("&Bold"));
BoxItalic = new wxCheckBox(this, -1, _("&Italic"));
BoxUnderline = new wxCheckBox(this, -1, _("&Underline"));
@ -194,13 +194,13 @@ DialogStyleEditor::DialogStyleEditor(wxWindow *parent, AssStyle *style, agi::Con
for (int i = 0; i < 3; i++)
margin[i] = spin_ctrl(this, style->Margin[i], 9999);
Alignment = new wxRadioBox(this, -1, _("Alignment"), wxDefaultPosition, wxDefaultSize, 9, alignValues, 3, wxRA_SPECIFY_COLS);
Outline = num_text_ctrl(this, style->outline_w, false, wxSize(50, -1));
Shadow = num_text_ctrl(this, style->shadow_w, true, wxSize(50, -1));
Outline = num_text_ctrl(this, &work->outline_w, false, wxSize(50, -1));
Shadow = num_text_ctrl(this, &work->shadow_w, true, wxSize(50, -1));
OutlineType = new wxCheckBox(this, -1, _("&Opaque box"));
ScaleX = num_text_ctrl(this, style->scalex, false);
ScaleY = num_text_ctrl(this, style->scaley, false);
Angle = num_text_ctrl(this, style->angle, true);
Spacing = num_text_ctrl(this, style->spacing, true);
ScaleX = num_text_ctrl(this, &work->scalex, false);
ScaleY = num_text_ctrl(this, &work->scaley, false);
Angle = num_text_ctrl(this, &work->angle, true);
Spacing = num_text_ctrl(this, &work->spacing, true);
Encoding = new wxComboBox(this, -1, "", wxDefaultPosition, wxDefaultSize, encodingStrings, wxCB_READONLY);
// Set control tooltips
@ -456,24 +456,16 @@ void DialogStyleEditor::Apply(bool apply, bool close) {
}
void DialogStyleEditor::UpdateWorkStyle() {
work->font = from_wx(FontName->GetValue());
FontSize->GetValue().ToDouble(&(work->fontsize));
TransferDataFromWindow();
ScaleX->GetValue().ToDouble(&(work->scalex));
ScaleY->GetValue().ToDouble(&(work->scaley));
work->font = from_wx(FontName->GetValue());
long templ = 0;
Encoding->GetValue().BeforeFirst('-').ToLong(&templ);
work->encoding = templ;
Angle->GetValue().ToDouble(&(work->angle));
Spacing->GetValue().ToDouble(&(work->spacing));
work->borderstyle = OutlineType->IsChecked() ? 3 : 1;
Shadow->GetValue().ToDouble(&(work->shadow_w));
Outline->GetValue().ToDouble(&(work->outline_w));
work->alignment = ControlToAlign(Alignment->GetSelection());
for (size_t i = 0; i < 3; ++i)

View file

@ -228,7 +228,7 @@ SubsEditBox::~SubsEditBox() {
}
wxTextCtrl *SubsEditBox::MakeMarginCtrl(wxString const& tooltip, int margin, wxString const& commit_msg) {
wxTextCtrl *ctrl = new wxTextCtrl(this, -1, "", wxDefaultPosition, wxSize(40,-1), wxTE_CENTRE | wxTE_PROCESS_ENTER, NumValidator());
wxTextCtrl *ctrl = new wxTextCtrl(this, -1, "", wxDefaultPosition, wxSize(40,-1), wxTE_CENTRE | wxTE_PROCESS_ENTER, IntValidator());
ctrl->SetMaxLength(4);
ctrl->SetToolTip(tooltip);
middle_left_sizer->Add(ctrl, wxSizerFlags().Center());

View file

@ -1,37 +1,19 @@
// Copyright (c) 2005, Rodrigo Braz Monteiro
// All rights reserved.
// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
/// @file validators.cpp
/// @brief Various validators for wx
/// @ingroup custom_control utility
///
#include "config.h"
#include "validators.h"
@ -40,171 +22,142 @@
#include "utils.h"
#include <libaegisub/exception.h>
#include <libaegisub/util.h>
#include <wx/combobox.h>
#include <wx/textctrl.h>
NumValidator::NumValidator(wxString val, bool isfloat, bool issigned)
: fValue(0)
, iValue(0)
, isFloat(isfloat)
, isSigned(issigned)
namespace {
std::string new_value(wxTextCtrl *ctrl, int chr) {
long from, to;
ctrl->GetSelection(&from, &to);
auto value = ctrl->GetValue();
return from_wx(value.substr(0, from) + (wxChar)chr + value.substr(to));
}
wxChar decimal_separator() {
auto sep = wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER);
return sep.empty() ? '.' : sep[0];
}
}
IntValidator::IntValidator(std::string const& initial)
: allow_negative(false)
{
if (isFloat) {
val.ToDouble(&fValue);
}
else {
long tLong = 0;
val.ToLong(&tLong);
iValue = tLong;
}
agi::util::try_parse(initial, &value);
Bind(wxEVT_CHAR, &IntValidator::OnChar, this);
}
NumValidator::NumValidator(int val, bool issigned)
: fValue(0)
, iValue(val)
, isFloat(false)
, isSigned(issigned)
IntValidator::IntValidator(int val, bool allow_negative)
: value(val)
, allow_negative(allow_negative)
{
Bind(wxEVT_CHAR, &IntValidator::OnChar, this);
}
NumValidator::NumValidator(int64_t val, bool issigned)
: fValue(0)
, iValue((int)val)
, isFloat(false)
, isSigned(issigned)
IntValidator::IntValidator(IntValidator const& rgt)
: allow_negative(rgt.allow_negative)
{
SetWindow(rgt.GetWindow());
Bind(wxEVT_CHAR, &IntValidator::OnChar, this);
}
NumValidator::NumValidator(double val, bool issigned)
: fValue(val)
, iValue(0)
, isFloat(true)
, isSigned(issigned)
{
}
NumValidator::NumValidator(const NumValidator &from)
: wxValidator()
, fValue(from.fValue)
, iValue(from.iValue)
, isFloat(from.isFloat)
, isSigned(from.isSigned)
{
SetWindow(from.GetWindow());
}
BEGIN_EVENT_TABLE(NumValidator, wxValidator)
EVT_CHAR(NumValidator::OnChar)
END_EVENT_TABLE()
wxObject* NumValidator::Clone() const {
return new NumValidator(*this);
}
bool NumValidator::Validate(wxWindow*) {
wxTextCtrl *ctrl = (wxTextCtrl*) GetWindow();
wxString value = ctrl->GetValue();
if (!ctrl->IsEnabled()) return true;
if (value.Length() < 1) return false;
bool gotDecimal = false;
for (size_t i = 0; i < value.Length(); i++) {
if (!CheckCharacter(value[i], !i, true, gotDecimal))
return false;
}
bool IntValidator::TransferToWindow() {
static_cast<wxTextCtrl *>(GetWindow())->SetValue(std::to_wstring(value));
return true;
}
bool NumValidator::CheckCharacter(int chr, bool isFirst, bool canSign, bool &gotDecimal) {
// Check sign
if (chr == '-' || chr == '+') {
return isFirst && canSign && isSigned;
}
// Don't allow anything before a sign
if (isFirst && !canSign) return false;
// Check decimal point
if (chr == '.' || chr == ',') {
if (!isFloat || gotDecimal)
return false;
else {
gotDecimal = true;
return true;
}
}
// Check digit
return chr >= '0' && chr <= '9';
}
void NumValidator::OnChar(wxKeyEvent& event) {
wxTextCtrl *ctrl = (wxTextCtrl*) GetWindow();
wxString value = ctrl->GetValue();
void IntValidator::OnChar(wxKeyEvent& event) {
int chr = event.GetKeyCode();
// Special keys
if (chr < WXK_SPACE || chr == WXK_DELETE || chr > WXK_START) {
event.Skip();
return;
}
// Get selection
long from,to;
ctrl->GetSelection(&from,&to);
auto ctrl = static_cast<wxTextCtrl *>(GetWindow());
auto str = new_value(ctrl, chr);
int parsed;
if (allow_negative && str == '-')
event.Skip();
else if (agi::util::try_parse(str, &parsed) && (allow_negative || parsed >= 0))
event.Skip();
else if (!wxValidator::IsSilent())
wxBell();
}
// Count decimal points and signs outside selection
int decimals = 0;
int signs = 0;
wxChar curchr;
for (size_t i=0;i<value.Length();i++) {
if (i >= (unsigned)from && i < (unsigned)to) continue;
curchr = value[i];
if (curchr == '.' || curchr == ',') decimals++;
if (curchr == '+' || curchr == '-') signs++;
}
bool gotDecimal = decimals > 0;
DoubleValidator::DoubleValidator(double *val, bool allow_negative)
: value(val)
, min(allow_negative ? std::numeric_limits<double>::lowest() : 0)
, max(std::numeric_limits<double>::max())
, decimal_sep(decimal_separator())
{
Bind(wxEVT_CHAR, &DoubleValidator::OnChar, this);
}
// Check character
if (!CheckCharacter(chr,!from,!signs,gotDecimal)) {
if (!wxValidator::IsSilent()) wxBell();
DoubleValidator::DoubleValidator(double *val, double min, double max)
: value(val)
, min(min)
, max(max)
, decimal_sep(decimal_separator())
{
Bind(wxEVT_CHAR, &DoubleValidator::OnChar, this);
}
DoubleValidator::DoubleValidator(DoubleValidator const& rgt)
: value(rgt.value)
, min(rgt.min)
, max(rgt.max)
, decimal_sep(rgt.decimal_sep)
{
Bind(wxEVT_CHAR, &DoubleValidator::OnChar, this);
SetWindow(rgt.GetWindow());
}
void DoubleValidator::OnChar(wxKeyEvent& event) {
int chr = event.GetKeyCode();
if (chr < WXK_SPACE || chr == WXK_DELETE || chr > WXK_START) {
event.Skip();
return;
}
// OK
event.Skip();
return;
if (chr == decimal_sep)
chr = '.';
auto str = new_value(static_cast<wxTextCtrl *>(GetWindow()), chr);
if (decimal_sep != '.')
replace(begin(str), end(str), (char)decimal_sep, '.');
double parsed;
bool can_parse = agi::util::try_parse(str, &parsed);
if ((min < 0 && str == '-') || str == '.')
event.Skip();
else if (can_parse && parsed >= min && parsed <= max)
event.Skip();
else if (can_parse && min < 0 && chr == '-') // allow negating an existing value even if it results in being out of range
event.Skip();
else if (!wxValidator::IsSilent())
wxBell();
}
bool NumValidator::TransferToWindow() {
wxTextCtrl *ctrl = (wxTextCtrl*) GetWindow();
if (isFloat)
ctrl->SetValue(wxString::Format("%g",fValue));
else
ctrl->SetValue(std::to_wstring(iValue));
bool DoubleValidator::TransferToWindow() {
auto str = wxString::Format("%g", *value);
if (decimal_sep != '.')
std::replace(str.begin(), str.end(), wxS('.'), decimal_sep);
if (str.find(decimal_sep) != str.npos) {
while (str.Last() == '0')
str.RemoveLast();
}
static_cast<wxTextCtrl *>(GetWindow())->SetValue(str);
return true;
}
bool NumValidator::TransferFromWindow() {
wxTextCtrl *ctrl = (wxTextCtrl*) GetWindow();
wxString value = ctrl->GetValue();
bool DoubleValidator::TransferFromWindow() {
auto ctrl = static_cast<wxTextCtrl *>(GetWindow());
if (!Validate(ctrl)) return false;
// Transfer
if (isFloat) {
value.ToDouble(&fValue);
}
else {
long tLong;
value.ToLong(&tLong);
iValue = tLong;
}
auto str = from_wx(ctrl->GetValue());
if (decimal_sep != '.')
replace(begin(str), end(str), (char)decimal_sep, '.');
agi::util::try_parse(str, value);
return true;
}

View file

@ -1,37 +1,19 @@
// Copyright (c) 2005, Rodrigo Braz Monteiro
// All rights reserved.
// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
/// @file validators.h
/// @see validators.cpp
/// @ingroup custom_control utility
///
#include <libaegisub/exception.h>
#include <string>
@ -39,59 +21,44 @@
#include <wx/radiobox.h>
#include <wx/validate.h>
/// A wx validator that only allows valid numbers
class NumValidator : public wxValidator {
double fValue; ///< Value if isFloat is true
int iValue; ///< Value if isFloat is false
bool isFloat; ///< Should decimals be allowed?
bool isSigned; ///< Can the number be negative?
class IntValidator : public wxValidator {
int value;
bool allow_negative;
/// Polymorphic copy
wxObject* Clone() const;
/// Check if the value in the passed window is valid
bool Validate(wxWindow* parent);
/// Copy the currently stored value to the associated window
bool TransferToWindow();
/// Read the value in the associated window and validate it
bool TransferFromWindow();
/// Check a single character
/// @param chr Character to check
/// @param isFirst Is this the first character in the string?
/// @param canSign Can this character be a sign?
/// @param gotDecimal[in,out] Has a decimal been found? Set to true if a chr is a decimal
/// @return Is this character valid?
bool CheckCharacter(int chr,bool isFirst,bool canSign,bool &gotDecimal);
/// wx character event handler
bool CheckCharacter(int chr, bool is_first) const;
void OnChar(wxKeyEvent& event);
bool Validate(wxWindow *) override { return true; }
wxObject* Clone() const override { return new IntValidator(*this); }
bool TransferToWindow() override;
bool TransferFromWindow() override { return true; }
IntValidator(IntValidator const& rgt);
public:
/// Constructor
/// @param val Initial value to set the associated control to
/// @param isfloat Allow floats, or just ints?
/// @param issigned Allow negative numbers?
explicit NumValidator(wxString val = wxString(), bool isfloat=false, bool issigned=false);
explicit IntValidator(std::string const& value = "");
explicit IntValidator(int val, bool allow_negative=false);
};
/// Constructor
/// @param val Initial value to set the associated control to
/// @param issigned Allow negative numbers?
explicit NumValidator(int val, bool issigned=false);
class DoubleValidator : public wxValidator {
double *value;
double min;
double max;
wxChar decimal_sep;
/// Constructor
/// @param val Initial value to set the associated control to
/// @param issigned Allow negative numbers?
explicit NumValidator(int64_t val, bool issigned=false);
bool Validate(wxWindow* parent) override { return true; }
bool CheckCharacter(int chr, bool is_first, bool *got_decimal) const;
void OnChar(wxKeyEvent& event);
/// Constructor
/// @param val Initial value to set the associated control to
/// @param issigned Allow negative numbers?
explicit NumValidator(double val, bool issigned=false);
DoubleValidator(DoubleValidator const& rgt);
/// Copy constructor
NumValidator(const NumValidator& from);
wxObject* Clone() const override { return new DoubleValidator(*this); }
bool TransferToWindow() override;
bool TransferFromWindow() override;
DECLARE_EVENT_TABLE()
public:
explicit DoubleValidator(double *val, bool allow_negative=false);
explicit DoubleValidator(double *val, double min, double max);
};
template<typename T>