kernelbase/tests: Use win_skip() for missing APIs.
[wine.git] / dlls / win32u / menu.c
bloba6b7ec38003cf7911ae41b2ea75699cc4bf8c8da
1 /*
2 * Menu functions
4 * Copyright 1993 Robert J. Amstadt
5 * Copyright 1995, 2009 Alexandre Julliard
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #if 0
23 #pragma makedep unix
24 #endif
26 #define OEMRESOURCE
27 #include "ntgdi_private.h"
28 #include "ntuser_private.h"
29 #include "wine/server.h"
30 #include "wine/debug.h"
32 WINE_DEFAULT_DEBUG_CHANNEL(menu);
33 WINE_DECLARE_DEBUG_CHANNEL(accel);
36 /* menu item structure */
37 struct menu_item
39 UINT fType; /* item type */
40 UINT fState; /* item state */
41 UINT_PTR wID; /* item id */
42 HMENU hSubMenu; /* pop-up menu */
43 HBITMAP hCheckBit; /* bitmap when checked */
44 HBITMAP hUnCheckBit; /* bitmap when unchecked */
45 LPWSTR text; /* item text */
46 ULONG_PTR dwItemData; /* application defined */
47 LPWSTR dwTypeData; /* depends on fMask */
48 HBITMAP hbmpItem; /* bitmap */
49 RECT rect; /* item area (relative to the items_rect), see adjust_menu_item_rect */
50 UINT xTab; /* X position of text after Tab */
51 SIZE bmpsize; /* size needed for the HBMMENU_CALLBACK bitmap */
54 /* menu user object */
55 struct menu
57 struct user_object obj;
58 struct menu_item *items; /* array of menu items */
59 WORD wFlags; /* menu flags (MF_POPUP, MF_SYSMENU) */
60 WORD Width; /* width of the whole menu */
61 WORD Height; /* height of the whole menu */
62 UINT nItems; /* number of items in the menu */
63 HWND hWnd; /* window containing the menu */
64 UINT FocusedItem; /* currently focused item */
65 HWND hwndOwner; /* window receiving the messages for ownerdraw */
66 BOOL bScrolling; /* scroll arrows are active */
67 UINT nScrollPos; /* current scroll position */
68 UINT nTotalHeight; /* total height of menu items inside menu */
69 RECT items_rect; /* rectangle within which the items lie, excludes margins and scroll arrows */
70 LONG refcount;
71 UINT dwStyle; /* extended menu style */
72 UINT cyMax; /* max height of the whole menu, 0 is screen height */
73 HBRUSH hbrBack; /* brush for menu background */
74 DWORD dwContextHelpID;
75 ULONG_PTR dwMenuData; /* application defined value */
76 HMENU hSysMenuOwner; /* handle to the dummy sys menu holder */
77 WORD textOffset; /* offset of text when items have both bitmaps and text */
80 /* the accelerator user object */
81 struct accelerator
83 struct user_object obj;
84 unsigned int count;
85 ACCEL table[1];
88 enum hittest
90 ht_nowhere, /* outside the menu */
91 ht_border, /* anywhere that's not an item or a scroll arrow */
92 ht_item, /* a menu item */
93 ht_scroll_up, /* scroll up arrow */
94 ht_scroll_down /* scroll down arrow */
97 typedef struct
99 UINT trackFlags;
100 HMENU hCurrentMenu; /* current submenu (can be equal to hTopMenu)*/
101 HMENU hTopMenu; /* initial menu */
102 HWND hOwnerWnd; /* where notifications are sent */
103 POINT pt;
104 } MTRACKER;
106 /* maximum allowed depth of any branch in the menu tree.
107 * This value is slightly larger than in windows (25) to
108 * stay on the safe side. */
109 #define MAXMENUDEPTH 30
111 /* (other menu->FocusedItem values give the position of the focused item) */
112 #define NO_SELECTED_ITEM 0xffff
114 /* internal flags for menu tracking */
115 #define TF_ENDMENU 0x10000
116 #define TF_SUSPENDPOPUP 0x20000
117 #define TF_SKIPREMOVE 0x40000
118 #define TF_RCVD_BTN_UP 0x80000
120 /* Internal track_menu() flags */
121 #define TPM_INTERNAL 0xf0000000
122 #define TPM_BUTTONDOWN 0x40000000 /* menu was clicked before tracking */
123 #define TPM_POPUPMENU 0x20000000 /* menu is a popup menu */
125 /* Space between 2 columns */
126 #define MENU_COL_SPACE 4
128 /* Margins for popup menus */
129 #define MENU_MARGIN 3
131 #define ITEM_PREV -1
132 #define ITEM_NEXT 1
134 #define MENU_ITEM_TYPE(flags) \
135 ((flags) & (MF_STRING | MF_BITMAP | MF_OWNERDRAW | MF_SEPARATOR))
137 /* macro to test that flags do not indicate bitmap, ownerdraw or separator */
138 #define IS_STRING_ITEM(flags) (MENU_ITEM_TYPE ((flags)) == MF_STRING)
139 #define IS_MAGIC_BITMAP(id) ((id) && ((INT_PTR)(id) < 12) && ((INT_PTR)(id) >= -1))
141 #define IS_SYSTEM_MENU(menu) \
142 (!((menu)->wFlags & MF_POPUP) && ((menu)->wFlags & MF_SYSMENU))
144 #define MENUITEMINFO_TYPE_MASK \
145 (MFT_STRING | MFT_BITMAP | MFT_OWNERDRAW | MFT_SEPARATOR | \
146 MFT_MENUBARBREAK | MFT_MENUBREAK | MFT_RADIOCHECK | \
147 MFT_RIGHTORDER | MFT_RIGHTJUSTIFY /* same as MF_HELP */ )
148 #define TYPE_MASK (MENUITEMINFO_TYPE_MASK | MF_POPUP | MF_SYSMENU)
149 #define STATE_MASK (~TYPE_MASK)
150 #define MENUITEMINFO_STATE_MASK (STATE_MASK & ~(MF_BYPOSITION | MF_MOUSESELECT))
153 /* Use global popup window because there's no way 2 menus can
154 * be tracked at the same time. */
155 static HWND top_popup;
156 static HMENU top_popup_hmenu;
158 /* Flag set by NtUserEndMenu() to force an exit from menu tracking */
159 static BOOL exit_menu = FALSE;
161 static SIZE menucharsize;
162 static UINT od_item_height; /* default owner drawn item height */
164 /**********************************************************************
165 * NtUserCopyAcceleratorTable (win32u.@)
167 INT WINAPI NtUserCopyAcceleratorTable( HACCEL src, ACCEL *dst, INT count )
169 struct accelerator *accel;
170 int i;
172 if (!(accel = get_user_handle_ptr( src, NTUSER_OBJ_ACCEL ))) return 0;
173 if (accel == OBJ_OTHER_PROCESS)
175 FIXME_(accel)( "other process handle %p?\n", src );
176 return 0;
178 if (dst)
180 if (count > accel->count) count = accel->count;
181 for (i = 0; i < count; i++)
183 dst[i].fVirt = accel->table[i].fVirt & 0x7f;
184 dst[i].key = accel->table[i].key;
185 dst[i].cmd = accel->table[i].cmd;
188 else count = accel->count;
189 release_user_handle_ptr( accel );
190 return count;
193 /*********************************************************************
194 * NtUserCreateAcceleratorTable (win32u.@)
196 HACCEL WINAPI NtUserCreateAcceleratorTable( ACCEL *table, INT count )
198 struct accelerator *accel;
199 HACCEL handle;
201 if (count < 1)
203 RtlSetLastWin32Error( ERROR_INVALID_PARAMETER );
204 return 0;
206 accel = malloc( FIELD_OFFSET( struct accelerator, table[count] ));
207 if (!accel) return 0;
208 accel->count = count;
209 memcpy( accel->table, table, count * sizeof(*table) );
211 if (!(handle = alloc_user_handle( &accel->obj, NTUSER_OBJ_ACCEL ))) free( accel );
212 TRACE_(accel)("returning %p\n", handle );
213 return handle;
216 /******************************************************************************
217 * NtUserDestroyAcceleratorTable (win32u.@)
219 BOOL WINAPI NtUserDestroyAcceleratorTable( HACCEL handle )
221 struct accelerator *accel;
223 if (!(accel = free_user_handle( handle, NTUSER_OBJ_ACCEL ))) return FALSE;
224 if (accel == OBJ_OTHER_PROCESS)
226 FIXME_(accel)( "other process handle %p\n", accel );
227 return FALSE;
229 free( accel );
230 return TRUE;
233 #define MENUFLAG(bit,text) \
234 do { \
235 if (flags & (bit)) { flags &= ~(bit); strcat(buf, (text)); } \
236 } while (0)
238 static const char *debugstr_menuitem( const struct menu_item *item )
240 static const char *const hbmmenus[] = { "HBMMENU_CALLBACK", "", "HBMMENU_SYSTEM",
241 "HBMMENU_MBAR_RESTORE", "HBMMENU_MBAR_MINIMIZE", "UNKNOWN BITMAP", "HBMMENU_MBAR_CLOSE",
242 "HBMMENU_MBAR_CLOSE_D", "HBMMENU_MBAR_MINIMIZE_D", "HBMMENU_POPUP_CLOSE",
243 "HBMMENU_POPUP_RESTORE", "HBMMENU_POPUP_MAXIMIZE", "HBMMENU_POPUP_MINIMIZE" };
244 char buf[256];
245 UINT flags;
247 if (!item) return "NULL";
249 sprintf( buf, "{ ID=0x%lx", (long)item->wID );
250 if (item->hSubMenu) sprintf( buf + strlen(buf), ", Sub=%p", item->hSubMenu );
252 flags = item->fType;
253 if (flags)
255 strcat( buf, ", fType=" );
256 MENUFLAG( MFT_SEPARATOR, "sep" );
257 MENUFLAG( MFT_OWNERDRAW, "own" );
258 MENUFLAG( MFT_BITMAP, "bit" );
259 MENUFLAG( MF_POPUP, "pop" );
260 MENUFLAG( MFT_MENUBARBREAK, "barbrk" );
261 MENUFLAG( MFT_MENUBREAK, "brk");
262 MENUFLAG( MFT_RADIOCHECK, "radio" );
263 MENUFLAG( MFT_RIGHTORDER, "rorder" );
264 MENUFLAG( MF_SYSMENU, "sys" );
265 MENUFLAG( MFT_RIGHTJUSTIFY, "right" ); /* same as MF_HELP */
266 if (flags) sprintf( buf + strlen(buf), "+0x%x", flags );
269 flags = item->fState;
270 if (flags)
272 strcat( buf, ", State=" );
273 MENUFLAG( MFS_GRAYED, "grey" );
274 MENUFLAG( MFS_DEFAULT, "default" );
275 MENUFLAG( MFS_DISABLED, "dis" );
276 MENUFLAG( MFS_CHECKED, "check" );
277 MENUFLAG( MFS_HILITE, "hi" );
278 MENUFLAG( MF_USECHECKBITMAPS, "usebit" );
279 MENUFLAG( MF_MOUSESELECT, "mouse" );
280 if (flags) sprintf( buf + strlen(buf), "+0x%x", flags );
283 if (item->hCheckBit) sprintf( buf + strlen(buf), ", Chk=%p", item->hCheckBit );
284 if (item->hUnCheckBit) sprintf( buf + strlen(buf), ", Unc=%p", item->hUnCheckBit );
285 if (item->text) sprintf( buf + strlen(buf), ", Text=%s", debugstr_w(item->text) );
286 if (item->dwItemData) sprintf( buf + strlen(buf), ", ItemData=0x%08lx", item->dwItemData );
288 if (item->hbmpItem)
290 if (IS_MAGIC_BITMAP( item->hbmpItem ))
291 sprintf( buf + strlen(buf), ", hbitmap=%s", hbmmenus[(INT_PTR)item->hbmpItem + 1] );
292 else
293 sprintf( buf + strlen(buf), ", hbitmap=%p", item->hbmpItem );
295 return wine_dbg_sprintf( "%s }", buf );
298 #undef MENUFLAG
300 static struct menu *grab_menu_ptr( HMENU handle )
302 struct menu *menu = get_user_handle_ptr( handle, NTUSER_OBJ_MENU );
304 if (menu == OBJ_OTHER_PROCESS)
306 WARN( "other process menu %p\n", handle );
307 return NULL;
310 if (menu)
311 menu->refcount++;
312 else
313 WARN( "invalid menu handle=%p\n", handle );
314 return menu;
317 static void release_menu_ptr( struct menu *menu )
319 if (menu)
321 menu->refcount--;
322 release_user_handle_ptr( menu );
327 * Validate the given menu handle and returns the menu structure pointer.
328 * FIXME: this is unsafe, we should use a better mechanism instead.
330 static struct menu *unsafe_menu_ptr( HMENU handle )
332 struct menu *menu = grab_menu_ptr( handle );
333 if (menu) release_menu_ptr( menu );
334 return menu;
337 /* see IsMenu */
338 BOOL is_menu( HMENU handle )
340 struct menu *menu;
341 BOOL is_menu;
343 menu = grab_menu_ptr( handle );
344 is_menu = menu != NULL;
345 release_menu_ptr( menu );
347 if (!is_menu) RtlSetLastWin32Error( ERROR_INVALID_MENU_HANDLE );
348 return is_menu;
351 /***********************************************************************
352 * get_win_sys_menu
354 * Get the system menu of a window
356 static HMENU get_win_sys_menu( HWND hwnd )
358 HMENU ret = 0;
359 WND *win = get_win_ptr( hwnd );
360 if (win && win != WND_OTHER_PROCESS && win != WND_DESKTOP)
362 ret = win->hSysMenu;
363 release_win_ptr( win );
365 return ret;
368 static struct menu *find_menu_item( HMENU handle, UINT id, UINT flags, UINT *pos )
370 UINT fallback_pos = ~0u, i;
371 struct menu *menu;
373 menu = grab_menu_ptr( handle );
374 if (!menu)
375 return NULL;
377 if (flags & MF_BYPOSITION)
379 if (id >= menu->nItems)
381 release_menu_ptr( menu );
382 return NULL;
385 if (pos) *pos = id;
386 return menu;
388 else
390 struct menu_item *item = menu->items;
391 for (i = 0; i < menu->nItems; i++, item++)
393 if (item->fType & MF_POPUP)
395 struct menu *submenu = find_menu_item( item->hSubMenu, id, flags, pos );
397 if (submenu)
399 release_menu_ptr( menu );
400 return submenu;
402 else if (item->wID == id)
404 /* fallback to this item if nothing else found */
405 fallback_pos = i;
408 else if (item->wID == id)
410 if (pos) *pos = i;
411 return menu;
416 if (fallback_pos != ~0u)
417 *pos = fallback_pos;
418 else
420 release_menu_ptr( menu );
421 menu = NULL;
424 return menu;
427 static struct menu *insert_menu_item( HMENU handle, UINT id, UINT flags, UINT *ret_pos )
429 struct menu_item *new_items;
430 struct menu *menu;
431 UINT pos = id;
433 /* Find where to insert new item */
434 if (!(menu = find_menu_item(handle, id, flags, &pos)))
436 if (!(menu = grab_menu_ptr(handle)))
437 return NULL;
438 pos = menu->nItems;
441 /* Make sure that MDI system buttons stay on the right side.
442 * Note: XP treats only bitmap handles 1 - 6 as "magic" ones
443 * regardless of their id.
445 while (pos > 0 && (INT_PTR)menu->items[pos - 1].hbmpItem >= (INT_PTR)HBMMENU_SYSTEM &&
446 (INT_PTR)menu->items[pos - 1].hbmpItem <= (INT_PTR)HBMMENU_MBAR_CLOSE_D)
447 pos--;
449 TRACE( "inserting at %u flags %x\n", pos, flags );
451 new_items = malloc( sizeof(*new_items) * (menu->nItems + 1) );
452 if (!new_items)
454 release_menu_ptr( menu );
455 return NULL;
457 if (menu->nItems > 0)
459 /* Copy the old array into the new one */
460 if (pos > 0) memcpy( new_items, menu->items, pos * sizeof(*new_items) );
461 if (pos < menu->nItems) memcpy( &new_items[pos + 1], &menu->items[pos],
462 (menu->nItems - pos) * sizeof(*new_items) );
463 free( menu->items );
465 menu->items = new_items;
466 menu->nItems++;
467 memset( &new_items[pos], 0, sizeof(*new_items) );
468 menu->Height = 0; /* force size recalculate */
470 *ret_pos = pos;
471 return menu;
474 static BOOL is_win_menu_disallowed( HWND hwnd )
476 return (get_window_long(hwnd, GWL_STYLE) & (WS_CHILD | WS_POPUP)) == WS_CHILD;
479 /***********************************************************************
480 * find_submenu
482 * Find a Sub menu. Return the position of the submenu, and modifies
483 * *hmenu in case it is found in another sub-menu.
484 * If the submenu cannot be found, NO_SELECTED_ITEM is returned.
486 static UINT find_submenu( HMENU *handle_ptr, HMENU target )
488 struct menu *menu;
489 struct menu_item *item;
490 UINT i;
492 if (*handle_ptr == (HMENU)0xffff || !(menu = grab_menu_ptr( *handle_ptr )))
493 return NO_SELECTED_ITEM;
495 item = menu->items;
496 for (i = 0; i < menu->nItems; i++, item++)
498 if(!(item->fType & MF_POPUP)) continue;
499 if (item->hSubMenu == target)
501 release_menu_ptr( menu );
502 return i;
504 else
506 HMENU hsubmenu = item->hSubMenu;
507 UINT pos = find_submenu( &hsubmenu, target );
508 if (pos != NO_SELECTED_ITEM)
510 *handle_ptr = hsubmenu;
511 release_menu_ptr( menu );
512 return pos;
517 release_menu_ptr( menu );
518 return NO_SELECTED_ITEM;
521 /* Adjust menu item rectangle according to scrolling state */
522 static void adjust_menu_item_rect( const struct menu *menu, RECT *rect )
524 INT scroll_offset = menu->bScrolling ? menu->nScrollPos : 0;
525 OffsetRect( rect, menu->items_rect.left, menu->items_rect.top - scroll_offset );
528 /***********************************************************************
529 * find_item_by_coords
531 * Find the item at the specified coordinates (screen coords). Does
532 * not work for child windows and therefore should not be called for
533 * an arbitrary system menu.
535 * Returns a hittest code. *pos will contain the position of the
536 * item or NO_SELECTED_ITEM. If the hittest code is ht_scroll_up
537 * or ht_scroll_down then *pos will contain the position of the
538 * item that's just outside the items_rect - ie, the one that would
539 * be scrolled completely into view.
541 static enum hittest find_item_by_coords( const struct menu *menu, POINT pt, UINT *pos )
543 enum hittest ht = ht_border;
544 struct menu_item *item;
545 RECT rect;
546 UINT i;
548 *pos = NO_SELECTED_ITEM;
550 if (!get_window_rect( menu->hWnd, &rect, get_thread_dpi() ) || !PtInRect( &rect, pt ))
551 return ht_nowhere;
553 if (get_window_long( menu->hWnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) pt.x = rect.right - 1 - pt.x;
554 else pt.x -= rect.left;
555 pt.y -= rect.top;
557 if (!PtInRect( &menu->items_rect, pt ))
559 if (!menu->bScrolling || pt.x < menu->items_rect.left || pt.x >= menu->items_rect.right)
560 return ht_border;
562 /* On a scroll arrow. Update pt so that it points to the item just outside items_rect */
563 if (pt.y < menu->items_rect.top)
565 ht = ht_scroll_up;
566 pt.y = menu->items_rect.top - 1;
568 else
570 ht = ht_scroll_down;
571 pt.y = menu->items_rect.bottom;
575 item = menu->items;
576 for (i = 0; i < menu->nItems; i++, item++)
578 rect = item->rect;
579 adjust_menu_item_rect( menu, &rect );
580 if (PtInRect( &rect, pt ))
582 *pos = i;
583 if (ht != ht_scroll_up && ht != ht_scroll_down) ht = ht_item;
584 break;
588 return ht;
591 /* see GetMenu */
592 HMENU get_menu( HWND hwnd )
594 return UlongToHandle( get_window_long( hwnd, GWLP_ID ));
597 /* see CreateMenu and CreatePopupMenu */
598 HMENU create_menu( BOOL is_popup )
600 struct menu *menu;
601 HMENU handle;
603 if (!(menu = calloc( 1, sizeof(*menu) ))) return 0;
604 menu->FocusedItem = NO_SELECTED_ITEM;
605 menu->refcount = 1;
606 if (is_popup) menu->wFlags |= MF_POPUP;
608 if (!(handle = alloc_user_handle( &menu->obj, NTUSER_OBJ_MENU ))) free( menu );
610 TRACE( "return %p\n", handle );
611 return handle;
614 /**********************************************************************
615 * NtUserDestroyMenu (win32u.@)
617 BOOL WINAPI NtUserDestroyMenu( HMENU handle )
619 struct menu *menu;
621 TRACE( "(%p)\n", handle );
623 if (!(menu = free_user_handle( handle, NTUSER_OBJ_MENU ))) return FALSE;
624 if (menu == OBJ_OTHER_PROCESS) return FALSE;
626 /* DestroyMenu should not destroy system menu popup owner */
627 if ((menu->wFlags & (MF_POPUP | MF_SYSMENU)) == MF_POPUP && menu->hWnd)
629 NtUserDestroyWindow( menu->hWnd );
630 menu->hWnd = 0;
633 /* recursively destroy submenus */
634 if (menu->items)
636 struct menu_item *item = menu->items;
637 int i;
639 for (i = menu->nItems; i > 0; i--, item++)
641 if (item->fType & MF_POPUP) NtUserDestroyMenu( item->hSubMenu );
642 free( item->text );
644 free( menu->items );
647 free( menu );
648 return TRUE;
651 /*******************************************************************
652 * set_window_menu
654 * Helper for NtUserSetMenu that does not call NtUserSetWindowPos.
656 BOOL set_window_menu( HWND hwnd, HMENU handle )
658 TRACE( "(%p, %p);\n", hwnd, handle );
660 if (handle && !is_menu( handle ))
662 WARN( "%p is not a menu handle\n", handle );
663 return FALSE;
666 if (is_win_menu_disallowed( hwnd ))
667 return FALSE;
669 hwnd = get_full_window_handle( hwnd );
670 if (get_capture() == hwnd)
671 set_capture_window( 0, GUI_INMENUMODE, NULL ); /* release the capture */
673 if (handle)
675 struct menu *menu;
677 if (!(menu = grab_menu_ptr( handle ))) return FALSE;
678 menu->hWnd = hwnd;
679 menu->Height = 0; /* Make sure we recalculate the size */
680 release_menu_ptr(menu);
683 NtUserSetWindowLong( hwnd, GWLP_ID, (LONG_PTR)handle, FALSE );
684 return TRUE;
687 /**********************************************************************
688 * NtUserSetMenu (win32u.@)
690 BOOL WINAPI NtUserSetMenu( HWND hwnd, HMENU menu )
692 if (!set_window_menu( hwnd, menu ))
693 return FALSE;
695 NtUserSetWindowPos( hwnd, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE |
696 SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED );
697 return TRUE;
700 /*******************************************************************
701 * NtUserCheckMenuItem (win32u.@)
703 DWORD WINAPI NtUserCheckMenuItem( HMENU handle, UINT id, UINT flags )
705 struct menu_item *item;
706 struct menu *menu;
707 DWORD ret;
708 UINT pos;
710 if (!(menu = find_menu_item(handle, id, flags, &pos)))
711 return -1;
712 item = &menu->items[pos];
714 ret = item->fState & MF_CHECKED;
715 if (flags & MF_CHECKED) item->fState |= MF_CHECKED;
716 else item->fState &= ~MF_CHECKED;
717 release_menu_ptr(menu);
718 return ret;
721 /**********************************************************************
722 * NtUserEnableMenuItem (win32u.@)
724 BOOL WINAPI NtUserEnableMenuItem( HMENU handle, UINT id, UINT flags )
726 UINT oldflags, pos;
727 struct menu *menu;
728 struct menu_item *item;
730 TRACE( "(%p, %04x, %04x)\n", handle, id, flags );
732 /* Get the Popupmenu to access the owner menu */
733 if (!(menu = find_menu_item( handle, id, flags, &pos )))
734 return ~0u;
736 item = &menu->items[pos];
737 oldflags = item->fState & (MF_GRAYED | MF_DISABLED);
738 item->fState ^= (oldflags ^ flags) & (MF_GRAYED | MF_DISABLED);
740 /* If the close item in the system menu change update the close button */
741 if (item->wID == SC_CLOSE && oldflags != flags && menu->hSysMenuOwner)
743 struct menu *parent_menu;
744 RECT rc;
745 HWND hwnd;
747 /* Get the parent menu to access */
748 parent_menu = grab_menu_ptr( menu->hSysMenuOwner );
749 release_menu_ptr( menu );
750 if (!parent_menu)
751 return ~0u;
753 hwnd = parent_menu->hWnd;
754 release_menu_ptr( parent_menu );
756 /* Refresh the frame to reflect the change */
757 get_window_rects( hwnd, COORDS_CLIENT, &rc, NULL, get_thread_dpi() );
758 rc.bottom = 0;
759 NtUserRedrawWindow( hwnd, &rc, 0, RDW_FRAME | RDW_INVALIDATE | RDW_NOCHILDREN );
761 else
762 release_menu_ptr( menu );
764 return oldflags;
767 /* see DrawMenuBar */
768 BOOL draw_menu_bar( HWND hwnd )
770 HMENU handle;
772 if (!is_window( hwnd )) return FALSE;
773 if (is_win_menu_disallowed( hwnd )) return TRUE;
775 if ((handle = get_menu( hwnd )))
777 struct menu *menu = grab_menu_ptr( handle );
778 if (menu)
780 menu->Height = 0; /* Make sure we call MENU_MenuBarCalcSize */
781 menu->hwndOwner = hwnd;
782 release_menu_ptr( menu );
786 return NtUserSetWindowPos( hwnd, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE |
787 SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED );
790 /**********************************************************************
791 * NtUserGetMenuItemRect (win32u.@)
793 BOOL WINAPI NtUserGetMenuItemRect( HWND hwnd, HMENU handle, UINT item, RECT *rect )
795 struct menu *menu;
796 UINT pos;
797 RECT window_rect;
799 TRACE( "(%p,%p,%d,%p)\n", hwnd, handle, item, rect );
801 if (!rect)
802 return FALSE;
804 if (!(menu = find_menu_item( handle, item, MF_BYPOSITION, &pos )))
805 return FALSE;
807 if (!hwnd) hwnd = menu->hWnd;
808 if (!hwnd)
810 release_menu_ptr( menu );
811 return FALSE;
814 *rect = menu->items[pos].rect;
815 OffsetRect( rect, menu->items_rect.left, menu->items_rect.top );
817 /* Popup menu item draws in the client area */
818 if (menu->wFlags & MF_POPUP) map_window_points( hwnd, 0, (POINT *)rect, 2, get_thread_dpi() );
819 else
821 /* Sysmenu draws in the non-client area */
822 get_window_rect( hwnd, &window_rect, get_thread_dpi() );
823 OffsetRect( rect, window_rect.left, window_rect.top );
826 release_menu_ptr(menu);
827 return TRUE;
830 static BOOL set_menu_info( HMENU handle, const MENUINFO *info )
832 struct menu *menu;
834 if (!(menu = grab_menu_ptr( handle ))) return FALSE;
836 if (info->fMask & MIM_BACKGROUND) menu->hbrBack = info->hbrBack;
837 if (info->fMask & MIM_HELPID) menu->dwContextHelpID = info->dwContextHelpID;
838 if (info->fMask & MIM_MAXHEIGHT) menu->cyMax = info->cyMax;
839 if (info->fMask & MIM_MENUDATA) menu->dwMenuData = info->dwMenuData;
840 if (info->fMask & MIM_STYLE) menu->dwStyle = info->dwStyle;
842 if (info->fMask & MIM_APPLYTOSUBMENUS)
844 int i;
845 struct menu_item *item = menu->items;
846 for (i = menu->nItems; i; i--, item++)
847 if (item->fType & MF_POPUP)
848 set_menu_info( item->hSubMenu, info);
851 release_menu_ptr( menu );
852 return TRUE;
855 /**********************************************************************
856 * NtUserThunkedMenuInfo (win32u.@)
858 BOOL WINAPI NtUserThunkedMenuInfo( HMENU menu, const MENUINFO *info )
860 TRACE( "(%p %p)\n", menu, info );
862 if (!info)
864 RtlSetLastWin32Error( ERROR_NOACCESS );
865 return FALSE;
868 if (!set_menu_info( menu, info ))
870 RtlSetLastWin32Error( ERROR_INVALID_MENU_HANDLE );
871 return FALSE;
874 if (info->fMask & MIM_STYLE)
876 if (info->dwStyle & MNS_AUTODISMISS) FIXME("MNS_AUTODISMISS unimplemented\n");
877 if (info->dwStyle & MNS_DRAGDROP) FIXME("MNS_DRAGDROP unimplemented\n");
878 if (info->dwStyle & MNS_MODELESS) FIXME("MNS_MODELESS unimplemented\n");
880 return TRUE;
883 /* see GetMenuInfo */
884 BOOL get_menu_info( HMENU handle, MENUINFO *info )
886 struct menu *menu;
888 TRACE( "(%p %p)\n", handle, info );
890 if (!info || info->cbSize != sizeof(MENUINFO) || !(menu = grab_menu_ptr( handle )))
892 RtlSetLastWin32Error( ERROR_INVALID_PARAMETER);
893 return FALSE;
896 if (info->fMask & MIM_BACKGROUND) info->hbrBack = menu->hbrBack;
897 if (info->fMask & MIM_HELPID) info->dwContextHelpID = menu->dwContextHelpID;
898 if (info->fMask & MIM_MAXHEIGHT) info->cyMax = menu->cyMax;
899 if (info->fMask & MIM_MENUDATA) info->dwMenuData = menu->dwMenuData;
900 if (info->fMask & MIM_STYLE) info->dwStyle = menu->dwStyle;
902 release_menu_ptr(menu);
903 return TRUE;
906 /**********************************************************************
907 * menu_depth
909 * detect if there are loops in the menu tree (or the depth is too large)
911 static int menu_depth( struct menu *pmenu, int depth)
913 int i, subdepth;
914 struct menu_item *item;
916 if (++depth > MAXMENUDEPTH) return depth;
917 item = pmenu->items;
918 subdepth = depth;
919 for (i = 0; i < pmenu->nItems && subdepth <= MAXMENUDEPTH; i++, item++)
921 struct menu *submenu = item->hSubMenu ? grab_menu_ptr( item->hSubMenu ) : NULL;
922 if (submenu)
924 int bdepth = menu_depth( submenu, depth);
925 if (bdepth > subdepth) subdepth = bdepth;
926 release_menu_ptr( submenu );
928 if (subdepth > MAXMENUDEPTH)
929 TRACE( "<- hmenu %p\n", item->hSubMenu );
932 return subdepth;
935 static BOOL set_menu_item_info( struct menu_item *menu, const MENUITEMINFOW *info )
937 if (!menu) return FALSE;
939 TRACE( "%s\n", debugstr_menuitem( menu ));
941 if (info->fMask & MIIM_FTYPE )
943 menu->fType &= ~MENUITEMINFO_TYPE_MASK;
944 menu->fType |= info->fType & MENUITEMINFO_TYPE_MASK;
946 if (info->fMask & MIIM_STRING )
948 const WCHAR *text = info->dwTypeData;
949 /* free the string when used */
950 free( menu->text );
951 if (!text)
952 menu->text = NULL;
953 else if ((menu->text = malloc( (lstrlenW(text) + 1) * sizeof(WCHAR) )))
954 lstrcpyW( menu->text, text );
957 if (info->fMask & MIIM_STATE)
958 /* Other menu items having MFS_DEFAULT are not converted
959 to normal items */
960 menu->fState = info->fState & MENUITEMINFO_STATE_MASK;
962 if (info->fMask & MIIM_ID)
963 menu->wID = info->wID;
965 if (info->fMask & MIIM_SUBMENU)
967 menu->hSubMenu = info->hSubMenu;
968 if (menu->hSubMenu)
970 struct menu *submenu = grab_menu_ptr( menu->hSubMenu );
971 if (!submenu)
973 RtlSetLastWin32Error( ERROR_INVALID_PARAMETER);
974 return FALSE;
976 if (menu_depth( submenu, 0 ) > MAXMENUDEPTH)
978 ERR( "Loop detected in menu hierarchy or maximum menu depth exceeded\n" );
979 menu->hSubMenu = 0;
980 release_menu_ptr( submenu );
981 return FALSE;
983 submenu->wFlags |= MF_POPUP;
984 menu->fType |= MF_POPUP;
985 release_menu_ptr( submenu );
987 else
988 menu->fType &= ~MF_POPUP;
991 if (info->fMask & MIIM_CHECKMARKS)
993 menu->hCheckBit = info->hbmpChecked;
994 menu->hUnCheckBit = info->hbmpUnchecked;
996 if (info->fMask & MIIM_DATA)
997 menu->dwItemData = info->dwItemData;
999 if (info->fMask & MIIM_BITMAP)
1000 menu->hbmpItem = info->hbmpItem;
1002 if (!menu->text && !(menu->fType & MFT_OWNERDRAW) && !menu->hbmpItem)
1003 menu->fType |= MFT_SEPARATOR;
1005 TRACE( "to: %s\n", debugstr_menuitem( menu ));
1006 return TRUE;
1009 /* see GetMenuState */
1010 UINT get_menu_state( HMENU handle, UINT item_id, UINT flags )
1012 struct menu *menu;
1013 UINT state, pos;
1014 struct menu_item *item;
1016 TRACE( "(menu=%p, id=%04x, flags=%04x);\n", handle, item_id, flags );
1018 if (!(menu = find_menu_item( handle, item_id, flags, &pos )))
1019 return -1;
1021 item = &menu->items[pos];
1022 TRACE( " item: %s\n", debugstr_menuitem( item ));
1023 if (item->fType & MF_POPUP)
1025 struct menu *submenu = grab_menu_ptr( item->hSubMenu );
1026 if (submenu)
1027 state = (submenu->nItems << 8) | ((item->fState | item->fType) & 0xff);
1028 else
1029 state = -1;
1030 release_menu_ptr( submenu );
1032 else
1034 state = item->fType | item->fState;
1036 release_menu_ptr(menu);
1037 return state;
1040 static BOOL get_menu_item_info( HMENU handle, UINT id, UINT flags, MENUITEMINFOW *info, BOOL ansi )
1042 struct menu *menu;
1043 struct menu_item *item;
1044 UINT pos;
1046 if (!info || info->cbSize != sizeof(*info))
1048 RtlSetLastWin32Error( ERROR_INVALID_PARAMETER );
1049 return FALSE;
1052 menu = find_menu_item( handle, id, flags, &pos );
1053 item = menu ? &menu->items[pos] : NULL;
1054 TRACE( "%s\n", debugstr_menuitem( item ));
1055 if (!menu)
1057 RtlSetLastWin32Error( ERROR_MENU_ITEM_NOT_FOUND);
1058 return FALSE;
1061 if (info->fMask & MIIM_TYPE)
1063 if (info->fMask & ( MIIM_STRING | MIIM_FTYPE | MIIM_BITMAP))
1065 release_menu_ptr( menu );
1066 WARN( "invalid combination of fMask bits used\n" );
1067 /* this does not happen on Win9x/ME */
1068 RtlSetLastWin32Error( ERROR_INVALID_PARAMETER );
1069 return FALSE;
1072 info->fType = item->fType & MENUITEMINFO_TYPE_MASK;
1073 if (item->hbmpItem && !IS_MAGIC_BITMAP(item->hbmpItem))
1074 info->fType |= MFT_BITMAP;
1075 info->hbmpItem = item->hbmpItem; /* not on Win9x/ME */
1076 if (info->fType & MFT_BITMAP)
1078 info->dwTypeData = (WCHAR *)item->hbmpItem;
1079 info->cch = 0;
1081 else if (info->fType & (MFT_OWNERDRAW | MFT_SEPARATOR))
1083 /* this does not happen on Win9x/ME */
1084 info->dwTypeData = 0;
1085 info->cch = 0;
1089 /* copy the text string */
1090 if ((info->fMask & (MIIM_TYPE|MIIM_STRING)))
1092 if (!item->text)
1094 if (info->dwTypeData && info->cch)
1096 if (ansi)
1097 *((char *)info->dwTypeData) = 0;
1098 else
1099 *((WCHAR *)info->dwTypeData) = 0;
1101 info->cch = 0;
1103 else
1105 DWORD len, text_len;
1106 if (ansi)
1108 text_len = wcslen( item->text );
1109 len = win32u_wctomb_size( &ansi_cp, item->text, text_len );
1110 if (info->dwTypeData && info->cch)
1111 if (!win32u_wctomb( &ansi_cp, (char *)info->dwTypeData, info->cch,
1112 item->text, text_len + 1 ))
1113 ((char *)info->dwTypeData)[info->cch - 1] = 0;
1115 else
1117 len = lstrlenW( item->text );
1118 if (info->dwTypeData && info->cch)
1119 lstrcpynW( info->dwTypeData, item->text, info->cch );
1122 if (info->dwTypeData && info->cch)
1124 /* if we've copied a substring we return its length */
1125 if (info->cch <= len + 1)
1126 info->cch--;
1127 else
1128 info->cch = len;
1130 else
1132 /* return length of string, not on Win9x/ME if fType & MFT_BITMAP */
1133 info->cch = len;
1138 if (info->fMask & MIIM_FTYPE) info->fType = item->fType & MENUITEMINFO_TYPE_MASK;
1139 if (info->fMask & MIIM_BITMAP) info->hbmpItem = item->hbmpItem;
1140 if (info->fMask & MIIM_STATE) info->fState = item->fState & MENUITEMINFO_STATE_MASK;
1141 if (info->fMask & MIIM_ID) info->wID = item->wID;
1142 if (info->fMask & MIIM_DATA) info->dwItemData = item->dwItemData;
1144 if (info->fMask & MIIM_SUBMENU) info->hSubMenu = item->hSubMenu;
1145 else info->hSubMenu = 0; /* hSubMenu is always cleared (not on Win9x/ME ) */
1147 if (info->fMask & MIIM_CHECKMARKS)
1149 info->hbmpChecked = item->hCheckBit;
1150 info->hbmpUnchecked = item->hUnCheckBit;
1153 release_menu_ptr( menu );
1154 return TRUE;
1157 static BOOL check_menu_radio_item( HMENU handle, UINT first, UINT last, UINT check, UINT flags )
1159 struct menu *first_menu = NULL, *check_menu;
1160 UINT i, check_pos;
1161 BOOL done = FALSE;
1163 for (i = first; i <= last; i++)
1165 struct menu_item *item;
1167 if (!(check_menu = find_menu_item( handle, i, flags, &check_pos ))) continue;
1168 if (!first_menu) first_menu = grab_menu_ptr( check_menu->obj.handle );
1170 if (first_menu != check_menu)
1172 release_menu_ptr(check_menu);
1173 continue;
1176 item = &check_menu->items[check_pos];
1177 if (item->fType != MFT_SEPARATOR)
1179 if (i == check)
1181 item->fType |= MFT_RADIOCHECK;
1182 item->fState |= MFS_CHECKED;
1183 done = TRUE;
1185 else
1187 /* Windows does not remove MFT_RADIOCHECK */
1188 item->fState &= ~MFS_CHECKED;
1192 release_menu_ptr( check_menu );
1195 release_menu_ptr( first_menu );
1196 return done;
1199 /* see GetSubMenu */
1200 static HMENU get_sub_menu( HMENU handle, INT pos )
1202 struct menu *menu;
1203 HMENU submenu;
1204 UINT i;
1206 if (!(menu = find_menu_item( handle, pos, MF_BYPOSITION, &i )))
1207 return 0;
1209 if (menu->items[i].fType & MF_POPUP)
1210 submenu = menu->items[i].hSubMenu;
1211 else
1212 submenu = 0;
1214 release_menu_ptr(menu);
1215 return submenu;
1218 /* see GetMenuDefaultItem */
1219 static UINT get_menu_default_item( HMENU handle, UINT bypos, UINT flags )
1221 struct menu_item *item = NULL;
1222 struct menu *menu;
1223 UINT i;
1225 TRACE( "(%p,%d,%d)\n", handle, bypos, flags );
1227 if (!(menu = grab_menu_ptr( handle ))) return -1;
1229 for (i = 0; i < menu->nItems; i++)
1231 if (!(menu->items[i].fState & MFS_DEFAULT)) continue;
1232 item = &menu->items[i];
1233 break;
1236 /* default: don't return disabled items */
1237 if (item && (!(GMDI_USEDISABLED & flags)) && (item->fState & MFS_DISABLED)) item = NULL;
1239 /* search submenu when needed */
1240 if (item && (item->fType & MF_POPUP) && (flags & GMDI_GOINTOPOPUPS))
1242 UINT ret = get_menu_default_item( item->hSubMenu, bypos, flags );
1243 if (ret != -1)
1245 release_menu_ptr( menu );
1246 return ret;
1248 /* when item not found in submenu, return the popup item */
1251 if (!item) i = -1;
1252 else if (!bypos) i = item->wID;
1253 release_menu_ptr( menu );
1254 return i;
1257 /**********************************************************************
1258 * NtUserThunkedMenuItemInfo (win32u.@)
1260 UINT WINAPI NtUserThunkedMenuItemInfo( HMENU handle, UINT pos, UINT flags, UINT method,
1261 MENUITEMINFOW *info, UNICODE_STRING *str )
1263 struct menu *menu;
1264 UINT i;
1265 BOOL ret;
1267 switch (method)
1269 case NtUserCheckMenuRadioItem:
1270 return check_menu_radio_item( handle, pos, info->cch, info->fMask, flags );
1272 case NtUserGetMenuDefaultItem:
1273 return get_menu_default_item( handle, pos, flags );
1275 case NtUserGetMenuItemID:
1276 if (!(menu = find_menu_item( handle, pos, flags, &i ))) return -1;
1277 ret = menu->items[i].fType & MF_POPUP ? -1 : menu->items[i].wID;
1278 release_menu_ptr( menu );
1279 break;
1281 case NtUserGetMenuItemInfoA:
1282 return get_menu_item_info( handle, pos, flags, info, TRUE );
1284 case NtUserGetMenuItemInfoW:
1285 return get_menu_item_info( handle, pos, flags, info, FALSE );
1287 case NtUserGetSubMenu:
1288 return HandleToUlong( get_sub_menu( handle, pos ));
1290 case NtUserInsertMenuItem:
1291 if (!info || info->cbSize != sizeof(*info))
1293 RtlSetLastWin32Error( ERROR_INVALID_PARAMETER );
1294 return FALSE;
1297 if (!(menu = insert_menu_item( handle, pos, flags, &i )))
1299 /* workaround for Word 95: pretend that SC_TASKLIST item exists */
1300 if (pos == SC_TASKLIST && !(flags & MF_BYPOSITION)) return TRUE;
1301 return FALSE;
1304 ret = set_menu_item_info( &menu->items[i], info );
1305 if (!ret) NtUserRemoveMenu( handle, pos, flags );
1306 release_menu_ptr(menu);
1307 break;
1309 case NtUserSetMenuItemInfo:
1310 if (!info || info->cbSize != sizeof(*info))
1312 RtlSetLastWin32Error( ERROR_INVALID_PARAMETER );
1313 return FALSE;
1316 if (!(menu = find_menu_item( handle, pos, flags, &i )))
1318 /* workaround for Word 95: pretend that SC_TASKLIST item exists */
1319 if (pos == SC_TASKLIST && !(flags & MF_BYPOSITION)) return TRUE;
1320 return FALSE;
1323 ret = set_menu_item_info( &menu->items[i], info );
1324 if (ret) menu->Height = 0; /* force size recalculate */
1325 release_menu_ptr(menu);
1326 break;
1328 case NtUserGetMenuState:
1329 return get_menu_state( handle, pos, flags );
1331 default:
1332 FIXME( "unsupported method %u\n", method );
1333 return FALSE;
1336 return ret;
1339 /* see GetMenuItemCount */
1340 INT get_menu_item_count( HMENU handle )
1342 struct menu *menu;
1343 INT count;
1345 if (!(menu = grab_menu_ptr( handle ))) return -1;
1346 count = menu->nItems;
1347 release_menu_ptr(menu);
1349 TRACE( "(%p) returning %d\n", handle, count );
1350 return count;
1353 /**********************************************************************
1354 * NtUserRemoveMenu (win32u.@)
1356 BOOL WINAPI NtUserRemoveMenu( HMENU handle, UINT id, UINT flags )
1358 struct menu *menu;
1359 UINT pos;
1361 TRACE( "(menu=%p id=%#x flags=%04x)\n", handle, id, flags );
1363 if (!(menu = find_menu_item( handle, id, flags, &pos )))
1364 return FALSE;
1366 /* Remove item */
1367 free( menu->items[pos].text );
1369 if (--menu->nItems == 0)
1371 free( menu->items );
1372 menu->items = NULL;
1374 else
1376 struct menu_item *new_items, *item = &menu->items[pos];
1378 while (pos < menu->nItems)
1380 *item = item[1];
1381 item++;
1382 pos++;
1384 new_items = realloc( menu->items, menu->nItems * sizeof(*item) );
1385 if (new_items) menu->items = new_items;
1388 release_menu_ptr(menu);
1389 return TRUE;
1392 /**********************************************************************
1393 * NtUserDeleteMenu (win32u.@)
1395 BOOL WINAPI NtUserDeleteMenu( HMENU handle, UINT id, UINT flags )
1397 struct menu *menu;
1398 UINT pos;
1400 if (!(menu = find_menu_item( handle, id, flags, &pos )))
1401 return FALSE;
1403 if (menu->items[pos].fType & MF_POPUP)
1404 NtUserDestroyMenu( menu->items[pos].hSubMenu );
1406 NtUserRemoveMenu( menu->obj.handle, pos, flags | MF_BYPOSITION );
1407 release_menu_ptr( menu );
1408 return TRUE;
1411 /**********************************************************************
1412 * NtUserSetMenuContextHelpId (win32u.@)
1414 BOOL WINAPI NtUserSetMenuContextHelpId( HMENU handle, DWORD id )
1416 struct menu *menu;
1418 TRACE( "(%p 0x%08x)\n", handle, (int)id );
1420 if (!(menu = grab_menu_ptr( handle ))) return FALSE;
1421 menu->dwContextHelpID = id;
1422 release_menu_ptr( menu );
1423 return TRUE;
1426 /***********************************************************************
1427 * copy_sys_popup
1429 * Return the default system menu.
1431 static HMENU copy_sys_popup( BOOL mdi )
1433 struct load_sys_menu_params params;
1434 MENUITEMINFOW item_info;
1435 MENUINFO menu_info;
1436 struct menu *menu;
1437 void *ret_ptr;
1438 ULONG ret_len;
1439 HMENU handle;
1441 params.mdi = mdi;
1442 handle = UlongToHandle( KeUserModeCallback( NtUserLoadSysMenu, &params, sizeof(params),
1443 &ret_ptr, &ret_len ));
1445 if (!handle || !(menu = grab_menu_ptr( handle )))
1447 ERR("Unable to load default system menu\n" );
1448 return 0;
1451 menu->wFlags |= MF_SYSMENU | MF_POPUP;
1452 release_menu_ptr( menu );
1454 /* decorate the menu with bitmaps */
1455 menu_info.cbSize = sizeof(MENUINFO);
1456 menu_info.dwStyle = MNS_CHECKORBMP;
1457 menu_info.fMask = MIM_STYLE;
1458 NtUserThunkedMenuInfo( handle, &menu_info );
1459 item_info.cbSize = sizeof(MENUITEMINFOW);
1460 item_info.fMask = MIIM_BITMAP;
1461 item_info.hbmpItem = HBMMENU_POPUP_CLOSE;
1462 NtUserThunkedMenuItemInfo( handle, SC_CLOSE, 0, NtUserSetMenuItemInfo, &item_info, NULL );
1463 item_info.hbmpItem = HBMMENU_POPUP_RESTORE;
1464 NtUserThunkedMenuItemInfo( handle, SC_RESTORE, 0, NtUserSetMenuItemInfo, &item_info, NULL );
1465 item_info.hbmpItem = HBMMENU_POPUP_MAXIMIZE;
1466 NtUserThunkedMenuItemInfo( handle, SC_MAXIMIZE, 0, NtUserSetMenuItemInfo, &item_info, NULL );
1467 item_info.hbmpItem = HBMMENU_POPUP_MINIMIZE;
1468 NtUserThunkedMenuItemInfo( handle, SC_MINIMIZE, 0, NtUserSetMenuItemInfo, &item_info, NULL );
1469 NtUserSetMenuDefaultItem( handle, SC_CLOSE, FALSE );
1471 TRACE( "returning %p (mdi=%d).\n", handle, mdi );
1472 return handle;
1475 /**********************************************************************
1476 * get_sys_menu
1478 * Create a copy of the system menu. System menu in Windows is
1479 * a special menu bar with the single entry - system menu popup.
1480 * This popup is presented to the outside world as a "system menu".
1481 * However, the real system menu handle is sometimes seen in the
1482 * WM_MENUSELECT parameters (and Word 6 likes it this way).
1484 static HMENU get_sys_menu( HWND hwnd, HMENU popup_menu )
1486 MENUITEMINFOW info;
1487 struct menu *menu;
1488 HMENU handle;
1490 TRACE("loading system menu, hwnd %p, popup_menu %p\n", hwnd, popup_menu);
1491 if (!(handle = create_menu( FALSE )))
1493 ERR("failed to load system menu!\n");
1494 return 0;
1497 if (!(menu = grab_menu_ptr( handle )))
1499 NtUserDestroyMenu( handle );
1500 return 0;
1502 menu->wFlags = MF_SYSMENU;
1503 menu->hWnd = get_full_window_handle( hwnd );
1504 release_menu_ptr( menu );
1505 TRACE("hwnd %p (handle %p)\n", menu->hWnd, handle);
1507 if (!popup_menu)
1509 if (get_window_long(hwnd, GWL_EXSTYLE) & WS_EX_MDICHILD)
1510 popup_menu = copy_sys_popup(TRUE);
1511 else
1512 popup_menu = copy_sys_popup(FALSE);
1514 if (!popup_menu)
1516 NtUserDestroyMenu( handle );
1517 return 0;
1520 if (get_class_long( hwnd, GCL_STYLE, FALSE ) & CS_NOCLOSE)
1521 NtUserDeleteMenu(popup_menu, SC_CLOSE, MF_BYCOMMAND);
1523 info.cbSize = sizeof(info);
1524 info.fMask = MIIM_STATE | MIIM_ID | MIIM_FTYPE | MIIM_SUBMENU;
1525 info.fState = 0;
1526 info.fType = MF_SYSMENU | MF_POPUP;
1527 info.wID = (UINT_PTR)popup_menu;
1528 info.hSubMenu = popup_menu;
1530 NtUserThunkedMenuItemInfo( handle, -1, MF_SYSMENU | MF_POPUP | MF_BYPOSITION,
1531 NtUserInsertMenuItem, &info, NULL );
1533 if ((menu = grab_menu_ptr( handle )))
1535 menu->items[0].fType = MF_SYSMENU | MF_POPUP;
1536 menu->items[0].fState = 0;
1537 release_menu_ptr( menu );
1539 if ((menu = grab_menu_ptr(popup_menu)))
1541 menu->wFlags |= MF_SYSMENU;
1542 release_menu_ptr( menu );
1545 TRACE("handle=%p (hPopup %p)\n", handle, popup_menu );
1546 return handle;
1549 /**********************************************************************
1550 * NtUserMenuItemFromPoint (win32u.@)
1552 INT WINAPI NtUserMenuItemFromPoint( HWND hwnd, HMENU handle, int x, int y )
1554 POINT pt = { .x = x, .y = y };
1555 struct menu *menu;
1556 UINT pos;
1558 if (!(menu = grab_menu_ptr(handle))) return -1;
1559 if (find_item_by_coords( menu, pt, &pos ) != ht_item) pos = -1;
1560 release_menu_ptr(menu);
1561 return pos;
1564 /**********************************************************************
1565 * NtUserGetSystemMenu (win32u.@)
1567 HMENU WINAPI NtUserGetSystemMenu( HWND hwnd, BOOL revert )
1569 WND *win = get_win_ptr( hwnd );
1570 HMENU retvalue = 0;
1572 if (win == WND_DESKTOP || !win) return 0;
1573 if (win == WND_OTHER_PROCESS)
1575 if (is_window( hwnd )) FIXME( "not supported on other process window %p\n", hwnd );
1576 return 0;
1579 if (win->hSysMenu && revert)
1581 NtUserDestroyMenu( win->hSysMenu );
1582 win->hSysMenu = 0;
1585 if (!win->hSysMenu && (win->dwStyle & WS_SYSMENU))
1586 win->hSysMenu = get_sys_menu( hwnd, 0 );
1588 if (win->hSysMenu)
1590 struct menu *menu;
1591 retvalue = get_sub_menu( win->hSysMenu, 0 );
1593 /* Store the dummy sysmenu handle to facilitate the refresh */
1594 /* of the close button if the SC_CLOSE item change */
1595 menu = grab_menu_ptr( retvalue );
1596 if (menu)
1598 menu->hSysMenuOwner = win->hSysMenu;
1599 release_menu_ptr( menu );
1603 release_win_ptr( win );
1604 return revert ? 0 : retvalue;
1607 /**********************************************************************
1608 * NtUserSetSystemMenu (win32u.@)
1610 BOOL WINAPI NtUserSetSystemMenu( HWND hwnd, HMENU menu )
1612 WND *win = get_win_ptr( hwnd );
1614 if (!win || win == WND_OTHER_PROCESS || win == WND_DESKTOP) return FALSE;
1616 if (win->hSysMenu) NtUserDestroyMenu( win->hSysMenu );
1617 win->hSysMenu = get_sys_menu( hwnd, menu );
1618 release_win_ptr( win );
1619 return TRUE;
1622 HMENU get_window_sys_sub_menu( HWND hwnd )
1624 WND *win;
1625 HMENU ret;
1627 if (!(win = get_win_ptr( hwnd )) || win == WND_OTHER_PROCESS || win == WND_DESKTOP) return 0;
1628 ret = win->hSysMenu;
1629 release_win_ptr( win );
1630 return get_sub_menu( ret, 0 );
1633 /**********************************************************************
1634 * NtUserSetMenuDefaultItem (win32u.@)
1636 BOOL WINAPI NtUserSetMenuDefaultItem( HMENU handle, UINT item, UINT bypos )
1638 struct menu_item *menu_item;
1639 struct menu *menu;
1640 unsigned int i;
1641 BOOL ret = FALSE;
1643 TRACE( "(%p,%d,%d)\n", handle, item, bypos );
1645 if (!(menu = grab_menu_ptr( handle ))) return FALSE;
1647 /* reset all default-item flags */
1648 menu_item = menu->items;
1649 for (i = 0; i < menu->nItems; i++, menu_item++)
1651 menu_item->fState &= ~MFS_DEFAULT;
1654 if (item != -1)
1656 menu_item = menu->items;
1658 if (bypos)
1660 ret = item < menu->nItems;
1661 if (ret) menu->items[item].fState |= MFS_DEFAULT;
1663 else
1665 for (i = 0; i < menu->nItems; i++)
1667 if (menu->items[i].wID == item)
1669 menu->items[i].fState |= MFS_DEFAULT;
1670 ret = TRUE;
1675 else ret = TRUE;
1677 release_menu_ptr( menu );
1678 return ret;
1681 /**********************************************************************
1682 * translate_accelerator
1684 static BOOL translate_accelerator( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam,
1685 BYTE virt, WORD key, WORD cmd )
1687 INT mask = 0;
1688 UINT msg = 0;
1690 if (wparam != key) return FALSE;
1692 if (NtUserGetKeyState( VK_CONTROL ) & 0x8000) mask |= FCONTROL;
1693 if (NtUserGetKeyState( VK_MENU ) & 0x8000) mask |= FALT;
1694 if (NtUserGetKeyState( VK_SHIFT ) & 0x8000) mask |= FSHIFT;
1696 if (message == WM_CHAR || message == WM_SYSCHAR)
1698 if (!(virt & FVIRTKEY) && (mask & FALT) == (virt & FALT))
1700 TRACE_(accel)( "found accel for WM_CHAR: ('%c')\n", LOWORD(wparam) & 0xff );
1701 goto found;
1704 else
1706 if (virt & FVIRTKEY)
1708 TRACE_(accel)( "found accel for virt_key %04x (scan %04x)\n",
1709 key, 0xff & HIWORD(lparam) );
1711 if (mask == (virt & (FSHIFT | FCONTROL | FALT))) goto found;
1712 TRACE_(accel)( ", but incorrect SHIFT/CTRL/ALT-state\n" );
1714 else
1716 if (!(lparam & 0x01000000)) /* no special_key */
1718 if ((virt & FALT) && (lparam & 0x20000000)) /* ALT pressed */
1720 TRACE_(accel)( "found accel for Alt-%c\n", LOWORD(wparam) & 0xff );
1721 goto found;
1726 return FALSE;
1728 found:
1729 if (message == WM_KEYUP || message == WM_SYSKEYUP)
1730 msg = 1;
1731 else
1733 HMENU menu_handle, submenu, sys_menu;
1734 UINT sys_stat = ~0u, stat = ~0u, pos;
1735 struct menu *menu;
1737 menu_handle = (get_window_long( hwnd, GWL_STYLE ) & WS_CHILD) ? 0 : get_menu(hwnd);
1738 sys_menu = get_win_sys_menu( hwnd );
1740 /* find menu item and ask application to initialize it */
1741 /* 1. in the system menu */
1742 if ((menu = find_menu_item( sys_menu, cmd, MF_BYCOMMAND, NULL )))
1744 submenu = menu->obj.handle;
1745 release_menu_ptr( menu );
1747 if (get_capture())
1748 msg = 2;
1749 if (!is_window_enabled( hwnd ))
1750 msg = 3;
1751 else
1753 send_message( hwnd, WM_INITMENU, (WPARAM)sys_menu, 0 );
1754 if (submenu != sys_menu)
1756 pos = find_submenu( &sys_menu, submenu );
1757 TRACE_(accel)( "sys_menu = %p, submenu = %p, pos = %d\n",
1758 sys_menu, submenu, pos );
1759 send_message( hwnd, WM_INITMENUPOPUP, (WPARAM)submenu, MAKELPARAM(pos, TRUE) );
1761 sys_stat = get_menu_state( get_sub_menu( sys_menu, 0 ), cmd, MF_BYCOMMAND );
1764 else /* 2. in the window's menu */
1766 if ((menu = find_menu_item( menu_handle, cmd, MF_BYCOMMAND, NULL )))
1768 submenu = menu->obj.handle;
1769 release_menu_ptr( menu );
1771 if (get_capture())
1772 msg = 2;
1773 if (!is_window_enabled( hwnd ))
1774 msg = 3;
1775 else
1777 send_message( hwnd, WM_INITMENU, (WPARAM)menu_handle, 0 );
1778 if(submenu != menu_handle)
1780 pos = find_submenu( &menu_handle, submenu );
1781 TRACE_(accel)( "menu_handle = %p, submenu = %p, pos = %d\n",
1782 menu_handle, submenu, pos );
1783 send_message( hwnd, WM_INITMENUPOPUP, (WPARAM)submenu,
1784 MAKELPARAM(pos, FALSE) );
1786 stat = get_menu_state( menu_handle, cmd, MF_BYCOMMAND );
1791 if (msg == 0)
1793 if (sys_stat != ~0u)
1795 if (sys_stat & (MF_DISABLED|MF_GRAYED))
1796 msg = 4;
1797 else
1798 msg = WM_SYSCOMMAND;
1800 else
1802 if (stat != ~0u)
1804 if (is_iconic( hwnd ))
1805 msg = 5;
1806 else
1808 if (stat & (MF_DISABLED|MF_GRAYED))
1809 msg = 6;
1810 else
1811 msg = WM_COMMAND;
1814 else
1815 msg = WM_COMMAND;
1820 if (msg == WM_COMMAND)
1822 TRACE_(accel)( ", sending WM_COMMAND, wparam=%0x\n", 0x10000 | cmd );
1823 send_message( hwnd, msg, 0x10000 | cmd, 0 );
1825 else if (msg == WM_SYSCOMMAND)
1827 TRACE_(accel)( ", sending WM_SYSCOMMAND, wparam=%0x\n", cmd );
1828 send_message( hwnd, msg, cmd, 0x00010000 );
1830 else
1832 /* some reasons for NOT sending the WM_{SYS}COMMAND message:
1833 * #0: unknown (please report!)
1834 * #1: for WM_KEYUP,WM_SYSKEYUP
1835 * #2: mouse is captured
1836 * #3: window is disabled
1837 * #4: it's a disabled system menu option
1838 * #5: it's a menu option, but window is iconic
1839 * #6: it's a menu option, but disabled
1841 TRACE_(accel)( ", but won't send WM_{SYS}COMMAND, reason is #%d\n", msg );
1842 if (!msg) ERR_(accel)( " unknown reason\n" );
1844 return TRUE;
1847 /**********************************************************************
1848 * NtUserTranslateAccelerator (win32u.@)
1850 INT WINAPI NtUserTranslateAccelerator( HWND hwnd, HACCEL accel, MSG *msg )
1852 ACCEL data[32], *ptr = data;
1853 int i, count;
1855 if (!hwnd) return 0;
1857 if (msg->message != WM_KEYDOWN &&
1858 msg->message != WM_SYSKEYDOWN &&
1859 msg->message != WM_CHAR &&
1860 msg->message != WM_SYSCHAR)
1861 return 0;
1863 TRACE_(accel)("accel %p, hwnd %p, msg->hwnd %p, msg->message %04x, wParam %08lx, lParam %08lx\n",
1864 accel, hwnd, msg->hwnd, msg->message, (long)msg->wParam, msg->lParam);
1866 if (!(count = NtUserCopyAcceleratorTable( accel, NULL, 0 ))) return 0;
1867 if (count > ARRAY_SIZE( data ))
1869 if (!(ptr = malloc( count * sizeof(*ptr) ))) return 0;
1871 count = NtUserCopyAcceleratorTable( accel, ptr, count );
1872 for (i = 0; i < count; i++)
1874 if (translate_accelerator( hwnd, msg->message, msg->wParam, msg->lParam,
1875 ptr[i].fVirt, ptr[i].key, ptr[i].cmd))
1876 break;
1878 if (ptr != data) free( ptr );
1879 return (i < count);
1882 static HFONT get_menu_font( BOOL bold )
1884 static HFONT menu_font, menu_font_bold;
1886 HFONT ret = bold ? menu_font_bold : menu_font;
1888 if (!ret)
1890 NONCLIENTMETRICSW ncm;
1891 HFONT prev;
1893 ncm.cbSize = sizeof(NONCLIENTMETRICSW);
1894 NtUserSystemParametersInfo( SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICSW), &ncm, 0 );
1896 if (bold)
1898 ncm.lfMenuFont.lfWeight += 300;
1899 if (ncm.lfMenuFont.lfWeight > 1000) ncm.lfMenuFont.lfWeight = 1000;
1901 if (!(ret = NtGdiHfontCreate( &ncm.lfMenuFont, sizeof(ncm.lfMenuFont), 0, 0, NULL )))
1902 return 0;
1903 prev = InterlockedCompareExchangePointer( (void **)(bold ? &menu_font_bold : &menu_font),
1904 ret, NULL );
1905 if (prev)
1907 /* another thread beat us to it */
1908 NtGdiDeleteObjectApp( ret );
1909 ret = prev;
1912 return ret;
1915 static HBITMAP get_arrow_bitmap(void)
1917 static HBITMAP arrow_bitmap;
1919 if (!arrow_bitmap)
1920 arrow_bitmap = LoadImageW( 0, MAKEINTRESOURCEW(OBM_MNARROW), IMAGE_BITMAP, 0, 0, 0 );
1921 return arrow_bitmap;
1924 /* Get the size of a bitmap item */
1925 static void get_bitmap_item_size( struct menu_item *item, SIZE *size, HWND owner )
1927 HBITMAP bmp = item->hbmpItem;
1928 BITMAP bm;
1930 size->cx = size->cy = 0;
1932 /* check if there is a magic menu item associated with this item */
1933 switch ((INT_PTR)bmp)
1935 case (INT_PTR)HBMMENU_CALLBACK:
1937 MEASUREITEMSTRUCT meas_item;
1938 meas_item.CtlType = ODT_MENU;
1939 meas_item.CtlID = 0;
1940 meas_item.itemID = item->wID;
1941 meas_item.itemWidth = item->rect.right - item->rect.left;
1942 meas_item.itemHeight = item->rect.bottom - item->rect.top;
1943 meas_item.itemData = item->dwItemData;
1944 send_message( owner, WM_MEASUREITEM, 0, (LPARAM)&meas_item );
1945 size->cx = meas_item.itemWidth;
1946 size->cy = meas_item.itemHeight;
1947 return;
1949 break;
1950 case (INT_PTR)HBMMENU_SYSTEM:
1951 if (item->dwItemData)
1953 bmp = (HBITMAP)item->dwItemData;
1954 break;
1956 /* fall through */
1957 case (INT_PTR)HBMMENU_MBAR_RESTORE:
1958 case (INT_PTR)HBMMENU_MBAR_MINIMIZE:
1959 case (INT_PTR)HBMMENU_MBAR_MINIMIZE_D:
1960 case (INT_PTR)HBMMENU_MBAR_CLOSE:
1961 case (INT_PTR)HBMMENU_MBAR_CLOSE_D:
1962 size->cx = get_system_metrics( SM_CYMENU ) - 4;
1963 size->cy = size->cx;
1964 return;
1965 case (INT_PTR)HBMMENU_POPUP_CLOSE:
1966 case (INT_PTR)HBMMENU_POPUP_RESTORE:
1967 case (INT_PTR)HBMMENU_POPUP_MAXIMIZE:
1968 case (INT_PTR)HBMMENU_POPUP_MINIMIZE:
1969 size->cx = get_system_metrics( SM_CXMENUSIZE );
1970 size->cy = get_system_metrics( SM_CYMENUSIZE );
1971 return;
1973 if (NtGdiExtGetObjectW( bmp, sizeof(bm), &bm ))
1975 size->cx = bm.bmWidth;
1976 size->cy = bm.bmHeight;
1980 /* Calculate the size of the menu item and store it in item->rect */
1981 static void calc_menu_item_size( HDC hdc, struct menu_item *item, HWND owner, INT org_x, INT org_y,
1982 BOOL menu_bar, struct menu *menu )
1984 UINT check_bitmap_width = get_system_metrics( SM_CXMENUCHECK );
1985 UINT arrow_bitmap_width;
1986 INT item_height;
1987 BITMAP bm;
1988 WCHAR *p;
1990 TRACE( "dc=%p owner=%p (%d,%d) item %s\n", hdc, owner, org_x, org_y, debugstr_menuitem( item ));
1992 NtGdiExtGetObjectW( get_arrow_bitmap(), sizeof(bm), &bm );
1993 arrow_bitmap_width = bm.bmWidth;
1995 if (!menucharsize.cx)
1997 int height;
1998 menucharsize.cx = get_char_dimensions( hdc, NULL, &height );
1999 menucharsize.cy = height;
2000 /* Win95/98/ME will use menucharsize.cy here. Testing is possible
2001 * but it is unlikely an application will depend on that */
2002 od_item_height = HIWORD( get_dialog_base_units() );
2005 SetRect( &item->rect, org_x, org_y, org_x, org_y );
2007 if (item->fType & MF_OWNERDRAW)
2009 MEASUREITEMSTRUCT mis;
2010 mis.CtlType = ODT_MENU;
2011 mis.CtlID = 0;
2012 mis.itemID = item->wID;
2013 mis.itemData = item->dwItemData;
2014 mis.itemHeight = od_item_height;
2015 mis.itemWidth = 0;
2016 send_message( owner, WM_MEASUREITEM, 0, (LPARAM)&mis );
2017 /* Tests reveal that Windows ( Win95 through WinXP) adds twice the average
2018 * width of a menufont character to the width of an owner-drawn menu. */
2019 item->rect.right += mis.itemWidth + 2 * menucharsize.cx;
2020 if (menu_bar)
2022 /* Under at least win95 you seem to be given a standard
2023 * height for the menu and the height value is ignored. */
2024 item->rect.bottom += get_system_metrics( SM_CYMENUSIZE );
2026 else
2027 item->rect.bottom += mis.itemHeight;
2029 TRACE( "id=%04lx size=%dx%d\n", (long)item->wID, (int)(item->rect.right - item->rect.left),
2030 (int)(item->rect.bottom - item->rect.top) );
2031 return;
2034 if (item->fType & MF_SEPARATOR)
2036 item->rect.bottom += get_system_metrics( SM_CYMENUSIZE ) / 2;
2037 if (!menu_bar) item->rect.right += arrow_bitmap_width + menucharsize.cx;
2038 return;
2041 item_height = 0;
2042 item->xTab = 0;
2044 if (!menu_bar)
2046 if (item->hbmpItem)
2048 SIZE size;
2050 get_bitmap_item_size( item, &size, owner );
2051 /* Keep the size of the bitmap in callback mode to be able
2052 * to draw it correctly */
2053 item->bmpsize = size;
2054 menu->textOffset = max( menu->textOffset, size.cx );
2055 item->rect.right += size.cx + 2;
2056 item_height = size.cy + 2;
2058 if (!(menu->dwStyle & MNS_NOCHECK)) item->rect.right += check_bitmap_width;
2059 item->rect.right += 4 + menucharsize.cx;
2060 item->xTab = item->rect.right;
2061 item->rect.right += arrow_bitmap_width;
2063 else if (item->hbmpItem) /* menu_bar */
2065 SIZE size;
2067 get_bitmap_item_size( item, &size, owner );
2068 item->bmpsize = size;
2069 item->rect.right += size.cx;
2070 if (item->text) item->rect.right += 2;
2071 item_height = size.cy;
2074 /* it must be a text item - unless it's the system menu */
2075 if (!(item->fType & MF_SYSMENU) && item->text)
2077 LONG txt_height, txt_width;
2078 HFONT prev_font = NULL;
2079 RECT rc = item->rect;
2081 if (item->fState & MFS_DEFAULT)
2082 prev_font = NtGdiSelectFont( hdc, get_menu_font(TRUE) );
2084 if (menu_bar)
2086 txt_height = DrawTextW( hdc, item->text, -1, &rc, DT_SINGLELINE | DT_CALCRECT );
2087 item->rect.right += rc.right - rc.left;
2088 item_height = max( max( item_height, txt_height ),
2089 get_system_metrics( SM_CYMENU ) - 1 );
2090 item->rect.right += 2 * menucharsize.cx;
2092 else
2094 if ((p = wcschr( item->text, '\t' )))
2096 RECT r = rc;
2097 int h, n = (int)(p - item->text);
2099 /* Item contains a tab (only meaningful in popup menus) */
2100 /* get text size before the tab */
2101 txt_height = DrawTextW( hdc, item->text, n, &rc, DT_SINGLELINE | DT_CALCRECT );
2102 txt_width = rc.right - rc.left;
2103 p += 1; /* advance past the Tab */
2104 /* get text size after the tab */
2105 h = DrawTextW( hdc, p, -1, &r, DT_SINGLELINE | DT_CALCRECT );
2106 item->xTab += txt_width;
2107 txt_height = max( txt_height, h );
2108 /* space for the tab and the short cut */
2109 txt_width += menucharsize.cx + r.right - r.left;
2111 else
2113 txt_height = DrawTextW( hdc, item->text, -1, &rc, DT_SINGLELINE | DT_CALCRECT );
2114 txt_width = rc.right - rc.left;
2115 item->xTab += txt_width;
2117 item->rect.right += 2 + txt_width;
2118 item_height = max( item_height, max( txt_height + 2, menucharsize.cy + 4 ));
2120 if (prev_font) NtGdiSelectFont( hdc, prev_font );
2122 else if (menu_bar)
2124 item_height = max( item_height, get_system_metrics( SM_CYMENU ) - 1 );
2126 item->rect.bottom += item_height;
2127 TRACE( "%s\n", wine_dbgstr_rect( &item->rect ));
2130 /* Calculate the size of the menu bar */
2131 static void calc_menu_bar_size( HDC hdc, RECT *rect, struct menu *menu, HWND owner )
2133 UINT start, i, help_pos;
2134 int org_x, org_y;
2135 struct menu_item *item;
2137 if (!rect || !menu || !menu->nItems) return;
2139 TRACE( "rect %p %s\n", rect, wine_dbgstr_rect( rect ));
2140 /* Start with a 1 pixel top border.
2141 This corresponds to the difference between SM_CYMENU and SM_CYMENUSIZE. */
2142 SetRect( &menu->items_rect, 0, 0, rect->right - rect->left, 1 );
2143 start = 0;
2144 help_pos = ~0u;
2145 menu->textOffset = 0;
2146 while (start < menu->nItems)
2148 item = &menu->items[start];
2149 org_x = menu->items_rect.left;
2150 org_y = menu->items_rect.bottom;
2152 /* Parse items until line break or end of menu */
2153 for (i = start; i < menu->nItems; i++, item++)
2155 if (help_pos == ~0u && (item->fType & MF_RIGHTJUSTIFY)) help_pos = i;
2156 if (i != start && (item->fType & (MF_MENUBREAK | MF_MENUBARBREAK))) break;
2158 TRACE("item org=(%d, %d) %s\n", org_x, org_y, debugstr_menuitem( item ));
2159 calc_menu_item_size( hdc, item, owner, org_x, org_y, TRUE, menu );
2161 if (item->rect.right > menu->items_rect.right)
2163 if (i != start) break;
2164 else item->rect.right = menu->items_rect.right;
2166 menu->items_rect.bottom = max( menu->items_rect.bottom, item->rect.bottom );
2167 org_x = item->rect.right;
2170 /* Finish the line (set all items to the largest height found) */
2171 while (start < i) menu->items[start++].rect.bottom = menu->items_rect.bottom;
2174 OffsetRect( &menu->items_rect, rect->left, rect->top );
2175 menu->Width = menu->items_rect.right - menu->items_rect.left;
2176 menu->Height = menu->items_rect.bottom - menu->items_rect.top;
2177 rect->bottom = menu->items_rect.bottom;
2179 /* Flush right all items between the MF_RIGHTJUSTIFY and */
2180 /* the last item (if several lines, only move the last line) */
2181 if (help_pos == ~0u) return;
2182 item = &menu->items[menu->nItems-1];
2183 org_y = item->rect.top;
2184 org_x = rect->right - rect->left;
2185 for (i = menu->nItems - 1; i >= help_pos; i--, item--)
2187 if (item->rect.top != org_y) break; /* other line */
2188 if (item->rect.right >= org_x) break; /* too far right already */
2189 item->rect.left += org_x - item->rect.right;
2190 item->rect.right = org_x;
2191 org_x = item->rect.left;
2195 UINT get_menu_bar_height( HWND hwnd, UINT width, INT org_x, INT org_y )
2197 struct menu *menu;
2198 RECT rect_bar;
2199 HDC hdc;
2201 TRACE( "hwnd %p, width %d, at (%d, %d).\n", hwnd, width, org_x, org_y );
2203 if (!(menu = unsafe_menu_ptr( get_menu( hwnd )))) return 0;
2205 hdc = NtUserGetDCEx( hwnd, 0, DCX_CACHE | DCX_WINDOW );
2206 NtGdiSelectFont( hdc, get_menu_font(FALSE));
2207 SetRect( &rect_bar, org_x, org_y, org_x + width, org_y + get_system_metrics( SM_CYMENU ));
2208 calc_menu_bar_size( hdc, &rect_bar, menu, hwnd );
2209 NtUserReleaseDC( hwnd, hdc );
2210 return menu->Height;
2213 static void draw_popup_arrow( HDC hdc, RECT rect, UINT arrow_width, UINT arrow_height )
2215 HDC mem_hdc = NtGdiCreateCompatibleDC( hdc );
2216 HBITMAP prev_bitmap;
2218 prev_bitmap = NtGdiSelectBitmap( mem_hdc, get_arrow_bitmap() );
2219 NtGdiBitBlt( hdc, rect.right - arrow_width - 1,
2220 (rect.top + rect.bottom - arrow_height) / 2,
2221 arrow_width, arrow_height, mem_hdc, 0, 0, SRCCOPY, 0, 0 );
2222 NtGdiSelectBitmap( mem_hdc, prev_bitmap );
2223 NtGdiDeleteObjectApp( mem_hdc );
2226 static void draw_bitmap_item( HWND hwnd, HDC hdc, struct menu_item *item, const RECT *rect,
2227 struct menu *menu, HWND owner, UINT odaction )
2229 int w = rect->right - rect->left;
2230 int h = rect->bottom - rect->top;
2231 int bmp_xoffset = 0, left, top;
2232 HBITMAP bmp_to_draw = item->hbmpItem;
2233 HBITMAP bmp = bmp_to_draw;
2234 BITMAP bm;
2235 DWORD rop;
2236 HDC mem_hdc;
2238 /* Check if there is a magic menu item associated with this item */
2239 if (IS_MAGIC_BITMAP( bmp_to_draw ))
2241 BOOL down = FALSE, grayed = FALSE;
2242 enum NONCLIENT_BUTTON_TYPE type;
2243 WCHAR bmchr = 0;
2244 RECT r;
2246 switch ((INT_PTR)bmp_to_draw)
2248 case (INT_PTR)HBMMENU_SYSTEM:
2249 if (item->dwItemData)
2251 bmp = (HBITMAP)item->dwItemData;
2252 if (!NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return;
2254 else
2256 static HBITMAP sys_menu_bmp;
2258 if (!sys_menu_bmp)
2259 sys_menu_bmp = LoadImageW( 0, MAKEINTRESOURCEW(OBM_CLOSE), IMAGE_BITMAP, 0, 0, 0 );
2260 bmp = sys_menu_bmp;
2261 if (!NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return;
2262 /* only use right half of the bitmap */
2263 bmp_xoffset = bm.bmWidth / 2;
2264 bm.bmWidth -= bmp_xoffset;
2266 goto got_bitmap;
2267 case (INT_PTR)HBMMENU_MBAR_RESTORE:
2268 type = MENU_RESTORE_BUTTON;
2269 break;
2270 case (INT_PTR)HBMMENU_MBAR_MINIMIZE:
2271 type = MENU_MIN_BUTTON;
2272 break;
2273 case (INT_PTR)HBMMENU_MBAR_MINIMIZE_D:
2274 type = MENU_MIN_BUTTON;
2275 grayed = TRUE;
2276 break;
2277 case (INT_PTR)HBMMENU_MBAR_CLOSE:
2278 type = MENU_CLOSE_BUTTON;
2279 break;
2280 case (INT_PTR)HBMMENU_MBAR_CLOSE_D:
2281 type = MENU_CLOSE_BUTTON;
2282 grayed = TRUE;
2283 break;
2284 case (INT_PTR)HBMMENU_CALLBACK:
2286 DRAWITEMSTRUCT drawItem;
2287 drawItem.CtlType = ODT_MENU;
2288 drawItem.CtlID = 0;
2289 drawItem.itemID = item->wID;
2290 drawItem.itemAction = odaction;
2291 drawItem.itemState = 0;
2292 if (item->fState & MF_CHECKED) drawItem.itemState |= ODS_CHECKED;
2293 if (item->fState & MF_DEFAULT) drawItem.itemState |= ODS_DEFAULT;
2294 if (item->fState & MF_DISABLED) drawItem.itemState |= ODS_DISABLED;
2295 if (item->fState & MF_GRAYED) drawItem.itemState |= ODS_GRAYED|ODS_DISABLED;
2296 if (item->fState & MF_HILITE) drawItem.itemState |= ODS_SELECTED;
2297 drawItem.hwndItem = (HWND)menu->obj.handle;
2298 drawItem.hDC = hdc;
2299 drawItem.itemData = item->dwItemData;
2300 drawItem.rcItem = *rect;
2301 send_message( owner, WM_DRAWITEM, 0, (LPARAM)&drawItem );
2302 return;
2304 break;
2305 case (INT_PTR)HBMMENU_POPUP_CLOSE:
2306 bmchr = 0x72;
2307 break;
2308 case (INT_PTR)HBMMENU_POPUP_RESTORE:
2309 bmchr = 0x32;
2310 break;
2311 case (INT_PTR)HBMMENU_POPUP_MAXIMIZE:
2312 bmchr = 0x31;
2313 break;
2314 case (INT_PTR)HBMMENU_POPUP_MINIMIZE:
2315 bmchr = 0x30;
2316 break;
2317 default:
2318 FIXME( "Magic %p not implemented\n", bmp_to_draw );
2319 return;
2322 if (bmchr)
2324 /* draw the magic bitmaps using marlett font characters */
2325 /* FIXME: fontsize and the position (x,y) could probably be better */
2326 HFONT hfont, prev_font;
2327 LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
2328 {'M','a','r','l','e','t','t'}};
2329 logfont.lfHeight = min( h, w) - 5 ;
2330 TRACE( " height %d rect %s\n", (int)logfont.lfHeight, wine_dbgstr_rect( rect ));
2331 hfont = NtGdiHfontCreate( &logfont, sizeof(logfont), 0, 0, NULL );
2332 prev_font = NtGdiSelectFont( hdc, hfont );
2333 NtGdiExtTextOutW( hdc, rect->left, rect->top + 2, 0, NULL, &bmchr, 1, NULL, 0 );
2334 NtGdiSelectFont( hdc, prev_font );
2335 NtGdiDeleteObjectApp( hfont );
2337 else
2339 r = *rect;
2340 InflateRect( &r, -1, -1 );
2341 if (item->fState & MF_HILITE) down = TRUE;
2342 draw_menu_button( hwnd, hdc, &r, type, down, grayed );
2344 return;
2347 if (!bmp || !NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return;
2349 got_bitmap:
2350 mem_hdc = NtGdiCreateCompatibleDC( hdc );
2351 NtGdiSelectBitmap( mem_hdc, bmp );
2353 /* handle fontsize > bitmap_height */
2354 top = (h>bm.bmHeight) ? rect->top + (h - bm.bmHeight) / 2 : rect->top;
2355 left=rect->left;
2356 rop= ((item->fState & MF_HILITE) && !IS_MAGIC_BITMAP(bmp_to_draw)) ? NOTSRCCOPY : SRCCOPY;
2357 if ((item->fState & MF_HILITE) && item->hbmpItem)
2358 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color( COLOR_HIGHLIGHT ), NULL );
2359 NtGdiBitBlt( hdc, left, top, w, h, mem_hdc, bmp_xoffset, 0, rop, 0, 0 );
2360 NtGdiDeleteObjectApp( mem_hdc );
2363 /* Draw a single menu item */
2364 static void draw_menu_item( HWND hwnd, struct menu *menu, HWND owner, HDC hdc,
2365 struct menu_item *item, BOOL menu_bar, UINT odaction )
2367 UINT arrow_width = 0, arrow_height = 0;
2368 HRGN old_clip = NULL, clip;
2369 BOOL flat_menu = FALSE;
2370 RECT rect, bmprc;
2371 int bkgnd;
2373 TRACE( "%s\n", debugstr_menuitem( item ));
2375 if (!menu_bar)
2377 BITMAP bmp;
2378 NtGdiExtGetObjectW( get_arrow_bitmap(), sizeof(bmp), &bmp );
2379 arrow_width = bmp.bmWidth;
2380 arrow_height = bmp.bmHeight;
2383 if (item->fType & MF_SYSMENU)
2385 if (!is_iconic( hwnd ))
2386 draw_nc_sys_button( hwnd, hdc, item->fState & (MF_HILITE | MF_MOUSESELECT) );
2387 return;
2390 TRACE( "rect=%s\n", wine_dbgstr_rect( &item->rect ));
2391 rect = item->rect;
2392 adjust_menu_item_rect( menu, &rect );
2393 if (!intersect_rect( &bmprc, &rect, &menu->items_rect )) /* bmprc is used as a dummy */
2394 return;
2396 NtUserSystemParametersInfo( SPI_GETFLATMENU, 0, &flat_menu, 0 );
2397 bkgnd = (menu_bar && flat_menu) ? COLOR_MENUBAR : COLOR_MENU;
2399 /* Setup colors */
2400 if (item->fState & MF_HILITE)
2402 if (menu_bar && !flat_menu)
2404 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color(COLOR_MENUTEXT), NULL );
2405 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color(COLOR_MENU), NULL );
2407 else
2409 if (item->fState & MF_GRAYED)
2410 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_GRAYTEXT ), NULL );
2411 else
2412 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_HIGHLIGHTTEXT ), NULL );
2413 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color( COLOR_HIGHLIGHT ), NULL );
2416 else
2418 if (item->fState & MF_GRAYED)
2419 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_GRAYTEXT ), NULL );
2420 else
2421 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_MENUTEXT ), NULL );
2422 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color( bkgnd ), NULL );
2425 old_clip = NtGdiCreateRectRgn( 0, 0, 0, 0 );
2426 if (NtGdiGetRandomRgn( hdc, old_clip, NTGDI_RGN_MIRROR_RTL | 1 ) <= 0)
2428 NtGdiDeleteObjectApp( old_clip );
2429 old_clip = NULL;
2431 clip = NtGdiCreateRectRgn( menu->items_rect.left, menu->items_rect.top,
2432 menu->items_rect.right, menu->items_rect.bottom );
2433 NtGdiExtSelectClipRgn( hdc, clip, RGN_AND );
2434 NtGdiDeleteObjectApp( clip );
2436 if (item->fType & MF_OWNERDRAW)
2439 * Experimentation under Windows reveals that an owner-drawn
2440 * menu is given the rectangle which includes the space it requested
2441 * in its response to WM_MEASUREITEM _plus_ width for a checkmark
2442 * and a popup-menu arrow. This is the value of item->rect.
2443 * Windows will leave all drawing to the application except for
2444 * the popup-menu arrow. Windows always draws that itself, after
2445 * the menu owner has finished drawing.
2447 DRAWITEMSTRUCT dis;
2448 DWORD old_bk, old_text;
2450 dis.CtlType = ODT_MENU;
2451 dis.CtlID = 0;
2452 dis.itemID = item->wID;
2453 dis.itemData = item->dwItemData;
2454 dis.itemState = 0;
2455 if (item->fState & MF_CHECKED) dis.itemState |= ODS_CHECKED;
2456 if (item->fState & MF_GRAYED) dis.itemState |= ODS_GRAYED|ODS_DISABLED;
2457 if (item->fState & MF_HILITE) dis.itemState |= ODS_SELECTED;
2458 dis.itemAction = odaction; /* ODA_DRAWENTIRE | ODA_SELECT | ODA_FOCUS; */
2459 dis.hwndItem = (HWND)menu->obj.handle;
2460 dis.hDC = hdc;
2461 dis.rcItem = rect;
2462 TRACE( "Ownerdraw: owner=%p itemID=%d, itemState=%d, itemAction=%d, "
2463 "hwndItem=%p, hdc=%p, rcItem=%s\n", owner,
2464 dis.itemID, dis.itemState, dis.itemAction, dis.hwndItem,
2465 dis.hDC, wine_dbgstr_rect( &dis.rcItem ));
2466 NtGdiGetDCDword( hdc, NtGdiGetBkColor, &old_bk );
2467 NtGdiGetDCDword( hdc, NtGdiGetTextColor, &old_text );
2468 send_message( owner, WM_DRAWITEM, 0, (LPARAM)&dis );
2469 /* Draw the popup-menu arrow */
2470 NtGdiGetAndSetDCDword( hdc, NtGdiGetBkColor, old_bk, NULL );
2471 NtGdiGetAndSetDCDword( hdc, NtGdiGetTextColor, old_text, NULL );
2472 if (item->fType & MF_POPUP)
2473 draw_popup_arrow( hdc, rect, arrow_width, arrow_height );
2474 goto done;
2477 if (menu_bar && (item->fType & MF_SEPARATOR)) goto done;
2479 if (item->fState & MF_HILITE)
2481 if (flat_menu)
2483 InflateRect (&rect, -1, -1);
2484 fill_rect( hdc, &rect, get_sys_color_brush( COLOR_MENUHILIGHT ));
2485 InflateRect (&rect, 1, 1);
2486 fill_rect( hdc, &rect, get_sys_color_brush( COLOR_HIGHLIGHT ));
2488 else
2490 if (menu_bar)
2491 draw_rect_edge( hdc, &rect, BDR_SUNKENOUTER, BF_RECT, 1 );
2492 else
2493 fill_rect( hdc, &rect, get_sys_color_brush( COLOR_HIGHLIGHT ));
2496 else
2497 fill_rect( hdc, &rect, get_sys_color_brush(bkgnd) );
2499 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkMode, TRANSPARENT, NULL );
2501 /* vertical separator */
2502 if (!menu_bar && (item->fType & MF_MENUBARBREAK))
2504 HPEN oldPen;
2505 RECT rc = rect;
2507 rc.left -= MENU_COL_SPACE / 2 + 1;
2508 rc.top = 3;
2509 rc.bottom = menu->Height - 3;
2510 if (flat_menu)
2512 oldPen = NtGdiSelectPen( hdc, get_sys_color_pen( COLOR_BTNSHADOW ));
2513 NtGdiMoveTo( hdc, rc.left, rc.top, NULL );
2514 NtGdiLineTo( hdc, rc.left, rc.bottom );
2515 NtGdiSelectPen( hdc, oldPen );
2517 else
2518 draw_rect_edge( hdc, &rc, EDGE_ETCHED, BF_LEFT, 1 );
2521 /* horizontal separator */
2522 if (item->fType & MF_SEPARATOR)
2524 HPEN oldPen;
2525 RECT rc = rect;
2527 InflateRect( &rc, -1, 0 );
2528 rc.top = ( rc.top + rc.bottom) / 2;
2529 if (flat_menu)
2531 oldPen = NtGdiSelectPen( hdc, get_sys_color_pen( COLOR_BTNSHADOW ));
2532 NtGdiMoveTo( hdc, rc.left, rc.top, NULL );
2533 NtGdiLineTo( hdc, rc.right, rc.top );
2534 NtGdiSelectPen( hdc, oldPen );
2536 else
2537 draw_rect_edge( hdc, &rc, EDGE_ETCHED, BF_TOP, 1 );
2538 goto done;
2541 if (item->hbmpItem)
2543 /* calculate the bitmap rectangle in coordinates relative
2544 * to the item rectangle */
2545 if (menu_bar)
2547 if (item->hbmpItem == HBMMENU_CALLBACK)
2548 bmprc.left = 3;
2549 else
2550 bmprc.left = item->text ? menucharsize.cx : 0;
2552 else if (menu->dwStyle & MNS_NOCHECK)
2553 bmprc.left = 4;
2554 else if (menu->dwStyle & MNS_CHECKORBMP)
2555 bmprc.left = 2;
2556 else
2557 bmprc.left = 4 + get_system_metrics( SM_CXMENUCHECK );
2558 bmprc.right = bmprc.left + item->bmpsize.cx;
2559 if (menu_bar && !(item->hbmpItem == HBMMENU_CALLBACK))
2560 bmprc.top = 0;
2561 else
2562 bmprc.top = (rect.bottom - rect.top - item->bmpsize.cy) / 2;
2563 bmprc.bottom = bmprc.top + item->bmpsize.cy;
2566 if (!menu_bar)
2568 HBITMAP bm;
2569 INT y = rect.top + rect.bottom;
2570 BOOL checked = FALSE;
2571 UINT check_bitmap_width = get_system_metrics( SM_CXMENUCHECK );
2572 UINT check_bitmap_height = get_system_metrics( SM_CYMENUCHECK );
2574 /* Draw the check mark */
2575 if (!(menu->dwStyle & MNS_NOCHECK))
2577 bm = (item->fState & MF_CHECKED) ? item->hCheckBit :
2578 item->hUnCheckBit;
2579 if (bm) /* we have a custom bitmap */
2581 HDC mem_hdc = NtGdiCreateCompatibleDC( hdc );
2583 NtGdiSelectBitmap( mem_hdc, bm );
2584 NtGdiBitBlt( hdc, rect.left, (y - check_bitmap_height) / 2,
2585 check_bitmap_width, check_bitmap_height,
2586 mem_hdc, 0, 0, SRCCOPY, 0, 0 );
2587 NtGdiDeleteObjectApp( mem_hdc );
2588 checked = TRUE;
2590 else if (item->fState & MF_CHECKED) /* standard bitmaps */
2592 RECT r;
2593 HBITMAP bm = NtGdiCreateBitmap( check_bitmap_width,
2594 check_bitmap_height, 1, 1, NULL );
2595 HDC mem_hdc = NtGdiCreateCompatibleDC( hdc );
2597 NtGdiSelectBitmap( mem_hdc, bm );
2598 SetRect( &r, 0, 0, check_bitmap_width, check_bitmap_height);
2599 draw_frame_menu( mem_hdc, &r,
2600 (item->fType & MFT_RADIOCHECK) ? DFCS_MENUBULLET : DFCS_MENUCHECK );
2601 NtGdiBitBlt( hdc, rect.left, (y - r.bottom) / 2, r.right, r.bottom,
2602 mem_hdc, 0, 0, SRCCOPY, 0, 0 );
2603 NtGdiDeleteObjectApp( mem_hdc );
2604 NtGdiDeleteObjectApp( bm );
2605 checked = TRUE;
2608 if (item->hbmpItem && !(checked && (menu->dwStyle & MNS_CHECKORBMP)))
2610 POINT origorg;
2611 /* some applications make this assumption on the DC's origin */
2612 set_viewport_org( hdc, rect.left, rect.top, &origorg );
2613 draw_bitmap_item( hwnd, hdc, item, &bmprc, menu, owner, odaction );
2614 set_viewport_org( hdc, origorg.x, origorg.y, NULL );
2616 /* Draw the popup-menu arrow */
2617 if (item->fType & MF_POPUP)
2618 draw_popup_arrow( hdc, rect, arrow_width, arrow_height);
2619 rect.left += 4;
2620 if (!(menu->dwStyle & MNS_NOCHECK))
2621 rect.left += check_bitmap_width;
2622 rect.right -= arrow_width;
2624 else if (item->hbmpItem)
2625 { /* Draw the bitmap */
2626 POINT origorg;
2628 set_viewport_org( hdc, rect.left, rect.top, &origorg);
2629 draw_bitmap_item( hwnd, hdc, item, &bmprc, menu, owner, odaction );
2630 set_viewport_org( hdc, origorg.x, origorg.y, NULL);
2632 /* process text if present */
2633 if (item->text)
2635 int i;
2636 HFONT prev_font = 0;
2637 UINT format = menu_bar ?
2638 DT_CENTER | DT_VCENTER | DT_SINGLELINE :
2639 DT_LEFT | DT_VCENTER | DT_SINGLELINE;
2641 if (!(menu->dwStyle & MNS_CHECKORBMP))
2642 rect.left += menu->textOffset;
2644 if (item->fState & MFS_DEFAULT)
2646 prev_font = NtGdiSelectFont(hdc, get_menu_font( TRUE ));
2649 if (menu_bar)
2651 if (item->hbmpItem)
2652 rect.left += item->bmpsize.cx;
2653 if (item->hbmpItem != HBMMENU_CALLBACK)
2654 rect.left += menucharsize.cx;
2655 rect.right -= menucharsize.cx;
2658 for (i = 0; item->text[i]; i++)
2659 if ((item->text[i] == '\t') || (item->text[i] == '\b'))
2660 break;
2662 if (item->fState & MF_GRAYED)
2664 if (!(item->fState & MF_HILITE) )
2666 ++rect.left; ++rect.top; ++rect.right; ++rect.bottom;
2667 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0xff, 0xff, 0xff), NULL );
2668 DrawTextW( hdc, item->text, i, &rect, format );
2669 --rect.left; --rect.top; --rect.right; --rect.bottom;
2671 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0x80, 0x80, 0x80), NULL );
2674 DrawTextW( hdc, item->text, i, &rect, format );
2676 /* paint the shortcut text */
2677 if (!menu_bar && item->text[i]) /* There's a tab or flush-right char */
2679 if (item->text[i] == '\t')
2681 rect.left = item->xTab;
2682 format = DT_LEFT | DT_VCENTER | DT_SINGLELINE;
2684 else
2686 rect.right = item->xTab;
2687 format = DT_RIGHT | DT_VCENTER | DT_SINGLELINE;
2690 if (item->fState & MF_GRAYED)
2692 if (!(item->fState & MF_HILITE) )
2694 ++rect.left; ++rect.top; ++rect.right; ++rect.bottom;
2695 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0xff, 0xff, 0xff), NULL );
2696 DrawTextW( hdc, item->text + i + 1, -1, &rect, format );
2697 --rect.left; --rect.top; --rect.right; --rect.bottom;
2699 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0x80, 0x80, 0x80), NULL );
2701 DrawTextW( hdc, item->text + i + 1, -1, &rect, format );
2704 if (prev_font) NtGdiSelectFont( hdc, prev_font );
2707 done:
2708 NtGdiExtSelectClipRgn( hdc, old_clip, RGN_COPY );
2709 if (old_clip) NtGdiDeleteObjectApp( old_clip );
2712 /***********************************************************************
2713 * NtUserDrawMenuBarTemp (win32u.@)
2715 DWORD WINAPI NtUserDrawMenuBarTemp( HWND hwnd, HDC hdc, RECT *rect, HMENU handle, HFONT font )
2717 BOOL flat_menu = FALSE;
2718 HFONT prev_font = 0;
2719 struct menu *menu;
2720 UINT i, retvalue;
2722 NtUserSystemParametersInfo( SPI_GETFLATMENU, 0, &flat_menu, 0 );
2724 if (!handle) handle = get_menu( hwnd );
2725 if (!font) font = get_menu_font(FALSE);
2727 menu = unsafe_menu_ptr( handle );
2728 if (!menu || !rect) return get_system_metrics( SM_CYMENU );
2730 TRACE( "(%p, %p, %p, %p, %p)\n", hwnd, hdc, rect, handle, font );
2732 prev_font = NtGdiSelectFont( hdc, font );
2734 if (!menu->Height) calc_menu_bar_size( hdc, rect, menu, hwnd );
2736 rect->bottom = rect->top + menu->Height;
2738 fill_rect( hdc, rect, get_sys_color_brush( flat_menu ? COLOR_MENUBAR : COLOR_MENU ));
2740 NtGdiSelectPen( hdc, get_sys_color_pen( COLOR_3DFACE ));
2741 NtGdiMoveTo( hdc, rect->left, rect->bottom, NULL );
2742 NtGdiLineTo( hdc, rect->right, rect->bottom );
2744 if (menu->nItems)
2746 for (i = 0; i < menu->nItems; i++)
2747 draw_menu_item( hwnd, menu, hwnd, hdc, &menu->items[i], TRUE, ODA_DRAWENTIRE );
2749 retvalue = menu->Height;
2751 else
2753 retvalue = get_system_metrics( SM_CYMENU );
2756 if (prev_font) NtGdiSelectFont( hdc, prev_font );
2757 return retvalue;
2760 static UINT get_scroll_arrow_height( const struct menu *menu )
2762 return menucharsize.cy + 4;
2765 static void draw_scroll_arrow( HDC hdc, int x, int top, int height, BOOL up, BOOL enabled )
2767 RECT rect, light_rect;
2768 HBRUSH brush = get_sys_color_brush( enabled ? COLOR_BTNTEXT : COLOR_BTNSHADOW );
2769 HBRUSH light = get_sys_color_brush( COLOR_3DLIGHT );
2771 if (!up)
2773 top = top + height;
2774 if (!enabled)
2776 SetRect( &rect, x + 1, top, x + 2, top + 1);
2777 fill_rect( hdc, &rect, light );
2779 top--;
2782 SetRect( &rect, x, top, x + 1, top + 1);
2783 while (height--)
2785 fill_rect( hdc, &rect, brush );
2786 if (!enabled && !up && height)
2788 SetRect( &light_rect, rect.right, rect.top, rect.right + 2, rect.bottom );
2789 fill_rect( hdc, &light_rect, light );
2791 InflateRect( &rect, 1, 0 );
2792 OffsetRect( &rect, 0, up ? 1 : -1 );
2795 if (!enabled && up)
2797 rect.left += 2;
2798 fill_rect( hdc, &rect, light );
2802 static void draw_scroll_arrows( const struct menu *menu, HDC hdc )
2804 UINT full_height = get_scroll_arrow_height( menu );
2805 UINT arrow_height = full_height / 3;
2806 BOOL at_end = menu->nScrollPos + menu->items_rect.bottom - menu->items_rect.top == menu->nTotalHeight;
2808 draw_scroll_arrow( hdc, menu->Width / 3, arrow_height, arrow_height,
2809 TRUE, menu->nScrollPos != 0);
2810 draw_scroll_arrow( hdc, menu->Width / 3, menu->Height - 2 * arrow_height, arrow_height,
2811 FALSE, !at_end );
2814 static int frame_rect( HDC hdc, const RECT *rect, HBRUSH hbrush )
2816 HBRUSH prev_brush;
2817 RECT r = *rect;
2819 if (IsRectEmpty(&r)) return 0;
2820 if (!(prev_brush = NtGdiSelectBrush( hdc, hbrush ))) return 0;
2822 NtGdiPatBlt( hdc, r.left, r.top, 1, r.bottom - r.top, PATCOPY );
2823 NtGdiPatBlt( hdc, r.right - 1, r.top, 1, r.bottom - r.top, PATCOPY );
2824 NtGdiPatBlt( hdc, r.left, r.top, r.right - r.left, 1, PATCOPY );
2825 NtGdiPatBlt( hdc, r.left, r.bottom - 1, r.right - r.left, 1, PATCOPY );
2827 NtGdiSelectBrush( hdc, prev_brush );
2828 return TRUE;
2831 static void draw_popup_menu( HWND hwnd, HDC hdc, HMENU hmenu )
2833 HBRUSH prev_hrush, brush = get_sys_color_brush( COLOR_MENU );
2834 struct menu *menu = unsafe_menu_ptr( hmenu );
2835 RECT rect;
2837 TRACE( "wnd=%p dc=%p menu=%p\n", hwnd, hdc, hmenu );
2839 get_client_rect( hwnd, &rect );
2841 if (menu && menu->hbrBack) brush = menu->hbrBack;
2842 if ((prev_hrush = NtGdiSelectBrush( hdc, brush ))
2843 && NtGdiSelectFont( hdc, get_menu_font( FALSE )))
2845 HPEN prev_pen;
2847 NtGdiRectangle( hdc, rect.left, rect.top, rect.right, rect.bottom );
2849 prev_pen = NtGdiSelectPen( hdc, GetStockObject( NULL_PEN ));
2850 if (prev_pen)
2852 BOOL flat_menu = FALSE;
2854 NtUserSystemParametersInfo( SPI_GETFLATMENU, 0, &flat_menu, 0 );
2855 if (flat_menu)
2856 frame_rect( hdc, &rect, get_sys_color_brush( COLOR_BTNSHADOW ));
2857 else
2858 draw_rect_edge( hdc, &rect, EDGE_RAISED, BF_RECT, 1 );
2860 if (menu)
2862 TRACE( "hmenu %p Style %08x\n", hmenu, menu->dwStyle );
2863 /* draw menu items */
2864 if (menu->nItems)
2866 struct menu_item *item;
2867 UINT u;
2869 item = menu->items;
2870 for (u = menu->nItems; u > 0; u--, item++)
2871 draw_menu_item( hwnd, menu, menu->hwndOwner, hdc,
2872 item, FALSE, ODA_DRAWENTIRE );
2874 if (menu->bScrolling) draw_scroll_arrows( menu, hdc );
2877 else
2879 NtGdiSelectBrush( hdc, prev_hrush );
2884 LRESULT popup_menu_window_proc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam )
2886 TRACE( "hwnd=%p msg=0x%04x wp=0x%04lx lp=0x%08lx\n", hwnd, message, (long)wparam, lparam );
2888 switch(message)
2890 case WM_CREATE:
2892 CREATESTRUCTW *cs = (CREATESTRUCTW *)lparam;
2893 NtUserSetWindowLongPtr( hwnd, 0, (LONG_PTR)cs->lpCreateParams, FALSE );
2894 return 0;
2897 case WM_MOUSEACTIVATE: /* We don't want to be activated */
2898 return MA_NOACTIVATE;
2900 case WM_PAINT:
2902 PAINTSTRUCT ps;
2903 NtUserBeginPaint( hwnd, &ps );
2904 draw_popup_menu( hwnd, ps.hdc, (HMENU)get_window_long_ptr( hwnd, 0, FALSE ));
2905 NtUserEndPaint( hwnd, &ps );
2906 return 0;
2909 case WM_PRINTCLIENT:
2911 draw_popup_menu( hwnd, (HDC)wparam, (HMENU)get_window_long_ptr( hwnd, 0, FALSE ));
2912 return 0;
2915 case WM_ERASEBKGND:
2916 return 1;
2918 case WM_DESTROY:
2919 /* zero out global pointer in case resident popup window was destroyed. */
2920 if (hwnd == top_popup)
2922 top_popup = 0;
2923 top_popup_hmenu = NULL;
2925 break;
2927 case WM_SHOWWINDOW:
2928 if (wparam)
2930 if (!get_window_long_ptr( hwnd, 0, FALSE )) ERR( "no menu to display\n" );
2932 else
2933 NtUserSetWindowLongPtr( hwnd, 0, 0, FALSE );
2934 break;
2936 case MN_GETHMENU:
2937 return get_window_long_ptr( hwnd, 0, FALSE );
2939 default:
2940 return default_window_proc( hwnd, message, wparam, lparam, FALSE );
2942 return 0;
2945 HWND is_menu_active(void)
2947 return top_popup;
2950 /* Calculate the size of a popup menu */
2951 static void calc_popup_menu_size( struct menu *menu, UINT max_height )
2953 BOOL textandbmp = FALSE, multi_col = FALSE;
2954 int org_x, org_y, max_tab, max_tab_width;
2955 struct menu_item *item;
2956 UINT start, i;
2957 HDC hdc;
2959 menu->Width = menu->Height = 0;
2960 SetRectEmpty( &menu->items_rect );
2962 if (menu->nItems == 0) return;
2963 hdc = NtUserGetDC( 0 );
2965 NtGdiSelectFont( hdc, get_menu_font( FALSE ));
2967 start = 0;
2968 menu->textOffset = 0;
2970 while (start < menu->nItems)
2972 item = &menu->items[start];
2973 org_x = menu->items_rect.right;
2974 if (item->fType & (MF_MENUBREAK | MF_MENUBARBREAK))
2975 org_x += MENU_COL_SPACE;
2976 org_y = menu->items_rect.top;
2978 max_tab = max_tab_width = 0;
2979 /* Parse items until column break or end of menu */
2980 for (i = start; i < menu->nItems; i++, item++)
2982 if (item->fType & (MF_MENUBREAK | MF_MENUBARBREAK))
2984 multi_col = TRUE;
2985 if (i != start) break;
2988 calc_menu_item_size( hdc, item, menu->hwndOwner, org_x, org_y, FALSE, menu );
2989 menu->items_rect.right = max( menu->items_rect.right, item->rect.right );
2990 org_y = item->rect.bottom;
2991 if (IS_STRING_ITEM( item->fType ) && item->xTab)
2993 max_tab = max( max_tab, item->xTab );
2994 max_tab_width = max( max_tab_width, item->rect.right-item->xTab );
2996 if (item->text && item->hbmpItem) textandbmp = TRUE;
2999 /* Finish the column (set all items to the largest width found) */
3000 menu->items_rect.right = max( menu->items_rect.right, max_tab + max_tab_width );
3001 for (item = &menu->items[start]; start < i; start++, item++)
3003 item->rect.right = menu->items_rect.right;
3004 if (IS_STRING_ITEM( item->fType ) && item->xTab)
3005 item->xTab = max_tab;
3007 menu->items_rect.bottom = max( menu->items_rect.bottom, org_y );
3010 /* If none of the items have both text and bitmap then
3011 * the text and bitmaps are all aligned on the left. If there is at
3012 * least one item with both text and bitmap then bitmaps are
3013 * on the left and texts left aligned with the right hand side
3014 * of the bitmaps */
3015 if (!textandbmp) menu->textOffset = 0;
3017 menu->nTotalHeight = menu->items_rect.bottom;
3019 /* space for the border */
3020 OffsetRect( &menu->items_rect, MENU_MARGIN, MENU_MARGIN );
3021 menu->Height = menu->items_rect.bottom + MENU_MARGIN;
3022 menu->Width = menu->items_rect.right + MENU_MARGIN;
3024 /* Adjust popup height if it exceeds maximum */
3025 if (menu->Height >= max_height)
3027 menu->Height = max_height;
3028 menu->bScrolling = !multi_col;
3029 /* When the scroll arrows are present, don't add the top/bottom margin as well */
3030 if (menu->bScrolling)
3032 menu->items_rect.top = get_scroll_arrow_height( menu );
3033 menu->items_rect.bottom = menu->Height - get_scroll_arrow_height( menu );
3036 else
3038 menu->bScrolling = FALSE;
3041 NtUserReleaseDC( 0, hdc );
3044 static BOOL show_popup( HWND owner, HMENU hmenu, UINT id, UINT flags,
3045 int x, int y, INT xanchor, INT yanchor )
3047 struct menu *menu;
3048 HMONITOR monitor;
3049 MONITORINFO info;
3050 UINT max_height;
3051 POINT pt;
3053 TRACE( "owner=%p hmenu=%p id=0x%04x x=0x%04x y=0x%04x xa=0x%04x ya=0x%04x\n",
3054 owner, hmenu, id, x, y, xanchor, yanchor );
3056 if (!(menu = unsafe_menu_ptr( hmenu ))) return FALSE;
3057 if (menu->FocusedItem != NO_SELECTED_ITEM)
3059 menu->items[menu->FocusedItem].fState &= ~(MF_HILITE|MF_MOUSESELECT);
3060 menu->FocusedItem = NO_SELECTED_ITEM;
3063 menu->nScrollPos = 0;
3065 /* FIXME: should use item rect */
3066 pt.x = x;
3067 pt.y = y;
3068 monitor = monitor_from_point( pt, MONITOR_DEFAULTTONEAREST, get_thread_dpi() );
3069 info.cbSize = sizeof(info);
3070 get_monitor_info( monitor, &info );
3072 max_height = info.rcWork.bottom - info.rcWork.top;
3073 if (menu->cyMax) max_height = min( max_height, menu->cyMax );
3074 calc_popup_menu_size( menu, max_height );
3076 /* adjust popup menu pos so that it fits within the desktop */
3077 if (flags & TPM_LAYOUTRTL) flags ^= TPM_RIGHTALIGN;
3079 if (flags & TPM_RIGHTALIGN) x -= menu->Width;
3080 if (flags & TPM_CENTERALIGN) x -= menu->Width / 2;
3082 if (flags & TPM_BOTTOMALIGN) y -= menu->Height;
3083 if (flags & TPM_VCENTERALIGN) y -= menu->Height / 2;
3085 if (x + menu->Width > info.rcWork.right)
3087 if (xanchor && x >= menu->Width - xanchor) x -= menu->Width - xanchor;
3088 if (x + menu->Width > info.rcWork.right) x = info.rcWork.right - menu->Width;
3090 if (x < info.rcWork.left) x = info.rcWork.left;
3092 if (y + menu->Height > info.rcWork.bottom)
3094 if (yanchor && y >= menu->Height + yanchor) y -= menu->Height + yanchor;
3095 if (y + menu->Height > info.rcWork.bottom) y = info.rcWork.bottom - menu->Height;
3097 if (y < info.rcWork.top) y = info.rcWork.top;
3099 if (!top_popup)
3101 top_popup = menu->hWnd;
3102 top_popup_hmenu = hmenu;
3105 /* Display the window */
3106 NtUserSetWindowPos( menu->hWnd, HWND_TOPMOST, x, y, menu->Width, menu->Height,
3107 SWP_SHOWWINDOW | SWP_NOACTIVATE );
3108 NtUserRedrawWindow( menu->hWnd, NULL, 0, RDW_UPDATENOW | RDW_ALLCHILDREN );
3109 return TRUE;
3112 static void ensure_menu_item_visible( struct menu *menu, UINT index, HDC hdc )
3114 if (menu->bScrolling)
3116 struct menu_item *item = &menu->items[index];
3117 UINT prev_pos = menu->nScrollPos;
3118 const RECT *rc = &menu->items_rect;
3119 UINT scroll_height = rc->bottom - rc->top;
3121 if (item->rect.bottom > menu->nScrollPos + scroll_height)
3123 menu->nScrollPos = item->rect.bottom - scroll_height;
3124 NtUserScrollWindowEx( menu->hWnd, 0, prev_pos - menu->nScrollPos, rc, rc, 0, NULL,
3125 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN | SW_NODCCACHE );
3127 else if (item->rect.top < menu->nScrollPos)
3129 menu->nScrollPos = item->rect.top;
3130 NtUserScrollWindowEx( menu->hWnd, 0, prev_pos - menu->nScrollPos, rc, rc, 0, NULL,
3131 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN | SW_NODCCACHE );
3134 /* Invalidate the scroll arrows if necessary */
3135 if (prev_pos != menu->nScrollPos)
3137 RECT arrow_rect = menu->items_rect;
3138 if (prev_pos == 0 || menu->nScrollPos == 0)
3140 arrow_rect.top = 0;
3141 arrow_rect.bottom = menu->items_rect.top;
3142 NtUserInvalidateRect( menu->hWnd, &arrow_rect, FALSE );
3144 if (prev_pos + scroll_height == menu->nTotalHeight ||
3145 menu->nScrollPos + scroll_height == menu->nTotalHeight)
3147 arrow_rect.top = menu->items_rect.bottom;
3148 arrow_rect.bottom = menu->Height;
3149 NtUserInvalidateRect( menu->hWnd, &arrow_rect, FALSE );
3155 static void select_item( HWND owner, HMENU hmenu, UINT index, BOOL send_select, HMENU topmenu )
3157 struct menu *menu;
3158 HDC hdc;
3160 TRACE( "owner %p menu %p index 0x%04x select 0x%04x\n", owner, hmenu, index, send_select );
3162 menu = unsafe_menu_ptr( hmenu );
3163 if (!menu || !menu->nItems || !menu->hWnd) return;
3165 if (menu->FocusedItem == index) return;
3166 if (menu->wFlags & MF_POPUP) hdc = NtUserGetDC( menu->hWnd );
3167 else hdc = NtUserGetDCEx( menu->hWnd, 0, DCX_CACHE | DCX_WINDOW);
3168 if (!top_popup)
3170 top_popup = menu->hWnd;
3171 top_popup_hmenu = hmenu;
3174 NtGdiSelectFont( hdc, get_menu_font( FALSE ));
3176 /* Clear previous highlighted item */
3177 if (menu->FocusedItem != NO_SELECTED_ITEM)
3179 menu->items[menu->FocusedItem].fState &= ~(MF_HILITE|MF_MOUSESELECT);
3180 draw_menu_item( menu->hWnd, menu, owner, hdc, &menu->items[menu->FocusedItem],
3181 !(menu->wFlags & MF_POPUP), ODA_SELECT );
3184 /* Highlight new item (if any) */
3185 menu->FocusedItem = index;
3186 if (menu->FocusedItem != NO_SELECTED_ITEM)
3188 if (!(menu->items[index].fType & MF_SEPARATOR))
3190 menu->items[index].fState |= MF_HILITE;
3191 ensure_menu_item_visible( menu, index, hdc );
3192 draw_menu_item( menu->hWnd, menu, owner, hdc, &menu->items[index],
3193 !(menu->wFlags & MF_POPUP), ODA_SELECT );
3195 if (send_select)
3197 struct menu_item *ip = &menu->items[menu->FocusedItem];
3198 send_message( owner, WM_MENUSELECT,
3199 MAKEWPARAM( ip->fType & MF_POPUP ? index: ip->wID,
3200 ip->fType | ip->fState | (menu->wFlags & MF_SYSMENU) ),
3201 (LPARAM)hmenu );
3204 else if (send_select)
3206 if (topmenu)
3208 int pos = find_submenu( &topmenu, hmenu );
3209 if (pos != NO_SELECTED_ITEM)
3211 struct menu *ptm = unsafe_menu_ptr( topmenu );
3212 struct menu_item *ip = &ptm->items[pos];
3213 send_message( owner, WM_MENUSELECT,
3214 MAKEWPARAM( pos, ip->fType | ip->fState | (ptm->wFlags & MF_SYSMENU) ),
3215 (LPARAM)topmenu );
3219 NtUserReleaseDC( menu->hWnd, hdc );
3222 /***********************************************************************
3223 * move_selection
3225 * Moves currently selected item according to the offset parameter.
3226 * If there is no selection then it should select the last item if
3227 * offset is ITEM_PREV or the first item if offset is ITEM_NEXT.
3229 static void move_selection( HWND owner, HMENU hmenu, INT offset )
3231 struct menu *menu;
3232 int i;
3234 TRACE( "hwnd %p hmenu %p off 0x%04x\n", owner, hmenu, offset );
3236 menu = unsafe_menu_ptr( hmenu );
3237 if (!menu || !menu->items) return;
3239 if (menu->FocusedItem != NO_SELECTED_ITEM)
3241 if (menu->nItems == 1) return;
3242 for (i = menu->FocusedItem + offset; i >= 0 && i < menu->nItems; i += offset)
3244 if (menu->items[i].fType & MF_SEPARATOR) continue;
3245 select_item( owner, hmenu, i, TRUE, 0 );
3246 return;
3250 for (i = (offset > 0) ? 0 : menu->nItems - 1; i >= 0 && i < menu->nItems; i += offset)
3252 if (menu->items[i].fType & MF_SEPARATOR) continue;
3253 select_item( owner, hmenu, i, TRUE, 0 );
3254 return;
3258 static void hide_sub_popups( HWND owner, HMENU hmenu, BOOL send_select, UINT flags )
3260 struct menu *menu = unsafe_menu_ptr( hmenu );
3262 TRACE( "owner=%p hmenu=%p 0x%04x\n", owner, hmenu, send_select );
3264 if (menu && top_popup)
3266 struct menu *submenu;
3267 struct menu_item *item;
3268 HMENU hsubmenu;
3270 if (menu->FocusedItem == NO_SELECTED_ITEM) return;
3272 item = &menu->items[menu->FocusedItem];
3273 if (!(item->fType & MF_POPUP) || !(item->fState & MF_MOUSESELECT)) return;
3274 item->fState &= ~MF_MOUSESELECT;
3275 hsubmenu = item->hSubMenu;
3277 if (!(submenu = unsafe_menu_ptr( hsubmenu ))) return;
3278 hide_sub_popups( owner, hsubmenu, FALSE, flags );
3279 select_item( owner, hsubmenu, NO_SELECTED_ITEM, send_select, 0 );
3280 NtUserDestroyWindow( submenu->hWnd );
3281 submenu->hWnd = 0;
3283 if (!(flags & TPM_NONOTIFY))
3284 send_message( owner, WM_UNINITMENUPOPUP, (WPARAM)hsubmenu,
3285 MAKELPARAM( 0, IS_SYSTEM_MENU( submenu )));
3289 static void init_sys_menu_popup( HMENU hmenu, DWORD style, DWORD class_style )
3291 BOOL gray;
3293 /* Grey the appropriate items in System menu */
3294 gray = !(style & WS_THICKFRAME) || (style & (WS_MAXIMIZE | WS_MINIMIZE));
3295 NtUserEnableMenuItem( hmenu, SC_SIZE, gray ? MF_GRAYED : MF_ENABLED );
3296 gray = ((style & WS_MAXIMIZE) != 0);
3297 NtUserEnableMenuItem( hmenu, SC_MOVE, gray ? MF_GRAYED : MF_ENABLED );
3298 gray = !(style & WS_MINIMIZEBOX) || (style & WS_MINIMIZE);
3299 NtUserEnableMenuItem( hmenu, SC_MINIMIZE, gray ? MF_GRAYED : MF_ENABLED );
3300 gray = !(style & WS_MAXIMIZEBOX) || (style & WS_MAXIMIZE);
3301 NtUserEnableMenuItem( hmenu, SC_MAXIMIZE, gray ? MF_GRAYED : MF_ENABLED );
3302 gray = !(style & (WS_MAXIMIZE | WS_MINIMIZE));
3303 NtUserEnableMenuItem( hmenu, SC_RESTORE, gray ? MF_GRAYED : MF_ENABLED );
3304 gray = (class_style & CS_NOCLOSE) != 0;
3306 /* The menu item must keep its state if it's disabled */
3307 if (gray) NtUserEnableMenuItem( hmenu, SC_CLOSE, MF_GRAYED );
3310 static BOOL init_popup( HWND owner, HMENU hmenu, UINT flags )
3312 UNICODE_STRING class_name = { .Buffer = MAKEINTRESOURCEW( POPUPMENU_CLASS_ATOM ) };
3313 DWORD ex_style = 0;
3314 struct menu *menu;
3316 TRACE( "owner %p hmenu %p\n", owner, hmenu );
3318 if (!(menu = unsafe_menu_ptr( hmenu ))) return FALSE;
3320 /* store the owner for DrawItem */
3321 if (!is_window( owner ))
3323 RtlSetLastWin32Error( ERROR_INVALID_WINDOW_HANDLE );
3324 return FALSE;
3326 menu->hwndOwner = owner;
3328 if (flags & TPM_LAYOUTRTL) ex_style = WS_EX_LAYOUTRTL;
3330 /* NOTE: In Windows, top menu popup is not owned. */
3331 menu->hWnd = NtUserCreateWindowEx( ex_style, &class_name, &class_name, NULL,
3332 WS_POPUP, 0, 0, 0, 0, owner, 0,
3333 (HINSTANCE)get_window_long_ptr( owner, GWLP_HINSTANCE, FALSE ),
3334 (void *)hmenu, 0, NULL, 0, FALSE );
3335 return !!menu->hWnd;
3339 /***********************************************************************
3340 * show_sub_popup
3342 * Display the sub-menu of the selected item of this menu.
3343 * Return the handle of the submenu, or hmenu if no submenu to display.
3345 static HMENU show_sub_popup( HWND owner, HMENU hmenu, BOOL select_first, UINT flags )
3347 struct menu *menu;
3348 struct menu_item *item;
3349 RECT rect;
3350 HDC hdc;
3352 TRACE( "owner %p hmenu %p 0x%04x\n", owner, hmenu, select_first );
3354 if (!(menu = unsafe_menu_ptr( hmenu ))) return hmenu;
3355 if (menu->FocusedItem == NO_SELECTED_ITEM) return hmenu;
3357 item = &menu->items[menu->FocusedItem];
3358 if (!(item->fType & MF_POPUP) || (item->fState & (MF_GRAYED | MF_DISABLED)))
3359 return hmenu;
3361 /* Send WM_INITMENUPOPUP message only if TPM_NONOTIFY flag is not specified */
3362 if (!(flags & TPM_NONOTIFY))
3363 send_message( owner, WM_INITMENUPOPUP, (WPARAM)item->hSubMenu,
3364 MAKELPARAM( menu->FocusedItem, IS_SYSTEM_MENU( menu )));
3366 item = &menu->items[menu->FocusedItem];
3367 rect = item->rect;
3369 /* correct item if modified as a reaction to WM_INITMENUPOPUP message */
3370 if (!(item->fState & MF_HILITE))
3372 if (menu->wFlags & MF_POPUP) hdc = NtUserGetDC( menu->hWnd );
3373 else hdc = NtUserGetDCEx( menu->hWnd, 0, DCX_CACHE | DCX_WINDOW );
3375 NtGdiSelectFont( hdc, get_menu_font( FALSE ));
3377 item->fState |= MF_HILITE;
3378 draw_menu_item( menu->hWnd, menu, owner, hdc, item, !(menu->wFlags & MF_POPUP), ODA_DRAWENTIRE );
3379 NtUserReleaseDC( menu->hWnd, hdc );
3381 if (!item->rect.top && !item->rect.left && !item->rect.bottom && !item->rect.right)
3382 item->rect = rect;
3384 item->fState |= MF_MOUSESELECT;
3386 if (IS_SYSTEM_MENU( menu ))
3388 init_sys_menu_popup( item->hSubMenu,
3389 get_window_long( menu->hWnd, GWL_STYLE ),
3390 get_class_long( menu->hWnd, GCL_STYLE, FALSE ));
3392 get_sys_popup_pos( menu->hWnd, &rect );
3393 if (flags & TPM_LAYOUTRTL) rect.left = rect.right;
3394 rect.top = rect.bottom;
3395 rect.right = get_system_metrics( SM_CXSIZE );
3396 rect.bottom = get_system_metrics( SM_CYSIZE );
3398 else
3400 RECT item_rect = item->rect;
3402 adjust_menu_item_rect( menu, &item_rect );
3403 get_window_rect( menu->hWnd, &rect, get_thread_dpi() );
3405 if (menu->wFlags & MF_POPUP)
3407 /* The first item in the popup menu has to be at the
3408 same y position as the focused menu item */
3409 if (flags & TPM_LAYOUTRTL)
3410 rect.left += get_system_metrics( SM_CXBORDER );
3411 else
3412 rect.left += item_rect.right - get_system_metrics( SM_CXBORDER );
3413 rect.top += item_rect.top - MENU_MARGIN;
3414 rect.right = item_rect.left - item_rect.right + get_system_metrics( SM_CXBORDER );
3415 rect.bottom = item_rect.top - item_rect.bottom - 2 * MENU_MARGIN;
3417 else
3419 if (flags & TPM_LAYOUTRTL)
3420 rect.left = rect.right - item_rect.left;
3421 else
3422 rect.left += item_rect.left;
3423 rect.top += item_rect.bottom;
3424 rect.right = item_rect.right - item_rect.left;
3425 rect.bottom = item_rect.bottom - item_rect.top;
3429 /* use default alignment for submenus */
3430 flags &= ~(TPM_CENTERALIGN | TPM_RIGHTALIGN | TPM_VCENTERALIGN | TPM_BOTTOMALIGN);
3431 init_popup( owner, item->hSubMenu, flags );
3432 show_popup( owner, item->hSubMenu, menu->FocusedItem, flags,
3433 rect.left, rect.top, rect.right, rect.bottom );
3434 if (select_first) move_selection( owner, item->hSubMenu, ITEM_NEXT );
3435 return item->hSubMenu;
3438 /***********************************************************************
3439 * exec_focused_item
3441 * Execute a menu item (for instance when user pressed Enter).
3442 * Return the wID of the executed item. Otherwise, -1 indicating
3443 * that no menu item was executed, -2 if a popup is shown;
3444 * Have to receive the flags for the NtUserTrackPopupMenuEx options to avoid
3445 * sending unwanted message.
3447 static INT exec_focused_item( MTRACKER *pmt, HMENU handle, UINT flags )
3449 struct menu_item *item;
3450 struct menu *menu = unsafe_menu_ptr( handle );
3452 TRACE( "%p hmenu=%p\n", pmt, handle );
3454 if (!menu || !menu->nItems || menu->FocusedItem == NO_SELECTED_ITEM) return -1;
3455 item = &menu->items[menu->FocusedItem];
3457 TRACE( "handle %p ID %08lx submenu %p type %04x\n", handle, (long)item->wID,
3458 item->hSubMenu, item->fType );
3460 if ((item->fType & MF_POPUP))
3462 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, handle, TRUE, flags );
3463 return -2;
3466 if ((item->fState & (MF_GRAYED | MF_DISABLED)) || (item->fType & MF_SEPARATOR))
3467 return -1;
3469 /* If TPM_RETURNCMD is set you return the id, but
3470 do not send a message to the owner */
3471 if (!(flags & TPM_RETURNCMD))
3473 if (menu->wFlags & MF_SYSMENU)
3474 NtUserPostMessage( pmt->hOwnerWnd, WM_SYSCOMMAND, item->wID,
3475 MAKELPARAM( (INT16)pmt->pt.x, (INT16)pmt->pt.y ));
3476 else
3478 struct menu *topmenu = unsafe_menu_ptr( pmt->hTopMenu );
3479 DWORD style = menu->dwStyle | (topmenu ? topmenu->dwStyle : 0);
3481 if (style & MNS_NOTIFYBYPOS)
3482 NtUserPostMessage( pmt->hOwnerWnd, WM_MENUCOMMAND, menu->FocusedItem,
3483 (LPARAM)handle);
3484 else
3485 NtUserPostMessage( pmt->hOwnerWnd, WM_COMMAND, item->wID, 0 );
3489 return item->wID;
3492 /***********************************************************************
3493 * switch_tracking
3495 * Helper function for menu navigation routines.
3497 static void switch_tracking( MTRACKER *pmt, HMENU pt_menu, UINT id, UINT flags )
3499 struct menu *ptmenu = unsafe_menu_ptr( pt_menu );
3500 struct menu *topmenu = unsafe_menu_ptr( pmt->hTopMenu );
3502 TRACE( "%p hmenu=%p 0x%04x\n", pmt, pt_menu, id );
3504 if (pmt->hTopMenu != pt_menu && !((ptmenu->wFlags | topmenu->wFlags) & MF_POPUP))
3506 /* both are top level menus (system and menu-bar) */
3507 hide_sub_popups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, flags );
3508 select_item( pmt->hOwnerWnd, pmt->hTopMenu, NO_SELECTED_ITEM, FALSE, 0 );
3509 pmt->hTopMenu = pt_menu;
3511 else hide_sub_popups( pmt->hOwnerWnd, pt_menu, FALSE, flags );
3512 select_item( pmt->hOwnerWnd, pt_menu, id, TRUE, 0 );
3515 /***********************************************************************
3516 * menu_button_down
3518 * Return TRUE if we can go on with menu tracking.
3520 static BOOL menu_button_down( MTRACKER *pmt, UINT message, HMENU pt_menu, UINT flags )
3522 TRACE( "%p pt_menu=%p\n", pmt, pt_menu );
3524 if (pt_menu)
3526 struct menu *ptmenu = unsafe_menu_ptr( pt_menu );
3527 enum hittest ht = ht_item;
3528 UINT pos;
3530 if (IS_SYSTEM_MENU( ptmenu ))
3532 if (message == WM_LBUTTONDBLCLK) return FALSE;
3533 pos = 0;
3535 else
3536 ht = find_item_by_coords( ptmenu, pmt->pt, &pos );
3538 if (pos != NO_SELECTED_ITEM)
3540 if (ptmenu->FocusedItem != pos)
3541 switch_tracking( pmt, pt_menu, pos, flags );
3543 /* If the popup menu is not already "popped" */
3544 if (!(ptmenu->items[pos].fState & MF_MOUSESELECT))
3545 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pt_menu, FALSE, flags );
3548 /* A click on an item or anywhere on a popup keeps tracking going */
3549 if (ht == ht_item || ((ptmenu->wFlags & MF_POPUP) && ht != ht_nowhere))
3550 return TRUE;
3552 return FALSE;
3555 /***********************************************************************
3556 * menu_button_up
3558 * Return the value of exec_focused_item if
3559 * the selected item was not a popup. Else open the popup.
3560 * A -1 return value indicates that we go on with menu tracking.
3563 static INT menu_button_up( MTRACKER *pmt, HMENU pt_menu, UINT flags )
3565 TRACE( "%p hmenu=%p\n", pmt, pt_menu );
3567 if (pt_menu)
3569 struct menu *ptmenu = unsafe_menu_ptr( pt_menu );
3570 UINT pos;
3572 if (IS_SYSTEM_MENU( ptmenu ))
3573 pos = 0;
3574 else if (find_item_by_coords( ptmenu, pmt->pt, &pos ) != ht_item)
3575 pos = NO_SELECTED_ITEM;
3577 if (pos != NO_SELECTED_ITEM && (ptmenu->FocusedItem == pos))
3579 TRACE( "%s\n", debugstr_menuitem( &ptmenu->items[pos] ));
3581 if (!(ptmenu->items[pos].fType & MF_POPUP))
3583 INT executedMenuId = exec_focused_item( pmt, pt_menu, flags );
3584 if (executedMenuId == -1 || executedMenuId == -2) return -1;
3585 return executedMenuId;
3588 /* If we are dealing with the menu bar and this is a click on an
3589 * already "popped" item: Stop the menu tracking and close the
3590 * opened submenus */
3591 if(((pmt->hTopMenu == pt_menu) || IS_SYSTEM_MENU( ptmenu )) &&
3592 (pmt->trackFlags & TF_RCVD_BTN_UP))
3593 return 0;
3596 if (get_menu( ptmenu->hWnd ) == pt_menu || IS_SYSTEM_MENU( ptmenu ))
3598 if (pos == NO_SELECTED_ITEM) return 0;
3599 pmt->trackFlags |= TF_RCVD_BTN_UP;
3602 return -1;
3605 /***********************************************************************
3606 * end_menu
3608 * Call NtUserEndMenu() if the hwnd parameter belongs to the menu owner.
3610 void end_menu( HWND hwnd )
3612 struct menu *menu;
3613 BOOL call_end = FALSE;
3614 if (top_popup_hmenu && (menu = grab_menu_ptr( top_popup_hmenu )))
3616 call_end = hwnd == menu->hWnd || hwnd == menu->hwndOwner;
3617 release_menu_ptr( menu );
3619 if (call_end) NtUserEndMenu();
3622 /***********************************************************************
3623 * menu_mouse_move
3625 * Return TRUE if we can go on with menu tracking.
3627 static BOOL menu_mouse_move( MTRACKER* pmt, HMENU pt_menu, UINT flags )
3629 UINT id = NO_SELECTED_ITEM;
3630 struct menu *ptmenu = NULL;
3632 if (pt_menu)
3634 ptmenu = unsafe_menu_ptr( pt_menu );
3635 if (IS_SYSTEM_MENU( ptmenu ))
3636 id = 0;
3637 else if (find_item_by_coords( ptmenu, pmt->pt, &id ) != ht_item)
3638 id = NO_SELECTED_ITEM;
3641 if (id == NO_SELECTED_ITEM)
3643 select_item( pmt->hOwnerWnd, pmt->hCurrentMenu, NO_SELECTED_ITEM, TRUE, pmt->hTopMenu );
3645 else if (ptmenu->FocusedItem != id)
3647 switch_tracking( pmt, pt_menu, id, flags );
3648 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pt_menu, FALSE, flags );
3650 return TRUE;
3653 static LRESULT do_next_menu( MTRACKER *pmt, UINT vk, UINT flags )
3655 struct menu *menu = unsafe_menu_ptr( pmt->hTopMenu );
3656 BOOL at_end = FALSE;
3658 if (vk == VK_LEFT && menu->FocusedItem == 0)
3660 /* When skipping left, we need to do something special after the first menu */
3661 at_end = TRUE;
3663 else if (vk == VK_RIGHT && !IS_SYSTEM_MENU( menu ))
3665 /* When skipping right, for the non-system menu, we need to
3666 * handle the last non-special menu item (ie skip any window
3667 * icons such as MDI maximize, restore or close) */
3668 UINT i = menu->FocusedItem + 1;
3669 while (i < menu->nItems)
3671 if (menu->items[i].wID < SC_SIZE || menu->items[i].wID > SC_RESTORE) break;
3672 i++;
3674 if (i == menu->nItems) at_end = TRUE;
3676 else if (vk == VK_RIGHT && IS_SYSTEM_MENU( menu ))
3678 /* When skipping right, we need to cater for the system menu */
3679 if (menu->FocusedItem == menu->nItems - 1) at_end = TRUE;
3682 if (at_end)
3684 MDINEXTMENU next_menu;
3685 HMENU new_menu;
3686 HWND new_hwnd;
3687 UINT id = 0;
3689 next_menu.hmenuIn = (IS_SYSTEM_MENU( menu )) ? get_sub_menu( pmt->hTopMenu, 0 ) : pmt->hTopMenu;
3690 next_menu.hmenuNext = 0;
3691 next_menu.hwndNext = 0;
3692 send_message( pmt->hOwnerWnd, WM_NEXTMENU, vk, (LPARAM)&next_menu );
3694 TRACE( "%p [%p] -> %p [%p]\n", pmt->hCurrentMenu, pmt->hOwnerWnd, next_menu.hmenuNext,
3695 next_menu.hwndNext );
3697 if (!next_menu.hmenuNext || !next_menu.hwndNext)
3699 DWORD style = get_window_long( pmt->hOwnerWnd, GWL_STYLE );
3700 new_hwnd = pmt->hOwnerWnd;
3701 if (IS_SYSTEM_MENU( menu ))
3703 /* switch to the menu bar */
3704 if ((style & WS_CHILD) || !(new_menu = get_menu( new_hwnd ))) return FALSE;
3706 if (vk == VK_LEFT)
3708 menu = unsafe_menu_ptr( new_menu );
3709 id = menu->nItems - 1;
3711 /* Skip backwards over any system predefined icons,
3712 * eg. MDI close, restore etc icons */
3713 while (id > 0 &&
3714 menu->items[id].wID >= SC_SIZE && menu->items[id].wID <= SC_RESTORE)
3715 id--;
3718 else if (style & WS_SYSMENU)
3720 /* switch to the system menu */
3721 new_menu = get_win_sys_menu( new_hwnd );
3723 else return FALSE;
3725 else /* application returned a new menu to switch to */
3727 new_menu = next_menu.hmenuNext;
3728 new_hwnd = get_full_window_handle( next_menu.hwndNext );
3730 if (is_menu( new_menu ) && is_window( new_hwnd ))
3732 DWORD style = get_window_long( new_hwnd, GWL_STYLE );
3734 if (style & WS_SYSMENU && get_sub_menu(get_win_sys_menu( new_hwnd ), 0) == new_menu)
3736 /* get the real system menu */
3737 new_menu = get_win_sys_menu( new_hwnd );
3739 else if (style & WS_CHILD || get_menu( new_hwnd ) != new_menu )
3741 /* FIXME: what should we do? */
3742 TRACE( " -- got confused.\n" );
3743 return FALSE;
3746 else return FALSE;
3749 if (new_menu != pmt->hTopMenu)
3751 select_item( pmt->hOwnerWnd, pmt->hTopMenu, NO_SELECTED_ITEM, FALSE, 0 );
3752 if (pmt->hCurrentMenu != pmt->hTopMenu)
3753 hide_sub_popups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, flags );
3756 if (new_hwnd != pmt->hOwnerWnd)
3758 pmt->hOwnerWnd = new_hwnd;
3759 set_capture_window( pmt->hOwnerWnd, GUI_INMENUMODE, NULL );
3762 pmt->hTopMenu = pmt->hCurrentMenu = new_menu; /* all subpopups are hidden */
3763 select_item( pmt->hOwnerWnd, pmt->hTopMenu, id, TRUE, 0 );
3764 return TRUE;
3767 return FALSE;
3770 /***********************************************************************
3771 * get_sub_popup
3773 * Return the handle of the selected sub-popup menu (if any).
3775 static HMENU get_sub_popup( HMENU hmenu )
3777 struct menu *menu;
3778 struct menu_item *item;
3780 menu = unsafe_menu_ptr( hmenu );
3782 if (!menu || menu->FocusedItem == NO_SELECTED_ITEM) return 0;
3784 item = &menu->items[menu->FocusedItem];
3785 if ((item->fType & MF_POPUP) && (item->fState & MF_MOUSESELECT))
3786 return item->hSubMenu;
3787 return 0;
3790 /***********************************************************************
3791 * menu_key_escape
3793 * Handle a VK_ESCAPE key event in a menu.
3795 static BOOL menu_key_escape( MTRACKER *pmt, UINT flags )
3797 BOOL ret = TRUE;
3799 if (pmt->hCurrentMenu != pmt->hTopMenu)
3801 struct menu *menu = unsafe_menu_ptr( pmt->hCurrentMenu );
3803 if (menu->wFlags & MF_POPUP)
3805 HMENU top, prev_menu;
3807 prev_menu = top = pmt->hTopMenu;
3809 /* close topmost popup */
3810 while (top != pmt->hCurrentMenu)
3812 prev_menu = top;
3813 top = get_sub_popup( prev_menu );
3816 hide_sub_popups( pmt->hOwnerWnd, prev_menu, TRUE, flags );
3817 pmt->hCurrentMenu = prev_menu;
3818 ret = FALSE;
3822 return ret;
3825 static UINT get_start_of_next_column( HMENU handle )
3827 struct menu *menu = unsafe_menu_ptr( handle );
3828 UINT i;
3830 if (!menu) return NO_SELECTED_ITEM;
3832 i = menu->FocusedItem + 1;
3833 if (i == NO_SELECTED_ITEM) return i;
3835 while (i < menu->nItems)
3837 if (menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK))
3838 return i;
3839 i++;
3842 return NO_SELECTED_ITEM;
3845 static UINT get_start_of_prev_column( HMENU handle )
3847 struct menu *menu = unsafe_menu_ptr( handle );
3848 UINT i;
3850 if (!menu) return NO_SELECTED_ITEM;
3852 if (menu->FocusedItem == 0 || menu->FocusedItem == NO_SELECTED_ITEM)
3853 return NO_SELECTED_ITEM;
3855 /* Find the start of the column */
3856 i = menu->FocusedItem;
3857 while (i != 0 && !(menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK))) i--;
3858 if (i == 0) return NO_SELECTED_ITEM;
3860 for (--i; i != 0; --i)
3862 if (menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK))
3863 break;
3866 TRACE( "ret %d.\n", i );
3867 return i;
3870 /***********************************************************************
3871 * suspend_popup
3873 * Avoid showing the popup if the next input message is going to hide it anyway.
3875 static BOOL suspend_popup( MTRACKER *pmt, UINT message )
3877 MSG msg;
3879 msg.hwnd = pmt->hOwnerWnd;
3880 NtUserPeekMessage( &msg, 0, message, message, PM_NOYIELD | PM_REMOVE );
3881 pmt->trackFlags |= TF_SKIPREMOVE;
3883 switch (message)
3885 case WM_KEYDOWN:
3886 NtUserPeekMessage( &msg, 0, 0, 0, PM_NOYIELD | PM_NOREMOVE );
3887 if (msg.message == WM_KEYUP || msg.message == WM_PAINT)
3889 NtUserPeekMessage( &msg, 0, 0, 0, PM_NOYIELD | PM_REMOVE );
3890 NtUserPeekMessage( &msg, 0, 0, 0, PM_NOYIELD | PM_NOREMOVE );
3891 if (msg.message == WM_KEYDOWN && (msg.wParam == VK_LEFT || msg.wParam == VK_RIGHT))
3893 pmt->trackFlags |= TF_SUSPENDPOPUP;
3894 return TRUE;
3897 break;
3900 /* failures go through this */
3901 pmt->trackFlags &= ~TF_SUSPENDPOPUP;
3902 return FALSE;
3905 static void menu_key_left( MTRACKER *pmt, UINT flags, UINT msg )
3907 struct menu *menu;
3908 HMENU tmp_menu, prev_menu;
3909 UINT prevcol;
3911 prev_menu = tmp_menu = pmt->hTopMenu;
3912 menu = unsafe_menu_ptr( tmp_menu );
3914 /* Try to move 1 column left (if possible) */
3915 if ((prevcol = get_start_of_prev_column( pmt->hCurrentMenu )) != NO_SELECTED_ITEM)
3917 select_item( pmt->hOwnerWnd, pmt->hCurrentMenu, prevcol, TRUE, 0 );
3918 return;
3921 /* close topmost popup */
3922 while (tmp_menu != pmt->hCurrentMenu)
3924 prev_menu = tmp_menu;
3925 tmp_menu = get_sub_popup( prev_menu );
3928 hide_sub_popups( pmt->hOwnerWnd, prev_menu, TRUE, flags );
3929 pmt->hCurrentMenu = prev_menu;
3931 if ((prev_menu == pmt->hTopMenu) && !(menu->wFlags & MF_POPUP))
3933 /* move menu bar selection if no more popups are left */
3934 if (!do_next_menu( pmt, VK_LEFT, flags ))
3935 move_selection( pmt->hOwnerWnd, pmt->hTopMenu, ITEM_PREV );
3937 if (prev_menu != tmp_menu || pmt->trackFlags & TF_SUSPENDPOPUP)
3939 /* A sublevel menu was displayed - display the next one
3940 * unless there is another displacement coming up */
3941 if (!suspend_popup( pmt, msg ))
3942 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pmt->hTopMenu, TRUE, flags );
3947 static void menu_right_key( MTRACKER *pmt, UINT flags, UINT msg )
3949 struct menu *menu = unsafe_menu_ptr( pmt->hTopMenu );
3950 HMENU tmp_menu;
3951 UINT nextcol;
3953 TRACE( "menu_right_key called, cur %p (%s), top %p (%s).\n",
3954 pmt->hCurrentMenu, debugstr_w(unsafe_menu_ptr( pmt->hCurrentMenu )->items[0].text ),
3955 pmt->hTopMenu, debugstr_w( menu->items[0].text ));
3957 if ((menu->wFlags & MF_POPUP) || (pmt->hCurrentMenu != pmt->hTopMenu))
3959 /* If already displaying a popup, try to display sub-popup */
3960 tmp_menu = pmt->hCurrentMenu;
3961 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, tmp_menu, TRUE, flags );
3963 /* if subpopup was displayed then we are done */
3964 if (tmp_menu != pmt->hCurrentMenu) return;
3967 /* Check to see if there's another column */
3968 if ((nextcol = get_start_of_next_column( pmt->hCurrentMenu )) != NO_SELECTED_ITEM)
3970 TRACE( "Going to %d.\n", nextcol );
3971 select_item( pmt->hOwnerWnd, pmt->hCurrentMenu, nextcol, TRUE, 0 );
3972 return;
3975 if (!(menu->wFlags & MF_POPUP)) /* menu bar tracking */
3977 if (pmt->hCurrentMenu != pmt->hTopMenu)
3979 hide_sub_popups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, flags );
3980 tmp_menu = pmt->hCurrentMenu = pmt->hTopMenu;
3982 else tmp_menu = 0;
3984 /* try to move to the next item */
3985 if (!do_next_menu( pmt, VK_RIGHT, flags ))
3986 move_selection( pmt->hOwnerWnd, pmt->hTopMenu, ITEM_NEXT );
3988 if (tmp_menu || pmt->trackFlags & TF_SUSPENDPOPUP)
3989 if (!suspend_popup( pmt, msg ))
3990 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pmt->hTopMenu, TRUE, flags );
3994 /***********************************************************************
3995 * menu_from_point
3997 * Walks menu chain trying to find a menu pt maps to.
3999 static HMENU menu_from_point( HMENU handle, POINT pt )
4001 struct menu *menu = unsafe_menu_ptr( handle );
4002 UINT item = menu->FocusedItem;
4003 HMENU ret = 0;
4005 /* try subpopup first (if any) */
4006 if (item != NO_SELECTED_ITEM && (menu->items[item].fType & MF_POPUP) &&
4007 (menu->items[item].fState & MF_MOUSESELECT))
4008 ret = menu_from_point( menu->items[item].hSubMenu, pt );
4010 if (!ret) /* check the current window (avoiding WM_HITTEST) */
4012 INT ht = handle_nc_hit_test( menu->hWnd, pt );
4013 if (menu->wFlags & MF_POPUP)
4015 if (ht != HTNOWHERE && ht != HTERROR) ret = handle;
4017 else if (ht == HTSYSMENU)
4018 ret = get_win_sys_menu( menu->hWnd );
4019 else if (ht == HTMENU)
4020 ret = get_menu( menu->hWnd );
4022 return ret;
4025 /***********************************************************************
4026 * find_item_by_key
4028 * Find the menu item selected by a key press.
4029 * Return item id, -1 if none, -2 if we should close the menu.
4031 static UINT find_item_by_key( HWND owner, HMENU hmenu, WCHAR key, BOOL force_menu_char )
4033 TRACE( "\tlooking for '%c' (0x%02x) in [%p]\n", (char)key, key, hmenu );
4035 if (!is_menu( hmenu )) hmenu = get_sub_menu( get_win_sys_menu( owner ), 0 );
4037 if (hmenu)
4039 struct menu *menu = unsafe_menu_ptr( hmenu );
4040 struct menu_item *item = menu->items;
4041 LRESULT menuchar;
4043 if (!force_menu_char)
4045 BOOL cjk = get_system_metrics( SM_DBCSENABLED );
4046 UINT i;
4048 for (i = 0; i < menu->nItems; i++, item++)
4050 if (item->text)
4052 const WCHAR *p = item->text - 2;
4055 const WCHAR *q = p + 2;
4056 p = wcschr( q, '&' );
4057 if (!p && cjk) p = wcschr( q, '\036' ); /* Japanese Win16 */
4059 while (p && p[1] == '&');
4060 if (p && !wcsnicmp( &p[1], &key, 1)) return i;
4064 menuchar = send_message( owner, WM_MENUCHAR,
4065 MAKEWPARAM( key, menu->wFlags ), (LPARAM)hmenu );
4066 if (HIWORD(menuchar) == MNC_EXECUTE) return LOWORD( menuchar );
4067 if (HIWORD(menuchar) == MNC_CLOSE) return (UINT)-2;
4069 return -1;
4072 static BOOL track_menu( HMENU hmenu, UINT flags, int x, int y, HWND hwnd, const RECT *rect )
4074 BOOL enter_idle_sent = FALSE;
4075 int executed_menu_id = -1;
4076 HWND capture_win;
4077 struct menu *menu;
4078 BOOL remove;
4079 MTRACKER mt;
4080 MSG msg;
4082 mt.trackFlags = 0;
4083 mt.hCurrentMenu = hmenu;
4084 mt.hTopMenu = hmenu;
4085 mt.hOwnerWnd = get_full_window_handle( hwnd );
4086 mt.pt.x = x;
4087 mt.pt.y = y;
4089 TRACE( "hmenu=%p flags=0x%08x (%d,%d) hwnd=%p %s\n",
4090 hmenu, flags, x, y, hwnd, wine_dbgstr_rect( rect ));
4092 if (!(menu = unsafe_menu_ptr( hmenu )))
4094 WARN( "Invalid menu handle %p\n", hmenu );
4095 RtlSetLastWin32Error( ERROR_INVALID_MENU_HANDLE );
4096 return FALSE;
4099 if (flags & TPM_BUTTONDOWN)
4101 /* Get the result in order to start the tracking or not */
4102 remove = menu_button_down( &mt, WM_LBUTTONDOWN, hmenu, flags );
4103 exit_menu = !remove;
4106 if (flags & TF_ENDMENU) exit_menu = TRUE;
4108 /* owner may not be visible when tracking a popup, so use the menu itself */
4109 capture_win = (flags & TPM_POPUPMENU) ? menu->hWnd : mt.hOwnerWnd;
4110 set_capture_window( capture_win, GUI_INMENUMODE, NULL );
4112 if ((flags & TPM_POPUPMENU) && menu->nItems == 0)
4113 return FALSE;
4115 while (!exit_menu)
4117 if (!(menu = unsafe_menu_ptr( mt.hCurrentMenu ))) break;
4119 /* we have to keep the message in the queue until it's
4120 * clear that menu loop is not over yet. */
4121 for (;;)
4123 if (NtUserPeekMessage( &msg, 0, 0, 0, PM_NOREMOVE ))
4125 if (!NtUserCallMsgFilter( &msg, MSGF_MENU )) break;
4126 /* remove the message from the queue */
4127 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
4129 else
4131 if (!enter_idle_sent)
4133 HWND win = (menu->wFlags & MF_POPUP) ? menu->hWnd : 0;
4134 enter_idle_sent = TRUE;
4135 send_message( mt.hOwnerWnd, WM_ENTERIDLE, MSGF_MENU, (LPARAM)win );
4137 NtUserMsgWaitForMultipleObjectsEx( 0, NULL, INFINITE, QS_ALLINPUT, 0 );
4141 /* check if NtUserEndMenu() tried to cancel us, by posting this message */
4142 if (msg.message == WM_CANCELMODE)
4144 exit_menu = TRUE;
4145 /* remove the message from the queue */
4146 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
4147 break;
4150 mt.pt = msg.pt;
4151 if (msg.hwnd == menu->hWnd || msg.message != WM_TIMER) enter_idle_sent = FALSE;
4153 remove = FALSE;
4154 if (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST)
4157 * Use the mouse coordinates in lParam instead of those in the MSG
4158 * struct to properly handle synthetic messages. They are already
4159 * in screen coordinates.
4161 mt.pt.x = (short)LOWORD( msg.lParam );
4162 mt.pt.y = (short)HIWORD( msg.lParam );
4164 /* Find a menu for this mouse event */
4165 hmenu = menu_from_point( mt.hTopMenu, mt.pt );
4167 switch (msg.message)
4169 /* no WM_NC... messages in captured state */
4170 case WM_RBUTTONDBLCLK:
4171 case WM_RBUTTONDOWN:
4172 if (!(flags & TPM_RIGHTBUTTON)) break;
4173 /* fall through */
4174 case WM_LBUTTONDBLCLK:
4175 case WM_LBUTTONDOWN:
4176 /* If the message belongs to the menu, removes it from the queue
4177 * Else, end menu tracking */
4178 remove = menu_button_down( &mt, msg.message, hmenu, flags );
4179 exit_menu = !remove;
4180 break;
4182 case WM_RBUTTONUP:
4183 if (!(flags & TPM_RIGHTBUTTON)) break;
4184 /* fall through */
4185 case WM_LBUTTONUP:
4186 /* Check if a menu was selected by the mouse */
4187 if (hmenu)
4189 executed_menu_id = menu_button_up( &mt, hmenu, flags);
4190 TRACE( "executed_menu_id %d\n", executed_menu_id );
4192 /* End the loop if executed_menu_id is an item ID
4193 * or if the job was done (executed_menu_id = 0). */
4194 exit_menu = remove = executed_menu_id != -1;
4196 else
4197 /* No menu was selected by the mouse. If the function was called by
4198 * NtUserTrackPopupMenuEx, continue with the menu tracking. */
4199 exit_menu = !(flags & TPM_POPUPMENU);
4201 break;
4203 case WM_MOUSEMOVE:
4204 /* the selected menu item must be changed every time the mouse moves. */
4205 if (hmenu) exit_menu |= !menu_mouse_move( &mt, hmenu, flags );
4206 break;
4209 else if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
4211 remove = TRUE; /* Keyboard messages are always removed */
4212 switch (msg.message)
4214 case WM_KEYDOWN:
4215 case WM_SYSKEYDOWN:
4216 switch(msg.wParam)
4218 case VK_MENU:
4219 case VK_F10:
4220 exit_menu = TRUE;
4221 break;
4223 case VK_HOME:
4224 case VK_END:
4225 select_item( mt.hOwnerWnd, mt.hCurrentMenu, NO_SELECTED_ITEM, FALSE, 0 );
4226 move_selection( mt.hOwnerWnd, mt.hCurrentMenu,
4227 msg.wParam == VK_HOME ? ITEM_NEXT : ITEM_PREV );
4228 break;
4230 case VK_UP:
4231 case VK_DOWN: /* If on menu bar, pull-down the menu */
4232 menu = unsafe_menu_ptr( mt.hCurrentMenu );
4233 if (!(menu->wFlags & MF_POPUP))
4234 mt.hCurrentMenu = show_sub_popup( mt.hOwnerWnd, mt.hTopMenu, TRUE, flags );
4235 else /* otherwise try to move selection */
4236 move_selection( mt.hOwnerWnd, mt.hCurrentMenu,
4237 msg.wParam == VK_UP ? ITEM_PREV : ITEM_NEXT );
4238 break;
4240 case VK_LEFT:
4241 menu_key_left( &mt, flags, msg.message );
4242 break;
4244 case VK_RIGHT:
4245 menu_right_key( &mt, flags, msg.message );
4246 break;
4248 case VK_ESCAPE:
4249 exit_menu = menu_key_escape( &mt, flags );
4250 break;
4252 case VK_F1:
4254 HELPINFO hi;
4255 hi.cbSize = sizeof(HELPINFO);
4256 hi.iContextType = HELPINFO_MENUITEM;
4257 if (menu->FocusedItem == NO_SELECTED_ITEM)
4258 hi.iCtrlId = 0;
4259 else
4260 hi.iCtrlId = menu->items[menu->FocusedItem].wID;
4261 hi.hItemHandle = hmenu;
4262 hi.dwContextId = menu->dwContextHelpID;
4263 hi.MousePos = msg.pt;
4264 send_message( hwnd, WM_HELP, 0, (LPARAM)&hi );
4265 break;
4268 default:
4269 NtUserTranslateMessage( &msg, 0 );
4270 break;
4272 break; /* WM_KEYDOWN */
4274 case WM_CHAR:
4275 case WM_SYSCHAR:
4277 UINT pos;
4279 if (msg.wParam == '\r' || msg.wParam == ' ')
4281 executed_menu_id = exec_focused_item( &mt, mt.hCurrentMenu, flags );
4282 exit_menu = executed_menu_id != -2;
4283 break;
4286 /* Hack to avoid control chars... */
4287 if (msg.wParam < 32) break;
4289 pos = find_item_by_key( mt.hOwnerWnd, mt.hCurrentMenu,
4290 LOWORD( msg.wParam ), FALSE );
4291 if (pos == -2) exit_menu = TRUE;
4292 else if (pos == -1) message_beep( 0 );
4293 else
4295 select_item( mt.hOwnerWnd, mt.hCurrentMenu, pos, TRUE, 0 );
4296 executed_menu_id = exec_focused_item( &mt,mt.hCurrentMenu, flags );
4297 exit_menu = executed_menu_id != -2;
4300 break;
4303 else
4305 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
4306 NtUserDispatchMessage( &msg );
4307 continue;
4310 if (!exit_menu) remove = TRUE;
4312 /* finally remove message from the queue */
4313 if (remove && !(mt.trackFlags & TF_SKIPREMOVE))
4314 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
4315 else mt.trackFlags &= ~TF_SKIPREMOVE;
4318 set_capture_window( 0, GUI_INMENUMODE, NULL );
4320 /* If dropdown is still painted and the close box is clicked on
4321 * then the menu will be destroyed as part of the DispatchMessage above.
4322 * This will then invalidate the menu handle in mt.hTopMenu. We should
4323 * check for this first. */
4324 if (is_menu( mt.hTopMenu ))
4326 menu = unsafe_menu_ptr( mt.hTopMenu );
4328 if (is_window( mt.hOwnerWnd ))
4330 hide_sub_popups( mt.hOwnerWnd, mt.hTopMenu, FALSE, flags );
4332 if (menu && (menu->wFlags & MF_POPUP))
4334 NtUserDestroyWindow( menu->hWnd );
4335 menu->hWnd = 0;
4337 if (!(flags & TPM_NONOTIFY))
4338 send_message( mt.hOwnerWnd, WM_UNINITMENUPOPUP, (WPARAM)mt.hTopMenu,
4339 MAKELPARAM( 0, IS_SYSTEM_MENU( menu )));
4341 select_item( mt.hOwnerWnd, mt.hTopMenu, NO_SELECTED_ITEM, FALSE, 0 );
4342 send_message( mt.hOwnerWnd, WM_MENUSELECT, MAKEWPARAM( 0, 0xffff ), 0 );
4346 RtlSetLastWin32Error( ERROR_SUCCESS );
4347 /* The return value is only used by NtUserTrackPopupMenuEx */
4348 if (!(flags & TPM_RETURNCMD)) return TRUE;
4349 if (executed_menu_id == -1) executed_menu_id = 0;
4350 return executed_menu_id;
4353 static BOOL init_tracking( HWND hwnd, HMENU handle, BOOL is_popup, UINT flags )
4355 struct menu *menu;
4357 TRACE( "hwnd=%p hmenu=%p\n", hwnd, handle );
4359 NtUserHideCaret( 0 );
4361 if (!(menu = unsafe_menu_ptr( handle ))) return FALSE;
4363 /* This makes the menus of applications built with Delphi work.
4364 * It also enables menus to be displayed in more than one window,
4365 * but there are some bugs left that need to be fixed in this case.
4367 if (!is_popup) menu->hWnd = hwnd;
4368 if (!top_popup)
4370 top_popup = menu->hWnd;
4371 top_popup_hmenu = handle;
4374 exit_menu = FALSE;
4376 /* Send WM_ENTERMENULOOP and WM_INITMENU message only if TPM_NONOTIFY flag is not specified */
4377 if (!(flags & TPM_NONOTIFY))
4378 send_message( hwnd, WM_ENTERMENULOOP, is_popup, 0 );
4380 send_message( hwnd, WM_SETCURSOR, (WPARAM)hwnd, HTCAPTION );
4382 if (!(flags & TPM_NONOTIFY))
4384 send_message( hwnd, WM_INITMENU, (WPARAM)handle, 0 );
4385 /* If an app changed/recreated menu bar entries in WM_INITMENU
4386 * menu sizes will be recalculated once the menu created/shown. */
4389 return TRUE;
4392 static BOOL exit_tracking( HWND hwnd, BOOL is_popup )
4394 TRACE( "hwnd=%p\n", hwnd );
4396 send_message( hwnd, WM_EXITMENULOOP, is_popup, 0 );
4397 NtUserShowCaret( 0 );
4398 top_popup = 0;
4399 top_popup_hmenu = NULL;
4400 return TRUE;
4403 void track_mouse_menu_bar( HWND hwnd, INT ht, int x, int y )
4405 HMENU handle = ht == HTSYSMENU ? get_win_sys_menu( hwnd ) : get_menu( hwnd );
4406 UINT flags = TPM_BUTTONDOWN | TPM_LEFTALIGN | TPM_LEFTBUTTON;
4408 TRACE( "wnd=%p ht=0x%04x %d,%d\n", hwnd, ht, x, y );
4410 if (get_window_long( hwnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) flags |= TPM_LAYOUTRTL;
4411 if (is_menu( handle ))
4413 init_tracking( hwnd, handle, FALSE, flags );
4415 /* fetch the window menu again, it may have changed */
4416 handle = ht == HTSYSMENU ? get_win_sys_menu( hwnd ) : get_menu( hwnd );
4417 track_menu( handle, flags, x, y, hwnd, NULL );
4418 exit_tracking( hwnd, FALSE );
4422 void track_keyboard_menu_bar( HWND hwnd, UINT wparam, WCHAR ch )
4424 UINT flags = TPM_LEFTALIGN | TPM_LEFTBUTTON;
4425 UINT item = NO_SELECTED_ITEM;
4426 HMENU menu;
4428 TRACE( "hwnd %p wparam 0x%04x ch 0x%04x\n", hwnd, wparam, ch );
4430 /* find window that has a menu */
4431 while (is_win_menu_disallowed( hwnd ))
4432 if (!(hwnd = NtUserGetAncestor( hwnd, GA_PARENT ))) return;
4434 /* check if we have to track a system menu */
4435 menu = get_menu( hwnd );
4436 if (!menu || is_iconic( hwnd ) || ch == ' ')
4438 if (!(get_window_long( hwnd, GWL_STYLE ) & WS_SYSMENU)) return;
4439 menu = get_win_sys_menu( hwnd );
4440 item = 0;
4441 wparam |= HTSYSMENU; /* prevent item lookup */
4443 if (get_window_long( hwnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) flags |= TPM_LAYOUTRTL;
4445 if (!is_menu( menu )) return;
4447 init_tracking( hwnd, menu, FALSE, flags );
4449 /* fetch the window menu again, it may have changed */
4450 menu = (wparam & HTSYSMENU) ? get_win_sys_menu( hwnd ) : get_menu( hwnd );
4452 if (ch && ch != ' ')
4454 item = find_item_by_key( hwnd, menu, ch, wparam & HTSYSMENU );
4455 if (item >= -2)
4457 if (item == -1) message_beep( 0 );
4458 /* schedule end of menu tracking */
4459 flags |= TF_ENDMENU;
4460 goto track_menu;
4464 select_item( hwnd, menu, item, TRUE, 0 );
4466 if (!(wparam & HTSYSMENU) || ch == ' ')
4468 if( item == NO_SELECTED_ITEM )
4469 move_selection( hwnd, menu, ITEM_NEXT );
4470 else
4471 NtUserPostMessage( hwnd, WM_KEYDOWN, VK_RETURN, 0 );
4474 track_menu:
4475 track_menu( menu, flags, 0, 0, hwnd, NULL );
4476 exit_tracking( hwnd, FALSE );
4479 /**********************************************************************
4480 * NtUserTrackPopupMenuEx (win32u.@)
4482 BOOL WINAPI NtUserTrackPopupMenuEx( HMENU handle, UINT flags, INT x, INT y, HWND hwnd,
4483 TPMPARAMS *params )
4485 struct menu *menu;
4486 BOOL ret = FALSE;
4488 TRACE( "hmenu %p flags %04x (%d,%d) hwnd %p params %p rect %s\n",
4489 handle, flags, x, y, hwnd, params,
4490 params ? wine_dbgstr_rect( &params->rcExclude ) : "-" );
4492 if (!(menu = unsafe_menu_ptr( handle )))
4494 RtlSetLastWin32Error( ERROR_INVALID_MENU_HANDLE );
4495 return FALSE;
4498 if (is_window(menu->hWnd))
4500 RtlSetLastWin32Error( ERROR_POPUP_ALREADY_ACTIVE );
4501 return FALSE;
4504 if (init_popup( hwnd, handle, flags ))
4506 init_tracking( hwnd, handle, TRUE, flags );
4508 /* Send WM_INITMENUPOPUP message only if TPM_NONOTIFY flag is not specified */
4509 if (!(flags & TPM_NONOTIFY))
4510 send_message( hwnd, WM_INITMENUPOPUP, (WPARAM)handle, 0 );
4512 if (menu->wFlags & MF_SYSMENU)
4513 init_sys_menu_popup( handle, get_window_long( hwnd, GWL_STYLE ),
4514 get_class_long( hwnd, GCL_STYLE, FALSE ));
4516 if (show_popup( hwnd, handle, 0, flags, x, y, 0, 0 ))
4517 ret = track_menu( handle, flags | TPM_POPUPMENU, 0, 0, hwnd,
4518 params ? &params->rcExclude : NULL );
4519 exit_tracking( hwnd, TRUE );
4521 if (menu->hWnd)
4523 NtUserDestroyWindow( menu->hWnd );
4524 menu->hWnd = 0;
4526 if (!(flags & TPM_NONOTIFY))
4527 send_message( hwnd, WM_UNINITMENUPOPUP, (WPARAM)handle,
4528 MAKELPARAM( 0, IS_SYSTEM_MENU( menu )));
4530 RtlSetLastWin32Error( 0 );
4533 return ret;
4536 /**********************************************************************
4537 * NtUserHiliteMenuItem (win32u.@)
4539 BOOL WINAPI NtUserHiliteMenuItem( HWND hwnd, HMENU handle, UINT item, UINT hilite )
4541 HMENU handle_menu;
4542 UINT focused_item;
4543 struct menu *menu;
4544 UINT pos;
4546 TRACE( "(%p, %p, %04x, %04x);\n", hwnd, handle, item, hilite );
4548 if (!(menu = find_menu_item(handle, item, hilite, &pos))) return FALSE;
4549 handle_menu = menu->obj.handle;
4550 focused_item = menu->FocusedItem;
4551 release_menu_ptr(menu);
4553 if (focused_item != pos)
4555 hide_sub_popups( hwnd, handle_menu, FALSE, 0 );
4556 select_item( hwnd, handle_menu, pos, TRUE, 0 );
4559 return TRUE;
4562 /**********************************************************************
4563 * NtUserGetMenuBarInfo (win32u.@)
4565 BOOL WINAPI NtUserGetMenuBarInfo( HWND hwnd, LONG id, LONG item, MENUBARINFO *info )
4567 HMENU hmenu = NULL;
4568 struct menu *menu;
4569 ATOM class_atom;
4571 TRACE( "(%p,0x%08x,0x%08x,%p)\n", hwnd, (int)id, (int)item, info );
4573 switch (id)
4575 case OBJID_CLIENT:
4576 class_atom = get_class_long( hwnd, GCW_ATOM, FALSE );
4577 if (!class_atom)
4578 return FALSE;
4579 if (class_atom != POPUPMENU_CLASS_ATOM)
4581 WARN("called on invalid window: %d\n", class_atom);
4582 RtlSetLastWin32Error(ERROR_INVALID_MENU_HANDLE);
4583 return FALSE;
4586 hmenu = (HMENU)get_window_long_ptr( hwnd, 0, FALSE );
4587 break;
4588 case OBJID_MENU:
4589 hmenu = get_menu( hwnd );
4590 break;
4591 case OBJID_SYSMENU:
4592 hmenu = NtUserGetSystemMenu( hwnd, FALSE );
4593 break;
4594 default:
4595 return FALSE;
4598 if (!hmenu)
4599 return FALSE;
4601 if (info->cbSize != sizeof(MENUBARINFO))
4603 RtlSetLastWin32Error( ERROR_INVALID_PARAMETER );
4604 return FALSE;
4607 if (!(menu = grab_menu_ptr( hmenu ))) return FALSE;
4608 if (item < 0 || item > menu->nItems)
4610 release_menu_ptr( menu );
4611 return FALSE;
4614 if (!menu->Height)
4616 SetRectEmpty( &info->rcBar );
4618 else if (item == 0)
4620 NtUserGetMenuItemRect( hwnd, hmenu, 0, &info->rcBar );
4621 info->rcBar.right = info->rcBar.left + menu->Width;
4622 info->rcBar.bottom = info->rcBar.top + menu->Height;
4624 else
4626 NtUserGetMenuItemRect( hwnd, hmenu, item - 1, &info->rcBar );
4629 info->hMenu = hmenu;
4630 info->hwndMenu = NULL;
4631 info->fBarFocused = top_popup_hmenu == hmenu;
4632 if (item)
4634 info->fFocused = menu->FocusedItem == item - 1;
4635 if (info->fFocused && (menu->items[item - 1].fType & MF_POPUP))
4637 struct menu *hwnd_menu = grab_menu_ptr( menu->items[item - 1].hSubMenu );
4638 if (hwnd_menu)
4640 info->hwndMenu = hwnd_menu->hWnd;
4641 release_menu_ptr( hwnd_menu );
4645 else
4647 info->fFocused = info->fBarFocused;
4650 release_menu_ptr( menu );
4651 return TRUE;
4654 /***********************************************************************
4655 * NtUserEndMenu (win32u.@)
4657 BOOL WINAPI NtUserEndMenu(void)
4659 /* if we are in the menu code, and it is active, terminate the menu handling code */
4660 if (!exit_menu && top_popup)
4662 exit_menu = TRUE;
4664 /* needs to be posted to wakeup the internal menu handler
4665 * which will now terminate the menu, in the event that
4666 * the main window was minimized, or lost focus, so we
4667 * don't end up with an orphaned menu */
4668 NtUserPostMessage( top_popup, WM_CANCELMODE, 0, 0 );
4670 return exit_menu;