diff --git a/docs/changes.txt b/docs/changes.txt index c60379766e..a1a5505f09 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -113,6 +113,7 @@ All (GUI): - Allow wxWebView::RunScript() return values (Jose Lorenzo, GSoC 2017). - Allow using fractional pen widths with wxGraphicsContext (Adrien Tétar). - Add support for loading fonts from external files (Arthur Norman). +- Add support for using arbitrary windows as wxStaticBox labels. - Improve wxSVGFileDC to support more of wxDC API (Maarten Bent). - Add support for wxAuiManager and wxAuiPaneInfo to XRC (Andrea Zanellato). - Add XRC handler for wxSpinCtrlDouble (Trylz). diff --git a/docs/doxygen/mainpages/const_cpp.h b/docs/doxygen/mainpages/const_cpp.h index 1756d18a82..6114326616 100644 --- a/docs/doxygen/mainpages/const_cpp.h +++ b/docs/doxygen/mainpages/const_cpp.h @@ -194,6 +194,8 @@ Currently the following symbols exist: @itemdef{wxHAS_RAW_KEY_CODES, Defined if raw key codes (see wxKeyEvent::GetRawKeyCode are supported.} @itemdef{wxHAS_REGEX_ADVANCED, Defined if advanced syntax is available in wxRegEx.} @itemdef{wxHAS_TASK_BAR_ICON, Defined if wxTaskBarIcon is available on the current platform.} +@itemdef{wxHAS_WINDOW_LABEL_IN_STATIC_BOX, Defined if wxStaticBox::Create() + overload taking @c wxWindow* instead of the text label is available on the current platform.} @itemdef{wxHAS_MODE_T, Defined when wxWidgets defines @c mode_t typedef for the compilers not providing it. If another library used in a wxWidgets application, such as ACE (http://www.cs.wustl.edu/~schmidt/ACE.html), also diff --git a/docs/doxygen/overviews/xrc_format.h b/docs/doxygen/overviews/xrc_format.h index ebc25b1513..75a9f54815 100644 --- a/docs/doxygen/overviews/xrc_format.h +++ b/docs/doxygen/overviews/xrc_format.h @@ -2430,6 +2430,9 @@ support the following properties: Sizer orientation, "wxHORIZONTAL" or "wxVERTICAL" (default: wxHORIZONTAL).} @row3col{label, @ref overview_xrcformat_type_text, Label to be used for the static box around the sizer (default: empty).} +@row3col{windowlabel, any window, + Window to be used instead of the plain text label (default: none). + This property is only available since wxWidgets 3.1.1.}} @endTable @subsection overview_xrcformat_wxgridsizer wxGridSizer diff --git a/include/wx/compositewin.h b/include/wx/compositewin.h index b443eaceb5..567d0e8999 100644 --- a/include/wx/compositewin.h +++ b/include/wx/compositewin.h @@ -26,24 +26,18 @@ class WXDLLIMPEXP_FWD_CORE wxToolTip; // base class name and implement GetCompositeWindowParts() pure virtual method. // ---------------------------------------------------------------------------- +// This is the base class of wxCompositeWindow which takes care of propagating +// colours, fonts etc changes to all the children, but doesn't bother with +// handling their events or focus. There should be rarely any need to use it +// rather than the full wxCompositeWindow. + // The template parameter W must be a wxWindow-derived class. template -class wxCompositeWindow : public W +class wxCompositeWindowSettersOnly : public W { public: typedef W BaseWindowClass; - // Default ctor doesn't do anything. - wxCompositeWindow() - { - this->Connect - ( - wxEVT_CREATE, - wxWindowCreateEventHandler(wxCompositeWindow::OnWindowCreate) - ); - - } - // Override all wxWindow methods which must be forwarded to the composite // window parts. @@ -109,7 +103,7 @@ public: // SetLayoutDirection(wxLayout_Default) wouldn't result in a re-layout // neither, but then we're not supposed to be called with it at all. if ( dir != wxLayout_Default ) - this->SetSize(-1, -1, -1, -1, wxSIZE_AUTO | wxSIZE_FORCE); + this->SetSize(-1, -1, -1, -1, wxSIZE_FORCE); } #if wxUSE_TOOLTIPS @@ -131,9 +125,10 @@ public: } #endif // wxUSE_TOOLTIPS - virtual void SetFocus() wxOVERRIDE +protected: + // Trivial but necessary default ctor. + wxCompositeWindowSettersOnly() { - wxSetFocusToChild(this, NULL); } private: @@ -141,6 +136,50 @@ private: // the public methods we override should forward to. virtual wxWindowList GetCompositeWindowParts() const = 0; + template + void SetForAllParts(R (wxWindowBase::*func)(TArg), T arg) + { + // Simply call the setters for all parts of this composite window. + const wxWindowList parts = GetCompositeWindowParts(); + for ( wxWindowList::const_iterator i = parts.begin(); + i != parts.end(); + ++i ) + { + wxWindow * const child = *i; + + // Allow NULL elements in the list, this makes the code of derived + // composite controls which may have optionally shown children + // simpler and it doesn't cost us much here. + if ( child ) + (child->*func)(arg); + } + } + + wxDECLARE_NO_COPY_TEMPLATE_CLASS(wxCompositeWindowSettersOnly, W); +}; + +// The real wxCompositeWindow itself, inheriting all the setters defined above. +template +class wxCompositeWindow : public wxCompositeWindowSettersOnly +{ +public: + virtual void SetFocus() wxOVERRIDE + { + wxSetFocusToChild(this, NULL); + } + +protected: + // Default ctor sets things up for handling children events correctly. + wxCompositeWindow() + { + this->Connect + ( + wxEVT_CREATE, + wxWindowCreateEventHandler(wxCompositeWindow::OnWindowCreate) + ); + } + +private: void OnWindowCreate(wxWindowCreateEvent& event) { event.Skip(); @@ -206,25 +245,6 @@ private: event.Skip(); } - template - void SetForAllParts(R (wxWindowBase::*func)(TArg), T arg) - { - // Simply call the setters for all parts of this composite window. - const wxWindowList parts = GetCompositeWindowParts(); - for ( wxWindowList::const_iterator i = parts.begin(); - i != parts.end(); - ++i ) - { - wxWindow * const child = *i; - - // Allow NULL elements in the list, this makes the code of derived - // composite controls which may have optionally shown children - // simpler and it doesn't cost us much here. - if ( child ) - (child->*func)(arg); - } - } - wxDECLARE_NO_COPY_TEMPLATE_CLASS(wxCompositeWindow, W); }; diff --git a/include/wx/gtk/statbox.h b/include/wx/gtk/statbox.h index 7dd72b86ff..cce262cbae 100644 --- a/include/wx/gtk/statbox.h +++ b/include/wx/gtk/statbox.h @@ -16,21 +16,53 @@ class WXDLLIMPEXP_CORE wxStaticBox : public wxStaticBoxBase { public: - wxStaticBox(); + wxStaticBox() + { + } + wxStaticBox( wxWindow *parent, wxWindowID id, const wxString &label, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = 0, - const wxString &name = wxStaticBoxNameStr ); + const wxString &name = wxStaticBoxNameStr ) + { + Create( parent, id, label, pos, size, style, name ); + } + + wxStaticBox( wxWindow *parent, + wxWindowID id, + wxWindow* label, + const wxPoint &pos = wxDefaultPosition, + const wxSize &size = wxDefaultSize, + long style = 0, + const wxString &name = wxStaticBoxNameStr ) + { + Create( parent, id, label, pos, size, style, name ); + } + bool Create( wxWindow *parent, wxWindowID id, const wxString &label, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = 0, - const wxString &name = wxStaticBoxNameStr ); + const wxString &name = wxStaticBoxNameStr ) + { + return DoCreate( parent, id, &label, NULL, pos, size, style, name ); + } + + bool Create( wxWindow *parent, + wxWindowID id, + wxWindow* label, + const wxPoint &pos = wxDefaultPosition, + const wxSize &size = wxDefaultSize, + long style = 0, + const wxString &name = wxStaticBoxNameStr ) + { + return DoCreate( parent, id, NULL, label, pos, size, style, name ); + } virtual void SetLabel( const wxString &label ) wxOVERRIDE; @@ -46,6 +78,17 @@ public: virtual void AddChild( wxWindowBase *child ) wxOVERRIDE; protected: + // Common implementation of both Create() overloads: exactly one of + // labelStr and labelWin parameters must be non-null. + bool DoCreate(wxWindow *parent, + wxWindowID id, + const wxString* labelStr, + wxWindow* labelWin, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString& name); + virtual bool GTKWidgetNeedsMnemonic() const wxOVERRIDE; virtual void GTKWidgetDoSetMnemonic(GtkWidget* w) wxOVERRIDE; @@ -54,4 +97,7 @@ protected: wxDECLARE_DYNAMIC_CLASS(wxStaticBox); }; +// Indicate that we have the ctor overload taking wxWindow as label. +#define wxHAS_WINDOW_LABEL_IN_STATIC_BOX + #endif // _WX_GTKSTATICBOX_H_ diff --git a/include/wx/gtk/window.h b/include/wx/gtk/window.h index fd3f683491..d7b55ae685 100644 --- a/include/wx/gtk/window.h +++ b/include/wx/gtk/window.h @@ -206,6 +206,15 @@ public: virtual void GTKHandleRealized(); void GTKHandleUnrealize(); + // Apply the widget style to the given window. Should normally only be + // called from the overridden DoApplyWidgetStyle() implementation in + // another window and exists solely to provide access to protected + // DoApplyWidgetStyle() when it's really needed. + static void GTKDoApplyWidgetStyle(wxWindowGTK* win, GtkRcStyle *style) + { + win->DoApplyWidgetStyle(style); + } + protected: // for controls composed of multiple GTK widgets, return true to eliminate // spurious focus events if the focus changes between GTK+ children within @@ -426,8 +435,11 @@ protected: void GTKApplyWidgetStyle(bool forceStyle = false); - // helper function to ease native widgets wrapping, called by - // ApplyWidgetStyle -- override this, not ApplyWidgetStyle + // Helper function to ease native widgets wrapping, called by + // GTKApplyWidgetStyle() and supposed to be overridden, not called. + // + // And if you actually need to call it, e.g. to propagate style change to a + // composite control, use public static GTKDoApplyWidgetStyle(). virtual void DoApplyWidgetStyle(GtkRcStyle *style); void GTKApplyStyle(GtkWidget* widget, GtkRcStyle* style); diff --git a/include/wx/msw/statbox.h b/include/wx/msw/statbox.h index 87ad173cba..294f1519ae 100644 --- a/include/wx/msw/statbox.h +++ b/include/wx/msw/statbox.h @@ -11,11 +11,16 @@ #ifndef _WX_MSW_STATBOX_H_ #define _WX_MSW_STATBOX_H_ +#include "wx/compositewin.h" + // Group box -class WXDLLIMPEXP_CORE wxStaticBox : public wxStaticBoxBase +class WXDLLIMPEXP_CORE wxStaticBox : public wxCompositeWindowSettersOnly { public: - wxStaticBox() { } + wxStaticBox() + : wxCompositeWindowSettersOnly() + { + } wxStaticBox(wxWindow *parent, wxWindowID id, const wxString& label, @@ -23,6 +28,18 @@ public: const wxSize& size = wxDefaultSize, long style = 0, const wxString& name = wxStaticBoxNameStr) + : wxCompositeWindowSettersOnly() + { + Create(parent, id, label, pos, size, style, name); + } + + wxStaticBox(wxWindow* parent, wxWindowID id, + wxWindow* label, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0, + const wxString &name = wxStaticBoxNameStr) + : wxCompositeWindowSettersOnly() { Create(parent, id, label, pos, size, style, name); } @@ -34,9 +51,19 @@ public: long style = 0, const wxString& name = wxStaticBoxNameStr); + bool Create(wxWindow *parent, wxWindowID id, + wxWindow* label, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0, + const wxString& name = wxStaticBoxNameStr); + /// Implementation only virtual void GetBordersForSizer(int *borderTop, int *borderOther) const wxOVERRIDE; + virtual bool SetBackgroundColour(const wxColour& colour) wxOVERRIDE; + virtual bool SetFont(const wxFont& font) wxOVERRIDE; + virtual WXDWORD MSWGetStyle(long style, WXDWORD *exstyle) const wxOVERRIDE; // returns true if the platform should explicitly apply a theme border @@ -49,6 +76,8 @@ public: virtual WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) wxOVERRIDE; protected: + virtual wxWindowList GetCompositeWindowParts() const wxOVERRIDE; + // return the region with all the windows inside this static box excluded virtual WXHRGN MSWGetRegionWithoutChildren(); @@ -63,8 +92,14 @@ protected: void OnPaint(wxPaintEvent& event); +private: + void PositionLabelWindow(); + wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxStaticBox); }; +// Indicate that we have the ctor overload taking wxWindow as label. +#define wxHAS_WINDOW_LABEL_IN_STATIC_BOX + #endif // _WX_MSW_STATBOX_H_ diff --git a/include/wx/statbox.h b/include/wx/statbox.h index 968903b009..d5d15a9f00 100644 --- a/include/wx/statbox.h +++ b/include/wx/statbox.h @@ -31,24 +31,36 @@ public: // overridden base class virtuals virtual bool HasTransparentBackground() wxOVERRIDE { return true; } + virtual bool Enable(bool enable = true) wxOVERRIDE; // implementation only: this is used by wxStaticBoxSizer to account for the // need for extra space taken by the static box // // the top border is the margin at the top (where the title is), // borderOther is the margin on all other sides - virtual void GetBordersForSizer(int *borderTop, int *borderOther) const - { - const int BORDER = FromDIP(5); // FIXME: hardcoded value + virtual void GetBordersForSizer(int *borderTop, int *borderOther) const; - *borderTop = GetLabel().empty() ? BORDER : GetCharHeight(); - *borderOther = BORDER; - } + // This is an internal function currently used by wxStaticBoxSizer only. + // + // Reparent all children of the static box under its parent and destroy the + // box itself. + void WXDestroyWithoutChildren(); protected: // choose the default border for this window virtual wxBorder GetDefaultBorder() const wxOVERRIDE { return wxBORDER_NONE; } + // If non-null, the window used as our label. This window is owned by the + // static box and will be deleted when it is. + wxWindow* m_labelWin; + + // For boxes with window label this member variable is used instead of + // m_isEnabled to remember the last value passed to Enable(). It is + // required because the box itself doesn't get disabled by Enable(false) in + // this case (see comments in Enable() implementation), and m_isEnabled + // must correspond to its real state. + bool m_areChildrenEnabled; + wxDECLARE_NO_COPY_CLASS(wxStaticBoxBase); }; diff --git a/include/wx/window.h b/include/wx/window.h index 38d70ab7a8..8046223a4a 100644 --- a/include/wx/window.h +++ b/include/wx/window.h @@ -48,22 +48,6 @@ #define wxUSE_MENUS_NATIVE wxUSE_MENUS #endif // __WXUNIVERSAL__/!__WXUNIVERSAL__ - -// Define this macro if the corresponding operating system handles the state -// of children windows automatically when the parent is enabled/disabled. -// Otherwise wx itself must ensure that when the parent is disabled its -// children are disabled too, and their initial state is restored when the -// parent is enabled back. -#if defined(__WXMSW__) - // must do everything ourselves - #undef wxHAS_NATIVE_ENABLED_MANAGEMENT -#elif defined(__WXOSX__) - // must do everything ourselves - #undef wxHAS_NATIVE_ENABLED_MANAGEMENT -#else - #define wxHAS_NATIVE_ENABLED_MANAGEMENT -#endif - // ---------------------------------------------------------------------------- // forward declarations // ---------------------------------------------------------------------------- diff --git a/interface/wx/statbox.h b/interface/wx/statbox.h index 46004f20f4..d9f2a564de 100644 --- a/interface/wx/statbox.h +++ b/interface/wx/statbox.h @@ -71,7 +71,11 @@ public: Checkbox size. If ::wxDefaultSize is specified then a default size is chosen. @param style - Window style. See wxStaticBox. + Window style. There are no wxStaticBox-specific styles, but generic + ::wxALIGN_LEFT, ::wxALIGN_CENTRE_HORIZONTAL and ::wxALIGN_RIGHT can + be used here to change the position of the static box label when + using wxGTK (these styles are ignored under the other platforms + currently). @param name Window name. @@ -84,6 +88,42 @@ public: long style = 0, const wxString& name = wxStaticBoxNameStr); + /** + Constructor for a static box using the given window as label. + + This constructor takes a pointer to an arbitrary window (although + usually a wxCheckBox or a wxRadioButton) instead of just the usual text + label and puts this window at the top of the box at the place where the + label would be shown. + + The @a label window must be a non-null, fully created window and will + become a child of this wxStaticBox, i.e. it will be owned by this + control and will be deleted when the wxStaticBox itself is deleted. + + An example of creating a wxStaticBox with window as a label: + @code + void MyFrame::CreateControls() + { + wxPanel* panel = new wxPanel(this); + wxCheckBox* checkbox = new wxCheckBox(panel, wxID_ANY, "Box checkbox"); + wxStaticBox* box = new wxStaticBox(panel, wxID_ANY, checkbox); + ... + } + @endcode + + Currently this constructor is only available in wxGTK and wxMSW, use + @c wxHAS_WINDOW_LABEL_IN_STATIC_BOX to check whether it can be used at + compile-time. + + @since 3.1.1 + */ + wxStaticBox(wxWindow* parent, wxWindowID id, + wxWindow* label, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0, + const wxString& name = wxStaticBoxNameStr); + /** Destructor, destroying the group box. */ @@ -97,5 +137,66 @@ public: const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0, const wxString& name = wxStaticBoxNameStr); -}; + /** + Creates the static box with the window as a label. + + This method can only be called for an object created using its default + constructor. + + See the constructor documentation for more details. + + Currently this overload is only available in wxGTK and wxMSW, use + @c wxHAS_WINDOW_LABEL_IN_STATIC_BOX to check whether it can be used at + compile-time. + + @since 3.1.1 + */ + bool Create(wxWindow* parent, wxWindowID id, + wxWindow* label, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0, + const wxString& name = wxStaticBoxNameStr); + + /** + Enables or disables the box without affecting its label window, if any. + + wxStaticBox overrides wxWindow::Enable() in order to avoid disabling + the control used as a label, if this box is using one. This is done in + order to allow using a wxCheckBox, for example, label and enable or + disable the box according to the state of the checkbox: if disabling + the box also disabled the checkbox in this situation, it would make it + impossible for the user to re-enable the box after disabling it, so the + checkbox stays enabled even if @c box->Enable(false) is called. + + However with the actual behaviour, implemented in this overridden + method, the following code (shown using C++11 only for convenience, + this behaviour is not C++11-specific): + @code + auto check = new wxCheckBox(parent, wxID_ANY, "Use the box"); + auto box = new wxStaticBox(parent, wxID_ANY, check); + check->Bind(wxEVT_CHECKBOX, + [box](wxCommandEvent& event) { + box->Enable(event.IsChecked()); + }); + @endcode + does work as expected. + + Please note that overriding Enable() to not actually disable this + window itself has two possibly unexpected consequences: + + - The box retains its enabled status, i.e. IsEnabled() still returns + @true, after calling @c Enable(false). + - The box children are enabled or disabled when the box is, which can + result in the loss of their original state. E.g. if a box child is + initially disabled, then the box itself is disabled and, finally, the + box is enabled again, this child will end up being enabled too (this + wouldn't happen with any other parent window as its children would + inherit the disabled state from the parent instead of being really + disabled themselves when it is disabled). To avoid this problem, + consider using ::wxEVT_UPDATE_UI to ensure that the child state is + always correct or restoring it manually after re-enabling the box. + */ + virtual bool Enable(bool enable = true); +}; diff --git a/misc/schema/xrc_schema.rnc b/misc/schema/xrc_schema.rnc index ada4360591..c9c3f0d69e 100644 --- a/misc/schema/xrc_schema.rnc +++ b/misc/schema/xrc_schema.rnc @@ -1879,6 +1879,7 @@ wxStaticBoxSizer_horz = stdObjectNodeAttributes & stdSizerProperties & [xrc:p="important"] element label {_, t_text }* & + element windowlabel {windowNode}* & [xrc:p="o"] element orient {_, "wxHORIZONTAL" }* & (wxBoxSizer_horz_item | objectRef)* } @@ -1889,6 +1890,7 @@ wxStaticBoxSizer_vert = stdObjectNodeAttributes & stdSizerProperties & [xrc:p="important"] element label {_, t_text }* & + element windowlabel {windowNode}* & element orient {_, "wxVERTICAL" } & (wxBoxSizer_vert_item | objectRef)* } diff --git a/samples/widgets/static.cpp b/samples/widgets/static.cpp index 36827f50ae..71b42b59b4 100644 --- a/samples/widgets/static.cpp +++ b/samples/widgets/static.cpp @@ -116,6 +116,9 @@ public: protected: // event handlers void OnCheckOrRadioBox(wxCommandEvent& event); +#ifdef wxHAS_WINDOW_LABEL_IN_STATIC_BOX + void OnBoxCheckBox(wxCommandEvent& event); +#endif // wxHAS_WINDOW_LABEL_IN_STATIC_BOX void OnButtonReset(wxCommandEvent& event); void OnButtonBoxText(wxCommandEvent& event); @@ -137,6 +140,9 @@ protected: // the check/radio boxes for styles wxCheckBox *m_chkVert, *m_chkGeneric, +#ifdef wxHAS_WINDOW_LABEL_IN_STATIC_BOX + *m_chkBoxWithCheck, +#endif // wxHAS_WINDOW_LABEL_IN_STATIC_BOX *m_chkAutoResize, *m_chkEllipsize; @@ -207,6 +213,9 @@ StaticWidgetsPage::StaticWidgetsPage(WidgetsBookCtrl *book, m_chkVert = m_chkAutoResize = m_chkGeneric = +#ifdef wxHAS_WINDOW_LABEL_IN_STATIC_BOX + m_chkBoxWithCheck = +#endif // wxHAS_WINDOW_LABEL_IN_STATIC_BOX #if wxUSE_MARKUP m_chkGreen = #endif // wxUSE_MARKUP @@ -243,6 +252,9 @@ void StaticWidgetsPage::CreateContent() m_chkGeneric = CreateCheckBoxAndAddToSizer(sizerLeft, "&Generic wxStaticText"); +#ifdef wxHAS_WINDOW_LABEL_IN_STATIC_BOX + m_chkBoxWithCheck = CreateCheckBoxAndAddToSizer(sizerLeft, "Checkable &box"); +#endif // wxHAS_WINDOW_LABEL_IN_STATIC_BOX m_chkVert = CreateCheckBoxAndAddToSizer(sizerLeft, "&Vertical line"); m_chkAutoResize = CreateCheckBoxAndAddToSizer(sizerLeft, "&Fit to text"); sizerLeft->Add(5, 5, 0, wxGROW | wxALL, 5); // spacer @@ -367,6 +379,9 @@ void StaticWidgetsPage::CreateContent() void StaticWidgetsPage::Reset() { m_chkGeneric->SetValue(false); +#ifdef wxHAS_WINDOW_LABEL_IN_STATIC_BOX + m_chkBoxWithCheck->SetValue(false); +#endif // wxHAS_WINDOW_LABEL_IN_STATIC_BOX m_chkVert->SetValue(false); m_chkAutoResize->SetValue(true); m_chkEllipsize->SetValue(true); @@ -469,10 +484,28 @@ void StaticWidgetsPage::CreateStatic() flagsText |= align; flagsBox |= align; - wxStaticBox *staticBox = new wxStaticBox(this, wxID_ANY, - m_textBox->GetValue(), - wxDefaultPosition, wxDefaultSize, - flagsBox); + wxStaticBox *staticBox; +#ifdef wxHAS_WINDOW_LABEL_IN_STATIC_BOX + if ( m_chkBoxWithCheck->GetValue() ) + { + wxCheckBox* const label = new wxCheckBox(this, wxID_ANY, + m_textBox->GetValue()); + label->Bind(wxEVT_CHECKBOX, &StaticWidgetsPage::OnBoxCheckBox, this); + + staticBox = new wxStaticBox(this, wxID_ANY, + label, + wxDefaultPosition, wxDefaultSize, + flagsBox); + } + else // normal static box +#endif // wxHAS_WINDOW_LABEL_IN_STATIC_BOX + { + staticBox = new wxStaticBox(this, wxID_ANY, + m_textBox->GetValue(), + wxDefaultPosition, wxDefaultSize, + flagsBox); + } + m_sizerStatBox = new wxStaticBoxSizer(staticBox, isVert ? wxHORIZONTAL : wxVERTICAL); @@ -518,12 +551,12 @@ void StaticWidgetsPage::CreateStatic() isVert ? wxLI_VERTICAL : wxLI_HORIZONTAL); #endif // wxUSE_STATLINE - m_sizerStatBox->Add(m_statText, 0, wxGROW | wxALL, 5); + m_sizerStatBox->Add(m_statText, 0, wxGROW); #if wxUSE_STATLINE - m_sizerStatBox->Add(m_statLine, 0, wxGROW | wxALL, 5); + m_sizerStatBox->Add(m_statLine, 0, wxGROW | wxTOP | wxBOTTOM, 10); #endif // wxUSE_STATLINE #if wxUSE_MARKUP - m_sizerStatBox->Add(m_statMarkup, 0, wxALL, 5); + m_sizerStatBox->Add(m_statMarkup); #endif // wxUSE_MARKUP m_sizerStatic->Add(m_sizerStatBox, 0, wxGROW); @@ -536,6 +569,8 @@ void StaticWidgetsPage::CreateStatic() staticBox->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(StaticWidgetsPage::OnMouseEvent), NULL, this); + + SetUpWidget(); } // ---------------------------------------------------------------------------- @@ -559,6 +594,14 @@ void StaticWidgetsPage::OnCheckOrRadioBox(wxCommandEvent& event) CreateStatic(); } +#ifdef wxHAS_WINDOW_LABEL_IN_STATIC_BOX +void StaticWidgetsPage::OnBoxCheckBox(wxCommandEvent& event) +{ + wxLogMessage("Box check box has been %schecked", + event.IsChecked() ? "": "un"); +} +#endif // wxHAS_WINDOW_LABEL_IN_STATIC_BOX + void StaticWidgetsPage::OnButtonBoxText(wxCommandEvent& WXUNUSED(event)) { m_sizerStatBox->GetStaticBox()->SetLabel(m_textBox->GetValue()); diff --git a/samples/xrc/rc/controls.xrc b/samples/xrc/rc/controls.xrc index a6c4406570..2ee51c1212 100644 --- a/samples/xrc/rc/controls.xrc +++ b/samples/xrc/rc/controls.xrc @@ -983,6 +983,58 @@ lay them out using wxSizers, absolute positioning, everything you like! wxVERTICAL + + wxALL + 5 + + + + + + + wxALL + 5 + + + + + + wxALL + 5 + + + 1 + + + + + + wxGROW|wxALL + 5 + + wxVERTICAL + + + + 1 + + + + wxALL + 5 + + + + + + + wxALL + 5 + + + 1 + + diff --git a/src/common/sizer.cpp b/src/common/sizer.cpp index ce59f88a1e..b0b94674b0 100644 --- a/src/common/sizer.cpp +++ b/src/common/sizer.cpp @@ -2582,20 +2582,7 @@ wxStaticBoxSizer::~wxStaticBoxSizer() // previous wxWidgets versions, so ensure they are left alive. if ( m_staticBox ) - { - // Notice that we must make a copy of the list as it will be changed by - // Reparent() calls in the loop. - const wxWindowList children = m_staticBox->GetChildren(); - wxWindow* const parent = m_staticBox->GetParent(); - for ( wxWindowList::const_iterator i = children.begin(); - i != children.end(); - ++i ) - { - (*i)->Reparent(parent); - } - - delete m_staticBox; - } + m_staticBox->WXDestroyWithoutChildren(); } void wxStaticBoxSizer::RecalcSizes() diff --git a/src/common/statboxcmn.cpp b/src/common/statboxcmn.cpp index 3539c3c356..f8f489363b 100644 --- a/src/common/statboxcmn.cpp +++ b/src/common/statboxcmn.cpp @@ -31,11 +31,90 @@ extern WXDLLEXPORT_DATA(const char) wxStaticBoxNameStr[] = "groupBox"; wxStaticBoxBase::wxStaticBoxBase() { + m_labelWin = NULL; + m_areChildrenEnabled = true; + #ifndef __WXGTK__ m_container.DisableSelfFocus(); #endif } +void wxStaticBoxBase::GetBordersForSizer(int *borderTop, int *borderOther) const +{ + const int BORDER = FromDIP(5); // FIXME: hardcoded value + + if ( m_labelWin ) + { + *borderTop = m_labelWin->GetSize().y; + } + else + { + *borderTop = GetLabel().empty() ? BORDER : GetCharHeight(); + } + + *borderOther = BORDER; +} + +void wxStaticBoxBase::WXDestroyWithoutChildren() +{ + // Notice that we must make a copy of the list as it will be changed by + // Reparent() calls in the loop. + const wxWindowList children = GetChildren(); + wxWindow* const parent = GetParent(); + for ( wxWindowList::const_iterator i = children.begin(); + i != children.end(); + ++i ) + { + // The label window doesn't count as our child, it's really a part of + // static box itself and it makes no sense to leave it alive when the + // box is destroyed, so do it even when it's supposed to be destroyed + // without destroying its children -- by not reparenting it, we ensure + // that it's destroyed when this object itself is below. + if ( *i != m_labelWin ) + { + (*i)->Reparent(parent); + } + } + + delete this; +} + +bool wxStaticBoxBase::Enable(bool enable) +{ +#ifdef wxHAS_WINDOW_LABEL_IN_STATIC_BOX + // We want to keep the window label enabled even if the static box is + // disabled because this label is often used to enable/disable the box + // (e.g. a checkbox or a radio button is commonly used for this purpose) + // and it would be impossible to re-enable the box back if disabling it + // also disabled the label control. + // + // Unfortunately it is _not_ enough to just disable the box and then enable + // the label window as it would still remain disabled for as long as its + // parent is disabled. So we avoid disabling the box at all in this case + // and only disable its children. + if ( m_labelWin ) + { + if ( enable == m_areChildrenEnabled ) + return false; + + m_areChildrenEnabled = enable; + + const wxWindowList& children = GetChildren(); + for ( wxWindowList::const_iterator i = children.begin(); + i != children.end(); + ++i ) + { + if ( *i != m_labelWin ) + (*i)->Enable(enable); + } + + return true; + } +#endif // wxHAS_WINDOW_LABEL_IN_STATIC_BOX + + return wxNavigationEnabled::Enable(enable); +} + // ---------------------------------------------------------------------------- // XTI // ---------------------------------------------------------------------------- diff --git a/src/common/wincmn.cpp b/src/common/wincmn.cpp index b0e542a171..cf33f259dd 100644 --- a/src/common/wincmn.cpp +++ b/src/common/wincmn.cpp @@ -1166,15 +1166,26 @@ bool wxWindowBase::IsEnabled() const return IsThisEnabled() && (IsTopLevel() || !GetParent() || GetParent()->IsEnabled()); } +// Define this macro if the corresponding operating system handles the state +// of children windows automatically when the parent is enabled/disabled. +// Otherwise wx itself must ensure that when the parent is disabled its +// children are disabled too, and their initial state is restored when the +// parent is enabled back. +#if defined(__WXMSW__) + // must do everything ourselves + #undef wxHAS_NATIVE_ENABLED_MANAGEMENT +#elif defined(__WXOSX__) + // must do everything ourselves + #undef wxHAS_NATIVE_ENABLED_MANAGEMENT +#else + #define wxHAS_NATIVE_ENABLED_MANAGEMENT +#endif + void wxWindowBase::NotifyWindowOnEnableChange(bool enabled) { - // Under some platforms there is no need to update the window state - // explicitly, it will become disabled when its parent is. On other ones we - // do need to disable all windows recursively though. -#ifndef wxHAS_NATIVE_ENABLED_MANAGEMENT DoEnable(enabled); -#endif // !defined(wxHAS_NATIVE_ENABLED_MANAGEMENT) +#ifndef wxHAS_NATIVE_ENABLED_MANAGEMENT // Disabling a top level window is typically done when showing a modal // dialog and we don't need to disable its children in this case, they will // be logically disabled anyhow (i.e. their IsEnabled() will return false) @@ -1190,7 +1201,6 @@ void wxWindowBase::NotifyWindowOnEnableChange(bool enabled) // they would still show as enabled even though they wouldn't actually // accept any input (at least under MSW where children don't accept input // if any of the windows in their parent chain is enabled). -#ifndef wxHAS_NATIVE_ENABLED_MANAGEMENT for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst(); node; node = node->GetNext() ) @@ -1209,12 +1219,6 @@ bool wxWindowBase::Enable(bool enable) m_isEnabled = enable; - // If we call DoEnable() from NotifyWindowOnEnableChange(), we don't need - // to do it from here. -#ifdef wxHAS_NATIVE_ENABLED_MANAGEMENT - DoEnable(enable); -#endif // !defined(wxHAS_NATIVE_ENABLED_MANAGEMENT) - NotifyWindowOnEnableChange(enable); return true; diff --git a/src/gtk/statbox.cpp b/src/gtk/statbox.cpp index 797d80caf8..6979591287 100644 --- a/src/gtk/statbox.cpp +++ b/src/gtk/statbox.cpp @@ -59,28 +59,14 @@ static gboolean expose_event(GtkWidget* widget, GdkEventExpose*, wxWindow*) // wxStaticBox //----------------------------------------------------------------------------- -wxStaticBox::wxStaticBox() -{ -} - -wxStaticBox::wxStaticBox( wxWindow *parent, - wxWindowID id, - const wxString &label, - const wxPoint& pos, - const wxSize& size, - long style, - const wxString& name ) -{ - Create( parent, id, label, pos, size, style, name ); -} - -bool wxStaticBox::Create( wxWindow *parent, - wxWindowID id, - const wxString& label, - const wxPoint& pos, - const wxSize& size, - long style, - const wxString& name ) +bool wxStaticBox::DoCreate(wxWindow *parent, + wxWindowID id, + const wxString* labelStr, + wxWindow* labelWin, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString& name) { if (!PreCreation( parent, pos, size ) || !CreateBase( parent, id, pos, size, style, wxDefaultValidator, name )) @@ -89,11 +75,41 @@ bool wxStaticBox::Create( wxWindow *parent, return false; } - m_widget = GTKCreateFrame(label); - g_object_ref(m_widget); + if ( labelStr ) + { + m_widget = GTKCreateFrame(*labelStr); - // only base SetLabel needs to be called after GTKCreateFrame - wxControl::SetLabel(label); + // only base SetLabel needs to be called after GTKCreateFrame + wxControl::SetLabel(*labelStr); + } + else // Use the given window as the label. + { + wxCHECK_MSG( labelWin, false, wxS("Label window can't be null") ); + + GtkWidget* const labelWidget = labelWin->m_widget; + wxCHECK_MSG( labelWidget, false, wxS("Label window must be created") ); + + // The widget must not have any parent at GTK+ level or setting it as + // label widget would fail. + GtkWidget* const oldParent = gtk_widget_get_parent(labelWidget); + gtk_container_remove(GTK_CONTAINER(oldParent), labelWidget); + gtk_widget_unparent(labelWidget); + + // It also should be our child at wx API level, but without being our + // child in wxGTK, i.e. it must not be added to the GtkFrame container, + // so we can't call Reparent() here (not even wxWindowBase version, as + // it still would end up in our overridden AddChild()), nor the normal + // AddChild() for the same reason. + labelWin->GetParent()->RemoveChild(labelWin); + wxWindowBase::AddChild(labelWin); + + m_labelWin = labelWin; + + m_widget = gtk_frame_new(NULL); + gtk_frame_set_label_widget(GTK_FRAME(m_widget), labelWidget); + } + + g_object_ref(m_widget); m_parent->DoAddChild( this ); @@ -140,12 +156,16 @@ void wxStaticBox::SetLabel( const wxString& label ) { wxCHECK_RET( m_widget != NULL, wxT("invalid staticbox") ); + wxCHECK_RET( !m_labelWin, wxS("Doesn't make sense when using label window") ); + GTKSetLabelForFrame(GTK_FRAME(m_widget), label); } void wxStaticBox::DoApplyWidgetStyle(GtkRcStyle *style) { GTKFrameApplyWidgetStyle(GTK_FRAME(m_widget), style); + if ( m_labelWin ) + GTKDoApplyWidgetStyle(m_labelWin, style); if (m_wxwindow) GTKApplyStyle(m_wxwindow, style); diff --git a/src/msw/statbox.cpp b/src/msw/statbox.cpp index 9ae5c230df..97a48d7bb6 100644 --- a/src/msw/statbox.cpp +++ b/src/msw/statbox.cpp @@ -54,6 +54,21 @@ #define TMT_FONT 210 +namespace +{ + +// Offset of the first pixel of the label from the box left border. +// +// FIXME: value is hardcoded as this is what it is on my system, no idea if +// it's true everywhere +const int LABEL_HORZ_OFFSET = 9; + +// Extra borders around the label on left/right and bottom sides. +const int LABEL_HORZ_BORDER = 2; +const int LABEL_VERT_BORDER = 2; + +} // anonymous namespace + // ---------------------------------------------------------------------------- // wxWin macros // ---------------------------------------------------------------------------- @@ -92,6 +107,41 @@ bool wxStaticBox::Create(wxWindow *parent, return true; } +bool wxStaticBox::Create(wxWindow* parent, + wxWindowID id, + wxWindow* labelWin, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString& name) +{ + wxCHECK_MSG( labelWin, false, wxS("Label window can't be null") ); + + if ( !Create(parent, id, wxString(), pos, size, style, name) ) + return false; + + m_labelWin = labelWin; + m_labelWin->Reparent(this); + + PositionLabelWindow(); + + return true; +} + +void wxStaticBox::PositionLabelWindow() +{ + m_labelWin->SetSize(m_labelWin->GetBestSize()); + m_labelWin->Move(FromDIP(LABEL_HORZ_OFFSET), 0); +} + +wxWindowList wxStaticBox::GetCompositeWindowParts() const +{ + wxWindowList parts; + if ( m_labelWin ) + parts.push_back(m_labelWin); + return parts; +} + WXDWORD wxStaticBox::MSWGetStyle(long style, WXDWORD *exstyle) const { long styleWin = wxStaticBoxBase::MSWGetStyle(style, exstyle); @@ -152,7 +202,31 @@ void wxStaticBox::GetBordersForSizer(int *borderTop, int *borderOther) const wxStaticBoxBase::GetBordersForSizer(borderTop, borderOther); // need extra space, don't know how much but this seems to be enough - *borderTop += GetCharHeight()/3; + *borderTop += FromDIP(LABEL_VERT_BORDER); +} + +bool wxStaticBox::SetBackgroundColour(const wxColour& colour) +{ + // Do _not_ call the immediate base class method, we don't need to set the + // label window (which is the only sub-window of this composite window) + // background explicitly because it will almost always be a wxCheckBox or + // wxRadioButton which inherits its background from the box anyhow, so + // setting it would be at best useless. + return wxStaticBoxBase::SetBackgroundColour(colour); +} + +bool wxStaticBox::SetFont(const wxFont& font) +{ + if ( !wxCompositeWindowSettersOnly::SetFont(font) ) + return false; + + // We need to reposition the label as its size may depend on the font. + if ( m_labelWin ) + { + PositionLabelWindow(); + } + + return true; } WXLRESULT wxStaticBox::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) @@ -189,9 +263,10 @@ WXLRESULT wxStaticBox::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lPar if ( !HandlePrintClient((WXHDC)wParam) ) { // no, we don't, erase the background ourselves - // (don't use our own) - see PaintBackground for explanation - wxBrush brush(GetParent()->GetBackgroundColour()); - wxFillRect(GetHwnd(), (HDC)wParam, GetHbrushOf(brush)); + RECT rc; + ::GetClientRect(GetHwnd(), &rc); + wxDCTemp dc((WXHDC)wParam); + PaintBackground(dc, rc); } return 0; @@ -253,7 +328,21 @@ void wxStaticBox::MSWGetRegionWithoutSelf(WXHRGN hRgn, int w, int h) GetBordersForSizer(&borderTop, &border); // top - SubtractRectFromRgn(hrgn, 0, 0, w, borderTop); + if ( m_labelWin ) + { + // Don't exclude the entire rectangle at the top, we do need to paint + // the background of the gap between the label window and the box + // frame. + const wxRect labelRect = m_labelWin->GetRect(); + const int gap = FromDIP(LABEL_HORZ_BORDER); + + SubtractRectFromRgn(hrgn, 0, 0, labelRect.GetLeft() - gap, borderTop); + SubtractRectFromRgn(hrgn, labelRect.GetRight() + gap, 0, w, borderTop); + } + else + { + SubtractRectFromRgn(hrgn, 0, 0, w, borderTop); + } // bottom SubtractRectFromRgn(hrgn, 0, h - border, w, h); @@ -399,7 +488,7 @@ void wxStaticBox::PaintForeground(wxDC& dc, const RECT&) // background mode doesn't change anything: the static box def window proc // still draws the label in its own colours, so we need to redraw the text // ourselves if we have a non default fg colour - if ( m_hasFgCol && wxUxThemeEngine::GetIfActive() ) + if ( m_hasFgCol && wxUxThemeEngine::GetIfActive() && !m_labelWin ) { // draw over the text in default colour in our colour HDC hdc = GetHdcOf(*impl); @@ -443,23 +532,17 @@ void wxStaticBox::PaintForeground(wxDC& dc, const RECT&) dc.GetTextExtent(wxStripMenuCodes(label, wxStrip_Mnemonics), &width, &height); - int x; - int y = height; - // first we need to correctly paint the background of the label // as Windows ignores the brush offset when doing it - // - // FIXME: value of x is hardcoded as this is what it is on my system, - // no idea if it's true everywhere - RECT dimensions = {0, 0, 0, y}; - x = 9; + const int x = FromDIP(LABEL_HORZ_OFFSET); + RECT dimensions = { x, 0, 0, height }; dimensions.left = x; dimensions.right = x + width; // need to adjust the rectangle to cover all the label background - dimensions.left -= 2; - dimensions.right += 2; - dimensions.bottom += 2; + dimensions.left -= FromDIP(LABEL_HORZ_BORDER); + dimensions.right += FromDIP(LABEL_HORZ_BORDER); + dimensions.bottom += FromDIP(LABEL_VERT_BORDER); if ( UseBgCol() ) { @@ -489,7 +572,7 @@ void wxStaticBox::PaintForeground(wxDC& dc, const RECT&) } // now draw the text - RECT rc2 = { x, 0, x + width, y }; + RECT rc2 = { x, 0, x + width, height }; ::DrawText(hdc, label.t_str(), label.length(), &rc2, drawTextFlags); } @@ -525,8 +608,31 @@ void wxStaticBox::OnPaint(wxPaintEvent& WXUNUSED(event)) GetBordersForSizer(&borderTop, &border); // top - dc.Blit(border, 0, rc.right - border, borderTop, - &memdc, border, 0); + if ( m_labelWin ) + { + // We also have to exclude the area taken by the label window, + // otherwise there would be flicker when it draws itself on top of it. + const wxRect labelRect = m_labelWin->GetRect(); + + // We also leave a small border around label window to make it appear + // more similarly to a plain text label. + const int gap = FromDIP(LABEL_HORZ_BORDER); + + dc.Blit(border, 0, + labelRect.GetLeft() - gap - border, + borderTop, + &memdc, border, 0); + dc.Blit(labelRect.GetRight() + gap, 0, + rc.right - (labelRect.GetRight() + gap), + borderTop, + &memdc, border, 0); + } + else + { + dc.Blit(border, 0, rc.right - border, borderTop, + &memdc, border, 0); + } + // bottom dc.Blit(border, rc.bottom - border, rc.right - border, border, &memdc, border, rc.bottom - border); diff --git a/src/xrc/xh_sizer.cpp b/src/xrc/xh_sizer.cpp index 8a91eb91e9..2a17184a55 100644 --- a/src/xrc/xh_sizer.cpp +++ b/src/xrc/xh_sizer.cpp @@ -320,14 +320,63 @@ wxSizer* wxSizerXmlHandler::Handle_wxBoxSizer() #if wxUSE_STATBOX wxSizer* wxSizerXmlHandler::Handle_wxStaticBoxSizer() { - return new wxStaticBoxSizer( - new wxStaticBox(m_parentAsWindow, - GetID(), - GetText(wxT("label")), - wxDefaultPosition, wxDefaultSize, - 0/*style*/, - GetName()), - GetStyle(wxT("orient"), wxHORIZONTAL)); + wxXmlNode* nodeWindowLabel = GetParamNode(wxS("windowlabel")); + wxString const& labelText = GetText(wxS("label")); + + wxStaticBox* box = NULL; + if ( nodeWindowLabel ) + { + if ( !labelText.empty() ) + { + ReportError("Either label or windowlabel can be used, but not both"); + return NULL; + } + +#ifdef wxHAS_WINDOW_LABEL_IN_STATIC_BOX + wxXmlNode* n = nodeWindowLabel->GetChildren(); + if ( !n ) + { + ReportError("windowlabel must have a window child"); + return NULL; + } + + if ( n->GetNext() ) + { + ReportError("windowlabel can only have a single child"); + return NULL; + } + + wxObject* const item = CreateResFromNode(n, m_parent, NULL); + wxWindow* const wndLabel = wxDynamicCast(item, wxWindow); + if ( !wndLabel ) + { + ReportError(n, "windowlabel child must be a window"); + return NULL; + } + + box = new wxStaticBox(m_parentAsWindow, + GetID(), + wndLabel, + wxDefaultPosition, wxDefaultSize, + 0/*style*/, + GetName()); +#else // !wxHAS_WINDOW_LABEL_IN_STATIC_BOX + ReportError("Support for using windows as wxStaticBox labels is " + "missing in this build of wxWidgets."); + return NULL; +#endif // wxHAS_WINDOW_LABEL_IN_STATIC_BOX/!wxHAS_WINDOW_LABEL_IN_STATIC_BOX + } + else // Using plain text label. + { + box = new wxStaticBox(m_parentAsWindow, + GetID(), + labelText, + wxDefaultPosition, wxDefaultSize, + 0/*style*/, + GetName()); + } + + return new wxStaticBoxSizer(box, GetStyle(wxS("orient"), wxHORIZONTAL)); } #endif // wxUSE_STATBOX