Merge branch 'spinctrl-digits'

Improve setting the number of digits in wxSpinCtrlDouble and make it
consistent on all platforms.

See https://github.com/wxWidgets/wxWidgets/pull/2348

Closes #17085.
This commit is contained in:
Vadim Zeitlin
2021-05-22 21:25:32 +01:00
8 changed files with 140 additions and 35 deletions

View File

@@ -382,13 +382,7 @@ public:
long style = wxSP_ARROW_KEYS, long style = wxSP_ARROW_KEYS,
double min = 0, double max = 100, double initial = 0, double min = 0, double max = 100, double initial = 0,
double inc = 1, double inc = 1,
const wxString& name = wxT("wxSpinCtrlDouble")) const wxString& name = wxT("wxSpinCtrlDouble"));
{
DetermineDigits(inc);
return wxSpinCtrlGenericBase::Create(parent, id, value, pos, size,
style, min, max, initial,
inc, name);
}
// accessors // accessors
double GetValue(wxSPINCTRL_GETVALUE_FIX) const { return DoGetValue(); } double GetValue(wxSPINCTRL_GETVALUE_FIX) const { return DoGetValue(); }
@@ -402,7 +396,7 @@ public:
{ wxSpinCtrlGenericBase::SetValue(value); } { wxSpinCtrlGenericBase::SetValue(value); }
void SetValue(double value) { DoSetValue(value, SendEvent_None); } void SetValue(double value) { DoSetValue(value, SendEvent_None); }
void SetRange(double minVal, double maxVal) { DoSetRange(minVal, maxVal); } void SetRange(double minVal, double maxVal) { DoSetRange(minVal, maxVal); }
void SetIncrement(double inc) { DoSetIncrement(inc); } void SetIncrement(double inc);
void SetDigits(unsigned digits); void SetDigits(unsigned digits);
// We don't implement bases support for floating point numbers, this is not // We don't implement bases support for floating point numbers, this is not
@@ -416,7 +410,6 @@ protected:
virtual bool DoTextToValue(const wxString& text, double *val) wxOVERRIDE; virtual bool DoTextToValue(const wxString& text, double *val) wxOVERRIDE;
virtual wxString DoValueToText(double val) wxOVERRIDE; virtual wxString DoValueToText(double val) wxOVERRIDE;
virtual void ResetTextValidator() wxOVERRIDE; virtual void ResetTextValidator() wxOVERRIDE;
void DetermineDigits(double inc);
unsigned m_digits; unsigned m_digits;
@@ -424,10 +417,16 @@ private:
// Common part of all ctors. // Common part of all ctors.
void Init() void Init()
{ {
m_digits = 0; DoSetDigits(0);
m_format = wxASCII_STR("%0.0f");
} }
// Just set the number of digits and the format unconditionally.
void DoSetDigits(unsigned digits);
// Call DoSetDigits() and update the appearance.
void DoSetDigitsAndUpdate(unsigned digits);
wxString m_format; wxString m_format;
wxDECLARE_DYNAMIC_CLASS(wxSpinCtrlDouble); wxDECLARE_DYNAMIC_CLASS(wxSpinCtrlDouble);

View File

@@ -243,7 +243,7 @@ public:
void SetValue(const wxString& value) wxOVERRIDE { wxSpinCtrlGTKBase::SetValue(value); } // visibility problem w/ gcc void SetValue(const wxString& value) wxOVERRIDE { wxSpinCtrlGTKBase::SetValue(value); } // visibility problem w/ gcc
void SetValue(double value) { DoSetValue(value); } void SetValue(double value) { DoSetValue(value); }
void SetRange(double minVal, double maxVal) { DoSetRange(minVal, maxVal); } void SetRange(double minVal, double maxVal) { DoSetRange(minVal, maxVal); }
void SetIncrement(double inc) { DoSetIncrement(inc); } void SetIncrement(double inc);
void SetDigits(unsigned digits); void SetDigits(unsigned digits);
virtual int GetBase() const wxOVERRIDE { return 10; } virtual int GetBase() const wxOVERRIDE { return 10; }

View File

@@ -28,6 +28,14 @@ extern wxSize GetBestSize(const wxControl* spin, int minVal, int maxVal, int bas
// Helper function to check if given combination of range and base is valid. // Helper function to check if given combination of range and base is valid.
extern bool IsBaseCompatibleWithRange(int minVal, int maxVal, int base); extern bool IsBaseCompatibleWithRange(int minVal, int maxVal, int base);
// Maximum number of digits returned by DetermineDigits().
const unsigned SPINCTRLDBL_MAX_DIGITS = 20;
// Return the number of digits required to show the numbers using the
// specified increment without loss of precision.
extern unsigned DetermineDigits(double inc);
} // namespace wxSpinCtrlImpl } // namespace wxSpinCtrlImpl
#endif // _WX_PRIVATE_SPINCTRL_H_ #endif // _WX_PRIVATE_SPINCTRL_H_

