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;