From: Vadim Zeitlin Date: Thu, 30 Aug 2012 20:24:38 +0000 (+0000) Subject: Add wxSpinCtrl::SetBase() to allow entering hexadecimal numbers. X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/9e565667d0f29605c948ad6e742310798e3b2d0d Add wxSpinCtrl::SetBase() to allow entering hexadecimal numbers. Add a generic SetBase() API even though right now only bases 10 and 16 are supported as we might support other ones (e.g. 8?) in the future. Implement it for MSW, GTK and generic versions. Add controls allowing to test this feature to the widgets sample. Add "base" property support to the XRC handler for wxSpinCtrl, document it and test it in the xrc sample. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@72414 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/docs/changes.txt b/docs/changes.txt index 44482c80e6..bf909d183e 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -537,6 +537,7 @@ All: All (GUI): - Add new wxSimplebook class. +- Support hexadecimal numbers in wxSpinCtrl. - Respect window max size in wxBoxSizer (Nathan Ridge). - Add support for searching in wxWebView for MSW and GTK (Allonii). - Add possibility to hide and show again wxRibbonBar pages (wxBen). diff --git a/docs/doxygen/overviews/xrc_format.h b/docs/doxygen/overviews/xrc_format.h index dd14c10f1e..eb9f73e49b 100644 --- a/docs/doxygen/overviews/xrc_format.h +++ b/docs/doxygen/overviews/xrc_format.h @@ -1495,7 +1495,12 @@ HTML markup. Note that the markup has to be escaped: @subsubsection xrc_wxspinctrl wxSpinCtrl -wxSpinCtrl supports the properties as @ref xrc_wxspinbutton. +wxSpinCtrl supports the same properties as @ref xrc_wxspinbutton and, since +wxWidgets 2.9.5, another one: +@beginTable +@row3col{base, integer, + Numeric base, currently can be only 10 or 16 (default: 10).} +@endTable @subsubsection xrc_wxsplitterwindow wxSplitterWindow diff --git a/include/wx/generic/spinctlg.h b/include/wx/generic/spinctlg.h index 4492bedef0..61c38b6a56 100644 --- a/include/wx/generic/spinctlg.h +++ b/include/wx/generic/spinctlg.h @@ -260,7 +260,7 @@ protected: class WXDLLIMPEXP_CORE wxSpinCtrl : public wxSpinCtrlGenericBase { public: - wxSpinCtrl() {} + wxSpinCtrl() { Init(); } wxSpinCtrl(wxWindow *parent, wxWindowID id = wxID_ANY, const wxString& value = wxEmptyString, @@ -270,6 +270,8 @@ public: int min = 0, int max = 100, int initial = 0, const wxString& name = wxT("wxSpinCtrl")) { + Init(); + Create(parent, id, value, pos, size, style, min, max, initial, name); } @@ -299,11 +301,24 @@ public: void SetRange( int minVal, int maxVal ) { DoSetRange(minVal, maxVal); } void SetIncrement(int inc) { DoSetIncrement(inc); } + virtual int GetBase() const { return m_base; } + virtual bool SetBase(int base); + protected: virtual void DoSendEvent(); virtual bool DoTextToValue(const wxString& text, double *val); virtual wxString DoValueToText(double val); + +private: + // Common part of all ctors. + void Init() + { + m_base = 10; + } + + int m_base; + DECLARE_DYNAMIC_CLASS(wxSpinCtrl) }; @@ -363,6 +378,11 @@ public: void SetIncrement(double inc) { DoSetIncrement(inc); } void SetDigits(unsigned digits); + // We don't implement bases support for floating point numbers, this is not + // very useful in practice. + virtual int GetBase() const { return 10; } + virtual bool SetBase(int WXUNUSED(base)) { return 0; } + protected: virtual void DoSendEvent(); diff --git a/include/wx/gtk/spinctrl.h b/include/wx/gtk/spinctrl.h index f57976df81..5e50fd8a80 100644 --- a/include/wx/gtk/spinctrl.h +++ b/include/wx/gtk/spinctrl.h @@ -89,7 +89,7 @@ protected: class WXDLLIMPEXP_CORE wxSpinCtrl : public wxSpinCtrlGTKBase { public: - wxSpinCtrl() {} + wxSpinCtrl() { Init(); } wxSpinCtrl(wxWindow *parent, wxWindowID id = wxID_ANY, const wxString& value = wxEmptyString, @@ -99,6 +99,8 @@ public: int min = 0, int max = 100, int initial = 0, const wxString& name = wxS("wxSpinCtrl")) { + Init(); + Create(parent, id, value, pos, size, style, min, max, initial, name); } @@ -127,6 +129,18 @@ public: void SetRange( int minVal, int maxVal ) { DoSetRange(minVal, maxVal); } void SetIncrement(int inc) { DoSetIncrement(inc); } + virtual int GetBase() const { return m_base; } + virtual bool SetBase(int base); + +private: + // Common part of all ctors. + void Init() + { + m_base = 10; + } + + int m_base; + DECLARE_DYNAMIC_CLASS(wxSpinCtrl) }; @@ -180,6 +194,9 @@ public: void SetIncrement(double inc) { DoSetIncrement(inc); } void SetDigits(unsigned digits); + virtual int GetBase() const { return 10; } + virtual bool SetBase(int WXUNUSED(base)) { return false; } + DECLARE_DYNAMIC_CLASS(wxSpinCtrlDouble) }; diff --git a/include/wx/msw/spinctrl.h b/include/wx/msw/spinctrl.h index fd8c7ab1d8..dd74f55649 100644 --- a/include/wx/msw/spinctrl.h +++ b/include/wx/msw/spinctrl.h @@ -62,6 +62,11 @@ public: // another wxTextCtrl-like method void SetSelection(long from, long to); + // wxSpinCtrlBase methods + virtual int GetBase() const; + virtual bool SetBase(int base); + + // implementation only from now on // ------------------------------- @@ -148,6 +153,12 @@ private: // Common part of all ctors. void Init(); + // Adjust the text control style depending on whether we need to enter only + // digits or may need to enter something else (e.g. "-" sign, "x" + // hexadecimal prefix, ...) in it. + void UpdateBuddyStyle(); + + DECLARE_DYNAMIC_CLASS(wxSpinCtrl) DECLARE_EVENT_TABLE() wxDECLARE_NO_COPY_CLASS(wxSpinCtrl); diff --git a/include/wx/spinctrl.h b/include/wx/spinctrl.h index 8a2ef4d90b..cf93212a66 100644 --- a/include/wx/spinctrl.h +++ b/include/wx/spinctrl.h @@ -51,6 +51,10 @@ public: virtual void SetSnapToTicks(bool snap_to_ticks) = 0; // void SetDigits(unsigned digits) - wxSpinCtrlDouble only + // The base for numbers display, e.g. 10 or 16. + virtual int GetBase() const = 0; + virtual bool SetBase(int base) = 0; + // Select text in the textctrl virtual void SetSelection(long from, long to) = 0; @@ -134,6 +138,15 @@ typedef void (wxEvtHandler::*wxSpinDoubleEventFunction)(wxSpinDoubleEvent&); #include "wx/generic/spinctlg.h" #endif +namespace wxPrivate +{ + +// This is an internal helper function currently used by all ports: return the +// string containing hexadecimal representation of the given number. +extern wxString wxSpinCtrlFormatAsHex(long val, long maxVal); + +} // namespace wxPrivate + #endif // wxUSE_SPINCTRL #endif // _WX_SPINCTRL_H_ diff --git a/interface/wx/spinctrl.h b/interface/wx/spinctrl.h index 3e2c6926c4..44ed624a7c 100644 --- a/interface/wx/spinctrl.h +++ b/interface/wx/spinctrl.h @@ -114,6 +114,15 @@ public: long style = wxSP_ARROW_KEYS, int min = 0, int max = 100, int initial = 0, const wxString& name = "wxSpinCtrl"); + /** + Returns the numerical base being currently used, 10 by default. + + @see SetBase() + + @since 2.9.5 + */ + int GetBase() const; + /** Gets maximal allowable value. */ @@ -129,6 +138,27 @@ public: */ int GetValue() const; + /** + Sets the base to use for the numbers in this control. + + Currently the only supported values are 10 (which is the default) and + 16. + + Changing the base allows the user to enter the numbers in the specified + base, e.g. with "0x" prefix for hexadecimal numbers, and also displays + the numbers in the specified base when they are changed using the spin + control arrows. + + @param base + Numeric base, currently only 10 and 16 are supported. + @return + @true if the base was successfully changed or @false if it failed, + usually meaning that either the base is not 10 or 16. + + @since 2.9.5 + */ + bool SetBase(int base); + /** Sets range of allowable values. diff --git a/samples/widgets/spinbtn.cpp b/samples/widgets/spinbtn.cpp index 821c2a7e85..8639747e11 100644 --- a/samples/widgets/spinbtn.cpp +++ b/samples/widgets/spinbtn.cpp @@ -58,10 +58,12 @@ enum SpinBtnPage_Clear, SpinBtnPage_SetValue, SpinBtnPage_SetMinAndMax, + SpinBtnPage_SetBase, SpinBtnPage_CurValueText, SpinBtnPage_ValueText, SpinBtnPage_MinText, SpinBtnPage_MaxText, + SpinBtnPage_BaseText, SpinBtnPage_SpinBtn, SpinBtnPage_SpinCtrl, SpinBtnPage_SpinCtrlDouble @@ -105,6 +107,7 @@ protected: void OnButtonClear(wxCommandEvent& event); void OnButtonSetValue(wxCommandEvent& event); void OnButtonSetMinAndMax(wxCommandEvent& event); + void OnButtonSetBase(wxCommandEvent& event); void OnCheckOrRadioBox(wxCommandEvent& event); @@ -118,6 +121,7 @@ protected: void OnUpdateUIValueButton(wxUpdateUIEvent& event); void OnUpdateUIMinMaxButton(wxUpdateUIEvent& event); + void OnUpdateUIBaseButton(wxUpdateUIEvent& event); void OnUpdateUIResetButton(wxUpdateUIEvent& event); @@ -136,6 +140,9 @@ protected: // the spinbtn range int m_min, m_max; + // and numeric base + int m_base; + // the controls // ------------ @@ -144,7 +151,7 @@ protected: *m_chkArrowKeys, *m_chkWrap, *m_chkProcessEnter; - wxRadioBox *m_radioAlign; + wxRadioBox *m_radioAlign; // the spinbtn and the spinctrl and the sizer containing them wxSpinButton *m_spinbtn; @@ -156,7 +163,8 @@ protected: // the text entries for set value/range wxTextCtrl *m_textValue, *m_textMin, - *m_textMax; + *m_textMax, + *m_textBase; private: DECLARE_EVENT_TABLE() @@ -171,9 +179,11 @@ BEGIN_EVENT_TABLE(SpinBtnWidgetsPage, WidgetsPage) EVT_BUTTON(SpinBtnPage_Reset, SpinBtnWidgetsPage::OnButtonReset) EVT_BUTTON(SpinBtnPage_SetValue, SpinBtnWidgetsPage::OnButtonSetValue) EVT_BUTTON(SpinBtnPage_SetMinAndMax, SpinBtnWidgetsPage::OnButtonSetMinAndMax) + EVT_BUTTON(SpinBtnPage_SetBase, SpinBtnWidgetsPage::OnButtonSetBase) EVT_UPDATE_UI(SpinBtnPage_SetValue, SpinBtnWidgetsPage::OnUpdateUIValueButton) EVT_UPDATE_UI(SpinBtnPage_SetMinAndMax, SpinBtnWidgetsPage::OnUpdateUIMinMaxButton) + EVT_UPDATE_UI(SpinBtnPage_SetBase, SpinBtnWidgetsPage::OnUpdateUIBaseButton) EVT_UPDATE_UI(SpinBtnPage_Reset, SpinBtnWidgetsPage::OnUpdateUIResetButton) @@ -218,13 +228,16 @@ SpinBtnWidgetsPage::SpinBtnWidgetsPage(WidgetsBookCtrl *book, m_spinbtn = NULL; m_spinctrl = NULL; m_spinctrldbl = NULL; - m_textValue = NULL; - m_textMin = NULL; - m_textMax = NULL; + m_textValue = + m_textMin = + m_textMax = + m_textBase = NULL; m_min = 0; m_max = 10; + m_base = 10; + m_sizerSpin = NULL; } @@ -295,6 +308,13 @@ void SpinBtnWidgetsPage::CreateContent() sizerMiddle->Add(sizerRow, 0, wxALL | wxGROW, 5); + sizerRow = CreateSizerWithTextAndButton(SpinBtnPage_SetBase, + "Set &base", + SpinBtnPage_BaseText, + &m_textBase); + m_textBase->SetValue("10"); + sizerMiddle->Add(sizerRow, 0, wxALL | wxGROW, 5); + // right pane wxSizer *sizerRight = new wxBoxSizer(wxVERTICAL); sizerRight->SetMinSize(150, 0); @@ -445,6 +465,22 @@ void SpinBtnWidgetsPage::OnButtonSetMinAndMax(wxCommandEvent& WXUNUSED(event)) m_spinctrldbl->SetRange(minNew, maxNew); } +void SpinBtnWidgetsPage::OnButtonSetBase(wxCommandEvent& WXUNUSED(event)) +{ + unsigned long base; + if ( !m_textBase->GetValue().ToULong(&base) || !base ) + { + wxLogWarning("Invalid base value."); + return; + } + + m_base = base; + if ( !m_spinctrl->SetBase(m_base) ) + { + wxLogWarning("Setting base %d failed.", m_base); + } +} + void SpinBtnWidgetsPage::OnButtonSetValue(wxCommandEvent& WXUNUSED(event)) { long val; @@ -474,6 +510,12 @@ void SpinBtnWidgetsPage::OnUpdateUIMinMaxButton(wxUpdateUIEvent& event) mn <= mx); } +void SpinBtnWidgetsPage::OnUpdateUIBaseButton(wxUpdateUIEvent& event) +{ + unsigned long base; + event.Enable( m_textBase->GetValue().ToULong(&base) && base ); +} + void SpinBtnWidgetsPage::OnUpdateUIResetButton(wxUpdateUIEvent& event) { event.Enable( !m_chkVert->GetValue() || diff --git a/samples/xrc/rc/controls.xrc b/samples/xrc/rc/controls.xrc index f0ad8ffac1..0a7306146e 100644 --- a/samples/xrc/rc/controls.xrc +++ b/samples/xrc/rc/controls.xrc @@ -854,7 +854,8 @@ lay them out using wxSizers, absolute positioning, everything you like! 100,-1 100 - 0 + 17 + 16 diff --git a/src/common/spinctrlcmn.cpp b/src/common/spinctrlcmn.cpp index 9ed3f6c3db..5da05418d7 100644 --- a/src/common/spinctrlcmn.cpp +++ b/src/common/spinctrlcmn.cpp @@ -103,4 +103,17 @@ wxCONSTRUCTOR_6( wxSpinCtrl, wxWindow*, Parent, wxWindowID, Id, \ wxSize, Size, long, WindowStyle ) +wxString wxPrivate::wxSpinCtrlFormatAsHex(long val, long maxVal) +{ + // We format the value like this is for compatibility with (native + // behaviour of) wxMSW + wxString text; + if ( maxVal < 0x10000 ) + text.Printf(wxS("0x%04lx"), val); + else + text.Printf(wxS("0x%08lx"), val); + + return text; +} + #endif // wxUSE_SPINCTRL diff --git a/src/generic/spinctlg.cpp b/src/generic/spinctlg.cpp index 5f6bc42512..52e28ff5f7 100644 --- a/src/generic/spinctlg.cpp +++ b/src/generic/spinctlg.cpp @@ -562,6 +562,30 @@ void wxSpinCtrlGenericBase::SetSelection(long from, long to) // wxSpinCtrl //----------------------------------------------------------------------------- +bool wxSpinCtrl::SetBase(int base) +{ + // Currently we only support base 10 and 16. We could add support for base + // 8 quite easily but wxMSW doesn't support it natively so don't bother. + if ( base != 10 && base != 16 ) + return false; + + if ( base == m_base ) + return true; + + // Update the current control contents to show in the new base: be careful + // to call DoTextToValue() before changing the base... + double val; + const bool hasValidVal = DoTextToValue(m_textCtrl->GetValue(), &val); + + m_base = base; + + // ... but DoValueToText() after doing it. + if ( hasValidVal ) + m_textCtrl->SetValue(DoValueToText(val)); + + return true; +} + void wxSpinCtrl::DoSendEvent() { wxSpinEvent event( wxEVT_COMMAND_SPINCTRL_UPDATED, GetId()); @@ -574,7 +598,7 @@ void wxSpinCtrl::DoSendEvent() bool wxSpinCtrl::DoTextToValue(const wxString& text, double *val) { long lval; - if ( !text.ToLong(&lval) ) + if ( !text.ToLong(&lval, GetBase()) ) return false; *val = static_cast(lval); @@ -584,7 +608,19 @@ bool wxSpinCtrl::DoTextToValue(const wxString& text, double *val) wxString wxSpinCtrl::DoValueToText(double val) { - return wxString::Format("%ld", static_cast(val)); + switch ( GetBase() ) + { + case 16: + return wxPrivate::wxSpinCtrlFormatAsHex(static_cast(val), + GetMax()); + + default: + wxFAIL_MSG( wxS("Unsupported spin control base") ); + // Fall through + + case 10: + return wxString::Format("%ld", static_cast(val)); + } } #endif // !wxHAS_NATIVE_SPINCTRL diff --git a/src/gtk/spinctrl.cpp b/src/gtk/spinctrl.cpp index 205cbc61a9..e28cc7a686 100644 --- a/src/gtk/spinctrl.cpp +++ b/src/gtk/spinctrl.cpp @@ -359,6 +359,78 @@ wxSpinCtrlGTKBase::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant)) // wxSpinCtrl //----------------------------------------------------------------------------- +extern "C" +{ + +static gboolean +wx_gtk_spin_input(GtkSpinButton* spin, gdouble* val, wxSpinCtrl* win) +{ + // We might use g_ascii_strtoll() here but it's 2.12+ only, so use our own + // wxString function even if this requires an extra conversion. + const wxString + text(wxString::FromUTF8(gtk_entry_get_text(GTK_ENTRY(spin)))); + + long lval; + if ( !text.ToLong(&lval, win->GetBase()) ) + return FALSE; + + *val = lval; + + return TRUE; +} + +static gint +wx_gtk_spin_output(GtkSpinButton* spin, wxSpinCtrl* win) +{ + const gint val = gtk_spin_button_get_value_as_int(spin); + + gtk_entry_set_text + ( + GTK_ENTRY(spin), + wxPrivate::wxSpinCtrlFormatAsHex(val, win->GetMax()).utf8_str() + ); + + return TRUE; +} + +} // extern "C" + +bool wxSpinCtrl::SetBase(int base) +{ + // Currently we only support base 10 and 16. We could add support for base + // 8 quite easily but wxMSW doesn't support it natively so don't bother + // with doing something wxGTK-specific here. + if ( base != 10 && base != 16 ) + return false; + + if ( base == m_base ) + return true; + + m_base = base; + + // We need to be able to enter letters for any base greater than 10. + gtk_spin_button_set_numeric( GTK_SPIN_BUTTON(m_widget), m_base <= 10 ); + + if ( m_base != 10 ) + { + g_signal_connect( GTK_SPIN_BUTTON(m_widget), "input", + G_CALLBACK(wx_gtk_spin_input), this); + g_signal_connect( GTK_SPIN_BUTTON(m_widget), "output", + G_CALLBACK(wx_gtk_spin_output), this); + } + else + { + g_signal_handlers_disconnect_by_func(GTK_SPIN_BUTTON(m_widget), + (gpointer)wx_gtk_spin_input, + this); + g_signal_handlers_disconnect_by_func(GTK_SPIN_BUTTON(m_widget), + (gpointer)wx_gtk_spin_output, + this); + } + + return true; +} + //----------------------------------------------------------------------------- // wxSpinCtrlDouble //----------------------------------------------------------------------------- diff --git a/src/msw/spinctrl.cpp b/src/msw/spinctrl.cpp index 95c63204e0..ea503b4c41 100644 --- a/src/msw/spinctrl.cpp +++ b/src/msw/spinctrl.cpp @@ -425,6 +425,27 @@ wxSpinCtrl::~wxSpinCtrl() gs_spinForTextCtrl.erase(GetBuddyHwnd()); } +// ---------------------------------------------------------------------------- +// wxSpinCtrl-specific methods +// ---------------------------------------------------------------------------- + +int wxSpinCtrl::GetBase() const +{ + return ::SendMessage(GetHwnd(), UDM_GETBASE, 0, 0); +} + +bool wxSpinCtrl::SetBase(int base) +{ + if ( !::SendMessage(GetHwnd(), UDM_SETBASE, base, 0) ) + return false; + + // Whether we need to be able enter "x" or not influences whether we should + // use ES_NUMBER for the buddy control. + UpdateBuddyStyle(); + + return true; +} + // ---------------------------------------------------------------------------- // wxTextCtrl-like methods // ---------------------------------------------------------------------------- @@ -443,16 +464,28 @@ void wxSpinCtrl::SetValue(int val) wxSpinButton::SetValue(val); - // normally setting the value of the spin button is enough as it updates - // its buddy control automatically ... - if ( wxGetWindowText(m_hwndBuddy).empty() ) + // Normally setting the value of the spin button is enough as it updates + // its buddy control automatically but in a couple of situations it doesn't + // do it, for whatever reason, do it explicitly then: + const wxString text = wxGetWindowText(m_hwndBuddy); + + // First case is when the text control is empty and the value is 0: the + // spin button just leaves it empty in this case, while we want to show 0 + // in it. + if ( text.empty() && !val ) + { + ::SetWindowText(GetBuddyHwnd(), wxT("0")); + } + + // Another one is when we're using hexadecimal base but the user input + // doesn't start with "0x" -- we prefer to show it to avoid ambiguity + // between decimal and hexadecimal. + if ( GetBase() == 16 && + (text.length() < 3 || text[0] != '0' || + (text[1] != 'x' && text[1] != 'X')) ) { - // ... but sometimes it doesn't, notably when the value is 0 and the - // text control is currently empty, the spin button seems to be happy - // to leave it like this, while we really want to always show the - // current value in the control, so do it manually ::SetWindowText(GetBuddyHwnd(), - wxString::Format(wxT("%d"), val).t_str()); + wxPrivate::wxSpinCtrlFormatAsHex(val, m_max).t_str()); } m_oldValue = GetValue(); @@ -462,10 +495,10 @@ void wxSpinCtrl::SetValue(int val) int wxSpinCtrl::GetValue() const { - wxString val = wxGetWindowText(m_hwndBuddy); + const wxString val = wxGetWindowText(m_hwndBuddy); long n; - if ( (wxSscanf(val, wxT("%ld"), &n) != 1) ) + if ( !val.ToLong(&n, GetBase()) ) n = INT_MIN; if ( n < m_min ) @@ -504,12 +537,19 @@ void wxSpinCtrl::SetRange(int minVal, int maxVal) wxSpinButton::SetRange(minVal, maxVal); + UpdateBuddyStyle(); +} + +void wxSpinCtrl::UpdateBuddyStyle() +{ // this control is used for numeric entry so restrict the input to numeric // keys only -- but only if we don't need to be able to enter "-" in it as - // otherwise this would become impossible + // otherwise this would become impossible and also if we don't use + // hexadecimal as entering "x" of the "0x" prefix wouldn't be allowed + // neither then const DWORD styleOld = ::GetWindowLong(GetBuddyHwnd(), GWL_STYLE); DWORD styleNew; - if ( minVal < 0 ) + if ( m_min < 0 || GetBase() != 10 ) styleNew = styleOld & ~ES_NUMBER; else styleNew = styleOld | ES_NUMBER; diff --git a/src/xrc/xh_spin.cpp b/src/xrc/xh_spin.cpp index 01b0a057b0..d9b1f4ef77 100644 --- a/src/xrc/xh_spin.cpp +++ b/src/xrc/xh_spin.cpp @@ -96,6 +96,10 @@ wxObject *wxSpinCtrlXmlHandler::DoCreateResource() GetLong(wxT("value"), DEFAULT_VALUE), GetName()); + const long base = GetLong(wxS("base"), 10); + if ( base != 10 ) + control->SetBase(base); + SetupWindow(control); return control;