Files
wxWidgets/src/msw/menuitem.cpp
Vadim Zeitlin 3f66f6a5b3 Remove all lines containing cvs/svn "$Id$" keyword.
This keyword is not expanded by Git which means it's not replaced with the
correct revision value in the releases made using git-based scripts and it's
confusing to have lines with unexpanded "$Id$" in the released files. As
expanding them with Git is not that simple (it could be done with git archive
and export-subst attribute) and there are not many benefits in having them in
the first place, just remove all these lines.

If nothing else, this will make an eventual transition to Git simpler.

Closes #14487.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@74602 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2013-07-26 16:02:46 +00:00

1265 lines
37 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: src/msw/menuitem.cpp
// Purpose: wxMenuItem implementation
// Author: Vadim Zeitlin
// Modified by:
// Created: 11.11.97
// 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_MENUS
#include "wx/menuitem.h"
#include "wx/stockitem.h"
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/dcmemory.h"
#include "wx/font.h"
#include "wx/bitmap.h"
#include "wx/settings.h"
#include "wx/window.h"
#include "wx/accel.h"
#include "wx/string.h"
#include "wx/log.h"
#include "wx/menu.h"
#endif
#if wxUSE_ACCEL
#include "wx/accel.h"
#endif // wxUSE_ACCEL
#include "wx/msw/private.h"
#include "wx/msw/dc.h"
#ifdef __WXWINCE__
// Implemented in menu.cpp
UINT GetMenuState(HMENU hMenu, UINT id, UINT flags) ;
#endif
#if wxUSE_UXTHEME
#include "wx/msw/uxtheme.h"
#endif
// ---------------------------------------------------------------------------
// macro
// ---------------------------------------------------------------------------
// hide the ugly cast
#define GetHMenuOf(menu) ((HMENU)menu->GetHMenu())
// ----------------------------------------------------------------------------
// helper classes for temporarily changing HDC parameters
// ----------------------------------------------------------------------------
namespace
{
// This class just stores an HDC.
class HDCHandler
{
protected:
HDCHandler(HDC hdc) : m_hdc(hdc) { }
const HDC m_hdc;
};
class HDCTextColChanger : HDCHandler
{
public:
HDCTextColChanger(HDC hdc, COLORREF col)
: HDCHandler(hdc),
m_colOld(::SetTextColor(hdc, col))
{
}
~HDCTextColChanger()
{
::SetTextColor(m_hdc, m_colOld);
}
private:
COLORREF m_colOld;
};
class HDCBgColChanger : HDCHandler
{
public:
HDCBgColChanger(HDC hdc, COLORREF col)
: HDCHandler(hdc),
m_colOld(::SetBkColor(hdc, col))
{
}
~HDCBgColChanger()
{
::SetBkColor(m_hdc, m_colOld);
}
private:
COLORREF m_colOld;
};
class HDCBgModeChanger : HDCHandler
{
public:
HDCBgModeChanger(HDC hdc, int mode)
: HDCHandler(hdc),
m_modeOld(::SetBkMode(hdc, mode))
{
}
~HDCBgModeChanger()
{
::SetBkMode(m_hdc, m_modeOld);
}
private:
int m_modeOld;
};
} // anonymous namespace
// ============================================================================
// 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
#if wxUSE_UXTHEME
enum MENUPARTS
{
MENU_MENUITEM_TMSCHEMA = 1,
MENU_SEPARATOR_TMSCHEMA = 6,
MENU_POPUPBACKGROUND = 9,
MENU_POPUPBORDERS = 10,
MENU_POPUPCHECK = 11,
MENU_POPUPCHECKBACKGROUND = 12,
MENU_POPUPGUTTER = 13,
MENU_POPUPITEM = 14,
MENU_POPUPSEPARATOR = 15,
MENU_POPUPSUBMENU = 16,
};
enum POPUPITEMSTATES
{
MPI_NORMAL = 1,
MPI_HOT = 2,
MPI_DISABLED = 3,
MPI_DISABLEDHOT = 4,
};
enum POPUPCHECKBACKGROUNDSTATES
{
MCB_DISABLED = 1,
MCB_NORMAL = 2,
MCB_BITMAP = 3,
};
enum POPUPCHECKSTATES
{
MC_CHECKMARKNORMAL = 1,
MC_CHECKMARKDISABLED = 2,
MC_BULLETNORMAL = 3,
MC_BULLETDISABLED = 4,
};
const int TMT_MENUFONT = 803;
const int TMT_BORDERSIZE = 2403;
const int TMT_CONTENTMARGINS = 3602;
const int TMT_SIZINGMARGINS = 3601;
#endif // wxUSE_UXTHEME
#endif // wxUSE_OWNER_DRAWN
// ----------------------------------------------------------------------------
// dynamic classes implementation
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// wxMenuItem
// ----------------------------------------------------------------------------
#if wxUSE_OWNER_DRAWN
namespace
{
// helper class to keep information about metrics and other stuff
// needed for measuring and drawing menu item
class MenuDrawData
{
public:
// Wrapper around standard MARGINS structure providing some helper
// functions and automatically initializing the margin fields to 0.
struct Margins : MARGINS
{
Margins()
{
cxLeftWidth =
cxRightWidth =
cyTopHeight =
cyBottomHeight = 0;
}
int GetTotalX() const { return cxLeftWidth + cxRightWidth; }
int GetTotalY() const { return cyTopHeight + cyBottomHeight; }
void ApplyTo(RECT& rect) const
{
rect.top += cyTopHeight;
rect.left += cxLeftWidth;
rect.right -= cyTopHeight;
rect.bottom -= cyBottomHeight;
}
void UnapplyFrom(RECT& rect) const
{
rect.top -= cyTopHeight;
rect.left -= cxLeftWidth;
rect.right += cyTopHeight;
rect.bottom += cyBottomHeight;
}
};
Margins ItemMargin; // popup item margins
Margins CheckMargin; // popup check margins
Margins CheckBgMargin; // popup check background margins
Margins ArrowMargin; // popup submenu arrow margins
Margins SeparatorMargin; // popup separator margins
SIZE CheckSize; // popup check size metric
SIZE ArrowSize; // popup submenu arrow size metric
SIZE SeparatorSize; // popup separator size metric
int TextBorder; // popup border space between
// item text and gutter
int AccelBorder; // popup border space between
// item text and accelerator
int ArrowBorder; // popup border space between
// item accelerator and submenu arrow
int Offset; // system added space at the end of the menu,
// add this offset for remove the extra space
wxFont Font; // default menu font
bool AlwaysShowCues; // must keyboard cues always be shown?
bool Theme; // is data initialized for FullTheme?
static const MenuDrawData* Get()
{
// notice that s_menuData can't be created as a global variable because
// it needs a window to initialize and no windows exist at the time of
// globals initialization yet
if ( !ms_instance )
{
static MenuDrawData s_menuData;
ms_instance = &s_menuData;
}
#if wxUSE_UXTHEME
bool theme = MenuLayout() == FullTheme;
if ( ms_instance->Theme != theme )
ms_instance->Init();
#endif // wxUSE_UXTHEME
return ms_instance;
}
MenuDrawData()
{
Init();
}
// get the theme engine or NULL if themes
// are not available or not supported on menu
static wxUxThemeEngine *GetUxThemeEngine()
{
#if wxUSE_UXTHEME
if ( MenuLayout() == FullTheme )
return wxUxThemeEngine::GetIfActive();
#endif // wxUSE_UXTHEME
return NULL;
}
enum MenuLayoutType
{
FullTheme, // full menu themes (Vista or new)
PseudoTheme, // pseudo menu themes (on XP)
Classic
};
static MenuLayoutType MenuLayout()
{
MenuLayoutType menu = Classic;
#if wxUSE_UXTHEME
if ( wxUxThemeEngine::GetIfActive() != NULL )
{
static wxWinVersion ver = wxGetWinVersion();
if ( ver >= wxWinVersion_Vista )
menu = FullTheme;
else if ( ver == wxWinVersion_XP )
menu = PseudoTheme;
}
#endif // wxUSE_UXTHEME
return menu;
}
private:
void Init();
static MenuDrawData* ms_instance;
};
MenuDrawData* MenuDrawData::ms_instance = NULL;
void MenuDrawData::Init()
{
#if wxUSE_UXTHEME
wxUxThemeEngine* theme = GetUxThemeEngine();
if ( theme )
{
wxWindow* window = static_cast<wxApp*>(wxApp::GetInstance())->GetTopWindow();
wxUxThemeHandle hTheme(window, L"MENU");
theme->GetThemeMargins(hTheme, NULL, MENU_POPUPITEM, 0,
TMT_CONTENTMARGINS, NULL,
&ItemMargin);
theme->GetThemeMargins(hTheme, NULL, MENU_POPUPCHECK, 0,
TMT_CONTENTMARGINS, NULL,
&CheckMargin);
theme->GetThemeMargins(hTheme, NULL, MENU_POPUPCHECKBACKGROUND, 0,
TMT_CONTENTMARGINS, NULL,
&CheckBgMargin);
theme->GetThemeMargins(hTheme, NULL, MENU_POPUPSUBMENU, 0,
TMT_CONTENTMARGINS, NULL,
&ArrowMargin);
theme->GetThemeMargins(hTheme, NULL, MENU_POPUPSEPARATOR, 0,
TMT_SIZINGMARGINS, NULL,
&SeparatorMargin);
theme->GetThemePartSize(hTheme, NULL, MENU_POPUPCHECK, 0,
NULL, TS_TRUE, &CheckSize);
theme->GetThemePartSize(hTheme, NULL, MENU_POPUPSUBMENU, 0,
NULL, TS_TRUE, &ArrowSize);
theme->GetThemePartSize(hTheme, NULL, MENU_POPUPSEPARATOR, 0,
NULL, TS_TRUE, &SeparatorSize);
theme->GetThemeInt(hTheme, MENU_POPUPBACKGROUND, 0, TMT_BORDERSIZE, &TextBorder);
AccelBorder = 34;
ArrowBorder = 0;
Offset = -14;
wxUxThemeFont themeFont;
theme->GetThemeSysFont(hTheme, TMT_MENUFONT, themeFont.GetPtr());
Font = wxFont(themeFont.GetLOGFONT());
Theme = true;
// native menu doesn't uses the vertical margins
ItemMargin.cyTopHeight =
ItemMargin.cyBottomHeight = 0;
// native menu uses small top margin for separator
if ( SeparatorMargin.cyTopHeight >= 2 )
SeparatorMargin.cyTopHeight -= 2;
}
else
#endif // wxUSE_UXTHEME
{
const NONCLIENTMETRICS& metrics = wxMSWImpl::GetNonClientMetrics();
CheckMargin.cxLeftWidth =
CheckMargin.cxRightWidth = ::GetSystemMetrics(SM_CXEDGE);
CheckMargin.cyTopHeight =
CheckMargin.cyBottomHeight = ::GetSystemMetrics(SM_CYEDGE);
CheckSize.cx = ::GetSystemMetrics(SM_CXMENUCHECK);
CheckSize.cy = ::GetSystemMetrics(SM_CYMENUCHECK);
ArrowSize = CheckSize;
// separator height with margins
int sepFullSize = metrics.iMenuHeight / 2;
SeparatorMargin.cxLeftWidth =
SeparatorMargin.cxRightWidth = 1;
SeparatorMargin.cyTopHeight =
SeparatorMargin.cyBottomHeight = sepFullSize / 2 - 1;
SeparatorSize.cx = 1;
SeparatorSize.cy = sepFullSize - SeparatorMargin.GetTotalY();
TextBorder = 0;
AccelBorder = 8;
ArrowBorder = 6;
Offset = -12;
Font = wxFont(wxNativeFontInfo(metrics.lfMenuFont));
Theme = false;
}
int value;
if ( ::SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &value, 0) == 0 )
{
// if it's not supported, we must be on an old Windows version
// which always shows them
value = 1;
}
AlwaysShowCues = value == 1;
}
} // anonymous namespace
#endif // wxUSE_OWNER_DRAWN
// ctor & dtor
// -----------
wxMenuItem::wxMenuItem(wxMenu *pParentMenu,
int id,
const wxString& text,
const wxString& strHelp,
wxItemKind kind,
wxMenu *pSubMenu)
: wxMenuItemBase(pParentMenu, id, text, strHelp, kind, pSubMenu)
{
Init();
}
#if WXWIN_COMPATIBILITY_2_8
wxMenuItem::wxMenuItem(wxMenu *parentMenu,
int id,
const wxString& text,
const wxString& help,
bool isCheckable,
wxMenu *subMenu)
: wxMenuItemBase(parentMenu, id, text, help,
isCheckable ? wxITEM_CHECK : wxITEM_NORMAL, subMenu)
{
Init();
}
#endif
void wxMenuItem::Init()
{
#if wxUSE_OWNER_DRAWN
// 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
// is restarted and our menus look bad
SetTextColour(wxNullColour);
SetBackgroundColour(wxNullColour);
// setting default colors switched ownerdraw on: switch it off again
SetOwnerDrawn(false);
// switch ownerdraw back on if using a non default margin
if ( !IsSeparator() )
SetMarginWidth(GetMarginWidth());
#endif // wxUSE_OWNER_DRAWN
}
wxMenuItem::~wxMenuItem()
{
}
// misc
// ----
// return the id for calling Win32 API functions
WXWPARAM wxMenuItem::GetMSWId() const
{
// we must use ids in unsigned short range with Windows functions, if we
// pass ids > USHRT_MAX to them they get very confused (e.g. start
// generating WM_COMMAND messages with negative high word of wParam), so
// use the cast to ensure the id is in range
return m_subMenu ? wxPtrToUInt(m_subMenu->GetHMenu())
: static_cast<unsigned short>(GetId());
}
// get item state
// --------------
bool wxMenuItem::IsChecked() const
{
// fix that RTTI is always getting the correct state (separators cannot be
// checked, but the Windows call below returns true
if ( IsSeparator() )
return false;
// the item might not be attached to a menu yet
//
// TODO: shouldn't we just always call the base class version? It seems
// like it ought to always be in sync
if ( !m_parentMenu )
return wxMenuItemBase::IsChecked();
HMENU hmenu = GetHMenuOf(m_parentMenu);
int flag = ::GetMenuState(hmenu, GetMSWId(), MF_BYCOMMAND);
return (flag & MF_CHECKED) != 0;
}
// change item state
// -----------------
void wxMenuItem::Enable(bool enable)
{
if ( m_isEnabled == enable )
return;
if ( m_parentMenu )
{
long rc = EnableMenuItem(GetHMenuOf(m_parentMenu),
GetMSWId(),
MF_BYCOMMAND |
(enable ? MF_ENABLED : MF_GRAYED));
if ( rc == -1 )
{
wxLogLastError(wxT("EnableMenuItem"));
}
}
wxMenuItemBase::Enable(enable);
}
void wxMenuItem::Check(bool check)
{
wxCHECK_RET( IsCheckable(), wxT("only checkable items may be checked") );
if ( m_isChecked == check )
return;
if ( m_parentMenu )
{
int flags = check ? MF_CHECKED : MF_UNCHECKED;
HMENU hmenu = GetHMenuOf(m_parentMenu);
if ( GetKind() == wxITEM_RADIO )
{
// it doesn't make sense to uncheck a radio item -- what would this
// do?
if ( !check )
return;
// get the index of this item in the menu
const wxMenuItemList& items = m_parentMenu->GetMenuItems();
int pos = items.IndexOf(this);
wxCHECK_RET( pos != wxNOT_FOUND,
wxT("menuitem not found in the menu items list?") );
// get the radio group range
int start,
end;
if ( !m_parentMenu->MSWGetRadioGroupRange(pos, &start, &end) )
{
wxFAIL_MSG( wxT("Menu radio item not part of radio group?") );
return;
}
#ifdef __WIN32__
// calling CheckMenuRadioItem() with such parameters hangs my system
// (NT4 SP6) and I suspect this could happen to the others as well,
// so don't do it!
wxCHECK_RET( start != -1 && end != -1,
wxT("invalid ::CheckMenuRadioItem() parameter(s)") );
if ( !::CheckMenuRadioItem(hmenu,
start, // the first radio group item
end, // the last one
pos, // the one to check
MF_BYPOSITION) )
{
wxLogLastError(wxT("CheckMenuRadioItem"));
}
#endif // __WIN32__
// also uncheck all the other items in this radio group
wxMenuItemList::compatibility_iterator node = items.Item(start);
for ( int n = start; n <= end && node; n++ )
{
if ( n != pos )
{
node->GetData()->m_isChecked = false;
}
node = node->GetNext();
}
}
else // check item
{
if ( ::CheckMenuItem(hmenu,
GetMSWId(),
MF_BYCOMMAND | flags) == (DWORD)-1 )
{
wxFAIL_MSG(wxT("CheckMenuItem() failed, item not in the menu?"));
}
}
}
wxMenuItemBase::Check(check);
}
void wxMenuItem::SetItemLabel(const wxString& txt)
{
wxString text = txt;
// don't do anything if label didn't change
if ( m_text == txt )
return;
// wxMenuItemBase will do stock ID checks
wxMenuItemBase::SetItemLabel(text);
// 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 )
return;
#if wxUSE_ACCEL
m_parentMenu->UpdateAccel(this);
#endif // wxUSE_ACCEL
const UINT id = GetMSWId();
HMENU hMenu = GetHMenuOf(m_parentMenu);
if ( !hMenu || ::GetMenuState(hMenu, id, MF_BYCOMMAND) == (UINT)-1 )
return;
// update the text of the native menu item
WinStruct<MENUITEMINFO> info;
// surprisingly, calling SetMenuItemInfo() with just MIIM_STRING doesn't
// work as it resets the menu bitmap, so we need to first get the old item
// state and then modify it
const bool isLaterThanWin95 = wxGetWinVersion() > wxWinVersion_95;
info.fMask = MIIM_STATE |
MIIM_ID |
MIIM_SUBMENU |
MIIM_CHECKMARKS |
MIIM_DATA;
if ( isLaterThanWin95 )
info.fMask |= MIIM_BITMAP | MIIM_FTYPE;
else
info.fMask |= MIIM_TYPE;
if ( !::GetMenuItemInfo(hMenu, id, FALSE, &info) )
{
wxLogLastError(wxT("GetMenuItemInfo"));
return;
}
#if wxUSE_OWNER_DRAWN
// Don't set the text for the owner drawn items, they don't use it and even
// though setting it doesn't seem to actually do any harm under Windows 7,
// avoid doing this relatively nonsensical operation just in case it does
// break something on other, past or future, Windows versions.
//
// Notice that we do need to call SetMenuItemInfo() even for the ownerdrawn
// items however as otherwise their size wouldn't be recalculated as
// WM_MEASUREITEM wouldn't be sent and this could result in display
// problems if the length of the menu item changed significantly.
if ( !IsOwnerDrawn() )
#endif // wxUSE_OWNER_DRAWN
{
if ( isLaterThanWin95 )
info.fMask |= MIIM_STRING;
//else: MIIM_TYPE already specified
info.dwTypeData = wxMSW_CONV_LPTSTR(m_text);
info.cch = m_text.length();
}
if ( !::SetMenuItemInfo(hMenu, id, FALSE, &info) )
{
wxLogLastError(wxT("SetMenuItemInfo"));
}
}
#if wxUSE_OWNER_DRAWN
int wxMenuItem::MeasureAccelWidth() const
{
wxString accel = GetItemLabel().AfterFirst(wxT('\t'));
wxMemoryDC dc;
wxFont font;
GetFontToUse(font);
dc.SetFont(font);
wxCoord w;
dc.GetTextExtent(accel, &w, NULL);
return w;
}
wxString wxMenuItem::GetName() const
{
return GetItemLabelText();
}
bool wxMenuItem::OnMeasureItem(size_t *width, size_t *height)
{
const MenuDrawData* data = MenuDrawData::Get();
if ( IsOwnerDrawn() )
{
*width = data->ItemMargin.GetTotalX();
*height = data->ItemMargin.GetTotalY();
if ( IsSeparator() )
{
*width += data->SeparatorSize.cx
+ data->SeparatorMargin.GetTotalX();
*height += data->SeparatorSize.cy
+ data->SeparatorMargin.GetTotalY();
return true;
}
wxString str = GetName();
wxMemoryDC dc;
wxFont font;
GetFontToUse(font);
dc.SetFont(font);
wxCoord w, h;
dc.GetTextExtent(str, &w, &h);
*width = data->TextBorder + w + data->AccelBorder;
*height = h;
w = m_parentMenu->GetMaxAccelWidth();
if ( w > 0 )
*width += w + data->ArrowBorder;
*width += data->Offset;
*width += data->ArrowMargin.GetTotalX() + data->ArrowSize.cx;
}
else // don't draw the text, just the bitmap (if any)
{
*width = 0;
*height = 0;
}
// bitmap
if ( IsOwnerDrawn() )
{
// width of menu icon with margins in ownerdrawn menu
// if any bitmap is not set, the width of space reserved for icon
// image is equal to the width of std check mark,
// if bitmap is set, then the width is set to the width of the widest
// bitmap in menu (GetMarginWidth()) unless std check mark is wider,
// then it's is set to std mark's width
int imgWidth = wxMax(GetMarginWidth(), data->CheckSize.cx)
+ data->CheckMargin.GetTotalX();
*width += imgWidth + data->CheckBgMargin.GetTotalX();
}
if ( m_bmpChecked.IsOk() || m_bmpUnchecked.IsOk() )
{
// get size of bitmap always return valid value (0 for invalid bitmap),
// so we don't needed check if bitmap is valid ;)
size_t heightBmp = wxMax(m_bmpChecked.GetHeight(), m_bmpUnchecked.GetHeight());
size_t widthBmp = wxMax(m_bmpChecked.GetWidth(), m_bmpUnchecked.GetWidth());
if ( IsOwnerDrawn() )
{
heightBmp += data->CheckMargin.GetTotalY();
}
else
{
// we must allocate enough space for the bitmap
*width += widthBmp;
}
// Is BMP height larger than text height?
if ( *height < heightBmp )
*height = heightBmp;
}
// make sure that this item is at least as tall as the system menu height
const size_t menuHeight = data->CheckMargin.GetTotalY()
+ data->CheckSize.cy;
if (*height < menuHeight)
*height = menuHeight;
return true;
}
bool wxMenuItem::OnDrawItem(wxDC& dc, const wxRect& rc,
wxODAction WXUNUSED(act), wxODStatus stat)
{
const MenuDrawData* data = MenuDrawData::Get();
wxMSWDCImpl *impl = (wxMSWDCImpl*) dc.GetImpl();
HDC hdc = GetHdcOf(*impl);
RECT rect;
wxCopyRectToRECT(rc, rect);
int imgWidth = wxMax(GetMarginWidth(), data->CheckSize.cx);
if ( IsOwnerDrawn() )
{
// font and colors to use
wxFont font;
GetFontToUse(font);
wxColour colText, colBack;
GetColourToUse(stat, colText, colBack);
// calculate metrics of item parts
RECT rcSelection = rect;
data->ItemMargin.ApplyTo(rcSelection);
RECT rcSeparator = rcSelection;
data->SeparatorMargin.ApplyTo(rcSeparator);
RECT rcGutter = rcSelection;
rcGutter.right = data->ItemMargin.cxLeftWidth
+ data->CheckBgMargin.cxLeftWidth
+ data->CheckMargin.cxLeftWidth
+ imgWidth
+ data->CheckMargin.cxRightWidth
+ data->CheckBgMargin.cxRightWidth;
RECT rcText = rcSelection;
rcText.left = rcGutter.right + data->TextBorder;
// we draw the text label vertically centered, but this results in it
// being 1px too low compared to native menus for some reason, fix it
if ( data->MenuLayout() != MenuDrawData::FullTheme )
rcText.top--;
#if wxUSE_UXTHEME
// If a custom background colour is explicitly specified, we should use
// it instead of the default theme background.
wxUxThemeEngine* const theme = GetBackgroundColour().IsOk()
? NULL
: MenuDrawData::GetUxThemeEngine();
if ( theme )
{
POPUPITEMSTATES state;
if ( stat & wxODDisabled )
{
state = (stat & wxODSelected) ? MPI_DISABLEDHOT
: MPI_DISABLED;
}
else if ( stat & wxODSelected )
{
state = MPI_HOT;
}
else
{
state = MPI_NORMAL;
}
wxUxThemeHandle hTheme(GetMenu()->GetWindow(), L"MENU");
if ( theme->IsThemeBackgroundPartiallyTransparent(hTheme,
MENU_POPUPITEM, state) )
{
theme->DrawThemeBackground(hTheme, hdc,
MENU_POPUPBACKGROUND,
0, &rect, NULL);
}
theme->DrawThemeBackground(hTheme, hdc, MENU_POPUPGUTTER,
0, &rcGutter, NULL);
if ( IsSeparator() )
{
rcSeparator.left = rcGutter.right;
theme->DrawThemeBackground(hTheme, hdc, MENU_POPUPSEPARATOR,
0, &rcSeparator, NULL);
return true;
}
theme->DrawThemeBackground(hTheme, hdc, MENU_POPUPITEM,
state, &rcSelection, NULL);
}
else
#endif // wxUSE_UXTHEME
{
if ( IsSeparator() )
{
DrawEdge(hdc, &rcSeparator, EDGE_ETCHED, BF_TOP);
return true;
}
AutoHBRUSH hbr(colBack.GetPixel());
SelectInHDC selBrush(hdc, hbr);
::FillRect(hdc, &rcSelection, hbr);
}
// draw text label
// using native API because it recognizes '&'
HDCTextColChanger changeTextCol(hdc, colText.GetPixel());
HDCBgColChanger changeBgCol(hdc, colBack.GetPixel());
HDCBgModeChanger changeBgMode(hdc, TRANSPARENT);
SelectInHDC selFont(hdc, GetHfontOf(font));
// item text name without mnemonic for calculating size
wxString text = GetName();
SIZE textSize;
::GetTextExtentPoint32(hdc, text.c_str(), text.length(), &textSize);
// item text name with mnemonic
text = GetItemLabel().BeforeFirst('\t');
int flags = DST_PREFIXTEXT;
// themes menu is using specified color for disabled labels
if ( data->MenuLayout() == MenuDrawData::Classic &&
(stat & wxODDisabled) && !(stat & wxODSelected) )
flags |= DSS_DISABLED;
if ( (stat & wxODHidePrefix) && !data->AlwaysShowCues )
flags |= DSS_HIDEPREFIX;
int x = rcText.left;
int y = rcText.top + (rcText.bottom - rcText.top - textSize.cy) / 2;
::DrawState(hdc, NULL, NULL, wxMSW_CONV_LPARAM(text),
text.length(), x, y, 0, 0, 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 accelSize;
::GetTextExtentPoint32(hdc, accel.c_str(), accel.length(), &accelSize);
int flags = DST_TEXT;
// themes menu is using specified color for disabled labels
if ( data->MenuLayout() == MenuDrawData::Classic &&
(stat & wxODDisabled) && !(stat & wxODSelected) )
flags |= DSS_DISABLED;
int x = rcText.right - data->ArrowMargin.GetTotalX()
- data->ArrowSize.cx
- data->ArrowBorder;
// right align accel on FullTheme menu, left otherwise
if ( data->MenuLayout() == MenuDrawData::FullTheme)
x -= accelSize.cx;
else
x -= m_parentMenu->GetMaxAccelWidth();
int y = rcText.top + (rcText.bottom - rcText.top - accelSize.cy) / 2;
::DrawState(hdc, NULL, NULL, wxMSW_CONV_LPARAM(accel),
accel.length(), x, y, 0, 0, flags);
}
}
// draw the bitmap
RECT rcImg;
SetRect(&rcImg,
rect.left + data->ItemMargin.cxLeftWidth
+ data->CheckBgMargin.cxLeftWidth
+ data->CheckMargin.cxLeftWidth,
rect.top + data->ItemMargin.cyTopHeight
+ data->CheckBgMargin.cyTopHeight
+ data->CheckMargin.cyTopHeight,
rect.left + data->ItemMargin.cxLeftWidth
+ data->CheckBgMargin.cxLeftWidth
+ data->CheckMargin.cxLeftWidth
+ imgWidth,
rect.bottom - data->ItemMargin.cyBottomHeight
- data->CheckBgMargin.cyBottomHeight
- data->CheckMargin.cyBottomHeight);
if ( IsCheckable() && !m_bmpChecked.IsOk() )
{
if ( stat & wxODChecked )
{
DrawStdCheckMark((WXHDC)hdc, &rcImg, stat);
}
}
else
{
wxBitmap bmp;
if ( stat & wxODDisabled )
{
bmp = GetDisabledBitmap();
}
if ( !bmp.IsOk() )
{
// 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.IsOk() && 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.IsOk() )
bmp = wxBitmap(imgGrey);
}
#endif // wxUSE_IMAGE
}
if ( bmp.IsOk() )
{
wxMemoryDC dcMem(&dc);
dcMem.SelectObjectAsSource(bmp);
// center bitmap
int nBmpWidth = bmp.GetWidth(),
nBmpHeight = bmp.GetHeight();
int x = rcImg.left + (imgWidth - nBmpWidth) / 2;
int y = rcImg.top + (rcImg.bottom - rcImg.top - nBmpHeight) / 2;
dc.Blit(x, y, nBmpWidth, nBmpHeight, &dcMem, 0, 0, wxCOPY, true);
}
}
return true;
}
namespace
{
// helper function for draw coloured check mark
void DrawColorCheckMark(HDC hdc, int x, int y, int cx, int cy, HDC hdcCheckMask, int idxColor)
{
const COLORREF colBlack = RGB(0, 0, 0);
const COLORREF colWhite = RGB(255, 255, 255);
HDCTextColChanger changeTextCol(hdc, colBlack);
HDCBgColChanger changeBgCol(hdc, colWhite);
HDCBgModeChanger changeBgMode(hdc, TRANSPARENT);
// memory DC for color bitmap
MemoryHDC hdcMem(hdc);
CompatibleBitmap hbmpMem(hdc, cx, cy);
SelectInHDC selMem(hdcMem, hbmpMem);
RECT rect = { 0, 0, cx, cy };
::FillRect(hdcMem, &rect, ::GetSysColorBrush(idxColor));
const COLORREF colCheck = ::GetSysColor(idxColor);
if ( colCheck == colWhite )
{
::BitBlt(hdc, x, y, cx, cy, hdcCheckMask, 0, 0, MERGEPAINT);
::BitBlt(hdc, x, y, cx, cy, hdcMem, 0, 0, SRCAND);
}
else
{
if ( colCheck != colBlack )
{
const DWORD ROP_DSna = 0x00220326; // dest = (NOT src) AND dest
::BitBlt(hdcMem, 0, 0, cx, cy, hdcCheckMask, 0, 0, ROP_DSna);
}
::BitBlt(hdc, x, y, cx, cy, hdcCheckMask, 0, 0, SRCAND);
::BitBlt(hdc, x, y, cx, cy, hdcMem, 0, 0, SRCPAINT);
}
}
} // anonymous namespace
void wxMenuItem::DrawStdCheckMark(WXHDC hdc_, const RECT* rc, wxODStatus stat)
{
HDC hdc = (HDC)hdc_;
#if wxUSE_UXTHEME
wxUxThemeEngine* theme = MenuDrawData::GetUxThemeEngine();
if ( theme )
{
wxUxThemeHandle hTheme(GetMenu()->GetWindow(), L"MENU");
const MenuDrawData* data = MenuDrawData::Get();
// rect for background must be without check margins
RECT rcBg = *rc;
data->CheckMargin.UnapplyFrom(rcBg);
POPUPCHECKBACKGROUNDSTATES stateCheckBg = (stat & wxODDisabled)
? MCB_DISABLED
: MCB_NORMAL;
theme->DrawThemeBackground(hTheme, hdc, MENU_POPUPCHECKBACKGROUND,
stateCheckBg, &rcBg, NULL);
POPUPCHECKSTATES stateCheck;
if ( GetKind() == wxITEM_CHECK )
{
stateCheck = (stat & wxODDisabled) ? MC_CHECKMARKDISABLED
: MC_CHECKMARKNORMAL;
}
else
{
stateCheck = (stat & wxODDisabled) ? MC_BULLETDISABLED
: MC_BULLETNORMAL;
}
theme->DrawThemeBackground(hTheme, hdc, MENU_POPUPCHECK,
stateCheck, rc, NULL);
}
else
#endif // wxUSE_UXTHEME
{
int cx = rc->right - rc->left;
int cy = rc->bottom - rc->top;
// first create mask of check mark
MemoryHDC hdcMask(hdc);
MonoBitmap hbmpMask(cx, cy);
SelectInHDC selMask(hdcMask,hbmpMask);
// then draw a check mark into it
UINT stateCheck = (GetKind() == wxITEM_CHECK) ? DFCS_MENUCHECK
: DFCS_MENUBULLET;
RECT rect = { 0, 0, cx, cy };
::DrawFrameControl(hdcMask, &rect, DFC_MENU, stateCheck);
// first draw shadow if disabled
if ( (stat & wxODDisabled) && !(stat & wxODSelected) )
{
DrawColorCheckMark(hdc, rc->left + 1, rc->top + 1,
cx, cy, hdcMask, COLOR_3DHILIGHT);
}
// then draw a check mark
int color = COLOR_MENUTEXT;
if ( stat & wxODDisabled )
color = COLOR_BTNSHADOW;
else if ( stat & wxODSelected )
color = COLOR_HIGHLIGHTTEXT;
DrawColorCheckMark(hdc, rc->left, rc->top, cx, cy, hdcMask, color);
}
}
void wxMenuItem::GetFontToUse(wxFont& font) const
{
font = GetFont();
if ( !font.IsOk() )
font = MenuDrawData::Get()->Font;
}
void wxMenuItem::GetColourToUse(wxODStatus stat, wxColour& colText, wxColour& colBack) const
{
#if wxUSE_UXTHEME
wxUxThemeEngine* theme = MenuDrawData::GetUxThemeEngine();
if ( theme )
{
wxUxThemeHandle hTheme(GetMenu()->GetWindow(), L"MENU");
if ( stat & wxODDisabled)
{
wxRGBToColour(colText, theme->GetThemeSysColor(hTheme, COLOR_GRAYTEXT));
}
else
{
colText = GetTextColour();
if ( !colText.IsOk() )
wxRGBToColour(colText, theme->GetThemeSysColor(hTheme, COLOR_MENUTEXT));
}
if ( stat & wxODSelected )
{
wxRGBToColour(colBack, theme->GetThemeSysColor(hTheme, COLOR_HIGHLIGHT));
}
else
{
colBack = GetBackgroundColour();
if ( !colBack.IsOk() )
wxRGBToColour(colBack, theme->GetThemeSysColor(hTheme, COLOR_MENU));
}
}
else
#endif // wxUSE_UXTHEME
{
wxOwnerDrawn::GetColourToUse(stat, colText, colBack);
}
}
#endif // wxUSE_OWNER_DRAWN
// ----------------------------------------------------------------------------
// wxMenuItemBase
// ----------------------------------------------------------------------------
wxMenuItem *wxMenuItemBase::New(wxMenu *parentMenu,
int id,
const wxString& name,
const wxString& help,
wxItemKind kind,
wxMenu *subMenu)
{
return new wxMenuItem(parentMenu, id, name, help, kind, subMenu);
}
#endif // wxUSE_MENUS