View File

@@ -354,8 +354,15 @@ public:
/** /**
Sets the increment value. Sets the increment value.
@note You may also need to change the precision of the value
using SetDigits(). Using this method changes the number of digits used by the control to
at least match the value of @a inc, e.g. using the increment of @c 0.01
sets the number of digits to 2 if it had been less than 2 before.
However it doesn't change the number of digits if it had been already
high enough.
In any case, you may call SetDigits() explicitly to override the
automatic determination of the number of digits.
*/ */
void SetIncrement(double inc); void SetIncrement(double inc);

View File

@@ -26,6 +26,8 @@
#include "wx/private/spinctrl.h" #include "wx/private/spinctrl.h"
#include <math.h>
wxDEFINE_EVENT(wxEVT_SPINCTRL, wxSpinEvent); wxDEFINE_EVENT(wxEVT_SPINCTRL, wxSpinEvent);
wxDEFINE_EVENT(wxEVT_SPINCTRLDOUBLE, wxSpinDoubleEvent); wxDEFINE_EVENT(wxEVT_SPINCTRLDOUBLE, wxSpinDoubleEvent);
@@ -140,4 +142,19 @@ bool wxSpinCtrlImpl::IsBaseCompatibleWithRange(int minVal, int maxVal, int base)
return base == 10 || (minVal >= 0 && maxVal >= 0); return base == 10 || (minVal >= 0 && maxVal >= 0);
} }
unsigned wxSpinCtrlImpl::DetermineDigits(double inc)
{
// TODO-C++11: Use std::modf() to get the fractional part.
inc = fabs(inc);
inc -= static_cast<int>(inc);
if ( inc > 0.0 )
{
return wxMin(SPINCTRLDBL_MAX_DIGITS, -static_cast<int>(floor(log10(inc))));
}
else
{
return 0;
}
}
#endif // wxUSE_SPINCTRL #endif // wxUSE_SPINCTRL

View File

@@ -723,10 +723,26 @@ void wxSpinCtrl::ResetTextValidator()
// wxSpinCtrlDouble // wxSpinCtrlDouble
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#define SPINCTRLDBL_MAX_DIGITS 20
wxIMPLEMENT_DYNAMIC_CLASS(wxSpinCtrlDouble, wxSpinCtrlGenericBase); wxIMPLEMENT_DYNAMIC_CLASS(wxSpinCtrlDouble, wxSpinCtrlGenericBase);
bool
wxSpinCtrlDouble::Create(wxWindow *parent,
wxWindowID id,
const wxString& value,
const wxPoint& pos,
const wxSize& size,
long style,
double min, double max, double initial,
double inc,
const wxString& name)
{
DoSetDigits(wxSpinCtrlImpl::DetermineDigits(inc));
return wxSpinCtrlGenericBase::Create(parent, id, value, pos, size,
style, min, max, initial,
inc, name);
}
void wxSpinCtrlDouble::DoSendEvent() void wxSpinCtrlDouble::DoSendEvent()
{ {
wxSpinDoubleEvent event( wxEVT_SPINCTRLDOUBLE, GetId()); wxSpinDoubleEvent event( wxEVT_SPINCTRLDOUBLE, GetId());
@@ -746,16 +762,36 @@ wxString wxSpinCtrlDouble::DoValueToText(double val)
return wxString::Format(m_format, val); return wxString::Format(m_format, val);
} }
void wxSpinCtrlDouble::SetIncrement(double inc)
{
if ( inc == m_increment )
return;
DoSetIncrement(inc);
const unsigned digits = wxSpinCtrlImpl::DetermineDigits(inc);
// We don't decrease the number of digits here, as this is unnecessary and
// could be undesirable, but we do increase it if the current number is not
// high enough to show the numbers without losing precision.
if ( digits > m_digits )
DoSetDigitsAndUpdate(digits);
}
void wxSpinCtrlDouble::SetDigits(unsigned digits) void wxSpinCtrlDouble::SetDigits(unsigned digits)
{ {
wxCHECK_RET( digits <= SPINCTRLDBL_MAX_DIGITS, "too many digits for wxSpinCtrlDouble" ); wxCHECK_RET( digits <= wxSpinCtrlImpl::SPINCTRLDBL_MAX_DIGITS,
"too many digits for wxSpinCtrlDouble" );
if ( digits == m_digits ) if ( digits == m_digits )
return; return;
m_digits = digits; DoSetDigitsAndUpdate(digits);
}
m_format.Printf(wxT("%%0.%ulf"), digits); void wxSpinCtrlDouble::DoSetDigitsAndUpdate(unsigned digits)
{
DoSetDigits(digits);
ResetTextValidator(); ResetTextValidator();
m_textCtrl->InvalidateBestSize(); m_textCtrl->InvalidateBestSize();
@@ -763,6 +799,13 @@ void wxSpinCtrlDouble::SetDigits(unsigned digits)
DoSetValue(m_value, SendEvent_None); DoSetValue(m_value, SendEvent_None);
} }
void wxSpinCtrlDouble::DoSetDigits(unsigned digits)
{
m_digits = digits;
m_format.Printf(wxT("%%0.%ulf"), digits);
}
void wxSpinCtrlDouble::ResetTextValidator() void wxSpinCtrlDouble::ResetTextValidator()
{ {
#if wxUSE_VALIDATORS #if wxUSE_VALIDATORS
@@ -772,16 +815,6 @@ void wxSpinCtrlDouble::ResetTextValidator()
#endif // wxUSE_VALIDATORS #endif // wxUSE_VALIDATORS
} }
void wxSpinCtrlDouble::DetermineDigits(double inc)
{
inc = fabs(inc);
if ( inc > 0.0 && inc < 1.0 )
{
m_digits = wxMin(SPINCTRLDBL_MAX_DIGITS, -static_cast<int>(floor(log10(inc))));
m_format.Printf("%%0.%ulf", m_digits);
}
}
#endif // wxUSE_SPINBTN #endif // wxUSE_SPINBTN
#endif // !wxPort-with-native-spinctrl #endif // !wxPort-with-native-spinctrl

