Rewrite wxToolBar resizing logic in wxMSW

wxToolBar handled its size in a very unique way as it waited to be
resized and then set its size correctly from its own WM_SIZE handler.
This was very confusing and, worse, broke a very natural assumption that
after calling SetSize(size) on a window this window does have the
specified size -- which wasn't necessarily the case for wxToolBar,
resulting in problems such as the one in #18294.

The reason for doing it in such weird way is that the native toolbar
control is weird itself and uses a specific message (TB_AUTOSIZE) to
update its size and, moreover, doesn't always do it correctly (notably
for the vertical toolbars). It seems that we can make it work more or
less as wanted if we use TB_SETBUTTONSIZE _after_ TB_AUTOSIZE (why does
it need to be done in this order remains a complete mystery) and if we
correct the width of vertical toolbars in UpdateSize() (which is not
called from WM_SIZE handler but only from Realize() and other methods
modifying the toolbar, so it's not a problem for it to change the size).

The only known problem with this commit known at this time is that
stretchable separators in vertical don't work any longer, it seems that
the size passed to TB_SETBUTTONSIZE is just ignored in this case.
However stretchable toolbar separators are pretty rare nowadays and even
more so in vertical toolbars, so, arguably, this is not a big loss.

Also tweak the layout of the labels for the embedded controls to make
them more similar for the labels used for the normal tools and, notably,
allocate enough space if the label is longer than the control itself.

Closes #18294.
This commit is contained in:
Vadim Zeitlin
2019-02-24 18:20:47 +01:00
parent 7c8ba45705
commit 1a79610361

View File

@@ -165,17 +165,7 @@ public:
if ( IsControl() && !m_label.empty() )
{
// Create a control to render the control's label.
// It has the same witdh as the control.
wxSize size(control->GetSize().GetWidth(), wxDefaultCoord);
m_staticText = new wxStaticText
(
m_tbar,
wxID_ANY,
m_label,
wxDefaultPosition,
size,
wxALIGN_CENTRE | wxST_NO_AUTORESIZE
);
m_staticText = new wxStaticText(m_tbar, wxID_ANY, m_label);
}
else // no label
{
@@ -1057,6 +1047,7 @@ bool wxToolBar::Realize()
// this array will hold the indices of all controls in the toolbar
wxArrayInt controlIds;
int minReqHeight = 0;
bool lastWasRadio = false;
int i = 0;
for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
@@ -1071,16 +1062,11 @@ bool wxToolBar::Realize()
switch ( tool->GetStyle() )
{
case wxTOOL_STYLE_CONTROL:
if ( wxStaticText *staticText = tool->GetStaticText() )
{
staticText->Show(AreControlLabelsShown());
}
// Set separator width/height to fit the control width/height
// taking into account tool padding value.
// (height is not used but it is set for the sake of consistency).
if ( !IsVertical() )
{
const wxSize size = MSWGetFittingtSizeForControl(tool);
if ( minReqHeight < size.y )
minReqHeight = size.y;
button.iBitmap = size.x;
}
@@ -1197,6 +1183,15 @@ bool wxToolBar::Realize()
break;
}
if ( IsVertical() )
{
// MSDN says that TBSTATE_WRAP should be used for all buttons in
// vertical toolbars, so do it even if it doesn't seem to actually
// change anything in practice (including the problem with
// TB_AUTOSIZE mentioned in UpdateSize()).
button.fsState |= TBSTATE_WRAP;
}
lastWasRadio = isRadio;
i++;
@@ -1208,6 +1203,24 @@ bool wxToolBar::Realize()
}
// Make sure the toolbar is tall enough to fit the embedded controls, which
// may be taller than the buttons.
wxSize toolSize = GetToolSize();
if ( toolSize.y < minReqHeight )
{
toolSize.y = minReqHeight;
// Surprisingly, we need to send TB_AUTOSIZE before TB_SETBUTTONSIZE
// and not after it, as might be expected: otherwise, the button size
// remains unchanged (doing TB_AUTOSIZE later doesn't seem to do any
// harm but doesn't change the button size neither).
::SendMessage(GetHwnd(), TB_AUTOSIZE, 0, 0);
::SendMessage(GetHwnd(), TB_SETBUTTONSIZE,
0, MAKELPARAM(toolSize.x, toolSize.y));
}
// Adjust controls and stretchable spaces
// --------------------------------------
@@ -1243,58 +1256,52 @@ bool wxToolBar::Realize()
// good and wxGTK doesn't do it neither (and the code below can't
// deal with this case)
control->Hide();
if ( wxStaticText * const staticText = tool->GetStaticText() )
staticText->Hide();
continue;
}
control->Show();
wxStaticText * const staticText = tool->GetStaticText();
wxSize size = control->GetSize();
wxSize staticTextSize;
if ( staticText && staticText->IsShown() )
{
staticTextSize = staticText->GetSize();
staticTextSize.y += MARGIN_CONTROL_LABEL;
}
// position the control itself correctly vertically centering it on the
// icon area of the toolbar
int height = r.bottom - r.top - staticTextSize.y;
int diff = height - size.y;
if ( diff < 0 || !HasFlag(wxTB_TEXT) )
{
// not enough room for the static text
if ( staticText )
staticText->Hide();
// recalculate height & diff without the staticText control
height = r.bottom - r.top;
diff = height - size.y;
if ( diff < 0 )
{
// the control is too high, resize to fit
// Actually don't set the size, otherwise we can never fit
// the toolbar around the controls.
// control->SetSize(wxDefaultCoord, height - 2);
diff = 2;
}
}
else // enough space for both the control and the label
{
if ( staticText )
staticText->Show();
}
const wxSize controlSize = control->GetSize();
// Take also into account tool padding value.
control->Move(r.left + m_toolPacking/2, r.top + (diff + 1) / 2);
if ( staticText )
const int x = r.left + m_toolPacking/2;
const int height = r.bottom - r.top;
// Greater of control and its label widths.
int totalWidth = controlSize.x;
// Height of control and its label, if any, including the margin
// between them.
int totalHeight = controlSize.y;
if ( wxStaticText * const staticText = tool->GetStaticText() )
{
staticText->Move(r.left + m_toolPacking/2 + (size.x - staticTextSize.x)/2,
r.bottom - staticTextSize.y);
const bool shown = AreControlLabelsShown();
staticText->Show(shown);
if ( shown )
{
const wxSize staticTextSize = staticText->GetSize();
if ( staticTextSize.x > totalWidth )
totalWidth = staticTextSize.x;
// Center the static text horizontally for consistency with the
// button labels and position it below the control vertically.
staticText->Move(x + (totalWidth - staticTextSize.x)/2,
r.top + (height + controlSize.y
- staticTextSize.y
+ MARGIN_CONTROL_LABEL)/2);
totalHeight += staticTextSize.y + MARGIN_CONTROL_LABEL;
}
}
control->Move(x + (totalWidth - controlSize.x)/2,
r.top + (height - totalHeight)/2);
m_totalFixedSize += r.right - r.left;
}
@@ -1638,14 +1645,25 @@ void wxToolBar::SetRows(int nRows)
const bool enable = (!IsVertical() && m_maxRows == 1) ||
(IsVertical() && (size_t)m_maxRows == m_nButtons);
const LPARAM state = MAKELONG(enable ? TBSTATE_ENABLED : TBSTATE_HIDDEN, 0);
LPARAM state = enable ? TBSTATE_ENABLED : TBSTATE_HIDDEN;
if ( IsVertical() )
{
// As in Realize(), ensure that TBSTATE_WRAP is used for all the
// tools, including separators, in vertical toolbar, and here it does
// make a difference: without it, the following tools wouldn't be
// visible because they would be on the same row as the separator.
state |= TBSTATE_WRAP;
}
wxToolBarToolsList::compatibility_iterator node;
for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
{
wxToolBarTool * const tool = (wxToolBarTool*)node->GetData();
if ( tool->IsStretchableSpace() )
{
if ( !::SendMessage(GetHwnd(), TB_SETSTATE, tool->GetId(), state) )
if ( !::SendMessage(GetHwnd(), TB_SETSTATE,
tool->GetId(), MAKELONG(state, 0)) )
{
wxLogLastError(wxT("TB_SETSTATE (stretchable spacer)"));
}
@@ -1683,6 +1701,40 @@ void wxToolBar::UpdateSize()
{
wxPoint pos = GetPosition();
::SendMessage(GetHwnd(), TB_AUTOSIZE, 0, 0);
// TB_AUTOSIZE doesn't seem to work for vertical toolbars which it resizes
// to have the same width as a horizontal toolbar would have, even though
// we do use TBSTATE_WRAP which should make it start a new row after each
// tool. So override its width determination explicitly.
if ( IsVertical() )
{
// Find the widest tool in the toolbar.
int maxWidth = 0;
int i = 0;
for ( wxToolBarToolsList::compatibility_iterator node = m_tools.GetFirst();
node;
node = node->GetNext(), ++i )
{
wxToolBarTool * const tool = (wxToolBarTool*)node->GetData();
if ( tool->IsSeparator() )
{
// Somehow, separators get resized to have huge width, which
// probably explains why TB_AUTOSIZE doesn't work correctly for
// us in the first place. Because of this, don't take them into
// account: they can't be wider than any normal button, anyhow.
continue;
}
const RECT rc = wxGetTBItemRect(GetHwnd(), i);
const int width = rc.right - rc.left;
if ( width > maxWidth )
maxWidth = width;
}
SetSize(maxWidth, GetSize().y);
}
if (pos != GetPosition())
Move(pos);
@@ -1867,115 +1919,12 @@ void wxToolBar::OnEraseBackground(wxEraseEvent& event)
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
}
bool wxToolBar::HandleSize(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam)
bool wxToolBar::HandleSize(WXWPARAM WXUNUSED(wParam), WXLPARAM WXUNUSED(lParam))
{
// wait until we have some tools
if ( !GetToolsCount() )
return false;
// calculate our minor dimension ourselves - we're confusing the standard
// logic (TB_AUTOSIZE) with our horizontal toolbars and other hacks
// Find bounding box for all rows.
RECT r;
::SetRectEmpty(&r);
// Bounding box for single (current) row
RECT rcRow;
::SetRectEmpty(&rcRow);
int rowPosX = INT_MIN;
wxToolBarToolsList::compatibility_iterator node;
int i = 0;
for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
{
wxToolBarTool * const
tool = static_cast<wxToolBarTool *>(node->GetData());
if ( tool->IsToBeDeleted() )
continue;
// Skip hidden buttons
const RECT rcItem = wxGetTBItemRect(GetHwnd(), i);
if ( ::IsRectEmpty(&rcItem) )
{
i++;
continue;
}
if ( rcItem.top > rowPosX )
{
// We have the next row.
rowPosX = rcItem.top;
// Shift origin to (0, 0) to make it the same as for the total rect.
::OffsetRect(&rcRow, -rcRow.left, -rcRow.top);
// And update the bounding box for all rows.
::UnionRect(&r, &r, &rcRow);
// Reset the current row bounding box for the next row.
::SetRectEmpty(&rcRow);
}
// Separators shouldn't be taken into account as they are sometimes
// reported to have the width of the entire client area by the toolbar.
// And we know that they are not the biggest items in the toolbar in
// any case, so just skip them.
if( !tool->IsSeparator() )
{
// Update bounding box of current row
::UnionRect(&rcRow, &rcRow, &rcItem);
}
i++;
}
// Take into account the last row rectangle too.
::OffsetRect(&rcRow, -rcRow.left, -rcRow.top);
::UnionRect(&r, &r, &rcRow);
if ( !r.right )
return false;
int w, h;
if ( IsVertical() )
{
w = r.right - r.left;
h = HIWORD(lParam);
}
else
{
w = LOWORD(lParam);
if (HasFlag( wxTB_FLAT ))
h = r.bottom - r.top - 3;
else
h = r.bottom - r.top;
// Take control height into account
for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
{
wxToolBarTool * const
tool = static_cast<wxToolBarTool *>(node->GetData());
if (tool->IsControl())
{
int y = (tool->GetControl()->GetSize().y - 2); // -2 since otherwise control height + 4 (below) is too much
if (y > h)
h = y;
}
}
if ( m_maxRows )
{
// FIXME: hardcoded separator line height...
h += HasFlag(wxTB_NODIVIDER) ? 4 : 6;
h *= m_maxRows;
}
}
if ( MAKELPARAM(w, h) != lParam )
{
// size really changed
SetSize(w, h);
}
UpdateStretchableSpacersSize();
// message processed