/* * 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 #include #define OEMRESOURCE #include "windef.h" #include "winbase.h" #include "wingdi.h" #include "winnls.h" #include "wine/server.h" #include "wine/exception.h" #include "win.h" #include "controls.h" #include "user_private.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(menu); /* 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 /* (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 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, NTUSER_OBJ_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, NTUSER_OBJ_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) { HMENU hMenu = LoadMenuW(user32_module, mdi ? L"SYSMENUMDI" : L"SYSMENU"); 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); NtUserSetMenuDefaultItem( 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). */ 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) NtUserDeleteMenu( 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; } NtUserDestroyMenu( 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)); NtUserEnableMenuItem( hmenu, SC_SIZE, (gray ? MF_GRAYED : MF_ENABLED) ); gray = ((style & WS_MAXIMIZE) != 0); NtUserEnableMenuItem( hmenu, SC_MOVE, (gray ? MF_GRAYED : MF_ENABLED) ); gray = !(style & WS_MINIMIZEBOX) || (style & WS_MINIMIZE); NtUserEnableMenuItem( hmenu, SC_MINIMIZE, (gray ? MF_GRAYED : MF_ENABLED) ); gray = !(style & WS_MAXIMIZEBOX) || (style & WS_MAXIMIZE); NtUserEnableMenuItem( hmenu, SC_MAXIMIZE, (gray ? MF_GRAYED : MF_ENABLED) ); gray = !(style & (WS_MAXIMIZE | WS_MINIMIZE)); NtUserEnableMenuItem( 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) NtUserEnableMenuItem( 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_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 = wcschr (q, '&'); if (!p && cjk) p = wcschr (q, '\036'); /* Japanese Win16 */ } while (p != NULL && p [1] == '&'); if (p && (towupper(p[1]) == towupper(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, L"Marlett" }; 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 = wcschr( 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; } NtUserReleaseDC( 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 */ NtUserSetWindowPos( 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 = NtUserGetDCEx( 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); } } } NtUserReleaseDC( 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; } } /********************************************************************** * 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 += (lstrlenW(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 + lstrlenW(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))) { NtUserDestroyMenu( 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 ); NtUserDestroyWindow( 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 = NtUserGetDCEx( 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 ); NtUserReleaseDC( 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 (!NtUserCallMsgFilter( &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)) { NtUserDestroyWindow( 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); NtUserHideCaret( 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 ); NtUserShowCaret( 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 = NtUserGetAncestor( 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) { NtUserDestroyWindow( 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; NtUserBeginPaint( hwnd, &ps ); MENU_DrawPopupMenu( hwnd, ps.hdc, (HMENU)GetWindowLongPtrW( hwnd, 0 ) ); NtUserEndPaint( 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 = NtUserGetDCEx( 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 ); NtUserReleaseDC( 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 NtUserDeleteMenu( hMenu, pos, flags & ~MF_DELETE ); if (flags & MF_CHANGE) return ModifyMenuA(hMenu, pos, flags & ~MF_CHANGE, id, data ); if (flags & MF_REMOVE) return NtUserRemoveMenu( 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 NtUserDeleteMenu( hMenu, pos, flags & ~MF_DELETE ); if (flags & MF_CHANGE) return ModifyMenuW(hMenu, pos, flags & ~MF_CHANGE, id, data ); if (flags & MF_REMOVE) return NtUserRemoveMenu( hMenu, flags & MF_BYPOSITION ? pos : id, flags & ~MF_REMOVE ); /* Default: MF_INSERT */ return InsertMenuW( hMenu, pos, flags, id, data ); } /******************************************************************* * 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 ? lstrlenW(item->text) : 0; else if (!item->text) { str[0] = 0; ret = 0; } else { lstrcpynW( str, item->text, nMaxSiz ); ret = lstrlenW(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 menu, UINT item, UINT flags ) { return NtUserThunkedMenuItemInfo( menu, item, flags, NtUserGetMenuState, NULL, NULL ); } /********************************************************************** * GetMenuItemCount (USER32.@) */ INT WINAPI GetMenuItemCount( HMENU menu ) { return NtUserGetMenuItemCount( menu ); } /********************************************************************** * GetMenuItemID (USER32.@) */ UINT WINAPI GetMenuItemID( HMENU hMenu, INT nPos ) { POPUPMENU *menu; UINT id, pos; if (!(menu = find_menu_item(hMenu, nPos, MF_BYPOSITION, &pos))) return -1; id = menu->items[pos].fType & MF_POPUP ? -1 : menu->items[pos].wID; release_menu_ptr(menu); return id; } /********************************************************************** * MENU_mnu2mnuii * * Uses flags, id and text ptr, passed by InsertMenu() and * ModifyMenu() to setup a MenuItemInfo structure. */ static void MENU_mnu2mnuii( UINT flags, UINT_PTR id, LPCWSTR str, LPMENUITEMINFOW pmii) { ZeroMemory( pmii, sizeof( MENUITEMINFOW)); pmii->cbSize = sizeof( MENUITEMINFOW); pmii->fMask = MIIM_STATE | MIIM_ID | MIIM_FTYPE; /* setting bitmap clears text and vice versa */ if( IS_STRING_ITEM(flags)) { pmii->fMask |= MIIM_STRING | MIIM_BITMAP; if( !str) flags |= MF_SEPARATOR; /* Item beginning with a backspace is a help item */ /* FIXME: wrong place, this is only true in win16 */ else if( *str == '\b') { flags |= MF_HELP; str++; } pmii->dwTypeData = (LPWSTR)str; } else if( flags & MFT_BITMAP){ pmii->fMask |= MIIM_BITMAP | MIIM_STRING; pmii->hbmpItem = (HBITMAP)str; } if( flags & MF_OWNERDRAW){ pmii->fMask |= MIIM_DATA; pmii->dwItemData = (ULONG_PTR) str; } if( flags & MF_POPUP && MENU_GetMenu((HMENU)id)) { pmii->fMask |= MIIM_SUBMENU; pmii->hSubMenu = (HMENU)id; } if( flags & MF_SEPARATOR) flags |= MF_GRAYED | MF_DISABLED; pmii->fState = flags & MENUITEMINFO_STATE_MASK & ~MFS_DEFAULT; pmii->fType = flags & MENUITEMINFO_TYPE_MASK; pmii->wID = (UINT)id; } /******************************************************************* * InsertMenuW (USER32.@) */ BOOL WINAPI InsertMenuW( HMENU hMenu, UINT pos, UINT flags, UINT_PTR id, LPCWSTR str ) { MENUITEMINFOW mii; if (IS_STRING_ITEM(flags) && str) TRACE("hMenu %p, pos %d, flags %08x, id %04lx, str %s\n", hMenu, pos, flags, id, debugstr_w(str) ); else TRACE("hMenu %p, pos %d, flags %08x, id %04lx, str %p (not a string)\n", hMenu, pos, flags, id, str ); MENU_mnu2mnuii( flags, id, str, &mii); mii.fMask |= MIIM_CHECKMARKS; return NtUserThunkedMenuItemInfo( hMenu, pos, flags, NtUserInsertMenuItem, &mii, NULL ); } /******************************************************************* * InsertMenuA (USER32.@) */ BOOL WINAPI InsertMenuA( HMENU hMenu, UINT pos, UINT flags, UINT_PTR id, LPCSTR str ) { BOOL ret = FALSE; if (IS_STRING_ITEM(flags) && str) { INT len = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 ); LPWSTR newstr = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ); if (newstr) { MultiByteToWideChar( CP_ACP, 0, str, -1, newstr, len ); ret = InsertMenuW( hMenu, pos, flags, id, newstr ); HeapFree( GetProcessHeap(), 0, newstr ); } return ret; } else return InsertMenuW( hMenu, pos, flags, id, (LPCWSTR)str ); } /******************************************************************* * AppendMenuA (USER32.@) */ BOOL WINAPI AppendMenuA( HMENU hMenu, UINT flags, UINT_PTR id, LPCSTR data ) { return InsertMenuA( hMenu, -1, flags | MF_BYPOSITION, id, data ); } /******************************************************************* * AppendMenuW (USER32.@) */ BOOL WINAPI AppendMenuW( HMENU hMenu, UINT flags, UINT_PTR id, LPCWSTR data ) { return InsertMenuW( hMenu, -1, flags | MF_BYPOSITION, id, data ); } /******************************************************************* * ModifyMenuW (USER32.@) */ BOOL WINAPI ModifyMenuW( HMENU hMenu, UINT pos, UINT flags, UINT_PTR id, LPCWSTR str ) { MENUITEMINFOW mii; if (IS_STRING_ITEM(flags)) TRACE("%p %d %04x %04lx %s\n", hMenu, pos, flags, id, debugstr_w(str) ); else TRACE("%p %d %04x %04lx %p\n", hMenu, pos, flags, id, str ); MENU_mnu2mnuii( flags, id, str, &mii); return NtUserThunkedMenuItemInfo( hMenu, pos, flags, NtUserSetMenuItemInfo, &mii, NULL ); } /******************************************************************* * ModifyMenuA (USER32.@) */ BOOL WINAPI ModifyMenuA( HMENU hMenu, UINT pos, UINT flags, UINT_PTR id, LPCSTR str ) { BOOL ret = FALSE; if (IS_STRING_ITEM(flags) && str) { INT len = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 ); LPWSTR newstr = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ); if (newstr) { MultiByteToWideChar( CP_ACP, 0, str, -1, newstr, len ); ret = ModifyMenuW( hMenu, pos, flags, id, newstr ); HeapFree( GetProcessHeap(), 0, newstr ); } return ret; } else return ModifyMenuW( hMenu, pos, flags, id, (LPCWSTR)str ); } /********************************************************************** * CreatePopupMenu (USER32.@) */ HMENU WINAPI CreatePopupMenu(void) { return NtUserCreateMenu( TRUE ); } /********************************************************************** * GetMenuCheckMarkDimensions (USER.417) * GetMenuCheckMarkDimensions (USER32.@) */ DWORD WINAPI GetMenuCheckMarkDimensions(void) { return MAKELONG( GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK) ); } /********************************************************************** * SetMenuItemBitmaps (USER32.@) */ BOOL WINAPI SetMenuItemBitmaps( HMENU hMenu, UINT nPos, UINT wFlags, HBITMAP hNewUnCheck, HBITMAP hNewCheck) { POPUPMENU *menu; MENUITEM *item; UINT pos; if (!(menu = find_menu_item(hMenu, nPos, wFlags, &pos))) return FALSE; item = &menu->items[pos]; if (!hNewCheck && !hNewUnCheck) { item->fState &= ~MF_USECHECKBITMAPS; } else /* Install new bitmaps */ { item->hCheckBit = hNewCheck; item->hUnCheckBit = hNewUnCheck; item->fState |= MF_USECHECKBITMAPS; } release_menu_ptr(menu); return TRUE; } /********************************************************************** * CreateMenu (USER32.@) */ HMENU WINAPI CreateMenu(void) { return NtUserCreateMenu( FALSE ); } /********************************************************************** * GetMenu (USER32.@) */ HMENU WINAPI GetMenu( HWND hWnd ) { HMENU retvalue = (HMENU)GetWindowLongPtrW( hWnd, GWLP_ID ); TRACE("for %p returning %p\n", hWnd, retvalue); return retvalue; } /********************************************************************** * GetMenuBarInfo (USER32.@) */ BOOL WINAPI GetMenuBarInfo( HWND hwnd, LONG idObject, LONG idItem, PMENUBARINFO pmbi ) { POPUPMENU *menu; HMENU hmenu = NULL; ATOM class_atom; TRACE( "(%p,0x%08x,0x%08x,%p)\n", hwnd, idObject, idItem, pmbi ); switch (idObject) { case OBJID_CLIENT: class_atom = GetClassLongW(hwnd, GCW_ATOM); if (!class_atom) return FALSE; if (class_atom != POPUPMENU_CLASS_ATOM) { WARN("called on invalid window: %d\n", class_atom); SetLastError(ERROR_INVALID_MENU_HANDLE); return FALSE; } hmenu = (HMENU)GetWindowLongPtrW(hwnd, 0); break; case OBJID_MENU: hmenu = GetMenu(hwnd); break; case OBJID_SYSMENU: hmenu = NtUserGetSystemMenu( hwnd, FALSE ); break; default: return FALSE; } if (!hmenu) return FALSE; if (pmbi->cbSize != sizeof(MENUBARINFO)) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } menu = MENU_GetMenu(hmenu); if (!menu) return FALSE; if (idItem < 0 || idItem > menu->nItems) return FALSE; if (!menu->Height) { SetRectEmpty(&pmbi->rcBar); } else if (idItem == 0) { NtUserGetMenuItemRect( hwnd, hmenu, 0, &pmbi->rcBar ); pmbi->rcBar.right = pmbi->rcBar.left + menu->Width; pmbi->rcBar.bottom = pmbi->rcBar.top + menu->Height; } else { NtUserGetMenuItemRect( hwnd, hmenu, idItem - 1, &pmbi->rcBar ); } pmbi->hMenu = hmenu; pmbi->hwndMenu = NULL; pmbi->fBarFocused = top_popup_hmenu == hmenu; if (idItem) { pmbi->fFocused = menu->FocusedItem == idItem - 1; if (pmbi->fFocused && (menu->items[idItem - 1].fType & MF_POPUP)) { menu = MENU_GetMenu(menu->items[idItem - 1].hSubMenu); if (menu) pmbi->hwndMenu = menu->hWnd; } } else { pmbi->fFocused = pmbi->fBarFocused; } return TRUE; } /********************************************************************** * GetSubMenu (USER32.@) */ HMENU WINAPI GetSubMenu( HMENU hMenu, INT nPos ) { POPUPMENU *menu; HMENU submenu; UINT pos; if (!(menu = find_menu_item(hMenu, nPos, MF_BYPOSITION, &pos))) return 0; if (menu->items[pos].fType & MF_POPUP) submenu = menu->items[pos].hSubMenu; else submenu = 0; release_menu_ptr(menu); return submenu; } /********************************************************************** * DrawMenuBar (USER32.@) */ BOOL WINAPI DrawMenuBar( HWND hwnd ) { return NtUserDrawMenuBar( hwnd ); } /*********************************************************************** * DrawMenuBarTemp (USER32.@) * * UNDOCUMENTED !! * * called by W98SE desk.cpl Control Panel Applet * * Not 100% sure about the param names, but close. */ DWORD WINAPI DrawMenuBarTemp(HWND hwnd, HDC hDC, LPRECT lprect, HMENU hMenu, HFONT hFont) { LPPOPUPMENU lppop; UINT i,retvalue; HFONT hfontOld = 0; BOOL flat_menu = FALSE; SystemParametersInfoW (SPI_GETFLATMENU, 0, &flat_menu, 0); if (!hMenu) hMenu = GetMenu(hwnd); if (!hFont) hFont = get_menu_font(FALSE); lppop = MENU_GetMenu( hMenu ); if (lppop == NULL || lprect == NULL) { retvalue = GetSystemMetrics(SM_CYMENU); goto END; } TRACE("(%p, %p, %p, %p, %p)\n", hwnd, hDC, lprect, hMenu, hFont); hfontOld = SelectObject( hDC, hFont); if (lppop->Height == 0) MENU_MenuBarCalcSize(hDC, lprect, lppop, hwnd); lprect->bottom = lprect->top + lppop->Height; FillRect(hDC, lprect, GetSysColorBrush(flat_menu ? COLOR_MENUBAR : COLOR_MENU) ); SelectObject( hDC, SYSCOLOR_GetPen(COLOR_3DFACE)); MoveToEx( hDC, lprect->left, lprect->bottom, NULL ); LineTo( hDC, lprect->right, lprect->bottom ); if (lppop->nItems == 0) { retvalue = GetSystemMetrics(SM_CYMENU); goto END; } for (i = 0; i < lppop->nItems; i++) MENU_DrawMenuItem( hwnd, lppop, hwnd, hDC, &lppop->items[i], TRUE, ODA_DRAWENTIRE ); retvalue = lppop->Height; END: if (hfontOld) SelectObject (hDC, hfontOld); return retvalue; } /*********************************************************************** * EndMenu (USER.187) * EndMenu (USER32.@) */ BOOL WINAPI EndMenu(void) { /* if we are in the menu code, and it is active */ if (!fEndMenu && top_popup) { /* terminate the menu handling code */ fEndMenu = TRUE; /* needs to be posted to wakeup the internal menu handler */ /* which will now terminate the menu, in the event that */ /* the main window was minimized, or lost focus, so we */ /* don't end up with an orphaned menu */ PostMessageW( top_popup, WM_CANCELMODE, 0, 0); } return fEndMenu; } /***************************************************************** * LoadMenuA (USER32.@) */ HMENU WINAPI LoadMenuA( HINSTANCE instance, LPCSTR name ) { HRSRC hrsrc = FindResourceA( instance, name, (LPSTR)RT_MENU ); if (!hrsrc) return 0; return LoadMenuIndirectA( LoadResource( instance, hrsrc )); } /***************************************************************** * LoadMenuW (USER32.@) */ HMENU WINAPI LoadMenuW( HINSTANCE instance, LPCWSTR name ) { HRSRC hrsrc = FindResourceW( instance, name, (LPWSTR)RT_MENU ); if (!hrsrc) return 0; return LoadMenuIndirectW( LoadResource( instance, hrsrc )); } /********************************************************************** * LoadMenuIndirectW (USER32.@) */ HMENU WINAPI LoadMenuIndirectW( LPCVOID template ) { HMENU hMenu; WORD version, offset; LPCSTR p = template; version = GET_WORD(p); p += sizeof(WORD); TRACE("%p, ver %d\n", template, version ); switch (version) { case 0: /* standard format is version of 0 */ offset = GET_WORD(p); p += sizeof(WORD) + offset; if (!(hMenu = CreateMenu())) return 0; if (!MENU_ParseResource( p, hMenu )) { NtUserDestroyMenu( hMenu ); return 0; } return hMenu; case 1: /* extended format is version of 1 */ offset = GET_WORD(p); p += sizeof(WORD) + offset; if (!(hMenu = CreateMenu())) return 0; if (!MENUEX_ParseResource( p, hMenu)) { NtUserDestroyMenu( hMenu ); return 0; } return hMenu; default: ERR("version %d not supported.\n", version); return 0; } } /********************************************************************** * LoadMenuIndirectA (USER32.@) */ HMENU WINAPI LoadMenuIndirectA( LPCVOID template ) { return LoadMenuIndirectW( template ); } /********************************************************************** * IsMenu (USER32.@) */ BOOL WINAPI IsMenu( HMENU menu ) { MENUINFO info; info.cbSize = sizeof(info); info.fMask = 0; if (GetMenuInfo( menu, &info )) return TRUE; SetLastError(ERROR_INVALID_MENU_HANDLE); return FALSE; } /********************************************************************** * GetMenuItemInfo_common */ static BOOL GetMenuItemInfo_common ( HMENU hmenu, UINT id, BOOL bypos, LPMENUITEMINFOW lpmii, BOOL unicode) { POPUPMENU *menu; MENUITEM *item; UINT pos; menu = find_menu_item(hmenu, id, bypos ? MF_BYPOSITION : 0, &pos); item = menu ? &menu->items[pos] : NULL; debug_print_menuitem("GetMenuItemInfo_common: ", item, ""); if (!menu) { SetLastError( ERROR_MENU_ITEM_NOT_FOUND); return FALSE; } if( lpmii->fMask & MIIM_TYPE) { if( lpmii->fMask & ( MIIM_STRING | MIIM_FTYPE | MIIM_BITMAP)) { release_menu_ptr(menu); WARN("invalid combination of fMask bits used\n"); /* this does not happen on Win9x/ME */ SetLastError( ERROR_INVALID_PARAMETER); return FALSE; } lpmii->fType = item->fType & MENUITEMINFO_TYPE_MASK; if (item->hbmpItem && !IS_MAGIC_BITMAP(item->hbmpItem)) lpmii->fType |= MFT_BITMAP; lpmii->hbmpItem = item->hbmpItem; /* not on Win9x/ME */ if( lpmii->fType & MFT_BITMAP) { lpmii->dwTypeData = (LPWSTR) item->hbmpItem; lpmii->cch = 0; } else if( lpmii->fType & (MFT_OWNERDRAW | MFT_SEPARATOR)) { /* this does not happen on Win9x/ME */ lpmii->dwTypeData = 0; lpmii->cch = 0; } } /* copy the text string */ if ((lpmii->fMask & (MIIM_TYPE|MIIM_STRING))) { if (!item->text) { if(lpmii->dwTypeData && lpmii->cch) { if( unicode) *((WCHAR *)lpmii->dwTypeData) = 0; else *((CHAR *)lpmii->dwTypeData) = 0; } lpmii->cch = 0; } else { int len; if (unicode) { len = lstrlenW(item->text); if(lpmii->dwTypeData && lpmii->cch) lstrcpynW(lpmii->dwTypeData, item->text, lpmii->cch); } else { len = WideCharToMultiByte( CP_ACP, 0, item->text, -1, NULL, 0, NULL, NULL ) - 1; if(lpmii->dwTypeData && lpmii->cch) if (!WideCharToMultiByte( CP_ACP, 0, item->text, -1, (LPSTR)lpmii->dwTypeData, lpmii->cch, NULL, NULL )) ((LPSTR)lpmii->dwTypeData)[lpmii->cch - 1] = 0; } /* if we've copied a substring we return its length */ if(lpmii->dwTypeData && lpmii->cch) if (lpmii->cch <= len + 1) lpmii->cch--; else lpmii->cch = len; else { /* return length of string */ /* not on Win9x/ME if fType & MFT_BITMAP */ lpmii->cch = len; } } } if (lpmii->fMask & MIIM_FTYPE) lpmii->fType = item->fType & MENUITEMINFO_TYPE_MASK; if (lpmii->fMask & MIIM_BITMAP) lpmii->hbmpItem = item->hbmpItem; if (lpmii->fMask & MIIM_STATE) lpmii->fState = item->fState & MENUITEMINFO_STATE_MASK; if (lpmii->fMask & MIIM_ID) lpmii->wID = item->wID; if (lpmii->fMask & MIIM_SUBMENU) lpmii->hSubMenu = item->hSubMenu; else { /* hSubMenu is always cleared * (not on Win9x/ME ) */ lpmii->hSubMenu = 0; } if (lpmii->fMask & MIIM_CHECKMARKS) { lpmii->hbmpChecked = item->hCheckBit; lpmii->hbmpUnchecked = item->hUnCheckBit; } if (lpmii->fMask & MIIM_DATA) lpmii->dwItemData = item->dwItemData; release_menu_ptr(menu); return TRUE; } /********************************************************************** * GetMenuItemInfoA (USER32.@) */ BOOL WINAPI GetMenuItemInfoA( HMENU hmenu, UINT item, BOOL bypos, LPMENUITEMINFOA lpmii) { BOOL ret; MENUITEMINFOA mii; if( lpmii->cbSize != sizeof( mii) && lpmii->cbSize != sizeof( mii) - sizeof ( mii.hbmpItem)) { SetLastError( ERROR_INVALID_PARAMETER); return FALSE; } memcpy( &mii, lpmii, lpmii->cbSize); mii.cbSize = sizeof( mii); ret = GetMenuItemInfo_common (hmenu, item, bypos, (LPMENUITEMINFOW)&mii, FALSE); mii.cbSize = lpmii->cbSize; memcpy( lpmii, &mii, mii.cbSize); return ret; } /********************************************************************** * GetMenuItemInfoW (USER32.@) */ BOOL WINAPI GetMenuItemInfoW( HMENU hmenu, UINT item, BOOL bypos, LPMENUITEMINFOW lpmii) { BOOL ret; MENUITEMINFOW mii; if( lpmii->cbSize != sizeof( mii) && lpmii->cbSize != sizeof( mii) - sizeof ( mii.hbmpItem)) { SetLastError( ERROR_INVALID_PARAMETER); return FALSE; } memcpy( &mii, lpmii, lpmii->cbSize); mii.cbSize = sizeof( mii); ret = GetMenuItemInfo_common (hmenu, item, bypos, &mii, TRUE); mii.cbSize = lpmii->cbSize; memcpy( lpmii, &mii, mii.cbSize); return ret; } /********************************************************************** * MENU_NormalizeMenuItemInfoStruct * * Helper for SetMenuItemInfo and InsertMenuItemInfo: * check, copy and extend the MENUITEMINFO struct from the version that the application * supplied to the version used by wine source. */ static BOOL MENU_NormalizeMenuItemInfoStruct( const MENUITEMINFOW *pmii_in, MENUITEMINFOW *pmii_out ) { /* do we recognize the size? */ if( !pmii_in || (pmii_in->cbSize != sizeof( MENUITEMINFOW) && pmii_in->cbSize != sizeof( MENUITEMINFOW) - sizeof( pmii_in->hbmpItem)) ) { SetLastError( ERROR_INVALID_PARAMETER); return FALSE; } /* copy the fields that we have */ memcpy( pmii_out, pmii_in, pmii_in->cbSize); /* if the hbmpItem member is missing then extend */ if( pmii_in->cbSize != sizeof( MENUITEMINFOW)) { pmii_out->cbSize = sizeof( MENUITEMINFOW); pmii_out->hbmpItem = NULL; } /* test for invalid bit combinations */ if( (pmii_out->fMask & MIIM_TYPE && pmii_out->fMask & (MIIM_STRING | MIIM_FTYPE | MIIM_BITMAP)) || (pmii_out->fMask & MIIM_FTYPE && pmii_out->fType & MFT_BITMAP)) { WARN("invalid combination of fMask bits used\n"); /* this does not happen on Win9x/ME */ SetLastError( ERROR_INVALID_PARAMETER); return FALSE; } /* convert old style (MIIM_TYPE) to the new */ if( pmii_out->fMask & MIIM_TYPE){ pmii_out->fMask |= MIIM_FTYPE; if( IS_STRING_ITEM(pmii_out->fType)){ pmii_out->fMask |= MIIM_STRING; } else if( (pmii_out->fType) & MFT_BITMAP){ pmii_out->fMask |= MIIM_BITMAP; pmii_out->hbmpItem = UlongToHandle(LOWORD(pmii_out->dwTypeData)); } } return TRUE; } /********************************************************************** * SetMenuItemInfoA (USER32.@) */ BOOL WINAPI SetMenuItemInfoA(HMENU hmenu, UINT item, BOOL bypos, const MENUITEMINFOA *lpmii) { WCHAR *strW = NULL; MENUITEMINFOW mii; BOOL ret; TRACE("hmenu %p, item %u, by pos %d, info %p\n", hmenu, item, bypos, lpmii); if (!MENU_NormalizeMenuItemInfoStruct( (const MENUITEMINFOW *)lpmii, &mii )) return FALSE; if ((mii.fMask & MIIM_STRING) && mii.dwTypeData) { const char *str = (const char *)mii.dwTypeData; UINT len = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 ); if (!(strW = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) return FALSE; MultiByteToWideChar( CP_ACP, 0, str, -1, strW, len ); mii.dwTypeData = strW; } ret = NtUserThunkedMenuItemInfo( hmenu, item, bypos ? MF_BYPOSITION : 0, NtUserSetMenuItemInfo, &mii, NULL ); HeapFree( GetProcessHeap(), 0, strW ); return ret; } /********************************************************************** * SetMenuItemInfoW (USER32.@) */ BOOL WINAPI SetMenuItemInfoW(HMENU hmenu, UINT item, BOOL bypos, const MENUITEMINFOW *lpmii) { MENUITEMINFOW mii; TRACE("hmenu %p, item %u, by pos %d, info %p\n", hmenu, item, bypos, lpmii); if (!MENU_NormalizeMenuItemInfoStruct( lpmii, &mii )) return FALSE; return NtUserThunkedMenuItemInfo( hmenu, item, bypos ? MF_BYPOSITION : 0, NtUserSetMenuItemInfo, &mii, NULL ); } /********************************************************************** * GetMenuDefaultItem (USER32.@) */ UINT WINAPI GetMenuDefaultItem(HMENU hmenu, UINT bypos, UINT flags) { POPUPMENU *menu; MENUITEM * item; UINT i = 0; TRACE("(%p,%d,%d)\n", hmenu, bypos, flags); if (!(menu = MENU_GetMenu(hmenu))) return -1; /* find default item */ item = menu->items; /* empty menu */ if (! item) return -1; while ( !( item->fState & MFS_DEFAULT ) ) { i++; item++; if (i >= menu->nItems ) return -1; } /* default: don't return disabled items */ if ( (!(GMDI_USEDISABLED & flags)) && (item->fState & MFS_DISABLED )) return -1; /* search rekursiv when needed */ if ( (item->fType & MF_POPUP) && (flags & GMDI_GOINTOPOPUPS) ) { UINT ret; ret = GetMenuDefaultItem( item->hSubMenu, bypos, flags ); if ( -1 != ret ) return ret; /* when item not found in submenu, return the popup item */ } return ( bypos ) ? i : item->wID; } /********************************************************************** * InsertMenuItemA (USER32.@) */ BOOL WINAPI InsertMenuItemA(HMENU hMenu, UINT uItem, BOOL bypos, const MENUITEMINFOA *lpmii) { WCHAR *strW = NULL; MENUITEMINFOW mii; BOOL ret; TRACE("hmenu %p, item %04x, by pos %d, info %p\n", hMenu, uItem, bypos, lpmii); if (!MENU_NormalizeMenuItemInfoStruct( (const MENUITEMINFOW *)lpmii, &mii )) return FALSE; if ((mii.fMask & MIIM_STRING) && mii.dwTypeData) { const char *str = (const char *)mii.dwTypeData; UINT len = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 ); if (!(strW = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) return FALSE; MultiByteToWideChar( CP_ACP, 0, str, -1, strW, len ); mii.dwTypeData = strW; } ret = NtUserThunkedMenuItemInfo( hMenu, uItem, bypos ? MF_BYPOSITION : 0, NtUserInsertMenuItem, &mii, NULL ); HeapFree( GetProcessHeap(), 0, strW ); return ret; } /********************************************************************** * InsertMenuItemW (USER32.@) */ BOOL WINAPI InsertMenuItemW(HMENU hMenu, UINT uItem, BOOL bypos, const MENUITEMINFOW *lpmii) { MENUITEMINFOW mii; TRACE("hmenu %p, item %04x, by pos %d, info %p\n", hMenu, uItem, bypos, lpmii); if (!MENU_NormalizeMenuItemInfoStruct( lpmii, &mii )) return FALSE; return NtUserThunkedMenuItemInfo( hMenu, uItem, bypos ? MF_BYPOSITION : 0, NtUserInsertMenuItem, &mii, NULL ); } /********************************************************************** * CheckMenuRadioItem (USER32.@) */ BOOL WINAPI CheckMenuRadioItem(HMENU hMenu, UINT first, UINT last, UINT check, UINT flags) { POPUPMENU *first_menu = NULL, *check_menu; UINT i, check_pos; BOOL done = FALSE; for (i = first; i <= last; i++) { MENUITEM *item; if (!(check_menu = find_menu_item(hMenu, i, flags, &check_pos))) continue; if (!first_menu) first_menu = grab_menu_ptr(check_menu->obj.handle); if (first_menu != check_menu) { release_menu_ptr(check_menu); continue; } item = &check_menu->items[check_pos]; if (item->fType != MFT_SEPARATOR) { if (i == check) { item->fType |= MFT_RADIOCHECK; item->fState |= MFS_CHECKED; done = TRUE; } else { /* MSDN is wrong, Windows does not remove MFT_RADIOCHECK */ item->fState &= ~MFS_CHECKED; } } release_menu_ptr(check_menu); } release_menu_ptr(first_menu); return done; } /********************************************************************** * SetMenuInfo (USER32.@) */ BOOL WINAPI SetMenuInfo( HMENU menu, const MENUINFO *info ) { TRACE( "(%p %p)\n", menu, info ); if (!info || info->cbSize != sizeof(*info)) { SetLastError( ERROR_INVALID_PARAMETER); return FALSE; } return NtUserThunkedMenuInfo( menu, info ); } /********************************************************************** * GetMenuInfo (USER32.@) */ BOOL WINAPI GetMenuInfo( HMENU menu, MENUINFO *info ) { return NtUserGetMenuInfo( menu, info ); } /********************************************************************** * GetMenuContextHelpId (USER32.@) */ DWORD WINAPI GetMenuContextHelpId( HMENU hMenu ) { DWORD help_id = 0; POPUPMENU *menu; TRACE("(%p)\n", hMenu); if ((menu = grab_menu_ptr(hMenu))) { help_id = menu->dwContextHelpID; release_menu_ptr(menu); } return help_id; } /********************************************************************** * MenuItemFromPoint (USER32.@) */ INT WINAPI MenuItemFromPoint(HWND hWnd, HMENU hMenu, POINT ptScreen) { POPUPMENU *menu = grab_menu_ptr(hMenu); UINT pos; /*FIXME: Do we have to handle hWnd here? */ if (!menu) return -1; if (MENU_FindItemByCoords( menu, ptScreen, &pos ) != ht_item) pos = -1; release_menu_ptr(menu); return pos; } /********************************************************************** * CalcMenuBar (USER32.@) */ DWORD WINAPI CalcMenuBar(HWND hwnd, DWORD left, DWORD right, DWORD top, RECT *rect) { FIXME("(%p, %d, %d, %d, %p): stub\n", hwnd, left, right, top, rect); return 0; } /********************************************************************** * TranslateAcceleratorA (USER32.@) * TranslateAccelerator (USER32.@) */ INT WINAPI TranslateAcceleratorA( HWND hWnd, HACCEL hAccel, LPMSG msg ) { switch (msg->message) { case WM_KEYDOWN: case WM_SYSKEYDOWN: return NtUserTranslateAccelerator( hWnd, hAccel, msg ); case WM_CHAR: case WM_SYSCHAR: { MSG msgW = *msg; char ch = LOWORD(msg->wParam); WCHAR wch; MultiByteToWideChar(CP_ACP, 0, &ch, 1, &wch, 1); msgW.wParam = MAKEWPARAM(wch, HIWORD(msg->wParam)); return NtUserTranslateAccelerator( hWnd, hAccel, &msgW ); } default: return 0; } }