Files
wxWidgets/src/msw/statusbar.cpp
Vadim Zeitlin 8e76e93199 Set the width of the last status bar pane correctly in wxMSW.
The total width of status bar panes must add up to the size of the status bar
as otherwise an extra unwanted border is drawn after the last pane and we did
have this border for status bar with a size grip.

So while we still use the width without the size grip for calculating the
fields widths, pass the width with the size grip to Windows to prevent this
from happening.

Also, don't pretend that the last field stretches up to the status bar edge
when it should end before the size grip and Windows even already helpfully
does it for us.

Closes #12655.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@66236 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2010-11-22 12:48:47 +00:00

656 lines
19 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: src/msw/statusbar.cpp
// Purpose: native implementation of wxStatusBar
// Author: Vadim Zeitlin
// Modified by:
// Created: 04.04.98
// RCS-ID: $Id$
// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#if wxUSE_STATUSBAR && wxUSE_NATIVE_STATUSBAR
#include "wx/statusbr.h"
#ifndef WX_PRECOMP
#include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
#include "wx/frame.h"
#include "wx/settings.h"
#include "wx/dcclient.h"
#include "wx/intl.h"
#include "wx/log.h"
#include "wx/control.h"
#endif
#include "wx/msw/private.h"
#include "wx/tooltip.h"
#include <windowsx.h>
#if wxUSE_UXTHEME
#include "wx/msw/uxtheme.h"
#endif
// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------
namespace
{
// no idea for a default width, just choose something
static const int DEFAULT_FIELD_WIDTH = 25;
} // anonymous namespace
// ----------------------------------------------------------------------------
// macros
// ----------------------------------------------------------------------------
// windowsx.h and commctrl.h don't define those, so we do it here
#define StatusBar_SetParts(h, n, w) SendMessage(h, SB_SETPARTS, (WPARAM)n, (LPARAM)w)
#define StatusBar_SetText(h, n, t) SendMessage(h, SB_SETTEXT, (WPARAM)n, (LPARAM)(LPCTSTR)t)
#define StatusBar_GetTextLen(h, n) LOWORD(SendMessage(h, SB_GETTEXTLENGTH, (WPARAM)n, 0))
#define StatusBar_GetText(h, n, s) LOWORD(SendMessage(h, SB_GETTEXT, (WPARAM)n, (LPARAM)(LPTSTR)s))
// ============================================================================
// implementation
// ============================================================================
// ----------------------------------------------------------------------------
// wxStatusBar class
// ----------------------------------------------------------------------------
wxStatusBar::wxStatusBar()
{
SetParent(NULL);
m_hWnd = 0;
m_windowId = 0;
m_pDC = NULL;
}
WXDWORD wxStatusBar::MSWGetStyle(long style, WXDWORD *exstyle) const
{
WXDWORD msStyle = wxStatusBarBase::MSWGetStyle(style, exstyle);
// wxSTB_SIZEGRIP is part of our default style but it doesn't make sense to
// show size grip if this is the status bar of a non-resizeable TLW so turn
// it off in such case
wxWindow * const parent = GetParent();
wxCHECK_MSG( parent, msStyle, wxS("Status bar must have a parent") );
if ( parent->IsTopLevel() && !parent->HasFlag(wxRESIZE_BORDER) )
style &= ~wxSTB_SIZEGRIP;
// setting SBARS_SIZEGRIP is perfectly useless: it's always on by default
// (at least in the version of comctl32.dll I'm using), and the only way to
// turn it off is to use CCS_TOP style - as we position the status bar
// manually anyhow (see DoMoveWindow), use CCS_TOP style if wxSTB_SIZEGRIP
// is not given
if ( !(style & wxSTB_SIZEGRIP) )
{
msStyle |= CCS_TOP;
}
else
{
#ifndef __WXWINCE__
// may be some versions of comctl32.dll do need it - anyhow, it won't
// do any harm
msStyle |= SBARS_SIZEGRIP;
#endif
}
return msStyle;
}
bool wxStatusBar::Create(wxWindow *parent,
wxWindowID id,
long style,
const wxString& name)
{
if ( !CreateControl(parent, id, wxDefaultPosition, wxDefaultSize,
style, wxDefaultValidator, name) )
return false;
if ( !MSWCreateControl(STATUSCLASSNAME, wxString(),
wxDefaultPosition, wxDefaultSize) )
return false;
SetFieldsCount(1);
// cache the DC instance used by DoUpdateStatusText:
m_pDC = new wxClientDC(this);
// we must refresh the frame size when the statusbar is created, because
// its client area might change
//
// notice that we must post the event, not send it, as the frame doesn't
// know that we're its status bar yet so laying it out right now wouldn't
// work correctly, we need to wait until we return to the main loop
PostSizeEventToParent();
return true;
}
wxStatusBar::~wxStatusBar()
{
// we must refresh the frame size when the statusbar is deleted but the
// frame is not - otherwise statusbar leaves a hole in the place it used to
// occupy
PostSizeEventToParent();
// delete existing tooltips
for (size_t i=0; i<m_tooltips.size(); i++)
{
wxDELETE(m_tooltips[i]);
}
wxDELETE(m_pDC);
}
bool wxStatusBar::SetFont(const wxFont& font)
{
if (!wxWindow::SetFont(font))
return false;
if (m_pDC) m_pDC->SetFont(font);
return true;
}
void wxStatusBar::SetFieldsCount(int nFields, const int *widths)
{
// this is a Windows limitation
wxASSERT_MSG( (nFields > 0) && (nFields < 255), "too many fields" );
wxStatusBarBase::SetFieldsCount(nFields, widths);
MSWUpdateFieldsWidths();
// keep in synch also our m_tooltips array
// reset all current tooltips
for (size_t i=0; i<m_tooltips.size(); i++)
{
wxDELETE(m_tooltips[i]);
}
// shrink/expand the array:
m_tooltips.resize(m_panes.GetCount(), NULL);
}
void wxStatusBar::SetStatusWidths(int n, const int widths[])
{
wxStatusBarBase::SetStatusWidths(n, widths);
MSWUpdateFieldsWidths();
}
void wxStatusBar::MSWUpdateFieldsWidths()
{
if ( m_panes.IsEmpty() )
return;
const int count = m_panes.GetCount();
const int extraWidth = MSWGetBorderWidth() + MSWGetMetrics().textMargin;
// compute the effectively available amount of space:
int widthAvailable = GetClientSize().x; // start with the entire width
widthAvailable -= extraWidth*(count - 1); // extra space between fields
widthAvailable -= MSWGetMetrics().textMargin; // and for the last field
// Deal with the grip: we shouldn't overflow onto the space occupied by it
// so the effectively available space is smaller.
const int gripWidth = HasFlag(wxSTB_SIZEGRIP) ? MSWGetMetrics().gripWidth
: 0;
widthAvailable -= gripWidth;
// distribute the available space (client width) among the various fields:
wxArrayInt widthsAbs = CalculateAbsWidths(widthAvailable);
// update the field widths in the native control:
int *pWidths = new int[count];
int nCurPos = 0;
for ( int i = 0; i < count; i++ )
{
nCurPos += widthsAbs[i] + extraWidth;
pWidths[i] = nCurPos;
}
// The total width of the panes passed to Windows must be equal to the
// total width available, including the grip. Otherwise we get an extra
// separator line just before it.
pWidths[count - 1] += gripWidth;
if ( !StatusBar_SetParts(GetHwnd(), count, pWidths) )
{
wxLogLastError("StatusBar_SetParts");
}
delete [] pWidths;
// FIXME: we may want to call DoUpdateStatusText() here since we may need to (de)ellipsize status texts
}
void wxStatusBar::DoUpdateStatusText(int nField)
{
if (!m_pDC)
return;
// Get field style, if any
int style;
switch(m_panes[nField].GetStyle())
{
case wxSB_RAISED:
style = SBT_POPOUT;
break;
case wxSB_FLAT:
style = SBT_NOBORDERS;
break;
case wxSB_NORMAL:
default:
style = 0;
break;
}
wxRect rc;
GetFieldRect(nField, rc);
const int maxWidth = rc.GetWidth() - MSWGetMetrics().textMargin;
wxString text = GetStatusText(nField);
// do we need to ellipsize this string?
wxEllipsizeMode ellmode = (wxEllipsizeMode)-1;
if (HasFlag(wxSTB_ELLIPSIZE_START)) ellmode = wxELLIPSIZE_START;
else if (HasFlag(wxSTB_ELLIPSIZE_MIDDLE)) ellmode = wxELLIPSIZE_MIDDLE;
else if (HasFlag(wxSTB_ELLIPSIZE_END)) ellmode = wxELLIPSIZE_END;
if (ellmode == (wxEllipsizeMode)-1)
{
// if we have the wxSTB_SHOW_TIPS we must set the ellipsized flag even if
// we don't ellipsize the text but just truncate it
if (HasFlag(wxSTB_SHOW_TIPS))
SetEllipsizedFlag(nField, m_pDC->GetTextExtent(text).GetWidth() > maxWidth);
}
else
{
text = wxControl::Ellipsize(text,
*m_pDC,
ellmode,
maxWidth,
wxELLIPSIZE_FLAGS_EXPAND_TABS);
// update the ellipsization status for this pane; this is used later to
// decide whether a tooltip should be shown or not for this pane
// (if we have wxSTB_SHOW_TIPS)
SetEllipsizedFlag(nField, text != GetStatusText(nField));
}
// Set the status text in the native control passing both field number and style.
// NOTE: MSDN library doesn't mention that nField and style have to be 'ORed'
if ( !StatusBar_SetText(GetHwnd(), nField | style, text.wx_str()) )
{
wxLogLastError("StatusBar_SetText");
}
if (HasFlag(wxSTB_SHOW_TIPS))
{
wxASSERT(m_tooltips.size() == m_panes.GetCount());
if (m_tooltips[nField])
{
if (GetField(nField).IsEllipsized())
{
// update the rect of this tooltip:
m_tooltips[nField]->SetRect(rc);
// update also the text:
m_tooltips[nField]->SetTip(GetStatusText(nField));
}
else
{
// delete the tooltip associated with this pane; it's not needed anymore
wxDELETE(m_tooltips[nField]);
}
}
else
{
// create a new tooltip for this pane if needed
if (GetField(nField).IsEllipsized())
m_tooltips[nField] = new wxToolTip(this, nField, GetStatusText(nField), rc);
//else: leave m_tooltips[nField]==NULL
}
}
}
wxStatusBar::MSWBorders wxStatusBar::MSWGetBorders() const
{
int aBorders[3];
SendMessage(GetHwnd(), SB_GETBORDERS, 0, (LPARAM)aBorders);
MSWBorders borders;
borders.horz = aBorders[0];
borders.vert = aBorders[1];
borders.between = aBorders[2];
return borders;
}
int wxStatusBar::GetBorderX() const
{
return MSWGetBorders().horz;
}
int wxStatusBar::GetBorderY() const
{
return MSWGetBorders().vert;
}
int wxStatusBar::MSWGetBorderWidth() const
{
return MSWGetBorders().between;
}
/* static */
const wxStatusBar::MSWMetrics& wxStatusBar::MSWGetMetrics()
{
static MSWMetrics s_metrics = { 0 };
if ( !s_metrics.textMargin )
{
// Grip size should be self explanatory (the only problem with it is
// that it's hard coded as we don't know how to find its size using
// API) but the margin might merit an explanation: Windows offsets the
// text drawn in status bar panes so we need to take this extra margin
// into account to make sure the text drawn by user fits inside the
// pane. Notice that it's not the value returned by SB_GETBORDERS
// which, at least on this Windows 2003 system, returns {0, 2, 2}
if ( wxUxThemeEngine::GetIfActive() )
{
s_metrics.gripWidth = 20;
s_metrics.textMargin = 8;
}
else // classic/unthemed look
{
s_metrics.gripWidth = 18;
s_metrics.textMargin = 4;
}
}
return s_metrics;
}
void wxStatusBar::SetMinHeight(int height)
{
SendMessage(GetHwnd(), SB_SETMINHEIGHT, height + 2*GetBorderY(), 0);
// we have to send a (dummy) WM_SIZE to redraw it now
SendMessage(GetHwnd(), WM_SIZE, 0, 0);
}
bool wxStatusBar::GetFieldRect(int i, wxRect& rect) const
{
wxCHECK_MSG( (i >= 0) && ((size_t)i < m_panes.GetCount()), false,
"invalid statusbar field index" );
RECT r;
if ( !::SendMessage(GetHwnd(), SB_GETRECT, i, (LPARAM)&r) )
{
wxLogLastError("SendMessage(SB_GETRECT)");
}
#if wxUSE_UXTHEME
wxUxThemeHandle theme(const_cast<wxStatusBar*>(this), L"Status");
if ( theme )
{
// by default Windows has a 2 pixel border to the right of the left
// divider (or it could be a bug) but it looks wrong so remove it
if ( i != 0 )
{
r.left -= 2;
}
wxUxThemeEngine::Get()->GetThemeBackgroundContentRect(theme, NULL,
1 /* SP_PANE */, 0,
&r, &r);
}
#endif
wxCopyRECTToRect(r, rect);
return true;
}
wxSize wxStatusBar::DoGetBestSize() const
{
const MSWBorders borders = MSWGetBorders();
// calculate width
int width = 0;
for ( size_t i = 0; i < m_panes.GetCount(); ++i )
{
int widthField =
m_bSameWidthForAllPanes ? DEFAULT_FIELD_WIDTH : m_panes[i].GetWidth();
if ( widthField >= 0 )
{
width += widthField;
}
else
{
// variable width field, its width is really a proportion
// related to the other fields
width += -widthField*DEFAULT_FIELD_WIDTH;
}
// add the space between fields
width += borders.between;
}
if ( !width )
{
// still need something even for an empty status bar
width = 2*DEFAULT_FIELD_WIDTH;
}
// calculate height
int height;
wxGetCharSize(GetHWND(), NULL, &height, GetFont());
height = EDIT_HEIGHT_FROM_CHAR_HEIGHT(height);
height += borders.vert;
wxSize best(width, height);
CacheBestSize(best);
return best;
}
void wxStatusBar::DoMoveWindow(int x, int y, int width, int height)
{
if ( GetParent()->IsSizeDeferred() )
{
wxWindowMSW::DoMoveWindow(x, y, width, height);
}
else
{
// parent pos/size isn't deferred so do it now but don't send
// WM_WINDOWPOSCHANGING since we don't want to change pos/size later
// we must use SWP_NOCOPYBITS here otherwise it paints incorrectly
// if other windows are size deferred
::SetWindowPos(GetHwnd(), NULL, x, y, width, height,
SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE
#ifndef __WXWINCE__
| SWP_NOCOPYBITS | SWP_NOSENDCHANGING
#endif
);
}
// adjust fields widths to the new size
MSWUpdateFieldsWidths();
// we have to trigger wxSizeEvent if there are children window in status
// bar because GetFieldRect returned incorrect (not updated) values up to
// here, which almost certainly resulted in incorrectly redrawn statusbar
if ( m_children.GetCount() > 0 )
{
wxSizeEvent event(GetClientSize(), m_windowId);
event.SetEventObject(this);
HandleWindowEvent(event);
}
}
void wxStatusBar::SetStatusStyles(int n, const int styles[])
{
wxStatusBarBase::SetStatusStyles(n, styles);
if (n != (int)m_panes.GetCount())
return;
for (int i = 0; i < n; i++)
{
int style;
switch(styles[i])
{
case wxSB_RAISED:
style = SBT_POPOUT;
break;
case wxSB_FLAT:
style = SBT_NOBORDERS;
break;
case wxSB_NORMAL:
default:
style = 0;
break;
}
// The SB_SETTEXT message is both used to set the field's text as well as
// the fields' styles.
// NOTE: MSDN library doesn't mention that nField and style have to be 'ORed'
wxString text = GetStatusText(i);
if (!StatusBar_SetText(GetHwnd(), style | i, text.wx_str()))
{
wxLogLastError("StatusBar_SetText");
}
}
}
WXLRESULT
wxStatusBar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
#ifndef __WXWINCE__
if ( nMsg == WM_WINDOWPOSCHANGING )
{
WINDOWPOS *lpPos = (WINDOWPOS *)lParam;
int x, y, w, h;
GetPosition(&x, &y);
GetSize(&w, &h);
// we need real window coords and not wx client coords
AdjustForParentClientOrigin(x, y);
lpPos->x = x;
lpPos->y = y;
lpPos->cx = w;
lpPos->cy = h;
return 0;
}
if ( nMsg == WM_NCLBUTTONDOWN )
{
// if hit-test is on gripper then send message to TLW to begin
// resizing. It is possible to send this message to any window.
if ( wParam == HTBOTTOMRIGHT )
{
wxWindow *win;
for ( win = GetParent(); win; win = win->GetParent() )
{
if ( win->IsTopLevel() )
{
SendMessage(GetHwndOf(win), WM_NCLBUTTONDOWN,
wParam, lParam);
return 0;
}
}
}
}
#endif
bool needsEllipsization = HasFlag(wxSTB_ELLIPSIZE_START) ||
HasFlag(wxSTB_ELLIPSIZE_MIDDLE) ||
HasFlag(wxSTB_ELLIPSIZE_END);
if ( nMsg == WM_SIZE && needsEllipsization )
{
for (int i=0; i<GetFieldsCount(); i++)
DoUpdateStatusText(i);
// re-set the field text, in case we need to ellipsize
// (or de-ellipsize) some parts of it
}
return wxStatusBarBase::MSWWindowProc(nMsg, wParam, lParam);
}
#if wxUSE_TOOLTIPS
bool wxStatusBar::MSWProcessMessage(WXMSG* pMsg)
{
if ( HasFlag(wxSTB_SHOW_TIPS) )
{
// for a tooltip to be shown, we need to relay mouse events to it;
// this is typically done by wxWindowMSW::MSWProcessMessage but only
// if wxWindow::m_tooltip pointer is non-NULL.
// Since wxStatusBar has multiple tooltips for a single HWND, it keeps
// wxWindow::m_tooltip == NULL and then relays mouse events here:
MSG *msg = (MSG *)pMsg;
if ( msg->message == WM_MOUSEMOVE )
wxToolTip::RelayEvent(pMsg);
}
return wxWindow::MSWProcessMessage(pMsg);
}
bool wxStatusBar::MSWOnNotify(int WXUNUSED(idCtrl), WXLPARAM lParam, WXLPARAM* WXUNUSED(result))
{
if ( HasFlag(wxSTB_SHOW_TIPS) )
{
// see comment in wxStatusBar::MSWProcessMessage for more info;
// basically we need to override wxWindow::MSWOnNotify because
// we have wxWindow::m_tooltip always NULL but we still use tooltips...
NMHDR* hdr = (NMHDR *)lParam;
wxString str;
if (hdr->idFrom < m_tooltips.size() && m_tooltips[hdr->idFrom])
str = m_tooltips[hdr->idFrom]->GetTip();
if ( HandleTooltipNotify(hdr->code, lParam, str))
{
// processed
return true;
}
}
return false;
}
#endif // wxUSE_TOOLTIPS
#endif // wxUSE_STATUSBAR && wxUSE_NATIVE_STATUSBAR