Refactor owner-drawing code.

Only keep common code in the base class and extract all menu/listbox-specific
stuff into derived classes.

This makes the code cleaner and more maintainable but introduces some problems
in wxCheckListBox appearance which will be fixed by the next patch.

Closes #10635.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@63220 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2010-01-23 13:21:12 +00:00
parent 937d5b6075
commit 98fbab9e7b
26 changed files with 1638 additions and 1394 deletions

View File

@@ -45,6 +45,7 @@
#endif // wxUSE_ACCEL
#include "wx/msw/private.h"
#include "wx/msw/dc.h"
#ifdef __WXWINCE__
// Implemented in menu.cpp
@@ -58,17 +59,25 @@ UINT GetMenuState(HMENU hMenu, UINT id, UINT flags) ;
// hide the ugly cast
#define GetHMenuOf(menu) ((HMENU)menu->GetHMenu())
// conditional compilation
#if wxUSE_OWNER_DRAWN
#define OWNER_DRAWN_ONLY( code ) if ( IsOwnerDrawn() ) code
#else // !wxUSE_OWNER_DRAWN
#define OWNER_DRAWN_ONLY( code )
#endif // wxUSE_OWNER_DRAWN/!wxUSE_OWNER_DRAWN
// ============================================================================
// implementation
// ============================================================================
#if wxUSE_OWNER_DRAWN
#include "wx/fontutil.h"
#include "wx/msw/private/metrics.h"
#ifndef SPI_GETKEYBOARDCUES
#define SPI_GETKEYBOARDCUES 0x100A
#endif
#ifndef DSS_HIDEPREFIX
#define DSS_HIDEPREFIX 0x0200
#endif
#endif // wxUSE_OWNER_DRAWN
// ----------------------------------------------------------------------------
// dynamic classes implementation
// ----------------------------------------------------------------------------
@@ -120,6 +129,17 @@ IMPLEMENT_DYNAMIC_CLASS(wxMenuItem, wxObject)
// wxMenuItem
// ----------------------------------------------------------------------------
#if wxUSE_OWNER_DRAWN
// these static variables are from the wxMenuItem object for cache
// system settings returned by the Win32 API's SystemParametersInfo() call
wxFont wxMenuItem::ms_systemMenuFont;
size_t wxMenuItem::ms_systemMenuHeight = 0;
bool wxMenuItem::ms_alwaysShowCues = false;
#endif // wxUSE_OWNER_DRAWN
// ctor & dtor
// -----------
@@ -130,9 +150,6 @@ wxMenuItem::wxMenuItem(wxMenu *pParentMenu,
wxItemKind kind,
wxMenu *pSubMenu)
: wxMenuItemBase(pParentMenu, id, text, strHelp, kind, pSubMenu)
#if wxUSE_OWNER_DRAWN
, wxOwnerDrawn(text, kind == wxITEM_CHECK, true)
#endif // owner drawn
{
Init();
}
@@ -146,9 +163,6 @@ wxMenuItem::wxMenuItem(wxMenu *parentMenu,
wxMenu *subMenu)
: wxMenuItemBase(parentMenu, id, text, help,
isCheckable ? wxITEM_CHECK : wxITEM_NORMAL, subMenu)
#if wxUSE_OWNER_DRAWN
, wxOwnerDrawn(text, isCheckable, true)
#endif // owner drawn
{
Init();
}
@@ -161,6 +175,24 @@ void wxMenuItem::Init()
#if wxUSE_OWNER_DRAWN
// init static varaibles
if ( !ms_systemMenuHeight )
{
const NONCLIENTMETRICS& metrics = wxMSWImpl::GetNonClientMetrics();
ms_systemMenuFont = wxFont(wxNativeFontInfo(metrics.lfMenuFont));
ms_systemMenuHeight = metrics.iMenuHeight;
if ( ::SystemParametersInfo(SPI_GETKEYBOARDCUES, 0,
&ms_alwaysShowCues, 0) == 0 )
{
// if it's not supported, we must be on an old Windows version
// which always shows them
ms_alwaysShowCues = true;
}
}
// when the color is not valid, wxOwnerDraw takes the default ones.
// If we set the colors here and they are changed by the user during
// the execution, then the colors are not updated until the application
@@ -169,14 +201,12 @@ void wxMenuItem::Init()
SetBackgroundColour(wxNullColour);
// setting default colors switched ownerdraw on: switch it off again
ResetOwnerDrawn();
SetOwnerDrawn(false);
// switch ownerdraw back on if using a non default margin
if ( !IsSeparator() )
SetMarginWidth(GetMarginWidth());
// tell the owner drawing code to show the accel string as well
SetAccelString(m_text.AfterFirst(wxT('\t')));
#endif // wxUSE_OWNER_DRAWN
}
@@ -365,15 +395,6 @@ void wxMenuItem::SetItemLabel(const wxString& txt)
// wxMenuItemBase will do stock ID checks
wxMenuItemBase::SetItemLabel(text);
// m_text could now be different from 'text' if we are a stock menu item,
// so use only m_text below
OWNER_DRAWN_ONLY( wxOwnerDrawn::SetName(m_text) );
#if wxUSE_OWNER_DRAWN
// tell the owner drawing code to to show the accel string as well
SetAccelString(m_text.AfterFirst(wxT('\t')));
#endif
// the item can be not attached to any menu yet and SetItemLabel() is still
// valid to call in this case and should do nothing else
if ( !m_parentMenu )
@@ -430,12 +451,318 @@ void wxMenuItem::SetItemLabel(const wxString& txt)
}
}
void wxMenuItem::SetCheckable(bool checkable)
#if wxUSE_OWNER_DRAWN
wxString wxMenuItem::GetName() const
{
wxMenuItemBase::SetCheckable(checkable);
OWNER_DRAWN_ONLY( wxOwnerDrawn::SetCheckable(checkable) );
return GetItemLabelText();
}
bool wxMenuItem::OnMeasureItem(size_t *width, size_t *height)
{
if ( IsOwnerDrawn() )
{
wxString str = GetName();
// if we have a valid accel string, then pad out
// the menu string so that the menu and accel string are not
// placed on top of each other.
wxString accel = GetItemLabel().AfterFirst(wxT('\t'));
if ( !accel.empty() )
{
str.Pad(str.length()%8);
str += accel;
}
wxMemoryDC dc;
wxFont font;
GetFontToUse(font);
dc.SetFont(font);
wxCoord w, h;
dc.GetTextExtent(str, &w, &h);
*width = w;
*height = h;
}
else // don't draw the text, just the bitmap (if any)
{
*width = 0;
*height = 0;
}
// increase size to accommodate bigger bitmaps if necessary
if (m_bmpChecked.Ok())
{
// Is BMP height larger than text height?
size_t adjustedHeight = m_bmpChecked.GetHeight();
if ( *height < adjustedHeight )
*height = adjustedHeight;
const int widthBmp = m_bmpChecked.GetWidth();
if ( IsOwnerDrawn() )
{
// widen the margin to fit the bitmap if necessary
if ( GetMarginWidth() < widthBmp )
SetMarginWidth(widthBmp);
}
else // we must allocate enough space for the bitmap
{
*width += widthBmp;
}
}
// add a 4-pixel separator, otherwise menus look cluttered
*width += 4;
// notice that this adjustment must be done after (possibly) changing the
// margin width above
if ( IsOwnerDrawn() )
{
// add space at the end of the menu for the submenu expansion arrow
// this will also allow offsetting the accel string from the right edge
*width += GetMarginWidth() + 16;
}
// make sure that this item is at least as tall as the system menu height
if ( *height < ms_systemMenuHeight )
*height = ms_systemMenuHeight;
return true;
}
bool wxMenuItem::OnDrawItem(wxDC& dc, const wxRect& rc,
wxODAction WXUNUSED(act), wxODStatus stat)
{
// this flag determines whether or not an edge will
// be drawn around the bitmap. In most "windows classic"
// applications, a 1-pixel highlight edge is drawn around
// the bitmap of an item when it is selected. However,
// with the new "luna" theme, no edge is drawn around
// the bitmap because the background is white (this applies
// only to "non-XP style" menus w/ bitmaps --
// see IE 6 menus for an example)
bool draw_bitmap_edge = true;
// set the colors
// --------------
wxColour colText1, colBack1;
GetColourToUse(stat, colText1, colBack1);
DWORD colText = wxColourToPalRGB(colText1);
DWORD colBack = wxColourToPalRGB(colBack1);
if ( IsOwnerDrawn() )
{
// don't draw an edge around the bitmap, if background is white ...
DWORD menu_bg_color = GetSysColor(COLOR_MENU);
if ( GetRValue( menu_bg_color ) >= 0xf0 &&
GetGValue( menu_bg_color ) >= 0xf0 &&
GetBValue( menu_bg_color ) >= 0xf0 )
{
draw_bitmap_edge = false;
}
}
else // edge doesn't look well with default Windows drawing
{
draw_bitmap_edge = false;
}
wxMSWDCImpl *impl = (wxMSWDCImpl*) dc.GetImpl();
HDC hdc = GetHdcOf(*impl);
COLORREF colOldText = ::SetTextColor(hdc, colText);
COLORREF colOldBack = ::SetBkColor(hdc, colBack);
// *2, as in wxSYS_EDGE_Y
int margin = GetMarginWidth() + 2 * wxSystemSettings::GetMetric(wxSYS_EDGE_X);
// select the font and draw the text
// ---------------------------------
// determine where to draw and leave space for a check-mark.
// + 1 pixel to separate the edge from the highlight rectangle
int xText = rc.x + margin + 1;
// using native API because it recognizes '&'
if ( IsOwnerDrawn() )
{
int prevMode = SetBkMode(hdc, TRANSPARENT);
AutoHBRUSH hbr(colBack);
SelectInHDC selBrush(hdc, hbr);
RECT rectFill;
wxCopyRectToRECT(rc, rectFill);
if ( (stat & wxODSelected) && m_bmpChecked.Ok() && draw_bitmap_edge )
{
// only draw the highlight under the text, not under
// the bitmap or checkmark
rectFill.left = xText;
}
::FillRect(hdc, &rectFill, hbr);
// use default font if no font set
wxFont font;
GetFontToUse(font);
SelectInHDC selFont(hdc, GetHfontOf(font));
// item text name with menemonic
wxString text = GetItemLabel().BeforeFirst('\t');
xText += 3; // separate text from the highlight rectangle
SIZE textRect;
::GetTextExtentPoint32(hdc, text.c_str(), text.length(), &textRect);
int flags = DST_PREFIXTEXT;
if ( (stat & wxODDisabled) && !(stat & wxODSelected) )
flags |= DSS_DISABLED;
if ( (stat & wxODHidePrefix) && !ms_alwaysShowCues )
flags |= DSS_HIDEPREFIX;
int x = xText;
int y = rc.y + (rc.GetHeight() - textRect.cy) / 2;
int cx = rc.GetWidth() - GetMarginWidth();
int cy = textRect.cy;
::DrawState(hdc, NULL, NULL, (LPARAM)text.wx_str(),
text.length(), x, y, cx, cy, flags);
// ::SetTextAlign(hdc, TA_RIGHT) doesn't work with DSS_DISABLED or DSS_MONO
// as the last parameter in DrawState() (at least with Windows98). So we have
// to take care of right alignment ourselves.
wxString accel = GetItemLabel().AfterFirst(wxT('\t'));
if ( !accel.empty() )
{
SIZE accelRect;
::GetTextExtentPoint32(hdc, accel.c_str(), accel.length(), &accelRect);
int flags = DST_TEXT;
if ( (stat & wxODDisabled) && !(stat & wxODSelected) )
flags |= DSS_DISABLED;
// right align accel string with right edge of menu
// (offset by the margin width)
int x = rc.GetWidth() - 16 - accelRect.cx;
int y = rc.y + (rc.GetHeight() - accelRect.cy) / 2;
::DrawState(hdc, NULL, NULL, (LPARAM)accel.wx_str(),
accel.length(), x, y, 0, 0, flags);
}
::SetBkMode(hdc, prevMode);
}
// draw the bitmap
// ---------------
if ( IsCheckable() && !m_bmpChecked.Ok() )
{
if ( stat & wxODChecked )
{
// what goes on: DrawFrameControl creates a b/w mask,
// then we copy it to screen to have right colors
// first create a monochrome bitmap in a memory DC
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmpCheck = CreateBitmap(margin, rc.GetHeight(), 1, 1, 0);
SelectObject(hdcMem, hbmpCheck);
// then draw a check mark into it
RECT rect = { 0, 0, margin, rc.GetHeight() };
if ( rc.GetHeight() > 0 )
{
::DrawFrameControl(hdcMem, &rect, DFC_MENU, DFCS_MENUCHECK);
}
// finally copy it to screen DC and clean up
BitBlt(hdc, rc.x, rc.y, margin, rc.GetHeight(), hdcMem, 0, 0, SRCCOPY);
DeleteDC(hdcMem);
DeleteObject(hbmpCheck);
}
}
else
{
wxBitmap bmp;
if ( stat & wxODDisabled )
{
bmp = GetDisabledBitmap();
}
if ( !bmp.Ok() )
{
// for not checkable bitmaps we should always use unchecked one
// because their checked bitmap is not set
bmp = GetBitmap(!IsCheckable() || (stat & wxODChecked));
#if wxUSE_IMAGE
if ( bmp.Ok() && stat & wxODDisabled )
{
// we need to grey out the bitmap as we don't have any specific
// disabled bitmap
wxImage imgGrey = bmp.ConvertToImage().ConvertToGreyscale();
if ( imgGrey.Ok() )
bmp = wxBitmap(imgGrey);
}
#endif // wxUSE_IMAGE
}
if ( bmp.Ok() )
{
wxMemoryDC dcMem(&dc);
dcMem.SelectObjectAsSource(bmp);
// center bitmap
int nBmpWidth = bmp.GetWidth(),
nBmpHeight = bmp.GetHeight();
// there should be enough space!
wxASSERT((nBmpWidth <= rc.GetWidth()) && (nBmpHeight <= rc.GetHeight()));
int heightDiff = rc.GetHeight() - nBmpHeight;
dc.Blit(rc.x + (margin - nBmpWidth) / 2,
rc.y + heightDiff / 2,
nBmpWidth, nBmpHeight,
&dcMem, 0, 0, wxCOPY, true /* use mask */);
if ( ( stat & wxODSelected ) && !( stat & wxODDisabled ) && draw_bitmap_edge )
{
RECT rectBmp = { rc.GetLeft(), rc.GetTop(),
rc.GetLeft() + margin,
rc.GetTop() + rc.GetHeight() };
SetBkColor(hdc, colBack);
DrawEdge(hdc, &rectBmp, BDR_RAISEDINNER, BF_RECT);
}
}
}
::SetTextColor(hdc, colOldText);
::SetBkColor(hdc, colOldBack);
return true;
}
void wxMenuItem::GetFontToUse(wxFont& font) const
{
font = GetFont();
if ( !font.IsOk() )
font = ms_systemMenuFont;
}
#endif // wxUSE_OWNER_DRAWN
// ----------------------------------------------------------------------------
// wxMenuItemBase
// ----------------------------------------------------------------------------