View File

@@ -643,6 +643,18 @@ void wxSpinCtrlDouble::GtkSetEntryWidth()
gtk_entry_set_width_chars(GTK_ENTRY(m_widget), wxMax(lenMin, lenMax)); gtk_entry_set_width_chars(GTK_ENTRY(m_widget), wxMax(lenMin, lenMax));
} }
void wxSpinCtrlDouble::SetIncrement(double inc)
{
DoSetIncrement(inc);
const unsigned digits = wxSpinCtrlImpl::DetermineDigits(inc);
// Increase the number of digits, if necessary, to show all numbers that
// can be obtained by using the new increment without loss of precision.
if ( digits > GetDigits() )
SetDigits(digits);
}
unsigned wxSpinCtrlDouble::GetDigits() const unsigned wxSpinCtrlDouble::GetDigits() const
{ {
wxCHECK_MSG( m_widget, 0, "invalid spin button" ); wxCHECK_MSG( m_widget, 0, "invalid spin button" );

View File

@@ -217,18 +217,47 @@ TEST_CASE_METHOD(SpinCtrlDoubleTestCase,
TEST_CASE_METHOD(SpinCtrlDoubleTestCase, TEST_CASE_METHOD(SpinCtrlDoubleTestCase,
"SpinCtrlDouble::Digits", "[spinctrl][spinctrldouble]") "SpinCtrlDouble::Digits", "[spinctrl][spinctrldouble]")
{ {
m_spin->SetDigits(5); // Setting increment should adjust the number of digits shown to be big
// enough to show numbers with the corresponding granularity.
m_spin->SetIncrement(0.1);
m_spin->SetValue(1.23456789);
CHECK( m_spin->GetTextValue() == "1.2" );
m_spin->SetIncrement(0.01);
m_spin->SetValue(1.23456789);
CHECK( m_spin->GetTextValue() == "1.23" );
m_spin->SetDigits(5);
CHECK( m_spin->GetDigits() == 5 ); CHECK( m_spin->GetDigits() == 5 );
m_spin->SetValue(1.23456789);
CHECK( m_spin->GetTextValue() == "1.23457" );
// The number of digits shouldn't (implicitly) decrease however.
m_spin->SetIncrement(0.001);
m_spin->SetValue(1.23456789);
CHECK( m_spin->GetTextValue() == "1.23457" );
// Check that using increment greater than 1 also works.
m_spin->SetDigits(0);
m_spin->SetIncrement(2.5);
m_spin->SetValue(7.5);
CHECK( m_spin->GetTextValue() == "7.5" );
} }
static inline unsigned int GetInitialDigits(double inc) static inline unsigned int GetInitialDigits(double inc)
{ {
wxSpinCtrlDouble* sc = new wxSpinCtrlDouble(wxTheApp->GetTopWindow(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, wxScopedPtr<wxSpinCtrlDouble> sc(new wxSpinCtrlDouble
0, 50, 0, inc); (
unsigned int digits = sc->GetDigits(); wxTheApp->GetTopWindow(),
delete sc; wxID_ANY,
return digits; wxEmptyString,
wxDefaultPosition,
wxDefaultSize,
wxSP_ARROW_KEYS,
0, 50, 0,
inc
));
return sc->GetDigits();
} }
TEST_CASE("SpinCtrlDouble::InitialDigits", "[spinctrldouble][initialdigits]") TEST_CASE("SpinCtrlDouble::InitialDigits", "[spinctrldouble][initialdigits]")