Merge branch 'statbox-any-label'

Add support for using any wxWindow (and not just the equivalent of
wxStaticText) as wxStaticBox label.

See https://github.com/wxWidgets/wxWidgets/pull/650
This commit is contained in:
Vadim Zeitlin
2018-01-23 15:40:31 +01:00
19 changed files with 710 additions and 152 deletions

View File

@@ -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()

View File

@@ -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<wxControl>::Enable(enable);
}
// ----------------------------------------------------------------------------
// XTI
// ----------------------------------------------------------------------------

View File

@@ -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;

View File

@@ -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);

View File

@@ -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<wxStaticBoxBase>::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);

View File

@@ -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