Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
5731 lines (4907 sloc) 170 KB
/*
* Menu functions
*
* Copyright 1993 Martin Ayotte
* Copyright 1994 Alexandre Julliard
* Copyright 1997 Morten Welinder
* Copyright 2005 Maxime Bellengé
* Copyright 2006 Phil Krylov
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
/*
* Note: the style MF_MOUSESELECT is used to mark popup items that
* have been selected, i.e. their popup menu is currently displayed.
* This is probably not the meaning this style has in MS-Windows.
*
* Note 2: where there is a difference, these menu API's are according
* the behavior of Windows 2k and Windows XP. Known differences with
* Windows 9x/ME are documented in the comments, in case an application
* is found to depend on the old behavior.
*
* TODO:
* implements styles :
* - MNS_AUTODISMISS
* - MNS_DRAGDROP
* - MNS_MODELESS
*/
#include "config.h"
#include "wine/port.h"
#include <stdarg.h>
#include <string.h>
#define OEMRESOURCE
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winnls.h"
#include "wine/server.h"
#include "wine/unicode.h"
#include "wine/exception.h"
#include "win.h"
#include "controls.h"
#include "user_private.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(menu);
WINE_DECLARE_DEBUG_CHANNEL(accel);
/* Menu item structure */
typedef struct {
/* ----------- MENUITEMINFO Stuff ----------- */
UINT fType; /* Item type. */
UINT fState; /* Item state. */
UINT_PTR wID; /* Item id. */
HMENU hSubMenu; /* Pop-up menu. */
HBITMAP hCheckBit; /* Bitmap when checked. */
HBITMAP hUnCheckBit; /* Bitmap when unchecked. */
LPWSTR text; /* Item text. */
ULONG_PTR dwItemData; /* Application defined. */
LPWSTR dwTypeData; /* depends on fMask */
HBITMAP hbmpItem; /* bitmap */
/* ----------- Wine stuff ----------- */
RECT rect; /* Item area (relative to the items_rect).
* See MENU_AdjustMenuItemRect(). */
UINT xTab; /* X position of text after Tab */
SIZE bmpsize; /* size needed for the HBMMENU_CALLBACK
* bitmap */
} MENUITEM;
/* Popup menu structure */
typedef struct {
struct user_object obj;
WORD wFlags; /* Menu flags (MF_POPUP, MF_SYSMENU) */
WORD Width; /* Width of the whole menu */
WORD Height; /* Height of the whole menu */
UINT nItems; /* Number of items in the menu */
HWND hWnd; /* Window containing the menu */
MENUITEM *items; /* Array of menu items */
UINT FocusedItem; /* Currently focused item */
HWND hwndOwner; /* window receiving the messages for ownerdraw */
BOOL bScrolling; /* Scroll arrows are active */
UINT nScrollPos; /* Current scroll position */
UINT nTotalHeight; /* Total height of menu items inside menu */
RECT items_rect; /* Rectangle within which the items lie. Excludes margins and scroll arrows */
LONG refcount;
/* ------------ MENUINFO members ------ */
DWORD dwStyle; /* Extended menu style */
UINT cyMax; /* max height of the whole menu, 0 is screen height */
HBRUSH hbrBack; /* brush for menu background */
DWORD dwContextHelpID;
DWORD dwMenuData; /* application defined value */
HMENU hSysMenuOwner; /* Handle to the dummy sys menu holder */
WORD textOffset; /* Offset of text when items have both bitmaps and text */
} POPUPMENU, *LPPOPUPMENU;
/* internal flags for menu tracking */
#define TF_ENDMENU 0x10000
#define TF_SUSPENDPOPUP 0x20000
#define TF_SKIPREMOVE 0x40000
#define TF_RCVD_BTN_UP 0x80000
typedef struct
{
UINT trackFlags;
HMENU hCurrentMenu; /* current submenu (can be equal to hTopMenu)*/
HMENU hTopMenu; /* initial menu */
HWND hOwnerWnd; /* where notifications are sent */
POINT pt;
} MTRACKER;
#define ITEM_PREV -1
#define ITEM_NEXT 1
/* Internal MENU_TrackMenu() flags */
#define TPM_INTERNAL 0xF0000000
#define TPM_BUTTONDOWN 0x40000000 /* menu was clicked before tracking */
#define TPM_POPUPMENU 0x20000000 /* menu is a popup menu */
/* Space between 2 columns */
#define MENU_COL_SPACE 4
/* Margins for popup menus */
#define MENU_MARGIN 3
/* maximum allowed depth of any branch in the menu tree.
* This value is slightly larger than in windows (25) to
* stay on the safe side. */
#define MAXMENUDEPTH 30
/* (other menu->FocusedItem values give the position of the focused item) */
#define NO_SELECTED_ITEM 0xffff
#define MENU_ITEM_TYPE(flags) \
((flags) & (MF_STRING | MF_BITMAP | MF_OWNERDRAW | MF_SEPARATOR))
/* macro to test that flags do not indicate bitmap, ownerdraw or separator */
#define IS_STRING_ITEM(flags) (MENU_ITEM_TYPE ((flags)) == MF_STRING)
#define IS_MAGIC_BITMAP(id) ((id) && ((INT_PTR)(id) < 12) && ((INT_PTR)(id) >= -1))
#define IS_SYSTEM_MENU(menu) \
(!((menu)->wFlags & MF_POPUP) && ((menu)->wFlags & MF_SYSMENU))
#define MENUITEMINFO_TYPE_MASK \
(MFT_STRING | MFT_BITMAP | MFT_OWNERDRAW | MFT_SEPARATOR | \
MFT_MENUBARBREAK | MFT_MENUBREAK | MFT_RADIOCHECK | \
MFT_RIGHTORDER | MFT_RIGHTJUSTIFY /* same as MF_HELP */ )
#define TYPE_MASK (MENUITEMINFO_TYPE_MASK | MF_POPUP | MF_SYSMENU)
#define STATE_MASK (~TYPE_MASK)
#define MENUITEMINFO_STATE_MASK (STATE_MASK & ~(MF_BYPOSITION | MF_MOUSESELECT))
static SIZE menucharsize;
static UINT ODitemheight; /* default owner drawn item height */
/* Use global popup window because there's no way 2 menus can
* be tracked at the same time. */
static HWND top_popup;
static HMENU top_popup_hmenu;
/* Flag set by EndMenu() to force an exit from menu tracking */
static BOOL fEndMenu = FALSE;
DWORD WINAPI DrawMenuBarTemp(HWND hwnd, HDC hDC, LPRECT lprect, HMENU hMenu, HFONT hFont);
static BOOL SetMenuItemInfo_common( MENUITEM *, const MENUITEMINFOW *, BOOL);
static BOOL is_win_menu_disallowed(HWND hwnd)
{
return (GetWindowLongW(hwnd, GWL_STYLE) & (WS_CHILD | WS_POPUP)) == WS_CHILD;
}
/*********************************************************************
* menu class descriptor
*/
const struct builtin_class_descr MENU_builtin_class =
{
(LPCWSTR)POPUPMENU_CLASS_ATOM, /* name */
CS_DROPSHADOW | CS_SAVEBITS | CS_DBLCLKS, /* style */
WINPROC_MENU, /* proc */
sizeof(HMENU), /* extra */
IDC_ARROW, /* cursor */
(HBRUSH)(COLOR_MENU+1) /* brush */
};
/***********************************************************************
* debug_print_menuitem
*
* Print a menuitem in readable form.
*/
#define debug_print_menuitem(pre, mp, post) \
do { if (TRACE_ON(menu)) do_debug_print_menuitem(pre, mp, post); } while (0)
#define MENUOUT(text) \
TRACE("%s%s", (count++ ? "," : ""), (text))
#define MENUFLAG(bit,text) \
do { \
if (flags & (bit)) { flags &= ~(bit); MENUOUT ((text)); } \
} while (0)
static void do_debug_print_menuitem(const char *prefix, const MENUITEM *mp,
const char *postfix)
{
static const char * const hbmmenus[] = { "HBMMENU_CALLBACK", "", "HBMMENU_SYSTEM",
"HBMMENU_MBAR_RESTORE", "HBMMENU_MBAR_MINIMIZE", "UNKNOWN BITMAP", "HBMMENU_MBAR_CLOSE",
"HBMMENU_MBAR_CLOSE_D", "HBMMENU_MBAR_MINIMIZE_D", "HBMMENU_POPUP_CLOSE",
"HBMMENU_POPUP_RESTORE", "HBMMENU_POPUP_MAXIMIZE", "HBMMENU_POPUP_MINIMIZE"};
TRACE("%s ", prefix);
if (mp) {
UINT flags = mp->fType;
TRACE( "{ ID=0x%lx", mp->wID);
if ( mp->hSubMenu)
TRACE( ", Sub=%p", mp->hSubMenu);
if (flags) {
int count = 0;
TRACE( ", fType=");
MENUFLAG( MFT_SEPARATOR, "sep");
MENUFLAG( MFT_OWNERDRAW, "own");
MENUFLAG( MFT_BITMAP, "bit");
MENUFLAG(MF_POPUP, "pop");
MENUFLAG(MFT_MENUBARBREAK, "barbrk");
MENUFLAG(MFT_MENUBREAK, "brk");
MENUFLAG(MFT_RADIOCHECK, "radio");
MENUFLAG(MFT_RIGHTORDER, "rorder");
MENUFLAG(MF_SYSMENU, "sys");
MENUFLAG(MFT_RIGHTJUSTIFY, "right"); /* same as MF_HELP */
if (flags)
TRACE( "+0x%x", flags);
}
flags = mp->fState;
if (flags) {
int count = 0;
TRACE( ", State=");
MENUFLAG(MFS_GRAYED, "grey");
MENUFLAG(MFS_DEFAULT, "default");
MENUFLAG(MFS_DISABLED, "dis");
MENUFLAG(MFS_CHECKED, "check");
MENUFLAG(MFS_HILITE, "hi");
MENUFLAG(MF_USECHECKBITMAPS, "usebit");
MENUFLAG(MF_MOUSESELECT, "mouse");
if (flags)
TRACE( "+0x%x", flags);
}
if (mp->hCheckBit)
TRACE( ", Chk=%p", mp->hCheckBit);
if (mp->hUnCheckBit)
TRACE( ", Unc=%p", mp->hUnCheckBit);
if (mp->text)
TRACE( ", Text=%s", debugstr_w(mp->text));
if (mp->dwItemData)
TRACE( ", ItemData=0x%08lx", mp->dwItemData);
if (mp->hbmpItem)
{
if( IS_MAGIC_BITMAP(mp->hbmpItem))
TRACE( ", hbitmap=%s", hbmmenus[ (INT_PTR)mp->hbmpItem + 1]);
else
TRACE( ", hbitmap=%p", mp->hbmpItem);
}
TRACE( " }");
} else
TRACE( "NULL");
TRACE(" %s\n", postfix);
}
#undef MENUOUT
#undef MENUFLAG
/***********************************************************************
* MENU_GetMenu
*
* Validate the given menu handle and returns the menu structure pointer.
*/
static POPUPMENU *MENU_GetMenu(HMENU hMenu)
{
POPUPMENU *menu = get_user_handle_ptr( hMenu, USER_MENU );
if (menu == OBJ_OTHER_PROCESS)
{
WARN( "other process menu %p?\n", hMenu);
return NULL;
}
if (menu) release_user_handle_ptr( menu ); /* FIXME! */
else WARN("invalid menu handle=%p\n", hMenu);
return menu;
}
static POPUPMENU *grab_menu_ptr(HMENU hMenu)
{
POPUPMENU *menu = get_user_handle_ptr( hMenu, USER_MENU );
if (menu == OBJ_OTHER_PROCESS)
{
WARN("other process menu %p?\n", hMenu);
return NULL;
}
if (menu)
menu->refcount++;
else
WARN("invalid menu handle=%p\n", hMenu);
return menu;
}
static void release_menu_ptr(POPUPMENU *menu)
{
if (menu)
{
menu->refcount--;
release_user_handle_ptr(menu);
}
}
/***********************************************************************
* get_win_sys_menu
*
* Get the system menu of a window
*/
static HMENU get_win_sys_menu( HWND hwnd )
{
HMENU ret = 0;
WND *win = WIN_GetPtr( hwnd );
if (win && win != WND_OTHER_PROCESS && win != WND_DESKTOP)
{
ret = win->hSysMenu;
WIN_ReleasePtr( win );
}
return ret;
}
/***********************************************************************
* get_menu_font
*/
static HFONT get_menu_font( BOOL bold )
{
static HFONT hMenuFont, hMenuFontBold;
HFONT ret = bold ? hMenuFontBold : hMenuFont;
if (!ret)
{
NONCLIENTMETRICSW ncm;
HFONT prev;
ncm.cbSize = sizeof(NONCLIENTMETRICSW);
SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICSW), &ncm, 0);
if (bold)
{
ncm.lfMenuFont.lfWeight += 300;
if (ncm.lfMenuFont.lfWeight > 1000) ncm.lfMenuFont.lfWeight = 1000;
}
if (!(ret = CreateFontIndirectW( &ncm.lfMenuFont ))) return 0;
prev = InterlockedCompareExchangePointer( (void **)(bold ? &hMenuFontBold : &hMenuFont),
ret, NULL );
if (prev)
{
/* another thread beat us to it */
DeleteObject( ret );
ret = prev;
}
}
return ret;
}
/***********************************************************************
* get_arrow_bitmap
*/
static HBITMAP get_arrow_bitmap(void)
{
static HBITMAP arrow_bitmap;
if (!arrow_bitmap) arrow_bitmap = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_MNARROW));
return arrow_bitmap;
}
static inline UINT get_scroll_arrow_height(const POPUPMENU *menu)
{
return menucharsize.cy + 4;
}
/***********************************************************************
* MENU_CopySysPopup
*
* Return the default system menu.
*/
static HMENU MENU_CopySysPopup(BOOL mdi)
{
static const WCHAR sysmenuW[] = {'S','Y','S','M','E','N','U',0};
static const WCHAR sysmenumdiW[] = {'S','Y','S','M','E','N','U','M','D','I',0};
HMENU hMenu = LoadMenuW(user32_module, (mdi ? sysmenumdiW : sysmenuW));
if( hMenu ) {
MENUINFO minfo;
MENUITEMINFOW miteminfo;
POPUPMENU* menu = MENU_GetMenu(hMenu);
menu->wFlags |= MF_SYSMENU | MF_POPUP;
/* decorate the menu with bitmaps */
minfo.cbSize = sizeof( MENUINFO);
minfo.dwStyle = MNS_CHECKORBMP;
minfo.fMask = MIM_STYLE;
SetMenuInfo( hMenu, &minfo);
miteminfo.cbSize = sizeof( MENUITEMINFOW);
miteminfo.fMask = MIIM_BITMAP;
miteminfo.hbmpItem = HBMMENU_POPUP_CLOSE;
SetMenuItemInfoW( hMenu, SC_CLOSE, FALSE, &miteminfo);
miteminfo.hbmpItem = HBMMENU_POPUP_RESTORE;
SetMenuItemInfoW( hMenu, SC_RESTORE, FALSE, &miteminfo);
miteminfo.hbmpItem = HBMMENU_POPUP_MAXIMIZE;
SetMenuItemInfoW( hMenu, SC_MAXIMIZE, FALSE, &miteminfo);
miteminfo.hbmpItem = HBMMENU_POPUP_MINIMIZE;
SetMenuItemInfoW( hMenu, SC_MINIMIZE, FALSE, &miteminfo);
SetMenuDefaultItem(hMenu, SC_CLOSE, FALSE);
}
else
ERR("Unable to load default system menu\n" );
TRACE("returning %p (mdi=%d).\n", hMenu, mdi );
return hMenu;
}
/**********************************************************************
* MENU_GetSysMenu
*
* Create a copy of the system menu. System menu in Windows is
* a special menu bar with the single entry - system menu popup.
* This popup is presented to the outside world as a "system menu".
* However, the real system menu handle is sometimes seen in the
* WM_MENUSELECT parameters (and Word 6 likes it this way).
*/
static HMENU MENU_GetSysMenu( HWND hWnd, HMENU hPopupMenu )
{
HMENU hMenu;
TRACE("loading system menu, hWnd %p, hPopupMenu %p\n", hWnd, hPopupMenu);
if ((hMenu = CreateMenu()))
{
POPUPMENU *menu = MENU_GetMenu(hMenu);
menu->wFlags = MF_SYSMENU;
menu->hWnd = WIN_GetFullHandle( hWnd );
TRACE("hWnd %p (hMenu %p)\n", menu->hWnd, hMenu);
if (!hPopupMenu)
{
if (GetWindowLongW(hWnd, GWL_EXSTYLE) & WS_EX_MDICHILD)
hPopupMenu = MENU_CopySysPopup(TRUE);
else
hPopupMenu = MENU_CopySysPopup(FALSE);
}
if (hPopupMenu)
{
if (GetClassLongW(hWnd, GCL_STYLE) & CS_NOCLOSE)
DeleteMenu(hPopupMenu, SC_CLOSE, MF_BYCOMMAND);
InsertMenuW( hMenu, -1, MF_SYSMENU | MF_POPUP | MF_BYPOSITION,
(UINT_PTR)hPopupMenu, NULL );
menu->items[0].fType = MF_SYSMENU | MF_POPUP;
menu->items[0].fState = 0;
if ((menu = MENU_GetMenu(hPopupMenu))) menu->wFlags |= MF_SYSMENU;
TRACE("hMenu=%p (hPopup %p)\n", hMenu, hPopupMenu );
return hMenu;
}
DestroyMenu( hMenu );
}
ERR("failed to load system menu!\n");
return 0;
}
/***********************************************************************
* MENU_InitSysMenuPopup
*
* Grey the appropriate items in System menu.
*/
static void MENU_InitSysMenuPopup( HMENU hmenu, DWORD style, DWORD clsStyle )
{
BOOL gray;
gray = !(style & WS_THICKFRAME) || (style & (WS_MAXIMIZE | WS_MINIMIZE));
EnableMenuItem( hmenu, SC_SIZE, (gray ? MF_GRAYED : MF_ENABLED) );
gray = ((style & WS_MAXIMIZE) != 0);
EnableMenuItem( hmenu, SC_MOVE, (gray ? MF_GRAYED : MF_ENABLED) );
gray = !(style & WS_MINIMIZEBOX) || (style & WS_MINIMIZE);
EnableMenuItem( hmenu, SC_MINIMIZE, (gray ? MF_GRAYED : MF_ENABLED) );
gray = !(style & WS_MAXIMIZEBOX) || (style & WS_MAXIMIZE);
EnableMenuItem( hmenu, SC_MAXIMIZE, (gray ? MF_GRAYED : MF_ENABLED) );
gray = !(style & (WS_MAXIMIZE | WS_MINIMIZE));
EnableMenuItem( hmenu, SC_RESTORE, (gray ? MF_GRAYED : MF_ENABLED) );
gray = (clsStyle & CS_NOCLOSE) != 0;
/* The menu item must keep its state if it's disabled */
if(gray)
EnableMenuItem( hmenu, SC_CLOSE, MF_GRAYED);
}
/******************************************************************************
*
* UINT MENU_GetStartOfNextColumn(
* HMENU hMenu )
*
*****************************************************************************/
static UINT MENU_GetStartOfNextColumn(
HMENU hMenu )
{
POPUPMENU *menu = MENU_GetMenu(hMenu);
UINT i;
if(!menu)
return NO_SELECTED_ITEM;
i = menu->FocusedItem + 1;
if( i == NO_SELECTED_ITEM )
return i;
for( ; i < menu->nItems; ++i ) {
if (menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK))
return i;
}
return NO_SELECTED_ITEM;
}
/******************************************************************************
*
* UINT MENU_GetStartOfPrevColumn(
* HMENU hMenu )
*
*****************************************************************************/
static UINT MENU_GetStartOfPrevColumn(
HMENU hMenu )
{
POPUPMENU *menu = MENU_GetMenu(hMenu);
UINT i;
if( !menu )
return NO_SELECTED_ITEM;
if( menu->FocusedItem == 0 || menu->FocusedItem == NO_SELECTED_ITEM )
return NO_SELECTED_ITEM;
/* Find the start of the column */
for(i = menu->FocusedItem; i != 0 &&
!(menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK));
--i); /* empty */
if(i == 0)
return NO_SELECTED_ITEM;
for(--i; i != 0; --i) {
if (menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK))
break;
}
TRACE("ret %d.\n", i );
return i;
}
static POPUPMENU *find_menu_item(HMENU hmenu, UINT id, UINT flags, UINT *pos)
{
UINT fallback_pos = ~0u, i;
POPUPMENU *menu;
menu = grab_menu_ptr(hmenu);
if (!menu)
return NULL;
if (flags & MF_BYPOSITION)
{
if (id >= menu->nItems)
{
release_menu_ptr(menu);
return NULL;
}
if (pos) *pos = id;
return menu;
}
else
{
MENUITEM *item = menu->items;
for (i = 0; i < menu->nItems; i++, item++)
{
if (item->fType & MF_POPUP)
{
POPUPMENU *submenu = find_menu_item(item->hSubMenu, id, flags, pos);
if (submenu)
{
release_menu_ptr(menu);
return submenu;
}
else if (item->wID == id)
{
/* fallback to this item if nothing else found */
fallback_pos = i;
}
}
else if (item->wID == id)
{
if (pos) *pos = i;
return menu;
}
}
}
if (fallback_pos != ~0u)
*pos = fallback_pos;
else
{
release_menu_ptr(menu);
menu = NULL;
}
return menu;
}
/***********************************************************************
* MENU_FindSubMenu
*
* Find a Sub menu. Return the position of the submenu, and modifies
* *hmenu in case it is found in another sub-menu.
* If the submenu cannot be found, NO_SELECTED_ITEM is returned.
*/
static UINT MENU_FindSubMenu( HMENU *hmenu, HMENU hSubTarget )
{
POPUPMENU *menu;
UINT i;
MENUITEM *item;
if (((*hmenu)==(HMENU)0xffff) ||
(!(menu = MENU_GetMenu(*hmenu))))
return NO_SELECTED_ITEM;
item = menu->items;
for (i = 0; i < menu->nItems; i++, item++) {
if(!(item->fType & MF_POPUP)) continue;
if (item->hSubMenu == hSubTarget) {
return i;
}
else {
HMENU hsubmenu = item->hSubMenu;
UINT pos = MENU_FindSubMenu( &hsubmenu, hSubTarget );
if (pos != NO_SELECTED_ITEM) {
*hmenu = hsubmenu;
return pos;
}
}
}
return NO_SELECTED_ITEM;
}
/***********************************************************************
* MENU_FreeItemData
*/
static void MENU_FreeItemData( MENUITEM* item )
{
/* delete text */
HeapFree( GetProcessHeap(), 0, item->text );
}
/***********************************************************************
* MENU_AdjustMenuItemRect
*
* Adjust menu item rectangle according to scrolling state.
*/
static void
MENU_AdjustMenuItemRect(const POPUPMENU *menu, LPRECT rect)
{
INT scroll_offset = menu->bScrolling ? menu->nScrollPos : 0;
OffsetRect( rect, menu->items_rect.left, menu->items_rect.top - scroll_offset );
}
enum hittest
{
ht_nowhere, /* outside the menu */
ht_border, /* anywhere that's not an item or a scroll arrow */
ht_item, /* a menu item */
ht_scroll_up, /* scroll up arrow */
ht_scroll_down /* scroll down arrow */
};
/***********************************************************************
* MENU_FindItemByCoords
*
* Find the item at the specified coordinates (screen coords). Does
* not work for child windows and therefore should not be called for
* an arbitrary system menu.
*
* Returns a hittest code. *pos will contain the position of the
* item or NO_SELECTED_ITEM. If the hittest code is ht_scroll_up
* or ht_scroll_down then *pos will contain the position of the
* item that's just outside the items_rect - ie, the one that would
* be scrolled completely into view.
*/
static enum hittest MENU_FindItemByCoords( const POPUPMENU *menu, POINT pt, UINT *pos )
{
MENUITEM *item;
UINT i;
RECT rect;
enum hittest ht = ht_border;
*pos = NO_SELECTED_ITEM;
if (!GetWindowRect(menu->hWnd, &rect) || !PtInRect(&rect, pt))
return ht_nowhere;
if (GetWindowLongW( menu->hWnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) pt.x = rect.right - 1 - pt.x;
else pt.x -= rect.left;
pt.y -= rect.top;
if (!PtInRect(&menu->items_rect, pt))
{
if (!menu->bScrolling || pt.x < menu->items_rect.left || pt.x >= menu->items_rect.right)
return ht_border;
/* On a scroll arrow. Update pt so that it points to the item just outside items_rect */
if (pt.y < menu->items_rect.top)
{
ht = ht_scroll_up;
pt.y = menu->items_rect.top - 1;
}
else
{
ht = ht_scroll_down;
pt.y = menu->items_rect.bottom;
}
}
item = menu->items;
for (i = 0; i < menu->nItems; i++, item++)
{
rect = item->rect;
MENU_AdjustMenuItemRect(menu, &rect);
if (PtInRect(&rect, pt))
{
*pos = i;
if (ht != ht_scroll_up && ht != ht_scroll_down) ht = ht_item;
break;
}
}
return ht;
}
/***********************************************************************
* MENU_FindItemByKey
*
* Find the menu item selected by a key press.
* Return item id, -1 if none, -2 if we should close the menu.
*/
static UINT MENU_FindItemByKey( HWND hwndOwner, HMENU hmenu,
WCHAR key, BOOL forceMenuChar )
{
TRACE("\tlooking for '%c' (0x%02x) in [%p]\n", (char)key, key, hmenu );
if (!IsMenu( hmenu )) hmenu = GetSubMenu( get_win_sys_menu(hwndOwner), 0);
if (hmenu)
{
POPUPMENU *menu = MENU_GetMenu( hmenu );
MENUITEM *item = menu->items;
LRESULT menuchar;
if( !forceMenuChar )
{
UINT i;
BOOL cjk = GetSystemMetrics( SM_DBCSENABLED );
for (i = 0; i < menu->nItems; i++, item++)
{
if( item->text)
{
const WCHAR *p = item->text - 2;
do
{
const WCHAR *q = p + 2;
p = strchrW (q, '&');
if (!p && cjk) p = strchrW (q, '\036'); /* Japanese Win16 */
}
while (p != NULL && p [1] == '&');
if (p && (toupperW(p[1]) == toupperW(key))) return i;
}
}
}
menuchar = SendMessageW( hwndOwner, WM_MENUCHAR,
MAKEWPARAM( key, menu->wFlags ), (LPARAM)hmenu );
if (HIWORD(menuchar) == MNC_EXECUTE) return LOWORD(menuchar);
if (HIWORD(menuchar) == MNC_CLOSE) return (UINT)(-2);
}
return (UINT)(-1);
}
/***********************************************************************
* MENU_GetBitmapItemSize
*
* Get the size of a bitmap item.
*/
static void MENU_GetBitmapItemSize( MENUITEM *lpitem, SIZE *size,
HWND hwndOwner)
{
BITMAP bm;
HBITMAP bmp = lpitem->hbmpItem;
size->cx = size->cy = 0;
/* check if there is a magic menu item associated with this item */
switch( (INT_PTR) bmp )
{
case (INT_PTR)HBMMENU_CALLBACK:
{
MEASUREITEMSTRUCT measItem;
measItem.CtlType = ODT_MENU;
measItem.CtlID = 0;
measItem.itemID = lpitem->wID;
measItem.itemWidth = lpitem->rect.right - lpitem->rect.left;
measItem.itemHeight = lpitem->rect.bottom - lpitem->rect.top;
measItem.itemData = lpitem->dwItemData;
SendMessageW( hwndOwner, WM_MEASUREITEM, 0, (LPARAM)&measItem);
size->cx = measItem.itemWidth;
size->cy = measItem.itemHeight;
return;
}
break;
case (INT_PTR)HBMMENU_SYSTEM:
if (lpitem->dwItemData)
{
bmp = (HBITMAP)lpitem->dwItemData;
break;
}
/* fall through */
case (INT_PTR)HBMMENU_MBAR_RESTORE:
case (INT_PTR)HBMMENU_MBAR_MINIMIZE:
case (INT_PTR)HBMMENU_MBAR_MINIMIZE_D:
case (INT_PTR)HBMMENU_MBAR_CLOSE:
case (INT_PTR)HBMMENU_MBAR_CLOSE_D:
size->cx = GetSystemMetrics( SM_CYMENU ) - 4;
size->cy = size->cx;
return;
case (INT_PTR)HBMMENU_POPUP_CLOSE:
case (INT_PTR)HBMMENU_POPUP_RESTORE:
case (INT_PTR)HBMMENU_POPUP_MAXIMIZE:
case (INT_PTR)HBMMENU_POPUP_MINIMIZE:
size->cx = GetSystemMetrics( SM_CXMENUSIZE);
size->cy = GetSystemMetrics( SM_CYMENUSIZE);
return;
}
if (GetObjectW(bmp, sizeof(bm), &bm ))
{
size->cx = bm.bmWidth;
size->cy = bm.bmHeight;
}
}
/***********************************************************************
* MENU_DrawBitmapItem
*
* Draw a bitmap item.
*/
static void MENU_DrawBitmapItem( HDC hdc, MENUITEM *lpitem, const RECT *rect,
POPUPMENU *menu, HWND hwndOwner, UINT odaction )
{
BITMAP bm;
DWORD rop;
HDC hdcMem;
HBITMAP bmp;
int w = rect->right - rect->left;
int h = rect->bottom - rect->top;
int bmp_xoffset = 0;
int left, top;
HBITMAP hbmToDraw = lpitem->hbmpItem;
bmp = hbmToDraw;
/* Check if there is a magic menu item associated with this item */
if (IS_MAGIC_BITMAP(hbmToDraw))
{
UINT flags = 0;
WCHAR bmchr = 0;
RECT r;
switch((INT_PTR)hbmToDraw)
{
case (INT_PTR)HBMMENU_SYSTEM:
if (lpitem->dwItemData)
{
bmp = (HBITMAP)lpitem->dwItemData;
if (!GetObjectW( bmp, sizeof(bm), &bm )) return;
}
else
{
static HBITMAP hBmpSysMenu;
if (!hBmpSysMenu) hBmpSysMenu = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_CLOSE));
bmp = hBmpSysMenu;
if (!GetObjectW( bmp, sizeof(bm), &bm )) return;
/* only use right half of the bitmap */
bmp_xoffset = bm.bmWidth / 2;
bm.bmWidth -= bmp_xoffset;
}
goto got_bitmap;
case (INT_PTR)HBMMENU_MBAR_RESTORE:
flags = DFCS_CAPTIONRESTORE;
break;
case (INT_PTR)HBMMENU_MBAR_MINIMIZE:
flags = DFCS_CAPTIONMIN;
break;
case (INT_PTR)HBMMENU_MBAR_MINIMIZE_D:
flags = DFCS_CAPTIONMIN | DFCS_INACTIVE;
break;
case (INT_PTR)HBMMENU_MBAR_CLOSE:
flags = DFCS_CAPTIONCLOSE;
break;
case (INT_PTR)HBMMENU_MBAR_CLOSE_D:
flags = DFCS_CAPTIONCLOSE | DFCS_INACTIVE;
break;
case (INT_PTR)HBMMENU_CALLBACK:
{
DRAWITEMSTRUCT drawItem;
drawItem.CtlType = ODT_MENU;
drawItem.CtlID = 0;
drawItem.itemID = lpitem->wID;
drawItem.itemAction = odaction;
drawItem.itemState = (lpitem->fState & MF_CHECKED)?ODS_CHECKED:0;
drawItem.itemState |= (lpitem->fState & MF_DEFAULT)?ODS_DEFAULT:0;
drawItem.itemState |= (lpitem->fState & MF_DISABLED)?ODS_DISABLED:0;
drawItem.itemState |= (lpitem->fState & MF_GRAYED)?ODS_GRAYED|ODS_DISABLED:0;
drawItem.itemState |= (lpitem->fState & MF_HILITE)?ODS_SELECTED:0;
drawItem.hwndItem = (HWND)menu->obj.handle;
drawItem.hDC = hdc;
drawItem.itemData = lpitem->dwItemData;
drawItem.rcItem = *rect;
SendMessageW( hwndOwner, WM_DRAWITEM, 0, (LPARAM)&drawItem);
return;
}
break;
case (INT_PTR)HBMMENU_POPUP_CLOSE:
bmchr = 0x72;
break;
case (INT_PTR)HBMMENU_POPUP_RESTORE:
bmchr = 0x32;
break;
case (INT_PTR)HBMMENU_POPUP_MAXIMIZE:
bmchr = 0x31;
break;
case (INT_PTR)HBMMENU_POPUP_MINIMIZE:
bmchr = 0x30;
break;
default:
FIXME("Magic %p not implemented\n", hbmToDraw);
return;
}
if (bmchr)
{
/* draw the magic bitmaps using marlett font characters */
/* FIXME: fontsize and the position (x,y) could probably be better */
HFONT hfont, hfontsav;
LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL,
0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
{ 'M','a','r','l','e','t','t',0 } };
logfont.lfHeight = min( h, w) - 5 ;
TRACE(" height %d rect %s\n", logfont.lfHeight, wine_dbgstr_rect( rect));
hfont = CreateFontIndirectW( &logfont);
hfontsav = SelectObject(hdc, hfont);
TextOutW( hdc, rect->left, rect->top + 2, &bmchr, 1);
SelectObject(hdc, hfontsav);
DeleteObject( hfont);
}
else
{
r = *rect;
InflateRect( &r, -1, -1 );
if (lpitem->fState & MF_HILITE) flags |= DFCS_PUSHED;
DrawFrameControl( hdc, &r, DFC_CAPTION, flags );
}
return;
}
if (!bmp || !GetObjectW( bmp, sizeof(bm), &bm )) return;
got_bitmap:
hdcMem = CreateCompatibleDC( hdc );
SelectObject( hdcMem, bmp );
/* handle fontsize > bitmap_height */
top = (h>bm.bmHeight) ? rect->top+(h-bm.bmHeight)/2 : rect->top;
left=rect->left;
rop=((lpitem->fState & MF_HILITE) && !IS_MAGIC_BITMAP(hbmToDraw)) ? NOTSRCCOPY : SRCCOPY;
if ((lpitem->fState & MF_HILITE) && lpitem->hbmpItem)
SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
BitBlt( hdc, left, top, w, h, hdcMem, bmp_xoffset, 0, rop );
DeleteDC( hdcMem );
}
/***********************************************************************
* MENU_CalcItemSize
*
* Calculate the size of the menu item and store it in lpitem->rect.
*/
static void MENU_CalcItemSize( HDC hdc, MENUITEM *lpitem, HWND hwndOwner,
INT orgX, INT orgY, BOOL menuBar, POPUPMENU* lppop )
{
WCHAR *p;
UINT check_bitmap_width = GetSystemMetrics( SM_CXMENUCHECK );
UINT arrow_bitmap_width;
BITMAP bm;
INT itemheight;
TRACE("dc=%p owner=%p (%d,%d)\n", hdc, hwndOwner, orgX, orgY);
debug_print_menuitem("MENU_CalcItemSize: menuitem:", lpitem,
(menuBar ? " (MenuBar)" : ""));
GetObjectW( get_arrow_bitmap(), sizeof(bm), &bm );
arrow_bitmap_width = bm.bmWidth;
/* not done in Menu_Init: GetDialogBaseUnits() breaks there */
if( !menucharsize.cx ) {
menucharsize.cx = GdiGetCharDimensions( hdc, NULL, &menucharsize.cy );
/* Win95/98/ME will use menucharsize.cy here. Testing is possible
* but it is unlikely an application will depend on that */
ODitemheight = HIWORD( GetDialogBaseUnits());
}
SetRect( &lpitem->rect, orgX, orgY, orgX, orgY );
if (lpitem->fType & MF_OWNERDRAW)
{
MEASUREITEMSTRUCT mis;
mis.CtlType = ODT_MENU;
mis.CtlID = 0;
mis.itemID = lpitem->wID;
mis.itemData = lpitem->dwItemData;
mis.itemHeight = ODitemheight;
mis.itemWidth = 0;
SendMessageW( hwndOwner, WM_MEASUREITEM, 0, (LPARAM)&mis );
/* Tests reveal that Windows ( Win95 through WinXP) adds twice the average
* width of a menufont character to the width of an owner-drawn menu.
*/
lpitem->rect.right += mis.itemWidth + 2 * menucharsize.cx;
if (menuBar) {
/* under at least win95 you seem to be given a standard
height for the menu and the height value is ignored */
lpitem->rect.bottom += GetSystemMetrics(SM_CYMENUSIZE);
} else
lpitem->rect.bottom += mis.itemHeight;
TRACE("id=%04lx size=%dx%d\n",
lpitem->wID, lpitem->rect.right-lpitem->rect.left,
lpitem->rect.bottom-lpitem->rect.top);
return;
}
if (lpitem->fType & MF_SEPARATOR)
{
lpitem->rect.bottom += GetSystemMetrics( SM_CYMENUSIZE)/2;
if( !menuBar)
lpitem->rect.right += arrow_bitmap_width + menucharsize.cx;
return;
}
itemheight = 0;
lpitem->xTab = 0;
if (!menuBar) {
if (lpitem->hbmpItem) {
SIZE size;
MENU_GetBitmapItemSize(lpitem, &size, hwndOwner);
/* Keep the size of the bitmap in callback mode to be able
* to draw it correctly */
lpitem->bmpsize = size;
lppop->textOffset = max( lppop->textOffset, size.cx);
lpitem->rect.right += size.cx + 2;
itemheight = size.cy + 2;
}
if( !(lppop->dwStyle & MNS_NOCHECK))
lpitem->rect.right += check_bitmap_width;
lpitem->rect.right += 4 + menucharsize.cx;
lpitem->xTab = lpitem->rect.right;
lpitem->rect.right += arrow_bitmap_width;
} else if (lpitem->hbmpItem) { /* menuBar */
SIZE size;
MENU_GetBitmapItemSize( lpitem, &size, hwndOwner );
lpitem->bmpsize = size;
lpitem->rect.right += size.cx;
if( lpitem->text) lpitem->rect.right += 2;
itemheight = size.cy;
}
/* it must be a text item - unless it's the system menu */
if (!(lpitem->fType & MF_SYSMENU) && lpitem->text) {
HFONT hfontOld = NULL;
RECT rc = lpitem->rect;
LONG txtheight, txtwidth;
if ( lpitem->fState & MFS_DEFAULT ) {
hfontOld = SelectObject( hdc, get_menu_font(TRUE) );
}
if (menuBar) {
txtheight = DrawTextW( hdc, lpitem->text, -1, &rc,
DT_SINGLELINE|DT_CALCRECT);
lpitem->rect.right += rc.right - rc.left;
itemheight = max( max( itemheight, txtheight),
GetSystemMetrics( SM_CYMENU) - 1);
lpitem->rect.right += 2 * menucharsize.cx;
} else {
if ((p = strchrW( lpitem->text, '\t' )) != NULL) {
RECT tmprc = rc;
LONG tmpheight;
int n = (int)( p - lpitem->text);
/* Item contains a tab (only meaningful in popup menus) */
/* get text size before the tab */
txtheight = DrawTextW( hdc, lpitem->text, n, &rc,
DT_SINGLELINE|DT_CALCRECT);
txtwidth = rc.right - rc.left;
p += 1; /* advance past the Tab */
/* get text size after the tab */
tmpheight = DrawTextW( hdc, p, -1, &tmprc,
DT_SINGLELINE|DT_CALCRECT);
lpitem->xTab += txtwidth;
txtheight = max( txtheight, tmpheight);
txtwidth += menucharsize.cx + /* space for the tab */
tmprc.right - tmprc.left; /* space for the short cut */
} else {
txtheight = DrawTextW( hdc, lpitem->text, -1, &rc,
DT_SINGLELINE|DT_CALCRECT);
txtwidth = rc.right - rc.left;
lpitem->xTab += txtwidth;
}
lpitem->rect.right += 2 + txtwidth;
itemheight = max( itemheight,
max( txtheight + 2, menucharsize.cy + 4));
}
if (hfontOld) SelectObject (hdc, hfontOld);
} else if( menuBar) {
itemheight = max( itemheight, GetSystemMetrics(SM_CYMENU)-1);
}
lpitem->rect.bottom += itemheight;
TRACE("%s\n", wine_dbgstr_rect( &lpitem->rect));
}
/***********************************************************************
* MENU_PopupMenuCalcSize
*
* Calculate the size of a popup menu.
*/
static void MENU_PopupMenuCalcSize( LPPOPUPMENU lppop, UINT max_height )
{
MENUITEM *lpitem;
HDC hdc;
UINT start, i;
BOOL textandbmp = FALSE, multi_col = FALSE;
int orgX, orgY, maxTab, maxTabWidth;
lppop->Width = lppop->Height = 0;
SetRectEmpty(&lppop->items_rect);
if (lppop->nItems == 0) return;
hdc = GetDC( 0 );
SelectObject( hdc, get_menu_font(FALSE));
start = 0;
lppop->textOffset = 0;
while (start < lppop->nItems)
{
lpitem = &lppop->items[start];
orgX = lppop->items_rect.right;
if( lpitem->fType & (MF_MENUBREAK | MF_MENUBARBREAK))
orgX += MENU_COL_SPACE;
orgY = lppop->items_rect.top;
maxTab = maxTabWidth = 0;
/* Parse items until column break or end of menu */
for (i = start; i < lppop->nItems; i++, lpitem++)
{
if (lpitem->fType & (MF_MENUBREAK | MF_MENUBARBREAK))
{
multi_col = TRUE;
if (i != start) break;
}
MENU_CalcItemSize( hdc, lpitem, lppop->hwndOwner, orgX, orgY, FALSE, lppop );
lppop->items_rect.right = max( lppop->items_rect.right, lpitem->rect.right );
orgY = lpitem->rect.bottom;
if (IS_STRING_ITEM(lpitem->fType) && lpitem->xTab)
{
maxTab = max( maxTab, lpitem->xTab );
maxTabWidth = max(maxTabWidth,lpitem->rect.right-lpitem->xTab);
}
if( lpitem->text && lpitem->hbmpItem) textandbmp = TRUE;
}
/* Finish the column (set all items to the largest width found) */
lppop->items_rect.right = max( lppop->items_rect.right, maxTab + maxTabWidth );
for (lpitem = &lppop->items[start]; start < i; start++, lpitem++)
{
lpitem->rect.right = lppop->items_rect.right;
if (IS_STRING_ITEM(lpitem->fType) && lpitem->xTab)
lpitem->xTab = maxTab;
}
lppop->items_rect.bottom = max( lppop->items_rect.bottom, orgY );
}
/* if none of the items have both text and bitmap then
* the text and bitmaps are all aligned on the left. If there is at
* least one item with both text and bitmap then bitmaps are
* on the left and texts left aligned with the right hand side
* of the bitmaps */
if( !textandbmp) lppop->textOffset = 0;
lppop->nTotalHeight = lppop->items_rect.bottom;
/* space for the border */
OffsetRect(&lppop->items_rect, MENU_MARGIN, MENU_MARGIN);
lppop->Height = lppop->items_rect.bottom + MENU_MARGIN;
lppop->Width = lppop->items_rect.right + MENU_MARGIN;
/* Adjust popup height if it exceeds maximum */
if (lppop->Height >= max_height)
{
lppop->Height = max_height;
lppop->bScrolling = !multi_col;
/* When the scroll arrows are present, don't add the top/bottom margin as well */
if (lppop->bScrolling)
{
lppop->items_rect.top = get_scroll_arrow_height(lppop);
lppop->items_rect.bottom = lppop->Height - get_scroll_arrow_height(lppop);
}
}
else
{
lppop->bScrolling = FALSE;
}
ReleaseDC( 0, hdc );
}
/***********************************************************************
* MENU_MenuBarCalcSize
*
* FIXME: Word 6 implements its own MDI and its own 'close window' bitmap
* height is off by 1 pixel which causes lengthy window relocations when
* active document window is maximized/restored.
*
* Calculate the size of the menu bar.
*/
static void MENU_MenuBarCalcSize( HDC hdc, LPRECT lprect,
LPPOPUPMENU lppop, HWND hwndOwner )
{
MENUITEM *lpitem;
UINT start, i, helpPos;
int orgX, orgY;
if ((lprect == NULL) || (lppop == NULL)) return;
if (lppop->nItems == 0) return;
TRACE("lprect %p %s\n", lprect, wine_dbgstr_rect( lprect));
/* Start with a 1 pixel top border.
This corresponds to the difference between SM_CYMENU and SM_CYMENUSIZE. */
SetRect(&lppop->items_rect, 0, 0, lprect->right - lprect->left, 1);
start = 0;
helpPos = ~0U;
lppop->textOffset = 0;
while (start < lppop->nItems)
{
lpitem = &lppop->items[start];
orgX = lppop->items_rect.left;
orgY = lppop->items_rect.bottom;
/* Parse items until line break or end of menu */
for (i = start; i < lppop->nItems; i++, lpitem++)
{
if ((helpPos == ~0U) && (lpitem->fType & MF_RIGHTJUSTIFY)) helpPos = i;
if ((i != start) &&
(lpitem->fType & (MF_MENUBREAK | MF_MENUBARBREAK))) break;
TRACE("calling MENU_CalcItemSize org=(%d, %d)\n", orgX, orgY );
debug_print_menuitem (" item: ", lpitem, "");
MENU_CalcItemSize( hdc, lpitem, hwndOwner, orgX, orgY, TRUE, lppop );
if (lpitem->rect.right > lppop->items_rect.right)
{
if (i != start) break;
else lpitem->rect.right = lppop->items_rect.right;
}
lppop->items_rect.bottom = max( lppop->items_rect.bottom, lpitem->rect.bottom );
orgX = lpitem->rect.right;
}
/* Finish the line (set all items to the largest height found) */
while (start < i) lppop->items[start++].rect.bottom = lppop->items_rect.bottom;
}
OffsetRect(&lppop->items_rect, lprect->left, lprect->top);
lppop->Width = lppop->items_rect.right - lppop->items_rect.left;
lppop->Height = lppop->items_rect.bottom - lppop->items_rect.top;
lprect->bottom = lppop->items_rect.bottom;
/* Flush right all items between the MF_RIGHTJUSTIFY and */
/* the last item (if several lines, only move the last line) */
if (helpPos == ~0U) return;
lpitem = &lppop->items[lppop->nItems-1];
orgY = lpitem->rect.top;
orgX = lprect->right - lprect->left;
for (i = lppop->nItems - 1; i >= helpPos; i--, lpitem--) {
if (lpitem->rect.top != orgY) break; /* Other line */
if (lpitem->rect.right >= orgX) break; /* Too far right already */
lpitem->rect.left += orgX - lpitem->rect.right;
lpitem->rect.right = orgX;
orgX = lpitem->rect.left;
}
}
static void draw_scroll_arrow(HDC hdc, int x, int top, int height, BOOL up, BOOL enabled)
{
RECT rect, light_rect;
HBRUSH brush = GetSysColorBrush( enabled ? COLOR_BTNTEXT : COLOR_BTNSHADOW );
HBRUSH light = GetSysColorBrush( COLOR_3DLIGHT );
if (!up)
{
top = top + height;
if (!enabled)
{
SetRect( &rect, x + 1, top, x + 2, top + 1);
FillRect( hdc, &rect, light );
}
top--;
}
SetRect( &rect, x, top, x + 1, top + 1);
while (height--)
{
FillRect( hdc, &rect, brush );
if (!enabled && !up && height)
{
SetRect( &light_rect, rect.right, rect.top, rect.right + 2, rect.bottom );
FillRect( hdc, &light_rect, light );
}
InflateRect( &rect, 1, 0 );
OffsetRect( &rect, 0, up ? 1 : -1 );
}
if (!enabled && up)
{
rect.left += 2;
FillRect( hdc, &rect, light );
}
}
/***********************************************************************
* MENU_DrawScrollArrows
*
* Draw scroll arrows.
*/
static void
MENU_DrawScrollArrows(const POPUPMENU *menu, HDC hdc)
{
UINT full_height = get_scroll_arrow_height( menu );
UINT arrow_height = full_height / 3;
BOOL at_end = menu->nScrollPos + menu->items_rect.bottom - menu->items_rect.top == menu->nTotalHeight;
draw_scroll_arrow( hdc, menu->Width / 3, arrow_height, arrow_height,
TRUE, menu->nScrollPos != 0);
draw_scroll_arrow( hdc, menu->Width / 3, menu->Height - 2 * arrow_height, arrow_height,
FALSE, !at_end );
}
/***********************************************************************
* draw_popup_arrow
*
* Draws the popup-menu arrow.
*/
static void draw_popup_arrow( HDC hdc, RECT rect, UINT arrow_bitmap_width,
UINT arrow_bitmap_height)
{
HDC hdcMem = CreateCompatibleDC( hdc );
HBITMAP hOrigBitmap;
hOrigBitmap = SelectObject( hdcMem, get_arrow_bitmap() );
BitBlt( hdc, rect.right - arrow_bitmap_width - 1,
(rect.top + rect.bottom - arrow_bitmap_height) / 2,
arrow_bitmap_width, arrow_bitmap_height,
hdcMem, 0, 0, SRCCOPY );
SelectObject( hdcMem, hOrigBitmap );
DeleteDC( hdcMem );
}
/***********************************************************************
* MENU_DrawMenuItem
*
* Draw a single menu item.
*/
static void MENU_DrawMenuItem( HWND hwnd, POPUPMENU *menu, HWND hwndOwner, HDC hdc,
MENUITEM *lpitem, BOOL menuBar, UINT odaction )
{
RECT rect, bmprc;
BOOL flat_menu = FALSE;
int bkgnd;
UINT arrow_bitmap_width = 0, arrow_bitmap_height = 0;
HRGN old_clip = NULL, clip;
debug_print_menuitem("MENU_DrawMenuItem: ", lpitem, "");
if (!menuBar) {
BITMAP bmp;
GetObjectW( get_arrow_bitmap(), sizeof(bmp), &bmp );
arrow_bitmap_width = bmp.bmWidth;
arrow_bitmap_height = bmp.bmHeight;
}
if (lpitem->fType & MF_SYSMENU)
{
if( !IsIconic(hwnd) )
NC_DrawSysButton( hwnd, hdc, lpitem->fState & (MF_HILITE | MF_MOUSESELECT) );
return;
}
TRACE( "rect=%s\n", wine_dbgstr_rect( &lpitem->rect ) );
rect = lpitem->rect;
MENU_AdjustMenuItemRect( menu, &rect );
if (!IntersectRect( &bmprc, &rect, &menu->items_rect )) /* bmprc is used as a dummy */
return;
SystemParametersInfoW (SPI_GETFLATMENU, 0, &flat_menu, 0);
bkgnd = (menuBar && flat_menu) ? COLOR_MENUBAR : COLOR_MENU;
/* Setup colors */
if (lpitem->fState & MF_HILITE)
{
if(menuBar && !flat_menu) {
SetTextColor(hdc, GetSysColor(COLOR_MENUTEXT));
SetBkColor(hdc, GetSysColor(COLOR_MENU));
} else {
if(lpitem->fState & MF_GRAYED)
SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
else
SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
}
}
else
{
if (lpitem->fState & MF_GRAYED)
SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
else
SetTextColor( hdc, GetSysColor( COLOR_MENUTEXT ) );
SetBkColor( hdc, GetSysColor( bkgnd ) );
}
old_clip = CreateRectRgn( 0, 0, 0, 0 );
if (GetClipRgn( hdc, old_clip ) <= 0)
{
DeleteObject( old_clip );
old_clip = NULL;
}
clip = CreateRectRgnIndirect( &menu->items_rect );
ExtSelectClipRgn( hdc, clip, RGN_AND );
DeleteObject( clip );
if (lpitem->fType & MF_OWNERDRAW)
{
/*
** Experimentation under Windows reveals that an owner-drawn
** menu is given the rectangle which includes the space it requested
** in its response to WM_MEASUREITEM _plus_ width for a checkmark
** and a popup-menu arrow. This is the value of lpitem->rect.
** Windows will leave all drawing to the application except for
** the popup-menu arrow. Windows always draws that itself, after
** the menu owner has finished drawing.
*/
DRAWITEMSTRUCT dis;
COLORREF old_bk, old_text;
dis.CtlType = ODT_MENU;
dis.CtlID = 0;
dis.itemID = lpitem->wID;
dis.itemData = lpitem->dwItemData;
dis.itemState = 0;
if (lpitem->fState & MF_CHECKED) dis.itemState |= ODS_CHECKED;
if (lpitem->fState & MF_GRAYED) dis.itemState |= ODS_GRAYED|ODS_DISABLED;
if (lpitem->fState & MF_HILITE) dis.itemState |= ODS_SELECTED;
dis.itemAction = odaction; /* ODA_DRAWENTIRE | ODA_SELECT | ODA_FOCUS; */
dis.hwndItem = (HWND)menu->obj.handle;
dis.hDC = hdc;
dis.rcItem = rect;
TRACE("Ownerdraw: owner=%p itemID=%d, itemState=%d, itemAction=%d, "
"hwndItem=%p, hdc=%p, rcItem=%s\n", hwndOwner,
dis.itemID, dis.itemState, dis.itemAction, dis.hwndItem,
dis.hDC, wine_dbgstr_rect( &dis.rcItem));
old_bk = GetBkColor( hdc );
old_text = GetTextColor( hdc );
SendMessageW( hwndOwner, WM_DRAWITEM, 0, (LPARAM)&dis );
/* Draw the popup-menu arrow */
SetBkColor( hdc, old_bk );
SetTextColor( hdc, old_text );
if (lpitem->fType & MF_POPUP)
draw_popup_arrow( hdc, rect, arrow_bitmap_width,
arrow_bitmap_height);
goto done;
}
if (menuBar && (lpitem->fType & MF_SEPARATOR)) goto done;
if (lpitem->fState & MF_HILITE)
{
if (flat_menu)
{
InflateRect (&rect, -1, -1);
FillRect(hdc, &rect, GetSysColorBrush(COLOR_MENUHILIGHT));
InflateRect (&rect, 1, 1);
FrameRect(hdc, &rect, GetSysColorBrush(COLOR_HIGHLIGHT));
}
else
{
if(menuBar)
DrawEdge(hdc, &rect, BDR_SUNKENOUTER, BF_RECT);
else
FillRect(hdc, &rect, GetSysColorBrush(COLOR_HIGHLIGHT));
}
}
else
FillRect( hdc, &rect, GetSysColorBrush(bkgnd) );
SetBkMode( hdc, TRANSPARENT );
/* vertical separator */
if (!menuBar && (lpitem->fType & MF_MENUBARBREAK))
{
HPEN oldPen;
RECT rc = rect;
rc.left -= MENU_COL_SPACE / 2 + 1;
rc.top = 3;
rc.bottom = menu->Height - 3;
if (flat_menu)
{
oldPen = SelectObject( hdc, SYSCOLOR_GetPen(COLOR_BTNSHADOW) );
MoveToEx( hdc, rc.left, rc.top, NULL );
LineTo( hdc, rc.left, rc.bottom );
SelectObject( hdc, oldPen );
}
else
DrawEdge (hdc, &rc, EDGE_ETCHED, BF_LEFT);
}
/* horizontal separator */
if (lpitem->fType & MF_SEPARATOR)
{
HPEN oldPen;
RECT rc = rect;
InflateRect( &rc, -1, 0 );
rc.top = ( rc.top + rc.bottom) / 2;
if (flat_menu)
{
oldPen = SelectObject( hdc, SYSCOLOR_GetPen(COLOR_BTNSHADOW) );
MoveToEx( hdc, rc.left, rc.top, NULL );
LineTo( hdc, rc.right, rc.top );
SelectObject( hdc, oldPen );
}
else
DrawEdge (hdc, &rc, EDGE_ETCHED, BF_TOP);
goto done;
}
/* helper lines for debugging */
/* FrameRect(hdc, &rect, GetStockObject(BLACK_BRUSH));
SelectObject( hdc, SYSCOLOR_GetPen(COLOR_WINDOWFRAME) );
MoveToEx( hdc, rect.left, (rect.top + rect.bottom)/2, NULL );
LineTo( hdc, rect.right, (rect.top + rect.bottom)/2 );
*/
if (lpitem->hbmpItem) {
/* calculate the bitmap rectangle in coordinates relative
* to the item rectangle */
if( menuBar) {
if( lpitem->hbmpItem == HBMMENU_CALLBACK)
bmprc.left = 3;
else
bmprc.left = lpitem->text ? menucharsize.cx : 0;
}
else if (menu->dwStyle & MNS_NOCHECK)
bmprc.left = 4;
else if (menu->dwStyle & MNS_CHECKORBMP)
bmprc.left = 2;
else
bmprc.left = 4 + GetSystemMetrics(SM_CXMENUCHECK);
bmprc.right = bmprc.left + lpitem->bmpsize.cx;
if( menuBar && !(lpitem->hbmpItem == HBMMENU_CALLBACK))
bmprc.top = 0;
else
bmprc.top = (rect.bottom - rect.top -
lpitem->bmpsize.cy) / 2;
bmprc.bottom = bmprc.top + lpitem->bmpsize.cy;
}
if (!menuBar)
{
HBITMAP bm;
INT y = rect.top + rect.bottom;
BOOL checked = FALSE;
UINT check_bitmap_width = GetSystemMetrics( SM_CXMENUCHECK );
UINT check_bitmap_height = GetSystemMetrics( SM_CYMENUCHECK );
/* Draw the check mark
*
* FIXME:
* Custom checkmark bitmaps are monochrome but not always 1bpp.
*/
if (!(menu->dwStyle & MNS_NOCHECK))
{
bm = (lpitem->fState & MF_CHECKED) ? lpitem->hCheckBit :
lpitem->hUnCheckBit;
if (bm) /* we have a custom bitmap */
{
HDC hdcMem = CreateCompatibleDC( hdc );
SelectObject( hdcMem, bm );
BitBlt( hdc, rect.left, (y - check_bitmap_height) / 2,
check_bitmap_width, check_bitmap_height,
hdcMem, 0, 0, SRCCOPY );
DeleteDC( hdcMem );
checked = TRUE;
}
else if (lpitem->fState & MF_CHECKED) /* standard bitmaps */
{
RECT r;
HBITMAP bm = CreateBitmap( check_bitmap_width,
check_bitmap_height, 1, 1, NULL );
HDC hdcMem = CreateCompatibleDC( hdc );
SelectObject( hdcMem, bm );
SetRect( &r, 0, 0, check_bitmap_width, check_bitmap_height);
DrawFrameControl( hdcMem, &r, DFC_MENU,
(lpitem->fType & MFT_RADIOCHECK) ?
DFCS_MENUBULLET : DFCS_MENUCHECK );
BitBlt( hdc, rect.left, (y - r.bottom) / 2, r.right, r.bottom,
hdcMem, 0, 0, SRCCOPY );
DeleteDC( hdcMem );
DeleteObject( bm );
checked = TRUE;
}
}
if (lpitem->hbmpItem && !(checked && (menu->dwStyle & MNS_CHECKORBMP)))
{
POINT origorg;
/* some applications make this assumption on the DC's origin */
SetViewportOrgEx( hdc, rect.left, rect.top, &origorg);
MENU_DrawBitmapItem( hdc, lpitem, &bmprc, menu, hwndOwner, odaction );
SetViewportOrgEx( hdc, origorg.x, origorg.y, NULL);
}
/* Draw the popup-menu arrow */
if (lpitem->fType & MF_POPUP)
draw_popup_arrow( hdc, rect, arrow_bitmap_width,
arrow_bitmap_height);
rect.left += 4;
if( !(menu->dwStyle & MNS_NOCHECK))
rect.left += check_bitmap_width;
rect.right -= arrow_bitmap_width;
}
else if (lpitem->hbmpItem)
{ /* Draw the bitmap */
POINT origorg;
SetViewportOrgEx( hdc, rect.left, rect.top, &origorg);
MENU_DrawBitmapItem( hdc, lpitem, &bmprc, menu, hwndOwner, odaction );
SetViewportOrgEx( hdc, origorg.x, origorg.y, NULL);
}
/* process text if present */
if (lpitem->text)
{
int i;
HFONT hfontOld = 0;
UINT uFormat = (menuBar) ?
DT_CENTER | DT_VCENTER | DT_SINGLELINE :
DT_LEFT | DT_VCENTER | DT_SINGLELINE;
if( !(menu->dwStyle & MNS_CHECKORBMP))
rect.left += menu->textOffset;
if ( lpitem->fState & MFS_DEFAULT )
{
hfontOld = SelectObject( hdc, get_menu_font(TRUE) );
}
if (menuBar) {
if( lpitem->hbmpItem)
rect.left += lpitem->bmpsize.cx;
if( !(lpitem->hbmpItem == HBMMENU_CALLBACK))
rect.left += menucharsize.cx;
rect.right -= menucharsize.cx;
}
for (i = 0; lpitem->text[i]; i++)
if ((lpitem->text[i] == '\t') || (lpitem->text[i] == '\b'))
break;
if(lpitem->fState & MF_GRAYED)
{
if (!(lpitem->fState & MF_HILITE) )
{
++rect.left; ++rect.top; ++rect.right; ++rect.bottom;
SetTextColor(hdc, RGB(0xff, 0xff, 0xff));
DrawTextW( hdc, lpitem->text, i, &rect, uFormat );
--rect.left; --rect.top; --rect.right; --rect.bottom;
}
SetTextColor(hdc, RGB(0x80, 0x80, 0x80));
}
DrawTextW( hdc, lpitem->text, i, &rect, uFormat);
/* paint the shortcut text */
if (!menuBar && lpitem->text[i]) /* There's a tab or flush-right char */
{
if (lpitem->text[i] == '\t')
{
rect.left = lpitem->xTab;
uFormat = DT_LEFT | DT_VCENTER | DT_SINGLELINE;
}
else
{
rect.right = lpitem->xTab;
uFormat = DT_RIGHT | DT_VCENTER | DT_SINGLELINE;
}
if(lpitem->fState & MF_GRAYED)
{
if (!(lpitem->fState & MF_HILITE) )
{
++rect.left; ++rect.top; ++rect.right; ++rect.bottom;
SetTextColor(hdc, RGB(0xff, 0xff, 0xff));
DrawTextW( hdc, lpitem->text + i + 1, -1, &rect, uFormat );
--rect.left; --rect.top; --rect.right; --rect.bottom;
}
SetTextColor(hdc, RGB(0x80, 0x80, 0x80));
}
DrawTextW( hdc, lpitem->text + i + 1, -1, &rect, uFormat );
}
if (hfontOld)
SelectObject (hdc, hfontOld);
}
done:
ExtSelectClipRgn( hdc, old_clip, RGN_COPY );
if (old_clip) DeleteObject( old_clip );
}
/***********************************************************************
* MENU_DrawPopupMenu
*
* Paint a popup menu.
*/
static void MENU_DrawPopupMenu( HWND hwnd, HDC hdc, HMENU hmenu )
{
HBRUSH hPrevBrush, brush = GetSysColorBrush( COLOR_MENU );
RECT rect;
POPUPMENU *menu = MENU_GetMenu( hmenu );
TRACE("wnd=%p dc=%p menu=%p\n", hwnd, hdc, hmenu);
GetClientRect( hwnd, &rect );
if (menu && menu->hbrBack) brush = menu->hbrBack;
if ((hPrevBrush = SelectObject( hdc, brush ))
&& SelectObject( hdc, get_menu_font(FALSE) ))
{
HPEN hPrevPen;
Rectangle( hdc, rect.left, rect.top, rect.right, rect.bottom );
hPrevPen = SelectObject( hdc, GetStockObject( NULL_PEN ) );
if( hPrevPen )
{
BOOL flat_menu = FALSE;
SystemParametersInfoW (SPI_GETFLATMENU, 0, &flat_menu, 0);
if (flat_menu)
FrameRect(hdc, &rect, GetSysColorBrush(COLOR_BTNSHADOW));
else
DrawEdge (hdc, &rect, EDGE_RAISED, BF_RECT);
if (menu)
{
TRACE("hmenu %p Style %08x\n", hmenu, menu->dwStyle);
/* draw menu items */
if (menu->nItems)
{
MENUITEM *item;
UINT u;
item = menu->items;
for (u = menu->nItems; u > 0; u--, item++)
MENU_DrawMenuItem( hwnd, menu, menu->hwndOwner, hdc,
item, FALSE, ODA_DRAWENTIRE );
}
/* draw scroll arrows */
if (menu->bScrolling)
MENU_DrawScrollArrows(menu, hdc);
}
} else
{
SelectObject( hdc, hPrevBrush );
}
}
}
/***********************************************************************
* MENU_DrawMenuBar
*
* Paint a menu bar. Returns the height of the menu bar.
* called from [windows/nonclient.c]
*/
UINT MENU_DrawMenuBar( HDC hDC, LPRECT lprect, HWND hwnd )
{
LPPOPUPMENU lppop;
HMENU hMenu = GetMenu(hwnd);
lppop = MENU_GetMenu( hMenu );
if (lppop == NULL || lprect == NULL)
{
return GetSystemMetrics(SM_CYMENU);
}
return DrawMenuBarTemp(hwnd, hDC, lprect, hMenu, NULL);
}
/***********************************************************************
* MENU_InitPopup
*
* Popup menu initialization before WM_ENTERMENULOOP.
*/
static BOOL MENU_InitPopup( HWND hwndOwner, HMENU hmenu, UINT flags )
{
POPUPMENU *menu;
DWORD ex_style = 0;
TRACE("owner=%p hmenu=%p\n", hwndOwner, hmenu);
if (!(menu = MENU_GetMenu( hmenu ))) return FALSE;
/* store the owner for DrawItem */
if (!IsWindow( hwndOwner ))
{
SetLastError( ERROR_INVALID_WINDOW_HANDLE );
return FALSE;
}
menu->hwndOwner = hwndOwner;
if (flags & TPM_LAYOUTRTL)
ex_style = WS_EX_LAYOUTRTL;
/* NOTE: In Windows, top menu popup is not owned. */
menu->hWnd = CreateWindowExW( ex_style, (LPCWSTR)POPUPMENU_CLASS_ATOM, NULL,
WS_POPUP, 0, 0, 0, 0,
hwndOwner, 0, (HINSTANCE)GetWindowLongPtrW(hwndOwner, GWLP_HINSTANCE),
(LPVOID)hmenu );
if( !menu->hWnd ) return FALSE;
return TRUE;
}
/***********************************************************************
* MENU_ShowPopup
*
* Display a popup menu.
*/
static BOOL MENU_ShowPopup( HWND hwndOwner, HMENU hmenu, UINT id, UINT flags,
INT x, INT y, INT xanchor, INT yanchor )
{
POPUPMENU *menu;
POINT pt;
HMONITOR monitor;
MONITORINFO info;
UINT max_height;
TRACE("owner=%p hmenu=%p id=0x%04x x=0x%04x y=0x%04x xa=0x%04x ya=0x%04x\n",
hwndOwner, hmenu, id, x, y, xanchor, yanchor);
if (!(menu = MENU_GetMenu( hmenu ))) return FALSE;
if (menu->FocusedItem != NO_SELECTED_ITEM)
{
menu->items[menu->FocusedItem].fState &= ~(MF_HILITE|MF_MOUSESELECT);
menu->FocusedItem = NO_SELECTED_ITEM;
}
menu->nScrollPos = 0;
/* FIXME: should use item rect */
pt.x = x;
pt.y = y;
monitor = MonitorFromPoint( pt, MONITOR_DEFAULTTONEAREST );
info.cbSize = sizeof(info);
GetMonitorInfoW( monitor, &info );
max_height = info.rcWork.bottom - info.rcWork.top;
if (menu->cyMax)
max_height = min( max_height, menu->cyMax );
MENU_PopupMenuCalcSize( menu, max_height );
/* adjust popup menu pos so that it fits within the desktop */
if (flags & TPM_LAYOUTRTL)
flags ^= TPM_RIGHTALIGN;
if( flags & TPM_RIGHTALIGN ) x -= menu->Width;
if( flags & TPM_CENTERALIGN ) x -= menu->Width / 2;
if( flags & TPM_BOTTOMALIGN ) y -= menu->Height;
if( flags & TPM_VCENTERALIGN ) y -= menu->Height / 2;
if( x + menu->Width > info.rcWork.right)
{
if( xanchor && x >= menu->Width - xanchor )
x -= menu->Width - xanchor;
if( x + menu->Width > info.rcWork.right)
x = info.rcWork.right - menu->Width;
}
if( x < info.rcWork.left ) x = info.rcWork.left;
if( y + menu->Height > info.rcWork.bottom)
{
if( yanchor && y >= menu->Height + yanchor )
y -= menu->Height + yanchor;
if( y + menu->Height > info.rcWork.bottom)
y = info.rcWork.bottom - menu->Height;
}
if( y < info.rcWork.top ) y = info.rcWork.top;
if (!top_popup) {
top_popup = menu->hWnd;
top_popup_hmenu = hmenu;
}
/* Display the window */
SetWindowPos( menu->hWnd, HWND_TOPMOST, x, y, menu->Width, menu->Height,
SWP_SHOWWINDOW | SWP_NOACTIVATE );
UpdateWindow( menu->hWnd );
return TRUE;
}
/***********************************************************************
* MENU_EnsureMenuItemVisible
*/
static void
MENU_EnsureMenuItemVisible(LPPOPUPMENU lppop, UINT wIndex, HDC hdc)
{
if (lppop->bScrolling)
{
MENUITEM *item = &lppop->items[wIndex];
UINT nOldPos = lppop->nScrollPos;
const RECT *rc = &lppop->items_rect;
UINT scroll_height = rc->bottom - rc->top;
if (item->rect.bottom > lppop->nScrollPos + scroll_height)
{
lppop->nScrollPos = item->rect.bottom - scroll_height;
ScrollWindow(lppop->hWnd, 0, nOldPos - lppop->nScrollPos, rc, rc);
}
else if (item->rect.top < lppop->nScrollPos)
{
lppop->nScrollPos = item->rect.top;
ScrollWindow(lppop->hWnd, 0, nOldPos - lppop->nScrollPos, rc, rc);
}
/* Invalidate the scroll arrows if necessary */
if (nOldPos != lppop->nScrollPos)
{
RECT arrow_rect = lppop->items_rect;
if (nOldPos == 0 || lppop->nScrollPos == 0)
{
arrow_rect.top = 0;
arrow_rect.bottom = lppop->items_rect.top;
InvalidateRect(lppop->hWnd, &arrow_rect, FALSE);
}
if (nOldPos + scroll_height == lppop->nTotalHeight ||
lppop->nScrollPos + scroll_height == lppop->nTotalHeight)
{
arrow_rect.top = lppop->items_rect.bottom;
arrow_rect.bottom = lppop->Height;
InvalidateRect(lppop->hWnd, &arrow_rect, FALSE);
}
}
}
}
/***********************************************************************
* MENU_SelectItem
*/
static void MENU_SelectItem( HWND hwndOwner, HMENU hmenu, UINT wIndex,
BOOL sendMenuSelect, HMENU topmenu )
{
LPPOPUPMENU lppop;
HDC hdc;
TRACE("owner=%p menu=%p index=0x%04x select=0x%04x\n", hwndOwner, hmenu, wIndex, sendMenuSelect);
lppop = MENU_GetMenu( hmenu );
if ((!lppop) || (!lppop->nItems) || (!lppop->hWnd)) return;
if (lppop->FocusedItem == wIndex) return;
if (lppop->wFlags & MF_POPUP) hdc = GetDC( lppop->hWnd );
else hdc = GetDCEx( lppop->hWnd, 0, DCX_CACHE | DCX_WINDOW);
if (!top_popup) {
top_popup = lppop->hWnd;
top_popup_hmenu = hmenu;
}
SelectObject( hdc, get_menu_font(FALSE));
/* Clear previous highlighted item */
if (lppop->FocusedItem != NO_SELECTED_ITEM)
{
lppop->items[lppop->FocusedItem].fState &= ~(MF_HILITE|MF_MOUSESELECT);
MENU_DrawMenuItem( lppop->hWnd, lppop, hwndOwner, hdc, &lppop->items[lppop->FocusedItem],
!(lppop->wFlags & MF_POPUP), ODA_SELECT );
}
/* Highlight new item (if any) */
lppop->FocusedItem = wIndex;
if (lppop->FocusedItem != NO_SELECTED_ITEM)
{
if(!(lppop->items[wIndex].fType & MF_SEPARATOR)) {
lppop->items[wIndex].fState |= MF_HILITE;
MENU_EnsureMenuItemVisible(lppop, wIndex, hdc);
MENU_DrawMenuItem( lppop->hWnd, lppop, hwndOwner, hdc, &lppop->items[wIndex],
!(lppop->wFlags & MF_POPUP), ODA_SELECT );
}
if (sendMenuSelect)
{
MENUITEM *ip = &lppop->items[lppop->FocusedItem];
SendMessageW( hwndOwner, WM_MENUSELECT,
MAKEWPARAM(ip->fType & MF_POPUP ? wIndex: ip->wID,
ip->fType | ip->fState |
(lppop->wFlags & MF_SYSMENU)), (LPARAM)hmenu);
}
}
else if (sendMenuSelect) {
if(topmenu){
int pos;
if((pos=MENU_FindSubMenu(&topmenu, hmenu))!=NO_SELECTED_ITEM){
POPUPMENU *ptm = MENU_GetMenu( topmenu );
MENUITEM *ip = &ptm->items[pos];
SendMessageW( hwndOwner, WM_MENUSELECT, MAKEWPARAM(pos,
ip->fType | ip->fState |
(ptm->wFlags & MF_SYSMENU)), (LPARAM)topmenu);
}
}
}
ReleaseDC( lppop->hWnd, hdc );
}
/***********************************************************************
* MENU_MoveSelection
*
* Moves currently selected item according to the offset parameter.
* If there is no selection then it should select the last item if
* offset is ITEM_PREV or the first item if offset is ITEM_NEXT.
*/
static void MENU_MoveSelection( HWND hwndOwner, HMENU hmenu, INT offset )
{
INT i;
POPUPMENU *menu;
TRACE("hwnd=%p hmenu=%p off=0x%04x\n", hwndOwner, hmenu, offset);
menu = MENU_GetMenu( hmenu );
if ((!menu) || (!menu->items)) return;
if ( menu->FocusedItem != NO_SELECTED_ITEM )
{
if( menu->nItems == 1 ) return; else
for (i = menu->FocusedItem + offset ; i >= 0 && i < menu->nItems
; i += offset)
if (!(menu->items[i].fType & MF_SEPARATOR))
{
MENU_SelectItem( hwndOwner, hmenu, i, TRUE, 0 );
return;
}
}
for ( i = (offset > 0) ? 0 : menu->nItems - 1;
i >= 0 && i < menu->nItems ; i += offset)
if (!(menu->items[i].fType & MF_SEPARATOR))
{
MENU_SelectItem( hwndOwner, hmenu, i, TRUE, 0 );
return;
}
}
/**********************************************************************
* insert_menu_item
*
* Insert (allocate) a new item into a menu.
*/
static POPUPMENU *insert_menu_item(HMENU hMenu, UINT id, UINT flags, UINT *ret_pos)
{
MENUITEM *newItems;
POPUPMENU *menu;
UINT pos = id;
/* Find where to insert new item */
if (!(menu = find_menu_item(hMenu, id, flags, &pos)))
{
if (!(menu = grab_menu_ptr(hMenu)))
return NULL;
pos = menu->nItems;
}
/* Make sure that MDI system buttons stay on the right side.
* Note: XP treats only bitmap handles 1 - 6 as "magic" ones
* regardless of their id.
*/
while (pos > 0 && (INT_PTR)menu->items[pos - 1].hbmpItem >= (INT_PTR)HBMMENU_SYSTEM &&
(INT_PTR)menu->items[pos - 1].hbmpItem <= (INT_PTR)HBMMENU_MBAR_CLOSE_D)
pos--;
TRACE("inserting at %u flags %x\n", pos, flags);
/* Create new items array */
newItems = HeapAlloc( GetProcessHeap(), 0, sizeof(MENUITEM) * (menu->nItems+1) );
if (!newItems)
{
release_menu_ptr(menu);
WARN("allocation failed\n" );
return NULL;
}
if (menu->nItems > 0)
{
/* Copy the old array into the new one */
if (pos > 0) memcpy( newItems, menu->items, pos * sizeof(MENUITEM) );
if (pos < menu->nItems) memcpy( &newItems[pos+1], &menu->items[pos],
(menu->nItems-pos)*sizeof(MENUITEM) );
HeapFree( GetProcessHeap(), 0, menu->items );
}
menu->items = newItems;
menu->nItems++;
memset( &newItems[pos], 0, sizeof(*newItems) );
menu->Height = 0; /* force size recalculate */
*ret_pos = pos;
return menu;
}
/**********************************************************************
* MENU_ParseResource
*
* Parse a standard menu resource and add items to the menu.
* Return a pointer to the end of the resource.
*
* NOTE: flags is equivalent to the mtOption field
*/
static LPCSTR MENU_ParseResource( LPCSTR res, HMENU hMenu )
{
WORD flags, id = 0;
LPCWSTR str;
BOOL end_flag;
do
{
flags = GET_WORD(res);
end_flag = flags & MF_END;
/* Remove MF_END because it has the same value as MF_HILITE */
flags &= ~MF_END;
res += sizeof(WORD);
if (!(flags & MF_POPUP))
{
id = GET_WORD(res);
res += sizeof(WORD);
}
str = (LPCWSTR)res;
res += (strlenW(str) + 1) * sizeof(WCHAR);
if (flags & MF_POPUP)
{
HMENU hSubMenu = CreatePopupMenu();
if (!hSubMenu) return NULL;
if (!(res = MENU_ParseResource( res, hSubMenu ))) return NULL;
AppendMenuW( hMenu, flags, (UINT_PTR)hSubMenu, str );
}
else /* Not a popup */
{
AppendMenuW( hMenu, flags, id, *str ? str : NULL );
}
} while (!end_flag);
return res;
}
/**********************************************************************
* MENUEX_ParseResource
*
* Parse an extended menu resource and add items to the menu.
* Return a pointer to the end of the resource.
*/
static LPCSTR MENUEX_ParseResource( LPCSTR res, HMENU hMenu)
{
WORD resinfo;
do {
MENUITEMINFOW mii;
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_STATE | MIIM_ID | MIIM_TYPE;
mii.fType = GET_DWORD(res);
res += sizeof(DWORD);
mii.fState = GET_DWORD(res);
res += sizeof(DWORD);
mii.wID = GET_DWORD(res);
res += sizeof(DWORD);
resinfo = GET_WORD(res); /* FIXME: for 16-bit apps this is a byte. */
res += sizeof(WORD);
/* Align the text on a word boundary. */
res += (~((UINT_PTR)res - 1)) & 1;
mii.dwTypeData = (LPWSTR) res;
res += (1 + strlenW(mii.dwTypeData)) * sizeof(WCHAR);
/* Align the following fields on a dword boundary. */
res += (~((UINT_PTR)res - 1)) & 3;
TRACE("Menu item: [%08x,%08x,%04x,%04x,%s]\n",
mii.fType, mii.fState, mii.wID, resinfo, debugstr_w(mii.dwTypeData));
if (resinfo & 1) { /* Pop-up? */
/* DWORD helpid = GET_DWORD(res); FIXME: use this. */
res += sizeof(DWORD);
mii.hSubMenu = CreatePopupMenu();
if (!mii.hSubMenu)
return NULL;
if (!(res = MENUEX_ParseResource(res, mii.hSubMenu))) {
DestroyMenu(mii.hSubMenu);
return NULL;
}
mii.fMask |= MIIM_SUBMENU;
mii.fType |= MF_POPUP;
}
else if(!*mii.dwTypeData && !(mii.fType & MF_SEPARATOR))
{
WARN("Converting NULL menu item %04x, type %04x to SEPARATOR\n",
mii.wID, mii.fType);
mii.fType |= MF_SEPARATOR;
}
InsertMenuItemW(hMenu, -1, MF_BYPOSITION, &mii);
} while (!(resinfo & MF_END));
return res;
}
/***********************************************************************
* MENU_GetSubPopup
*
* Return the handle of the selected sub-popup menu (if any).
*/
static HMENU MENU_GetSubPopup( HMENU hmenu )
{
POPUPMENU *menu;
MENUITEM *item;
menu = MENU_GetMenu( hmenu );
if ((!menu) || (menu->FocusedItem == NO_SELECTED_ITEM)) return 0;
item = &menu->items[menu->FocusedItem];
if ((item->fType & MF_POPUP) && (item->fState & MF_MOUSESELECT))
return item->hSubMenu;
return 0;
}
/***********************************************************************
* MENU_HideSubPopups
*
* Hide the sub-popup menus of this menu.
*/
static void MENU_HideSubPopups( HWND hwndOwner, HMENU hmenu,
BOOL sendMenuSelect, UINT wFlags )
{
POPUPMENU *menu = MENU_GetMenu( hmenu );
TRACE("owner=%p hmenu=%p 0x%04x\n", hwndOwner, hmenu, sendMenuSelect);
if (menu && top_popup)
{
HMENU hsubmenu;
POPUPMENU *submenu;
MENUITEM *item;
if (menu->FocusedItem != NO_SELECTED_ITEM)
{
item = &menu->items[menu->FocusedItem];
if (!(item->fType & MF_POPUP) ||
!(item->fState & MF_MOUSESELECT)) return;
item->fState &= ~MF_MOUSESELECT;
hsubmenu = item->hSubMenu;
} else return;
if (!(submenu = MENU_GetMenu( hsubmenu ))) return;
MENU_HideSubPopups( hwndOwner, hsubmenu, FALSE, wFlags );
MENU_SelectItem( hwndOwner, hsubmenu, NO_SELECTED_ITEM, sendMenuSelect, 0 );
DestroyWindow( submenu->hWnd );
submenu->hWnd = 0;
if (!(wFlags & TPM_NONOTIFY))
SendMessageW( hwndOwner, WM_UNINITMENUPOPUP, (WPARAM)hsubmenu,
MAKELPARAM(0, IS_SYSTEM_MENU(submenu)) );
}
}
/***********************************************************************
* MENU_ShowSubPopup
*
* Display the sub-menu of the selected item of this menu.
* Return the handle of the submenu, or hmenu if no submenu to display.
*/
static HMENU MENU_ShowSubPopup( HWND hwndOwner, HMENU hmenu,
BOOL selectFirst, UINT wFlags )
{
RECT rect;
POPUPMENU *menu;
MENUITEM *item;
HDC hdc;
TRACE("owner=%p hmenu=%p 0x%04x\n", hwndOwner, hmenu, selectFirst);
if (!(menu = MENU_GetMenu( hmenu ))) return hmenu;
if (menu->FocusedItem == NO_SELECTED_ITEM) return hmenu;
item = &menu->items[menu->FocusedItem];
if (!(item->fType & MF_POPUP) || (item->fState & (MF_GRAYED | MF_DISABLED)))
return hmenu;
/* message must be sent before using item,
because nearly everything may be changed by the application ! */
/* Send WM_INITMENUPOPUP message only if TPM_NONOTIFY flag is not specified */
if (!(wFlags & TPM_NONOTIFY))
SendMessageW( hwndOwner, WM_INITMENUPOPUP, (WPARAM)item->hSubMenu,
MAKELPARAM( menu->FocusedItem, IS_SYSTEM_MENU(menu) ));
item = &menu->items[menu->FocusedItem];
rect = item->rect;
/* correct item if modified as a reaction to WM_INITMENUPOPUP message */
if (!(item->fState & MF_HILITE))
{
if (menu->wFlags & MF_POPUP) hdc = GetDC( menu->hWnd );
else hdc = GetDCEx( menu->hWnd, 0, DCX_CACHE | DCX_WINDOW);
SelectObject( hdc, get_menu_font(FALSE));
item->fState |= MF_HILITE;
MENU_DrawMenuItem( menu->hWnd, menu, hwndOwner, hdc, item, !(menu->wFlags & MF_POPUP), ODA_DRAWENTIRE );
ReleaseDC( menu->hWnd, hdc );
}
if (!item->rect.top && !item->rect.left && !item->rect.bottom && !item->rect.right)
item->rect = rect;
item->fState |= MF_MOUSESELECT;
if (IS_SYSTEM_MENU(menu))
{
MENU_InitSysMenuPopup(item->hSubMenu,
GetWindowLongW( menu->hWnd, GWL_STYLE ),
GetClassLongW( menu->hWnd, GCL_STYLE));
NC_GetSysPopupPos( menu->hWnd, &rect );
if (wFlags & TPM_LAYOUTRTL) rect.left = rect.right;
rect.top = rect.bottom;
rect.right = GetSystemMetrics(SM_CXSIZE);
rect.bottom = GetSystemMetrics(SM_CYSIZE);
}
else
{
RECT item_rect = item->rect;
MENU_AdjustMenuItemRect(menu, &item_rect);
GetWindowRect( menu->hWnd, &rect );
if (menu->wFlags & MF_POPUP)
{
/* The first item in the popup menu has to be at the
same y position as the focused menu item */
if (wFlags & TPM_LAYOUTRTL)
rect.left += GetSystemMetrics(SM_CXBORDER);
else
rect.left += item_rect.right - GetSystemMetrics(SM_CXBORDER);
rect.top += item_rect.top - MENU_MARGIN;
rect.right = item_rect.left - item_rect.right + GetSystemMetrics(SM_CXBORDER);
rect.bottom = item_rect.top - item_rect.bottom - 2 * MENU_MARGIN;
}
else
{
if (wFlags & TPM_LAYOUTRTL)
rect.left = rect.right - item_rect.left;
else
rect.left += item_rect.left;
rect.top += item_rect.bottom;
rect.right = item_rect.right - item_rect.left;
rect.bottom = item_rect.bottom - item_rect.top;
}
}
/* use default alignment for submenus */
wFlags &= ~(TPM_CENTERALIGN | TPM_RIGHTALIGN | TPM_VCENTERALIGN | TPM_BOTTOMALIGN);
MENU_InitPopup( hwndOwner, item->hSubMenu, wFlags );
MENU_ShowPopup( hwndOwner, item->hSubMenu, menu->FocusedItem, wFlags,
rect.left, rect.top, rect.right, rect.bottom );
if (selectFirst)
MENU_MoveSelection( hwndOwner, item->hSubMenu, ITEM_NEXT );
return item->hSubMenu;
}
/**********************************************************************
* MENU_IsMenuActive
*/
HWND MENU_IsMenuActive(void)
{
return top_popup;
}
/**********************************************************************
* MENU_EndMenu
*
* Calls EndMenu() if the hwnd parameter belongs to the menu owner
*
* Does the (menu stuff) of the default window handling of WM_CANCELMODE
*/
void MENU_EndMenu( HWND hwnd )
{
POPUPMENU *menu;
menu = top_popup_hmenu ? MENU_GetMenu( top_popup_hmenu ) : NULL;
if (menu && (hwnd == menu->hWnd || hwnd == menu->hwndOwner)) EndMenu();
}
/***********************************************************************
* MENU_PtMenu
*
* Walks menu chain trying to find a menu pt maps to.
*/
static HMENU MENU_PtMenu( HMENU hMenu, POINT pt )
{
POPUPMENU *menu = MENU_GetMenu( hMenu );
UINT item = menu->FocusedItem;
HMENU ret;
/* try subpopup first (if any) */
ret = (item != NO_SELECTED_ITEM &&
(menu->items[item].fType & MF_POPUP) &&
(menu->items[item].fState & MF_MOUSESELECT))
? MENU_PtMenu(menu->items[item].hSubMenu, pt) : 0;
if (!ret) /* check the current window (avoiding WM_HITTEST) */
{
INT ht = NC_HandleNCHitTest( menu->hWnd, pt );
if( menu->wFlags & MF_POPUP )
{
if (ht != HTNOWHERE && ht != HTERROR) ret = hMenu;
}
else if (ht == HTSYSMENU)
ret = get_win_sys_menu( menu->hWnd );
else if (ht == HTMENU)
ret = GetMenu( menu->hWnd );
}
return ret;
}
/***********************************************************************
* MENU_ExecFocusedItem
*
* Execute a menu item (for instance when user pressed Enter).
* Return the wID of the executed item. Otherwise, -1 indicating
* that no menu item was executed, -2 if a popup is shown;
* Have to receive the flags for the TrackPopupMenu options to avoid
* sending unwanted message.
*
*/
static INT MENU_ExecFocusedItem( MTRACKER* pmt, HMENU hMenu, UINT wFlags )
{
MENUITEM *item;
POPUPMENU *menu = MENU_GetMenu( hMenu );
TRACE("%p hmenu=%p\n", pmt, hMenu);
if (!menu || !menu->nItems ||
(menu->FocusedItem == NO_SELECTED_ITEM)) return -1;
item = &menu->items[menu->FocusedItem];
TRACE("hMenu %p wID %08lx hSubMenu %p fType %04x\n", hMenu, item->wID, item->hSubMenu, item->fType);
if (!(item->fType & MF_POPUP))
{
if (!(item->fState & (MF_GRAYED | MF_DISABLED)) && !(item->fType & MF_SEPARATOR))
{
/* If TPM_RETURNCMD is set you return the id, but
do not send a message to the owner */
if(!(wFlags & TPM_RETURNCMD))
{
if( menu->wFlags & MF_SYSMENU )
PostMessageW( pmt->hOwnerWnd, WM_SYSCOMMAND, item->wID,
MAKELPARAM((INT16)pmt->pt.x, (INT16)pmt->pt.y) );
else
{
POPUPMENU *topmenu = MENU_GetMenu( pmt->hTopMenu );
DWORD dwStyle = menu->dwStyle | (topmenu ? topmenu->dwStyle : 0);
if (dwStyle & MNS_NOTIFYBYPOS)
PostMessageW( pmt->hOwnerWnd, WM_MENUCOMMAND, menu->FocusedItem,
(LPARAM)hMenu);
else
PostMessageW( pmt->hOwnerWnd, WM_COMMAND, item->wID, 0 );
}
}
return item->wID;
}
}
else
{
pmt->hCurrentMenu = MENU_ShowSubPopup(pmt->hOwnerWnd, hMenu, TRUE, wFlags);
return -2;
}
return -1;
}
/***********************************************************************
* MENU_SwitchTracking
*
* Helper function for menu navigation routines.
*/
static void MENU_SwitchTracking( MTRACKER* pmt, HMENU hPtMenu, UINT id, UINT wFlags )
{
POPUPMENU *ptmenu = MENU_GetMenu( hPtMenu );
POPUPMENU *topmenu = MENU_GetMenu( pmt->hTopMenu );
TRACE("%p hmenu=%p 0x%04x\n", pmt, hPtMenu, id);
if( pmt->hTopMenu != hPtMenu &&
!((ptmenu->wFlags | topmenu->wFlags) & MF_POPUP) )
{
/* both are top level menus (system and menu-bar) */
MENU_HideSubPopups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, wFlags );
MENU_SelectItem( pmt->hOwnerWnd, pmt->hTopMenu, NO_SELECTED_ITEM, FALSE, 0 );
pmt->hTopMenu = hPtMenu;
}
else MENU_HideSubPopups( pmt->hOwnerWnd, hPtMenu, FALSE, wFlags );
MENU_SelectItem( pmt->hOwnerWnd, hPtMenu, id, TRUE, 0 );
}
/***********************************************************************
* MENU_ButtonDown
*
* Return TRUE if we can go on with menu tracking.
*/
static BOOL MENU_ButtonDown( MTRACKER* pmt, UINT message, HMENU hPtMenu, UINT wFlags )
{
TRACE("%p hPtMenu=%p\n", pmt, hPtMenu);
if (hPtMenu)
{
UINT pos;
POPUPMENU *ptmenu = MENU_GetMenu( hPtMenu );
enum hittest ht = ht_item;
if( IS_SYSTEM_MENU(ptmenu) )
{
if (message == WM_LBUTTONDBLCLK) return FALSE;
pos = 0;
}
else
ht = MENU_FindItemByCoords( ptmenu, pmt->pt, &pos );
if (pos != NO_SELECTED_ITEM)
{
if (ptmenu->FocusedItem != pos)
MENU_SwitchTracking( pmt, hPtMenu, pos, wFlags );
/* If the popup menu is not already "popped" */
if (!(ptmenu->items[pos].fState & MF_MOUSESELECT))
pmt->hCurrentMenu = MENU_ShowSubPopup( pmt->hOwnerWnd, hPtMenu, FALSE, wFlags );
}
/* A click on an item or anywhere on a popup keeps tracking going */
if (ht == ht_item || ((ptmenu->wFlags & MF_POPUP) && ht != ht_nowhere))
return TRUE;
}
return FALSE;
}
/***********************************************************************
* MENU_ButtonUp
*
* Return the value of MENU_ExecFocusedItem if
* the selected item was not a popup. Else open the popup.
* A -1 return value indicates that we go on with menu tracking.
*
*/
static INT MENU_ButtonUp( MTRACKER* pmt, HMENU hPtMenu, UINT wFlags)
{
TRACE("%p hmenu=%p\n", pmt, hPtMenu);
if (hPtMenu)
{
UINT pos;
POPUPMENU *ptmenu = MENU_GetMenu( hPtMenu );
if( IS_SYSTEM_MENU(ptmenu) )
pos = 0;
else if (MENU_FindItemByCoords( ptmenu, pmt->pt, &pos ) != ht_item)
pos = NO_SELECTED_ITEM;
if (pos != NO_SELECTED_ITEM && (ptmenu->FocusedItem == pos))
{
debug_print_menuitem ("FocusedItem: ", &ptmenu->items[pos], "");
if (!(ptmenu->items[pos].fType & MF_POPUP))
{
INT executedMenuId = MENU_ExecFocusedItem( pmt, hPtMenu, wFlags);
if (executedMenuId == -1 || executedMenuId == -2) return -1;
return executedMenuId;
}
/* If we are dealing with the menu bar */
/* and this is a click on an already "popped" item: */
/* Stop the menu tracking and close the opened submenus */
if(((pmt->hTopMenu == hPtMenu) || IS_SYSTEM_MENU(ptmenu)) && (pmt->trackFlags & TF_RCVD_BTN_UP))
return 0;
}
if( GetMenu(ptmenu->hWnd) == hPtMenu || IS_SYSTEM_MENU(ptmenu) )
{
if (pos == NO_SELECTED_ITEM) return 0;
pmt->trackFlags |= TF_RCVD_BTN_UP;
}
}
return -1;
}
/***********************************************************************
* MENU_MouseMove
*
* Return TRUE if we can go on with menu tracking.
*/
static BOOL MENU_MouseMove( MTRACKER* pmt, HMENU hPtMenu, UINT wFlags )
{
UINT id = NO_SELECTED_ITEM;
POPUPMENU *ptmenu = NULL;
if( hPtMenu )
{
ptmenu = MENU_GetMenu( hPtMenu );
if( IS_SYSTEM_MENU(ptmenu) )
id = 0;
else if (MENU_FindItemByCoords( ptmenu, pmt->pt, &id ) != ht_item)
id = NO_SELECTED_ITEM;
}
if( id == NO_SELECTED_ITEM )
{
MENU_SelectItem( pmt->hOwnerWnd, pmt->hCurrentMenu,
NO_SELECTED_ITEM, TRUE, pmt->hTopMenu);
}
else if( ptmenu->FocusedItem != id )
{
MENU_SwitchTracking( pmt, hPtMenu, id, wFlags );
pmt->hCurrentMenu = MENU_ShowSubPopup(pmt->hOwnerWnd, hPtMenu, FALSE, wFlags);
}
return TRUE;
}
/***********************************************************************
* MENU_DoNextMenu
*
* NOTE: WM_NEXTMENU documented in Win32 is a bit different.
*/
static LRESULT MENU_DoNextMenu( MTRACKER* pmt, UINT vk, UINT wFlags )
{
POPUPMENU *menu = MENU_GetMenu( pmt->hTopMenu );
BOOL atEnd = FALSE;
/* When skipping left, we need to do something special after the
first menu. */
if (vk == VK_LEFT && menu->FocusedItem == 0)
{
atEnd = TRUE;
}
/* When skipping right, for the non-system menu, we need to
handle the last non-special menu item (ie skip any window
icons such as MDI maximize, restore or close) */
else if ((vk == VK_RIGHT) && !IS_SYSTEM_MENU(menu))
{
UINT i = menu->FocusedItem + 1;
while (i < menu->nItems) {
if ((menu->items[i].wID >= SC_SIZE &&
menu->items[i].wID <= SC_RESTORE)) {
i++;
} else break;
}
if (i == menu->nItems) {
atEnd = TRUE;
}
}
/* When skipping right, we need to cater for the system menu */
else if ((vk == VK_RIGHT) && IS_SYSTEM_MENU(menu))
{
if (menu->FocusedItem == (menu->nItems - 1)) {
atEnd = TRUE;
}
}
if( atEnd )
{
MDINEXTMENU next_menu;
HMENU hNewMenu;
HWND hNewWnd;
UINT id = 0;
next_menu.hmenuIn = (IS_SYSTEM_MENU(menu)) ? GetSubMenu(pmt->hTopMenu,0) : pmt->hTopMenu;
next_menu.hmenuNext = 0;
next_menu.hwndNext = 0;
SendMessageW( pmt->hOwnerWnd, WM_NEXTMENU, vk, (LPARAM)&next_menu );
TRACE("%p [%p] -> %p [%p]\n",
pmt->hCurrentMenu, pmt->hOwnerWnd, next_menu.hmenuNext, next_menu.hwndNext );
if (!next_menu.hmenuNext || !next_menu.hwndNext)
{
DWORD style = GetWindowLongW( pmt->hOwnerWnd, GWL_STYLE );
hNewWnd = pmt->hOwnerWnd;
if( IS_SYSTEM_MENU(menu) )
{
/* switch to the menu bar */
if(style & WS_CHILD || !(hNewMenu = GetMenu(hNewWnd))) return FALSE;
if( vk == VK_LEFT )
{
menu = MENU_GetMenu( hNewMenu );
id = menu->nItems - 1;
/* Skip backwards over any system predefined icons,
eg. MDI close, restore etc icons */
while ((id > 0) &&
(menu->items[id].wID >= SC_SIZE &&
menu->items[id].wID <= SC_RESTORE)) id--;
}
}
else if (style & WS_SYSMENU )
{
/* switch to the system menu */
hNewMenu = get_win_sys_menu( hNewWnd );
}
else return FALSE;
}
else /* application returned a new menu to switch to */
{
hNewMenu = next_menu.hmenuNext;
hNewWnd = WIN_GetFullHandle( next_menu.hwndNext );
if( IsMenu(hNewMenu) && IsWindow(hNewWnd) )
{
DWORD style = GetWindowLongW( hNewWnd, GWL_STYLE );
if (style & WS_SYSMENU &&
GetSubMenu(get_win_sys_menu(hNewWnd), 0) == hNewMenu )
{
/* get the real system menu */
hNewMenu = get_win_sys_menu(hNewWnd);
}
else if (style & WS_CHILD || GetMenu(hNewWnd) != hNewMenu )
{
/* FIXME: Not sure what to do here;
* perhaps try to track hNewMenu as a popup? */
TRACE(" -- got confused.\n");
return FALSE;
}
}
else return FALSE;
}
if( hNewMenu != pmt->hTopMenu )
{
MENU_SelectItem( pmt->hOwnerWnd, pmt->hTopMenu, NO_SELECTED_ITEM,
FALSE, 0 );
if( pmt->hCurrentMenu != pmt->hTopMenu )
MENU_HideSubPopups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, wFlags );
}
if( hNewWnd != pmt->hOwnerWnd )
{
pmt->hOwnerWnd = hNewWnd;
set_capture_window( pmt->hOwnerWnd, GUI_INMENUMODE, NULL );
}
pmt->hTopMenu = pmt->hCurrentMenu = hNewMenu; /* all subpopups are hidden */
MENU_SelectItem( pmt->hOwnerWnd, pmt->hTopMenu, id, TRUE, 0 );
return TRUE;
}
return FALSE;
}
/***********************************************************************
* MENU_SuspendPopup
*
* The idea is not to show the popup if the next input message is
* going to hide it anyway.
*/
static BOOL MENU_SuspendPopup( MTRACKER* pmt, UINT uMsg )
{
MSG msg;
msg.hwnd = pmt->hOwnerWnd;
PeekMessageW( &msg, 0, uMsg, uMsg, PM_NOYIELD | PM_REMOVE);
pmt->trackFlags |= TF_SKIPREMOVE;
switch( uMsg )
{
case WM_KEYDOWN:
PeekMessageW( &msg, 0, 0, 0, PM_NOYIELD | PM_NOREMOVE);
if( msg.message == WM_KEYUP || msg.message == WM_PAINT )
{
PeekMessageW( &msg, 0, 0, 0, PM_NOYIELD | PM_REMOVE);
PeekMessageW( &msg, 0, 0, 0, PM_NOYIELD | PM_NOREMOVE);
if( msg.message == WM_KEYDOWN &&
(msg.wParam == VK_LEFT || msg.wParam == VK_RIGHT))
{
pmt->trackFlags |= TF_SUSPENDPOPUP;
return TRUE;
}
}
break;
}
/* failures go through this */
pmt->trackFlags &= ~TF_SUSPENDPOPUP;
return FALSE;
}
/***********************************************************************
* MENU_KeyEscape
*
* Handle a VK_ESCAPE key event in a menu.
*/
static BOOL MENU_KeyEscape(MTRACKER* pmt, UINT wFlags)
{
BOOL bEndMenu = TRUE;
if (pmt->hCurrentMenu != pmt->hTopMenu)
{
POPUPMENU *menu = MENU_GetMenu(pmt->hCurrentMenu);
if (menu->wFlags & MF_POPUP)
{
HMENU hmenutmp, hmenuprev;
hmenuprev = hmenutmp = pmt->hTopMenu;
/* close topmost popup */
while (hmenutmp != pmt->hCurrentMenu)
{
hmenuprev = hmenutmp;
hmenutmp = MENU_GetSubPopup( hmenuprev );
}
MENU_HideSubPopups( pmt->hOwnerWnd, hmenuprev, TRUE, wFlags );
pmt->hCurrentMenu = hmenuprev;
bEndMenu = FALSE;
}
}
return bEndMenu;
}
/***********************************************************************
* MENU_KeyLeft
*
* Handle a VK_LEFT key event in a menu.
*/
static void MENU_KeyLeft( MTRACKER* pmt, UINT wFlags, UINT msg )
{
POPUPMENU *menu;
HMENU hmenutmp, hmenuprev;
UINT prevcol;
hmenuprev = hmenutmp = pmt->hTopMenu;
menu = MENU_GetMenu( hmenutmp );
/* Try to move 1 column left (if possible) */
if( (prevcol = MENU_GetStartOfPrevColumn( pmt->hCurrentMenu )) !=
NO_SELECTED_ITEM ) {
MENU_SelectItem( pmt->hOwnerWnd, pmt->hCurrentMenu,
prevcol, TRUE, 0 );
return;
}
/* close topmost popup */
while (hmenutmp != pmt->hCurrentMenu)
{
hmenuprev = hmenutmp;
hmenutmp = MENU_GetSubPopup( hmenuprev );
}
MENU_HideSubPopups( pmt->hOwnerWnd, hmenuprev, TRUE, wFlags );
pmt->hCurrentMenu = hmenuprev;
if ( (hmenuprev == pmt->hTopMenu) && !(menu->wFlags & MF_POPUP) )
{
/* move menu bar selection if no more popups are left */
if( !MENU_DoNextMenu( pmt, VK_LEFT, wFlags ) )
MENU_MoveSelection( pmt->hOwnerWnd, pmt->hTopMenu, ITEM_PREV );
if ( hmenuprev != hmenutmp || pmt->trackFlags & TF_SUSPENDPOPUP )
{
/* A sublevel menu was displayed - display the next one
* unless there is another displacement coming up */
if( !MENU_SuspendPopup( pmt, msg ) )
pmt->hCurrentMenu = MENU_ShowSubPopup(pmt->hOwnerWnd,
pmt->hTopMenu, TRUE, wFlags);
}
}
}
/***********************************************************************
* MENU_KeyRight
*
* Handle a VK_RIGHT key event in a menu.
*/
static void MENU_KeyRight( MTRACKER* pmt, UINT wFlags, UINT msg )
{
HMENU hmenutmp;
POPUPMENU *menu = MENU_GetMenu( pmt->hTopMenu );
UINT nextcol;
TRACE("MENU_KeyRight called, cur %p (%s), top %p (%s).\n",
pmt->hCurrentMenu,
debugstr_w((MENU_GetMenu(pmt->hCurrentMenu))->items[0].text),
pmt->hTopMenu, debugstr_w(menu->items[0].text) );
if ( (menu->wFlags & MF_POPUP) || (pmt->hCurrentMenu != pmt->hTopMenu))
{
/* If already displaying a popup, try to display sub-popup */
hmenutmp = pmt->hCurrentMenu;
pmt->hCurrentMenu = MENU_ShowSubPopup(pmt->hOwnerWnd, hmenutmp, TRUE, wFlags);
/* if subpopup was displayed then we are done */
if (hmenutmp != pmt->hCurrentMenu) return;
}
/* Check to see if there's another column */
if( (nextcol = MENU_GetStartOfNextColumn( pmt->hCurrentMenu )) !=
NO_SELECTED_ITEM ) {
TRACE("Going to %d.\n", nextcol );
MENU_SelectItem( pmt->hOwnerWnd, pmt->hCurrentMenu,
nextcol, TRUE, 0 );
return;
}
if (!(menu->wFlags & MF_POPUP)) /* menu bar tracking */
{
if( pmt->hCurrentMenu != pmt->hTopMenu )
{
MENU_HideSubPopups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, wFlags );
hmenutmp = pmt->hCurrentMenu = pmt->hTopMenu;
} else hmenutmp = 0;
/* try to move to the next item */
if( !MENU_DoNextMenu( pmt, VK_RIGHT, wFlags ) )
MENU_MoveSelection( pmt->hOwnerWnd, pmt->hTopMenu, ITEM_NEXT );
if( hmenutmp || pmt->trackFlags & TF_SUSPENDPOPUP )
if( !MENU_SuspendPopup( pmt, msg ) )
pmt->hCurrentMenu = MENU_ShowSubPopup(pmt->hOwnerWnd,
pmt->hTopMenu, TRUE, wFlags);
}
}
static void CALLBACK release_capture( BOOL __normal )
{
set_capture_window( 0, GUI_INMENUMODE, NULL );
}
/***********************************************************************
* MENU_TrackMenu
*
* Menu tracking code.
*/
static BOOL MENU_TrackMenu( HMENU hmenu, UINT wFlags, INT x, INT y,
HWND hwnd, const RECT *lprect )
{
MSG msg;
POPUPMENU *menu;
BOOL fRemove;
INT executedMenuId = -1;
MTRACKER mt;
BOOL enterIdleSent = FALSE;
HWND capture_win;
mt.trackFlags = 0;
mt.hCurrentMenu = hmenu;
mt.hTopMenu = hmenu;
mt.hOwnerWnd = WIN_GetFullHandle( hwnd );
mt.pt.x = x;
mt.pt.y = y;
TRACE("hmenu=%p flags=0x%08x (%d,%d) hwnd=%p %s\n",
hmenu, wFlags, x, y, hwnd, wine_dbgstr_rect( lprect));
if (!(menu = MENU_GetMenu( hmenu )))
{
WARN("Invalid menu handle %p\n", hmenu);
SetLastError(ERROR_INVALID_MENU_HANDLE);
return FALSE;
}
if (wFlags & TPM_BUTTONDOWN)
{
/* Get the result in order to start the tracking or not */
fRemove = MENU_ButtonDown( &mt, WM_LBUTTONDOWN, hmenu, wFlags );
fEndMenu = !fRemove;
}
if (wFlags & TF_ENDMENU) fEndMenu = TRUE;
/* owner may not be visible when tracking a popup, so use the menu itself */
capture_win = (wFlags & TPM_POPUPMENU) ? menu->hWnd : mt.hOwnerWnd;
set_capture_window( capture_win, GUI_INMENUMODE, NULL );
if ((wFlags & TPM_POPUPMENU) && menu->nItems == 0)
return FALSE;
__TRY while (!fEndMenu)
{
menu = MENU_GetMenu( mt.hCurrentMenu );
if (!menu) /* sometimes happens if I do a window manager close */
break;
/* we have to keep the message in the queue until it's
* clear that menu loop is not over yet. */
for (;;)
{
if (PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE ))
{
if (!CallMsgFilterW( &msg, MSGF_MENU )) break;
/* remove the message from the queue */
PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
}
else
{
if (!enterIdleSent)
{
HWND win = menu->wFlags & MF_POPUP ? menu->hWnd : 0;
enterIdleSent = TRUE;
SendMessageW( mt.hOwnerWnd, WM_ENTERIDLE, MSGF_MENU, (LPARAM)win );
}
WaitMessage();
}
}
/* check if EndMenu() tried to cancel us, by posting this message */
if(msg.message == WM_CANCELMODE)
{
/* we are now out of the loop */
fEndMenu = TRUE;
/* remove the message from the queue */
PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
/* break out of internal loop, ala ESCAPE */
break;
}
mt.pt = msg.pt;
if ( (msg.hwnd==menu->hWnd) || (msg.message!=WM_TIMER) )
enterIdleSent=FALSE;
fRemove = FALSE;
if ((msg.message >= WM_MOUSEFIRST) && (msg.message <= WM_MOUSELAST))
{
/*
* Use the mouse coordinates in lParam instead of those in the MSG
* struct to properly handle synthetic messages. They are already
* in screen coordinates.
*/
mt.pt.x = (short)LOWORD(msg.lParam);
mt.pt.y = (short)HIWORD(msg.lParam);
/* Find a menu for this mouse event */
hmenu = MENU_PtMenu( mt.hTopMenu, mt.pt );
switch(msg.message)
{
/* no WM_NC... messages in captured state */
case WM_RBUTTONDBLCLK:
case WM_RBUTTONDOWN:
if (!(wFlags & TPM_RIGHTBUTTON)) break;
/* fall through */
case WM_LBUTTONDBLCLK:
case WM_LBUTTONDOWN:
/* If the message belongs to the menu, removes it from the queue */
/* Else, end menu tracking */
fRemove = MENU_ButtonDown( &mt, msg.message, hmenu, wFlags );
fEndMenu = !fRemove;
break;
case WM_RBUTTONUP:
if (!(wFlags & TPM_RIGHTBUTTON)) break;
/* fall through */
case WM_LBUTTONUP:
/* Check if a menu was selected by the mouse */
if (hmenu)
{
executedMenuId = MENU_ButtonUp( &mt, hmenu, wFlags);
TRACE("executedMenuId %d\n", executedMenuId);
/* End the loop if executedMenuId is an item ID */
/* or if the job was done (executedMenuId = 0). */
fEndMenu = fRemove = (executedMenuId != -1);
}
/* No menu was selected by the mouse */
/* if the function was called by TrackPopupMenu, continue
with the menu tracking. If not, stop it */
else
fEndMenu = !(wFlags & TPM_POPUPMENU);
break;
case WM_MOUSEMOVE:
/* the selected menu item must be changed every time */
/* the mouse moves. */
if (hmenu)
fEndMenu |= !MENU_MouseMove( &mt, hmenu, wFlags );
} /* switch(msg.message) - mouse */
}
else if ((msg.message >= WM_KEYFIRST) && (msg.message <= WM_KEYLAST))
{
fRemove = TRUE; /* Keyboard messages are always removed */
switch(msg.message)
{
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
switch(msg.wParam)
{
case VK_MENU:
case VK_F10:
fEndMenu = TRUE;
break;
case VK_HOME:
case VK_END:
MENU_SelectItem( mt.hOwnerWnd, mt.hCurrentMenu,
NO_SELECTED_ITEM, FALSE, 0 );
MENU_MoveSelection( mt.hOwnerWnd, mt.hCurrentMenu,
(msg.wParam == VK_HOME)? ITEM_NEXT : ITEM_PREV );
break;
case VK_UP:
case VK_DOWN: /* If on menu bar, pull-down the menu */
menu = MENU_GetMenu( mt.hCurrentMenu );
if (!(menu->wFlags & MF_POPUP))
mt.hCurrentMenu = MENU_ShowSubPopup(mt.hOwnerWnd, mt.hTopMenu, TRUE, wFlags);
else /* otherwise try to move selection */
MENU_MoveSelection( mt.hOwnerWnd, mt.hCurrentMenu,
(msg.wParam == VK_UP)? ITEM_PREV : ITEM_NEXT );
break;
case VK_LEFT:
MENU_KeyLeft( &mt, wFlags, msg.message );
break;
case VK_RIGHT:
MENU_KeyRight( &mt, wFlags, msg.message );
break;
case VK_ESCAPE:
fEndMenu = MENU_KeyEscape(&mt, wFlags);
break;
case VK_F1:
{
HELPINFO hi;
hi.cbSize = sizeof(HELPINFO);
hi.iContextType = HELPINFO_MENUITEM;
if (menu->FocusedItem == NO_SELECTED_ITEM)
hi.iCtrlId = 0;
else
hi.iCtrlId = menu->items[menu->FocusedItem].wID;
hi.hItemHandle = hmenu;
hi.dwContextId = menu->dwContextHelpID;
hi.MousePos = msg.pt;
SendMessageW(hwnd, WM_HELP, 0, (LPARAM)&hi);
break;
}
default:
TranslateMessage( &msg );
break;
}
break; /* WM_KEYDOWN */
case WM_CHAR:
case WM_SYSCHAR:
{
UINT pos;
if (msg.wParam == '\r' || msg.wParam == ' ')
{
executedMenuId = MENU_ExecFocusedItem(&mt,mt.hCurrentMenu, wFlags);
fEndMenu = (executedMenuId != -2);
break;
}
/* Hack to avoid control chars. */
/* We will find a better way real soon... */
if (msg.wParam < 32) break;
pos = MENU_FindItemByKey( mt.hOwnerWnd, mt.hCurrentMenu,
LOWORD(msg.wParam), FALSE );
if (pos == (UINT)-2) fEndMenu = TRUE;
else if (pos == (UINT)-1) MessageBeep(0);
else
{
MENU_SelectItem( mt.hOwnerWnd, mt.hCurrentMenu, pos,
TRUE, 0 );
executedMenuId = MENU_ExecFocusedItem(&mt,mt.hCurrentMenu, wFlags);
fEndMenu = (executedMenuId != -2);
}
}
break;
} /* switch(msg.message) - kbd */
}
else
{
PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
DispatchMessageW( &msg );
continue;
}
if (!fEndMenu) fRemove = TRUE;
/* finally remove message from the queue */
if (fRemove && !(mt.trackFlags & TF_SKIPREMOVE) )
PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
else mt.trackFlags &= ~TF_SKIPREMOVE;
}
__FINALLY( release_capture )
/* If dropdown is still painted and the close box is clicked on
then the menu will be destroyed as part of the DispatchMessage above.
This will then invalidate the menu handle in mt.hTopMenu. We should
check for this first. */
if( IsMenu( mt.hTopMenu ) )
{
menu = MENU_GetMenu( mt.hTopMenu );
if( IsWindow( mt.hOwnerWnd ) )
{
MENU_HideSubPopups( mt.hOwnerWnd, mt.hTopMenu, FALSE, wFlags );
if (menu && (menu->wFlags & MF_POPUP))
{
DestroyWindow( menu->hWnd );
menu->hWnd = 0;
if (!(wFlags & TPM_NONOTIFY))
SendMessageW( mt.hOwnerWnd, WM_UNINITMENUPOPUP, (WPARAM)mt.hTopMenu,
MAKELPARAM(0, IS_SYSTEM_MENU(menu)) );
}
MENU_SelectItem( mt.hOwnerWnd, mt.hTopMenu, NO_SELECTED_ITEM, FALSE, 0 );
SendMessageW( mt.hOwnerWnd, WM_MENUSELECT, MAKEWPARAM(0,0xffff), 0 );
}
}
SetLastError( ERROR_SUCCESS );
/* The return value is only used by TrackPopupMenu */
if (!(wFlags & TPM_RETURNCMD)) return TRUE;
if (executedMenuId == -1) executedMenuId = 0;
return executedMenuId;
}
/***********************************************************************
* MENU_InitTracking
*/
static BOOL MENU_InitTracking(HWND hWnd, HMENU hMenu, BOOL bPopup, UINT wFlags)
{
POPUPMENU *menu;
TRACE("hwnd=%p hmenu=%p\n", hWnd, hMenu);
HideCaret(0);
if (!(menu = MENU_GetMenu( hMenu ))) return FALSE;
/* This makes the menus of applications built with Delphi work.
* It also enables menus to be displayed in more than one window,
* but there are some bugs left that need to be fixed in this case.
*/
if (!bPopup) menu->hWnd = hWnd;
if (!top_popup)
{
top_popup = menu->hWnd;
top_popup_hmenu = hMenu;
}
fEndMenu = FALSE;
/* Send WM_ENTERMENULOOP and WM_INITMENU message only if TPM_NONOTIFY flag is not specified */
if (!(wFlags & TPM_NONOTIFY))
SendMessageW( hWnd, WM_ENTERMENULOOP, bPopup, 0 );
SendMessageW( hWnd, WM_SETCURSOR, (WPARAM)hWnd, HTCAPTION );
if (!(wFlags & TPM_NONOTIFY))
{
SendMessageW( hWnd, WM_INITMENU, (WPARAM)hMenu, 0 );
/* If an app changed/recreated menu bar entries in WM_INITMENU
* menu sizes will be recalculated once the menu created/shown.
*/
}
return TRUE;
}
/***********************************************************************
* MENU_ExitTracking
*/
static BOOL MENU_ExitTracking(HWND hWnd, BOOL bPopup)
{
TRACE("hwnd=%p\n", hWnd);
SendMessageW( hWnd, WM_EXITMENULOOP, bPopup, 0 );
ShowCaret(0);
top_popup = 0;
top_popup_hmenu = NULL;
return TRUE;
}
/***********************************************************************
* MENU_TrackMouseMenuBar
*
* Menu-bar tracking upon a mouse event. Called from NC_HandleSysCommand().
*/
void MENU_TrackMouseMenuBar( HWND hWnd, INT ht, POINT pt )
{
HMENU hMenu = (ht == HTSYSMENU) ? get_win_sys_menu( hWnd ) : GetMenu( hWnd );
UINT wFlags = TPM_BUTTONDOWN | TPM_LEFTALIGN | TPM_LEFTBUTTON;
TRACE("wnd=%p ht=0x%04x %s\n", hWnd, ht, wine_dbgstr_point( &pt));
if (GetWindowLongW( hWnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) wFlags |= TPM_LAYOUTRTL;
if (IsMenu(hMenu))
{
MENU_InitTracking( hWnd, hMenu, FALSE, wFlags );
/* fetch the window menu again, it may have changed */
hMenu = (ht == HTSYSMENU) ? get_win_sys_menu( hWnd ) : GetMenu( hWnd );
MENU_TrackMenu( hMenu, wFlags, pt.x, pt.y, hWnd, NULL );
MENU_ExitTracking(hWnd, FALSE);
}
}
/***********************************************************************
* MENU_TrackKbdMenuBar
*
* Menu-bar tracking upon a keyboard event. Called from NC_HandleSysCommand().
*/
void MENU_TrackKbdMenuBar( HWND hwnd, UINT wParam, WCHAR wChar)
{
UINT uItem = NO_SELECTED_ITEM;
HMENU hTrackMenu;
UINT wFlags = TPM_LEFTALIGN | TPM_LEFTBUTTON;
TRACE("hwnd %p wParam 0x%04x wChar 0x%04x\n", hwnd, wParam, wChar);
/* find window that has a menu */
while (is_win_menu_disallowed(hwnd))
if (!(hwnd = GetAncestor( hwnd, GA_PARENT ))) return;
/* check if we have to track a system menu */
hTrackMenu = GetMenu( hwnd );
if (!hTrackMenu || IsIconic(hwnd) || wChar == ' ' )
{
if (!(GetWindowLongW( hwnd, GWL_STYLE ) & WS_SYSMENU)) return;
hTrackMenu = get_win_sys_menu( hwnd );
uItem = 0;
wParam |= HTSYSMENU; /* prevent item lookup */
}
if (GetWindowLongW( hwnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) wFlags |= TPM_LAYOUTRTL;
if (!IsMenu( hTrackMenu )) return;
MENU_InitTracking( hwnd, hTrackMenu, FALSE, wFlags );
/* fetch the window menu again, it may have changed */
hTrackMenu = (wParam & HTSYSMENU) ? get_win_sys_menu( hwnd ) : GetMenu( hwnd );
if( wChar && wChar != ' ' )
{
uItem = MENU_FindItemByKey( hwnd, hTrackMenu, wChar, (wParam & HTSYSMENU) );
if ( uItem >= (UINT)(-2) )
{
if( uItem == (UINT)(-1) ) MessageBeep(0);
/* schedule end of menu tracking */
wFlags |= TF_ENDMENU;
goto track_menu;
}
}
MENU_SelectItem( hwnd, hTrackMenu, uItem, TRUE, 0 );
if (!(wParam & HTSYSMENU) || wChar == ' ')
{
if( uItem == NO_SELECTED_ITEM )
MENU_MoveSelection( hwnd, hTrackMenu, ITEM_NEXT );
else
PostMessageW( hwnd, WM_KEYDOWN, VK_RETURN, 0 );
}
track_menu:
MENU_TrackMenu( hTrackMenu, wFlags, 0, 0, hwnd, NULL );
MENU_ExitTracking( hwnd, FALSE );
}
/**********************************************************************
* TrackPopupMenuEx (USER32.@)
*/
BOOL WINAPI TrackPopupMenuEx( HMENU hMenu, UINT wFlags, INT x, INT y,
HWND hWnd, LPTPMPARAMS lpTpm )
{
POPUPMENU *menu;
BOOL ret = FALSE;
TRACE("hmenu %p flags %04x (%d,%d) hwnd %p lpTpm %p rect %s\n",
hMenu, wFlags, x, y, hWnd, lpTpm,
lpTpm ? wine_dbgstr_rect( &lpTpm->rcExclude) : "-" );
/* Parameter check */
/* FIXME: this check is performed several times, here and in the called
functions. That could be optimized */
if (!(menu = MENU_GetMenu( hMenu )))
{
SetLastError( ERROR_INVALID_MENU_HANDLE );
return FALSE;
}
if (IsWindow(menu->hWnd))
{
SetLastError( ERROR_POPUP_ALREADY_ACTIVE );
return FALSE;
}
if (MENU_InitPopup( hWnd, hMenu, wFlags ))
{
MENU_InitTracking(hWnd, hMenu, TRUE, wFlags);
/* Send WM_INITMENUPOPUP message only if TPM_NONOTIFY flag is not specified */
if (!(wFlags & TPM_NONOTIFY))
SendMessageW( hWnd, WM_INITMENUPOPUP, (WPARAM)hMenu, 0);
if (menu->wFlags & MF_SYSMENU)
MENU_InitSysMenuPopup( hMenu, GetWindowLongW( hWnd, GWL_STYLE ),
GetClassLongW( hWnd, GCL_STYLE));
if (MENU_ShowPopup( hWnd, hMenu, 0, wFlags, x, y, 0, 0 ))
ret = MENU_TrackMenu( hMenu, wFlags | TPM_POPUPMENU, 0, 0, hWnd,
lpTpm ? &lpTpm->rcExclude : NULL );
MENU_ExitTracking(hWnd, TRUE);
if (menu->hWnd)
{
DestroyWindow( menu->hWnd );
menu->hWnd = 0;
if (!(wFlags & TPM_NONOTIFY))
SendMessageW( hWnd, WM_UNINITMENUPOPUP, (WPARAM)hMenu,
MAKELPARAM(0, IS_SYSTEM_MENU(menu)) );
}
SetLastError(0);
}
return ret;
}
/**********************************************************************
* TrackPopupMenu (USER32.@)
*
* Like the win32 API, the function return the command ID only if the
* flag TPM_RETURNCMD is on.
*
*/
BOOL WINAPI TrackPopupMenu( HMENU hMenu, UINT wFlags, INT x, INT y,
INT nReserved, HWND hWnd, const RECT *lpRect )
{
return TrackPopupMenuEx( hMenu, wFlags, x, y, hWnd, NULL);
}
/***********************************************************************
* PopupMenuWndProc
*
* NOTE: Windows has totally different (and undocumented) popup wndproc.
*/
LRESULT WINAPI PopupMenuWndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
TRACE("hwnd=%p msg=0x%04x wp=0x%04lx lp=0x%08lx\n", hwnd, message, wParam, lParam);
switch(message)
{
case WM_CREATE:
{
CREATESTRUCTW *cs = (CREATESTRUCTW*)lParam;
SetWindowLongPtrW( hwnd, 0, (LONG_PTR)cs->lpCreateParams );
return 0;
}
case WM_MOUSEACTIVATE: /* We don't want to be activated */
return MA_NOACTIVATE;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint( hwnd, &ps );
MENU_DrawPopupMenu( hwnd, ps.hdc,
(HMENU)GetWindowLongPtrW( hwnd, 0 ) );
EndPaint( hwnd, &ps );
return 0;
}
case WM_PRINTCLIENT:
{
MENU_DrawPopupMenu( hwnd, (HDC)wParam,
(HMENU)GetWindowLongPtrW( hwnd, 0 ) );
return 0;
}
case WM_ERASEBKGND:
return 1;
case WM_DESTROY:
/* zero out global pointer in case resident popup window was destroyed. */
if (hwnd == top_popup) {
top_popup = 0;
top_popup_hmenu = NULL;
}
break;
case WM_SHOWWINDOW:
if( wParam )
{
if (!GetWindowLongPtrW( hwnd, 0 )) ERR("no menu to display\n");
}
else
SetWindowLongPtrW( hwnd, 0, 0 );
break;
case MN_GETHMENU:
return GetWindowLongPtrW( hwnd, 0 );
default:
return DefWindowProcW( hwnd, message, wParam, lParam );
}
return 0;
}
/***********************************************************************
* MENU_GetMenuBarHeight
*
* Compute the size of the menu bar height. Used by NC_HandleNCCalcSize().
*/
UINT MENU_GetMenuBarHeight( HWND hwnd, UINT menubarWidth,
INT orgX, INT orgY )
{
HDC hdc;
RECT rectBar;
LPPOPUPMENU lppop;
TRACE("HWND %p, width %d, at (%d, %d).\n", hwnd, menubarWidth, orgX, orgY );
if (!(lppop = MENU_GetMenu( GetMenu(hwnd) ))) return 0;
hdc = GetDCEx( hwnd, 0, DCX_CACHE | DCX_WINDOW );
SelectObject( hdc, get_menu_font(FALSE));
SetRect(&rectBar, orgX, orgY, orgX+menubarWidth, orgY+GetSystemMetrics(SM_CYMENU));
MENU_MenuBarCalcSize( hdc, &rectBar, lppop, hwnd );
ReleaseDC( hwnd, hdc );
return lppop->Height;
}
/*******************************************************************
* ChangeMenuA (USER32.@)
*/
BOOL WINAPI ChangeMenuA( HMENU hMenu, UINT pos, LPCSTR data,
UINT id, UINT flags )
{
TRACE("menu=%p pos=%d data=%p id=%08x flags=%08x\n", hMenu, pos, data, id, flags );
if (flags & MF_APPEND) return AppendMenuA( hMenu, flags & ~MF_APPEND,
id, data );
if (flags & MF_DELETE) return DeleteMenu(hMenu, pos, flags & ~MF_DELETE);
if (flags & MF_CHANGE) return ModifyMenuA(hMenu, pos, flags & ~MF_CHANGE,
id, data );
if (flags & MF_REMOVE) return RemoveMenu( hMenu,
flags & MF_BYPOSITION ? pos : id,
flags & ~MF_REMOVE );
/* Default: MF_INSERT */
return InsertMenuA( hMenu, pos, flags, id, data );
}
/*******************************************************************
* ChangeMenuW (USER32.@)
*/
BOOL WINAPI ChangeMenuW( HMENU hMenu, UINT pos, LPCWSTR data,
UINT id, UINT flags )
{
TRACE("menu=%p pos=%d data=%p id=%08x flags=%08x\n", hMenu, pos, data, id, flags );
if (flags & MF_APPEND) return AppendMenuW( hMenu, flags & ~MF_APPEND,
id, data );
if (flags & MF_DELETE) return DeleteMenu(hMenu, pos, flags & ~MF_DELETE);
if (flags & MF_CHANGE) return ModifyMenuW(hMenu, pos, flags & ~MF_CHANGE,
id, data );
if (flags & MF_REMOVE) return RemoveMenu( hMenu,
flags & MF_BYPOSITION ? pos : id,
flags & ~MF_REMOVE );
/* Default: MF_INSERT */
return InsertMenuW( hMenu, pos, flags, id, data );
}
/*******************************************************************
* CheckMenuItem (USER32.@)
*/
DWORD WINAPI CheckMenuItem( HMENU hMenu, UINT id, UINT flags )
{
POPUPMENU *menu;
MENUITEM *item;
DWORD ret;
UINT pos;
if (!(menu = find_menu_item(hMenu, id, flags, &pos)))
return -1;
item = &menu->items[pos];
ret = item->fState & MF_CHECKED;
if (flags & MF_CHECKED) item->fState |= MF_CHECKED;
else item->fState &= ~MF_CHECKED;
release_menu_ptr(menu);
return ret;
}
/**********************************************************************
* EnableMenuItem (USER32.@)
*/
BOOL WINAPI EnableMenuItem( HMENU hMenu, UINT id, UINT wFlags )
{
UINT oldflags, pos;
POPUPMENU *menu;
MENUITEM *item;
TRACE("(%p, %04x, %04x)\n", hMenu, id, wFlags);
/* Get the Popupmenu to access the owner menu */
if (!(menu = find_menu_item(hMenu, id, wFlags, &pos)))
return (UINT)-1;
item = &menu->items[pos];
oldflags = item->fState & (MF_GRAYED | MF_DISABLED);
item->fState ^= (oldflags ^ wFlags) & (MF_GRAYED | MF_DISABLED);
/* If the close item in the system menu change update the close button */
if ((item->wID == SC_CLOSE) && (oldflags != wFlags) && menu->hSysMenuOwner)
{
RECT rc;
POPUPMENU* parentMenu;
HWND hwnd;
/* Get the parent menu to access */
parentMenu = grab_menu_ptr(menu->hSysMenuOwner);
release_menu_ptr(menu);
if (!parentMenu)
return (UINT)-1;
hwnd = parentMenu->hWnd;
release_menu_ptr(parentMenu);
/* Refresh the frame to reflect the change */
WIN_GetRectangles(hwnd, COORDS_CLIENT, &rc, NULL);
rc.bottom = 0;
RedrawWindow(hwnd, &rc, 0, RDW_FRAME | RDW_INVALIDATE | RDW_NOCHILDREN);
}
else
release_menu_ptr(menu);
return oldflags;
}
/*******************************************************************
* GetMenuStringA (USER32.@)
*/
INT WINAPI GetMenuStringA(
HMENU hMenu, /* [in] menuhandle */
UINT wItemID, /* [in] menu item (dep. on wFlags) */
LPSTR str, /* [out] outbuffer. If NULL, func returns entry length*/
INT nMaxSiz, /* [in] length of buffer. if 0, func returns entry len*/
UINT wFlags /* [in] MF_ flags */
)
{
POPUPMENU *menu;
MENUITEM *item;
UINT pos;
INT ret;
TRACE("menu=%p item=%04x ptr=%p len=%d flags=%04x\n", hMenu, wItemID, str, nMaxSiz, wFlags );
if (str && nMaxSiz) str[0] = '\0';
if (!(menu = find_menu_item(hMenu, wItemID, wFlags, &pos)))
{
SetLastError( ERROR_MENU_ITEM_NOT_FOUND);
return 0;
}
item = &menu->items[pos];
if (!item->text)
ret = 0;
else if (!str || !nMaxSiz)
ret = WideCharToMultiByte( CP_ACP, 0, item->text, -1, NULL, 0, NULL, NULL );
else
{
if (!WideCharToMultiByte( CP_ACP, 0, item->text, -1, str, nMaxSiz, NULL, NULL ))
str[nMaxSiz-1] = 0;
ret = strlen(str);
}
release_menu_ptr(menu);
TRACE("returning %s\n", debugstr_a(str));
return ret;
}
/*******************************************************************
* GetMenuStringW (USER32.@)
*/
INT WINAPI GetMenuStringW( HMENU hMenu, UINT wItemID,
LPWSTR str, INT nMaxSiz, UINT wFlags )
{
POPUPMENU *menu;
MENUITEM *item;
UINT pos;
INT ret;
TRACE("menu=%p item=%04x ptr=%p len=%d flags=%04x\n", hMenu, wItemID, str, nMaxSiz, wFlags );
if (str && nMaxSiz) str[0] = '\0';
if (!(menu = find_menu_item(hMenu, wItemID, wFlags, &pos)))
{
SetLastError( ERROR_MENU_ITEM_NOT_FOUND);
return 0;
}
item = &menu->items[pos];
if (!str || !nMaxSiz)
ret = item->text ? strlenW(item->text) : 0;
else if (!item->text)
{
str[0] = 0;
ret = 0;
}
else
{
lstrcpynW( str, item->text, nMaxSiz );
ret = strlenW(str);
}
release_menu_ptr(menu);
TRACE("returning %s\n", debugstr_w(str));
return ret;
}
/**********************************************************************
* HiliteMenuItem (USER32.@)
*/
BOOL WINAPI HiliteMenuItem( HWND hWnd, HMENU hMenu, UINT wItemID,
UINT wHilite )
{
POPUPMENU *menu;
UINT pos;
HMENU handle_menu;
UINT focused_item;
TRACE("(%p, %p, %04x, %04x);\n", hWnd, hMenu, wItemID, wHilite);
if (!(menu = find_menu_item(hMenu, wItemID, wHilite, &pos))) return FALSE;
handle_menu = menu->obj.handle;
focused_item = menu->FocusedItem;
release_menu_ptr(menu);
if (focused_item != pos)
{
MENU_HideSubPopups( hWnd, handle_menu, FALSE, 0 );
MENU_SelectItem( hWnd, handle_menu, pos, TRUE, 0 );
}
return TRUE;
}
/**********************************************************************
* GetMenuState (USER32.@)
*/
UINT WINAPI GetMenuState( HMENU hMenu, UINT wItemID, UINT wFlags )
{
POPUPMENU *menu;
UINT state, pos;
MENUITEM *item;
TRACE("(menu=%p, id=%04x, flags=%04x);\n", hMenu, wItemID, wFlags);
if (!(menu = find_menu_item(hMenu, wItemID, wFlags, &pos)))
return -1;
item = &menu->items[pos];
debug_print_menuitem (" item: ", item, "");
if (item->fType & MF_POPUP)
{
POPUPMENU *submenu = grab_menu_ptr(item->hSubMenu);
if (submenu)
state = (submenu->nItems << 8) | ((item->fState | item->fType) & 0xff);
else
state = -1;
release_menu_ptr(submenu);
}
else
{
/* We used to (from way back then) mask the result to 0xff. */
/* I don't know why