user32/edit: Handle IME composition result string only when EIMES_GETCOMPSTRATONCE...
[wine.git] / dlls / win32u / menu.c
blobff11fbb804a1c4c752baaeab6748eb4da49e24fd
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 DWORD 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", 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, 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 %04lx (scan %04x)\n",
1709 wparam, 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,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 menucharsize.cx = get_char_dimensions( hdc, NULL, &menucharsize.cy );
1998 /* Win95/98/ME will use menucharsize.cy here. Testing is possible
1999 * but it is unlikely an application will depend on that */
2000 od_item_height = HIWORD( get_dialog_base_units() );
2003 SetRect( &item->rect, org_x, org_y, org_x, org_y );
2005 if (item->fType & MF_OWNERDRAW)
2007 MEASUREITEMSTRUCT mis;
2008 mis.CtlType = ODT_MENU;
2009 mis.CtlID = 0;
2010 mis.itemID = item->wID;
2011 mis.itemData = item->dwItemData;
2012 mis.itemHeight = od_item_height;
2013 mis.itemWidth = 0;
2014 send_message( owner, WM_MEASUREITEM, 0, (LPARAM)&mis );
2015 /* Tests reveal that Windows ( Win95 through WinXP) adds twice the average
2016 * width of a menufont character to the width of an owner-drawn menu. */
2017 item->rect.right += mis.itemWidth + 2 * menucharsize.cx;
2018 if (menu_bar)
2020 /* Under at least win95 you seem to be given a standard
2021 * height for the menu and the height value is ignored. */
2022 item->rect.bottom += get_system_metrics( SM_CYMENUSIZE );
2024 else
2025 item->rect.bottom += mis.itemHeight;
2027 TRACE( "id=%04lx size=%dx%d\n", item->wID, item->rect.right-item->rect.left,
2028 item->rect.bottom-item->rect.top );
2029 return;
2032 if (item->fType & MF_SEPARATOR)
2034 item->rect.bottom += get_system_metrics( SM_CYMENUSIZE ) / 2;
2035 if (!menu_bar) item->rect.right += arrow_bitmap_width + menucharsize.cx;
2036 return;
2039 item_height = 0;
2040 item->xTab = 0;
2042 if (!menu_bar)
2044 if (item->hbmpItem)
2046 SIZE size;
2048 get_bitmap_item_size( item, &size, owner );
2049 /* Keep the size of the bitmap in callback mode to be able
2050 * to draw it correctly */
2051 item->bmpsize = size;
2052 menu->textOffset = max( menu->textOffset, size.cx );
2053 item->rect.right += size.cx + 2;
2054 item_height = size.cy + 2;
2056 if (!(menu->dwStyle & MNS_NOCHECK)) item->rect.right += check_bitmap_width;
2057 item->rect.right += 4 + menucharsize.cx;
2058 item->xTab = item->rect.right;
2059 item->rect.right += arrow_bitmap_width;
2061 else if (item->hbmpItem) /* menu_bar */
2063 SIZE size;
2065 get_bitmap_item_size( item, &size, owner );
2066 item->bmpsize = size;
2067 item->rect.right += size.cx;
2068 if (item->text) item->rect.right += 2;
2069 item_height = size.cy;
2072 /* it must be a text item - unless it's the system menu */
2073 if (!(item->fType & MF_SYSMENU) && item->text)
2075 LONG txt_height, txt_width;
2076 HFONT prev_font = NULL;
2077 RECT rc = item->rect;
2079 if (item->fState & MFS_DEFAULT)
2080 prev_font = NtGdiSelectFont( hdc, get_menu_font(TRUE) );
2082 if (menu_bar)
2084 txt_height = DrawTextW( hdc, item->text, -1, &rc, DT_SINGLELINE | DT_CALCRECT );
2085 item->rect.right += rc.right - rc.left;
2086 item_height = max( max( item_height, txt_height ),
2087 get_system_metrics( SM_CYMENU ) - 1 );
2088 item->rect.right += 2 * menucharsize.cx;
2090 else
2092 if ((p = wcschr( item->text, '\t' )))
2094 RECT r = rc;
2095 int h, n = (int)(p - item->text);
2097 /* Item contains a tab (only meaningful in popup menus) */
2098 /* get text size before the tab */
2099 txt_height = DrawTextW( hdc, item->text, n, &rc, DT_SINGLELINE | DT_CALCRECT );
2100 txt_width = rc.right - rc.left;
2101 p += 1; /* advance past the Tab */
2102 /* get text size after the tab */
2103 h = DrawTextW( hdc, p, -1, &r, DT_SINGLELINE | DT_CALCRECT );
2104 item->xTab += txt_width;
2105 txt_height = max( txt_height, h );
2106 /* space for the tab and the short cut */
2107 txt_width += menucharsize.cx + r.right - r.left;
2109 else
2111 txt_height = DrawTextW( hdc, item->text, -1, &rc, DT_SINGLELINE | DT_CALCRECT );
2112 txt_width = rc.right - rc.left;
2113 item->xTab += txt_width;
2115 item->rect.right += 2 + txt_width;
2116 item_height = max( item_height, max( txt_height + 2, menucharsize.cy + 4 ));
2118 if (prev_font) NtGdiSelectFont( hdc, prev_font );
2120 else if (menu_bar)
2122 item_height = max( item_height, get_system_metrics( SM_CYMENU ) - 1 );
2124 item->rect.bottom += item_height;
2125 TRACE( "%s\n", wine_dbgstr_rect( &item->rect ));
2128 /* Calculate the size of the menu bar */
2129 static void calc_menu_bar_size( HDC hdc, RECT *rect, struct menu *menu, HWND owner )
2131 UINT start, i, help_pos;
2132 int org_x, org_y;
2133 struct menu_item *item;
2135 if (!rect || !menu || !menu->nItems) return;
2137 TRACE( "rect %p %s\n", rect, wine_dbgstr_rect( rect ));
2138 /* Start with a 1 pixel top border.
2139 This corresponds to the difference between SM_CYMENU and SM_CYMENUSIZE. */
2140 SetRect( &menu->items_rect, 0, 0, rect->right - rect->left, 1 );
2141 start = 0;
2142 help_pos = ~0u;
2143 menu->textOffset = 0;
2144 while (start < menu->nItems)
2146 item = &menu->items[start];
2147 org_x = menu->items_rect.left;
2148 org_y = menu->items_rect.bottom;
2150 /* Parse items until line break or end of menu */
2151 for (i = start; i < menu->nItems; i++, item++)
2153 if (help_pos == ~0u && (item->fType & MF_RIGHTJUSTIFY)) help_pos = i;
2154 if (i != start && (item->fType & (MF_MENUBREAK | MF_MENUBARBREAK))) break;
2156 TRACE("item org=(%d, %d) %s\n", org_x, org_y, debugstr_menuitem( item ));
2157 calc_menu_item_size( hdc, item, owner, org_x, org_y, TRUE, menu );
2159 if (item->rect.right > menu->items_rect.right)
2161 if (i != start) break;
2162 else item->rect.right = menu->items_rect.right;
2164 menu->items_rect.bottom = max( menu->items_rect.bottom, item->rect.bottom );
2165 org_x = item->rect.right;
2168 /* Finish the line (set all items to the largest height found) */
2169 while (start < i) menu->items[start++].rect.bottom = menu->items_rect.bottom;
2172 OffsetRect( &menu->items_rect, rect->left, rect->top );
2173 menu->Width = menu->items_rect.right - menu->items_rect.left;
2174 menu->Height = menu->items_rect.bottom - menu->items_rect.top;
2175 rect->bottom = menu->items_rect.bottom;
2177 /* Flush right all items between the MF_RIGHTJUSTIFY and */
2178 /* the last item (if several lines, only move the last line) */
2179 if (help_pos == ~0u) return;
2180 item = &menu->items[menu->nItems-1];
2181 org_y = item->rect.top;
2182 org_x = rect->right - rect->left;
2183 for (i = menu->nItems - 1; i >= help_pos; i--, item--)
2185 if (item->rect.top != org_y) break; /* other line */
2186 if (item->rect.right >= org_x) break; /* too far right already */
2187 item->rect.left += org_x - item->rect.right;
2188 item->rect.right = org_x;
2189 org_x = item->rect.left;
2193 UINT get_menu_bar_height( HWND hwnd, UINT width, INT org_x, INT org_y )
2195 struct menu *menu;
2196 RECT rect_bar;
2197 HDC hdc;
2199 TRACE( "hwnd %p, width %d, at (%d, %d).\n", hwnd, width, org_x, org_y );
2201 if (!(menu = unsafe_menu_ptr( get_menu( hwnd )))) return 0;
2203 hdc = NtUserGetDCEx( hwnd, 0, DCX_CACHE | DCX_WINDOW );
2204 NtGdiSelectFont( hdc, get_menu_font(FALSE));
2205 SetRect( &rect_bar, org_x, org_y, org_x + width, org_y + get_system_metrics( SM_CYMENU ));
2206 calc_menu_bar_size( hdc, &rect_bar, menu, hwnd );
2207 NtUserReleaseDC( hwnd, hdc );
2208 return menu->Height;
2211 static void draw_popup_arrow( HDC hdc, RECT rect, UINT arrow_width, UINT arrow_height )
2213 HDC mem_hdc = NtGdiCreateCompatibleDC( hdc );
2214 HBITMAP prev_bitmap;
2216 prev_bitmap = NtGdiSelectBitmap( mem_hdc, get_arrow_bitmap() );
2217 NtGdiBitBlt( hdc, rect.right - arrow_width - 1,
2218 (rect.top + rect.bottom - arrow_height) / 2,
2219 arrow_width, arrow_height, mem_hdc, 0, 0, SRCCOPY, 0, 0 );
2220 NtGdiSelectBitmap( mem_hdc, prev_bitmap );
2221 NtGdiDeleteObjectApp( mem_hdc );
2224 static void draw_bitmap_item( HDC hdc, struct menu_item *item, const RECT *rect,
2225 struct menu *menu, HWND owner, UINT odaction )
2227 int w = rect->right - rect->left;
2228 int h = rect->bottom - rect->top;
2229 int bmp_xoffset = 0, left, top;
2230 HBITMAP bmp_to_draw = item->hbmpItem;
2231 HBITMAP bmp = bmp_to_draw;
2232 BITMAP bm;
2233 DWORD rop;
2234 HDC mem_hdc;
2236 /* Check if there is a magic menu item associated with this item */
2237 if (IS_MAGIC_BITMAP( bmp_to_draw ))
2239 UINT flags = 0;
2240 WCHAR bmchr = 0;
2241 RECT r;
2243 switch ((INT_PTR)bmp_to_draw)
2245 case (INT_PTR)HBMMENU_SYSTEM:
2246 if (item->dwItemData)
2248 bmp = (HBITMAP)item->dwItemData;
2249 if (!NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return;
2251 else
2253 static HBITMAP sys_menu_bmp;
2255 if (!sys_menu_bmp)
2256 sys_menu_bmp = LoadImageW( 0, MAKEINTRESOURCEW(OBM_CLOSE), IMAGE_BITMAP, 0, 0, 0 );
2257 bmp = sys_menu_bmp;
2258 if (!NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return;
2259 /* only use right half of the bitmap */
2260 bmp_xoffset = bm.bmWidth / 2;
2261 bm.bmWidth -= bmp_xoffset;
2263 goto got_bitmap;
2264 case (INT_PTR)HBMMENU_MBAR_RESTORE:
2265 flags = DFCS_CAPTIONRESTORE;
2266 break;
2267 case (INT_PTR)HBMMENU_MBAR_MINIMIZE:
2268 flags = DFCS_CAPTIONMIN;
2269 break;
2270 case (INT_PTR)HBMMENU_MBAR_MINIMIZE_D:
2271 flags = DFCS_CAPTIONMIN | DFCS_INACTIVE;
2272 break;
2273 case (INT_PTR)HBMMENU_MBAR_CLOSE:
2274 flags = DFCS_CAPTIONCLOSE;
2275 break;
2276 case (INT_PTR)HBMMENU_MBAR_CLOSE_D:
2277 flags = DFCS_CAPTIONCLOSE | DFCS_INACTIVE;
2278 break;
2279 case (INT_PTR)HBMMENU_CALLBACK:
2281 DRAWITEMSTRUCT drawItem;
2282 drawItem.CtlType = ODT_MENU;
2283 drawItem.CtlID = 0;
2284 drawItem.itemID = item->wID;
2285 drawItem.itemAction = odaction;
2286 drawItem.itemState = 0;
2287 if (item->fState & MF_CHECKED) drawItem.itemState |= ODS_CHECKED;
2288 if (item->fState & MF_DEFAULT) drawItem.itemState |= ODS_DEFAULT;
2289 if (item->fState & MF_DISABLED) drawItem.itemState |= ODS_DISABLED;
2290 if (item->fState & MF_GRAYED) drawItem.itemState |= ODS_GRAYED|ODS_DISABLED;
2291 if (item->fState & MF_HILITE) drawItem.itemState |= ODS_SELECTED;
2292 drawItem.hwndItem = (HWND)menu->obj.handle;
2293 drawItem.hDC = hdc;
2294 drawItem.itemData = item->dwItemData;
2295 drawItem.rcItem = *rect;
2296 send_message( owner, WM_DRAWITEM, 0, (LPARAM)&drawItem );
2297 return;
2299 break;
2300 case (INT_PTR)HBMMENU_POPUP_CLOSE:
2301 bmchr = 0x72;
2302 break;
2303 case (INT_PTR)HBMMENU_POPUP_RESTORE:
2304 bmchr = 0x32;
2305 break;
2306 case (INT_PTR)HBMMENU_POPUP_MAXIMIZE:
2307 bmchr = 0x31;
2308 break;
2309 case (INT_PTR)HBMMENU_POPUP_MINIMIZE:
2310 bmchr = 0x30;
2311 break;
2312 default:
2313 FIXME( "Magic %p not implemented\n", bmp_to_draw );
2314 return;
2317 if (bmchr)
2319 /* draw the magic bitmaps using marlett font characters */
2320 /* FIXME: fontsize and the position (x,y) could probably be better */
2321 HFONT hfont, prev_font;
2322 LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
2323 {'M','a','r','l','e','t','t'}};
2324 logfont.lfHeight = min( h, w) - 5 ;
2325 TRACE( " height %d rect %s\n", logfont.lfHeight, wine_dbgstr_rect( rect ));
2326 hfont = NtGdiHfontCreate( &logfont, sizeof(logfont), 0, 0, NULL );
2327 prev_font = NtGdiSelectFont( hdc, hfont );
2328 NtGdiExtTextOutW( hdc, rect->left, rect->top + 2, 0, NULL, &bmchr, 1, NULL, 0 );
2329 NtGdiSelectFont( hdc, prev_font );
2330 NtGdiDeleteObjectApp( hfont );
2332 else
2334 r = *rect;
2335 InflateRect( &r, -1, -1 );
2336 if (item->fState & MF_HILITE) flags |= DFCS_PUSHED;
2337 draw_frame_caption( hdc, &r, flags );
2339 return;
2342 if (!bmp || !NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return;
2344 got_bitmap:
2345 mem_hdc = NtGdiCreateCompatibleDC( hdc );
2346 NtGdiSelectBitmap( mem_hdc, bmp );
2348 /* handle fontsize > bitmap_height */
2349 top = (h>bm.bmHeight) ? rect->top + (h - bm.bmHeight) / 2 : rect->top;
2350 left=rect->left;
2351 rop= ((item->fState & MF_HILITE) && !IS_MAGIC_BITMAP(bmp_to_draw)) ? NOTSRCCOPY : SRCCOPY;
2352 if ((item->fState & MF_HILITE) && item->hbmpItem)
2353 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color( COLOR_HIGHLIGHT ), NULL );
2354 NtGdiBitBlt( hdc, left, top, w, h, mem_hdc, bmp_xoffset, 0, rop, 0, 0 );
2355 NtGdiDeleteObjectApp( mem_hdc );
2358 /* Draw a single menu item */
2359 static void draw_menu_item( HWND hwnd, struct menu *menu, HWND owner, HDC hdc,
2360 struct menu_item *item, BOOL menu_bar, UINT odaction )
2362 UINT arrow_width = 0, arrow_height = 0;
2363 HRGN old_clip = NULL, clip;
2364 BOOL flat_menu = FALSE;
2365 RECT rect, bmprc;
2366 int bkgnd;
2368 TRACE( "%s\n", debugstr_menuitem( item ));
2370 if (!menu_bar)
2372 BITMAP bmp;
2373 NtGdiExtGetObjectW( get_arrow_bitmap(), sizeof(bmp), &bmp );
2374 arrow_width = bmp.bmWidth;
2375 arrow_height = bmp.bmHeight;
2378 if (item->fType & MF_SYSMENU)
2380 if (!is_iconic( hwnd ))
2381 draw_nc_sys_button( hwnd, hdc, item->fState & (MF_HILITE | MF_MOUSESELECT) );
2382 return;
2385 TRACE( "rect=%s\n", wine_dbgstr_rect( &item->rect ));
2386 rect = item->rect;
2387 adjust_menu_item_rect( menu, &rect );
2388 if (!intersect_rect( &bmprc, &rect, &menu->items_rect )) /* bmprc is used as a dummy */
2389 return;
2391 NtUserSystemParametersInfo( SPI_GETFLATMENU, 0, &flat_menu, 0 );
2392 bkgnd = (menu_bar && flat_menu) ? COLOR_MENUBAR : COLOR_MENU;
2394 /* Setup colors */
2395 if (item->fState & MF_HILITE)
2397 if (menu_bar && !flat_menu)
2399 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color(COLOR_MENUTEXT), NULL );
2400 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color(COLOR_MENU), NULL );
2402 else
2404 if (item->fState & MF_GRAYED)
2405 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_GRAYTEXT ), NULL );
2406 else
2407 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_HIGHLIGHTTEXT ), NULL );
2408 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color( COLOR_HIGHLIGHT ), NULL );
2411 else
2413 if (item->fState & MF_GRAYED)
2414 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_GRAYTEXT ), NULL );
2415 else
2416 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_MENUTEXT ), NULL );
2417 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color( bkgnd ), NULL );
2420 old_clip = NtGdiCreateRectRgn( 0, 0, 0, 0 );
2421 if (NtGdiGetRandomRgn( hdc, old_clip, NTGDI_RGN_MIRROR_RTL | 1 ) <= 0)
2423 NtGdiDeleteObjectApp( old_clip );
2424 old_clip = NULL;
2426 clip = NtGdiCreateRectRgn( menu->items_rect.left, menu->items_rect.top,
2427 menu->items_rect.right, menu->items_rect.bottom );
2428 NtGdiExtSelectClipRgn( hdc, clip, RGN_AND );
2429 NtGdiDeleteObjectApp( clip );
2431 if (item->fType & MF_OWNERDRAW)
2434 * Experimentation under Windows reveals that an owner-drawn
2435 * menu is given the rectangle which includes the space it requested
2436 * in its response to WM_MEASUREITEM _plus_ width for a checkmark
2437 * and a popup-menu arrow. This is the value of item->rect.
2438 * Windows will leave all drawing to the application except for
2439 * the popup-menu arrow. Windows always draws that itself, after
2440 * the menu owner has finished drawing.
2442 DRAWITEMSTRUCT dis;
2443 DWORD old_bk, old_text;
2445 dis.CtlType = ODT_MENU;
2446 dis.CtlID = 0;
2447 dis.itemID = item->wID;
2448 dis.itemData = item->dwItemData;
2449 dis.itemState = 0;
2450 if (item->fState & MF_CHECKED) dis.itemState |= ODS_CHECKED;
2451 if (item->fState & MF_GRAYED) dis.itemState |= ODS_GRAYED|ODS_DISABLED;
2452 if (item->fState & MF_HILITE) dis.itemState |= ODS_SELECTED;
2453 dis.itemAction = odaction; /* ODA_DRAWENTIRE | ODA_SELECT | ODA_FOCUS; */
2454 dis.hwndItem = (HWND)menu->obj.handle;
2455 dis.hDC = hdc;
2456 dis.rcItem = rect;
2457 TRACE( "Ownerdraw: owner=%p itemID=%d, itemState=%d, itemAction=%d, "
2458 "hwndItem=%p, hdc=%p, rcItem=%s\n", owner,
2459 dis.itemID, dis.itemState, dis.itemAction, dis.hwndItem,
2460 dis.hDC, wine_dbgstr_rect( &dis.rcItem ));
2461 NtGdiGetDCDword( hdc, NtGdiGetBkColor, &old_bk );
2462 NtGdiGetDCDword( hdc, NtGdiGetTextColor, &old_text );
2463 send_message( owner, WM_DRAWITEM, 0, (LPARAM)&dis );
2464 /* Draw the popup-menu arrow */
2465 NtGdiGetAndSetDCDword( hdc, NtGdiGetBkColor, old_bk, NULL );
2466 NtGdiGetAndSetDCDword( hdc, NtGdiGetTextColor, old_text, NULL );
2467 if (item->fType & MF_POPUP)
2468 draw_popup_arrow( hdc, rect, arrow_width, arrow_height );
2469 goto done;
2472 if (menu_bar && (item->fType & MF_SEPARATOR)) goto done;
2474 if (item->fState & MF_HILITE)
2476 if (flat_menu)
2478 InflateRect (&rect, -1, -1);
2479 fill_rect( hdc, &rect, get_sys_color_brush( COLOR_MENUHILIGHT ));
2480 InflateRect (&rect, 1, 1);
2481 fill_rect( hdc, &rect, get_sys_color_brush( COLOR_HIGHLIGHT ));
2483 else
2485 if (menu_bar)
2486 draw_rect_edge( hdc, &rect, BDR_SUNKENOUTER, BF_RECT, 1 );
2487 else
2488 fill_rect( hdc, &rect, get_sys_color_brush( COLOR_HIGHLIGHT ));
2491 else
2492 fill_rect( hdc, &rect, get_sys_color_brush(bkgnd) );
2494 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkMode, TRANSPARENT, NULL );
2496 /* vertical separator */
2497 if (!menu_bar && (item->fType & MF_MENUBARBREAK))
2499 HPEN oldPen;
2500 RECT rc = rect;
2502 rc.left -= MENU_COL_SPACE / 2 + 1;
2503 rc.top = 3;
2504 rc.bottom = menu->Height - 3;
2505 if (flat_menu)
2507 oldPen = NtGdiSelectPen( hdc, get_sys_color_pen( COLOR_BTNSHADOW ));
2508 NtGdiMoveTo( hdc, rc.left, rc.top, NULL );
2509 NtGdiLineTo( hdc, rc.left, rc.bottom );
2510 NtGdiSelectPen( hdc, oldPen );
2512 else
2513 draw_rect_edge( hdc, &rc, EDGE_ETCHED, BF_LEFT, 1 );
2516 /* horizontal separator */
2517 if (item->fType & MF_SEPARATOR)
2519 HPEN oldPen;
2520 RECT rc = rect;
2522 InflateRect( &rc, -1, 0 );
2523 rc.top = ( rc.top + rc.bottom) / 2;
2524 if (flat_menu)
2526 oldPen = NtGdiSelectPen( hdc, get_sys_color_pen( COLOR_BTNSHADOW ));
2527 NtGdiMoveTo( hdc, rc.left, rc.top, NULL );
2528 NtGdiLineTo( hdc, rc.right, rc.top );
2529 NtGdiSelectPen( hdc, oldPen );
2531 else
2532 draw_rect_edge( hdc, &rc, EDGE_ETCHED, BF_TOP, 1 );
2533 goto done;
2536 if (item->hbmpItem)
2538 /* calculate the bitmap rectangle in coordinates relative
2539 * to the item rectangle */
2540 if (menu_bar)
2542 if (item->hbmpItem == HBMMENU_CALLBACK)
2543 bmprc.left = 3;
2544 else
2545 bmprc.left = item->text ? menucharsize.cx : 0;
2547 else if (menu->dwStyle & MNS_NOCHECK)
2548 bmprc.left = 4;
2549 else if (menu->dwStyle & MNS_CHECKORBMP)
2550 bmprc.left = 2;
2551 else
2552 bmprc.left = 4 + get_system_metrics( SM_CXMENUCHECK );
2553 bmprc.right = bmprc.left + item->bmpsize.cx;
2554 if (menu_bar && !(item->hbmpItem == HBMMENU_CALLBACK))
2555 bmprc.top = 0;
2556 else
2557 bmprc.top = (rect.bottom - rect.top - item->bmpsize.cy) / 2;
2558 bmprc.bottom = bmprc.top + item->bmpsize.cy;
2561 if (!menu_bar)
2563 HBITMAP bm;
2564 INT y = rect.top + rect.bottom;
2565 BOOL checked = FALSE;
2566 UINT check_bitmap_width = get_system_metrics( SM_CXMENUCHECK );
2567 UINT check_bitmap_height = get_system_metrics( SM_CYMENUCHECK );
2569 /* Draw the check mark */
2570 if (!(menu->dwStyle & MNS_NOCHECK))
2572 bm = (item->fState & MF_CHECKED) ? item->hCheckBit :
2573 item->hUnCheckBit;
2574 if (bm) /* we have a custom bitmap */
2576 HDC mem_hdc = NtGdiCreateCompatibleDC( hdc );
2578 NtGdiSelectBitmap( mem_hdc, bm );
2579 NtGdiBitBlt( hdc, rect.left, (y - check_bitmap_height) / 2,
2580 check_bitmap_width, check_bitmap_height,
2581 mem_hdc, 0, 0, SRCCOPY, 0, 0 );
2582 NtGdiDeleteObjectApp( mem_hdc );
2583 checked = TRUE;
2585 else if (item->fState & MF_CHECKED) /* standard bitmaps */
2587 RECT r;
2588 HBITMAP bm = NtGdiCreateBitmap( check_bitmap_width,
2589 check_bitmap_height, 1, 1, NULL );
2590 HDC mem_hdc = NtGdiCreateCompatibleDC( hdc );
2592 NtGdiSelectBitmap( mem_hdc, bm );
2593 SetRect( &r, 0, 0, check_bitmap_width, check_bitmap_height);
2594 draw_frame_menu( mem_hdc, &r,
2595 (item->fType & MFT_RADIOCHECK) ? DFCS_MENUBULLET : DFCS_MENUCHECK );
2596 NtGdiBitBlt( hdc, rect.left, (y - r.bottom) / 2, r.right, r.bottom,
2597 mem_hdc, 0, 0, SRCCOPY, 0, 0 );
2598 NtGdiDeleteObjectApp( mem_hdc );
2599 NtGdiDeleteObjectApp( bm );
2600 checked = TRUE;
2603 if (item->hbmpItem && !(checked && (menu->dwStyle & MNS_CHECKORBMP)))
2605 POINT origorg;
2606 /* some applications make this assumption on the DC's origin */
2607 set_viewport_org( hdc, rect.left, rect.top, &origorg );
2608 draw_bitmap_item( hdc, item, &bmprc, menu, owner, odaction );
2609 set_viewport_org( hdc, origorg.x, origorg.y, NULL );
2611 /* Draw the popup-menu arrow */
2612 if (item->fType & MF_POPUP)
2613 draw_popup_arrow( hdc, rect, arrow_width, arrow_height);
2614 rect.left += 4;
2615 if (!(menu->dwStyle & MNS_NOCHECK))
2616 rect.left += check_bitmap_width;
2617 rect.right -= arrow_width;
2619 else if (item->hbmpItem)
2620 { /* Draw the bitmap */
2621 POINT origorg;
2623 set_viewport_org( hdc, rect.left, rect.top, &origorg);
2624 draw_bitmap_item( hdc, item, &bmprc, menu, owner, odaction );
2625 set_viewport_org( hdc, origorg.x, origorg.y, NULL);
2627 /* process text if present */
2628 if (item->text)
2630 int i;
2631 HFONT prev_font = 0;
2632 UINT format = menu_bar ?
2633 DT_CENTER | DT_VCENTER | DT_SINGLELINE :
2634 DT_LEFT | DT_VCENTER | DT_SINGLELINE;
2636 if (!(menu->dwStyle & MNS_CHECKORBMP))
2637 rect.left += menu->textOffset;
2639 if (item->fState & MFS_DEFAULT)
2641 prev_font = NtGdiSelectFont(hdc, get_menu_font( TRUE ));
2644 if (menu_bar)
2646 if (item->hbmpItem)
2647 rect.left += item->bmpsize.cx;
2648 if (item->hbmpItem != HBMMENU_CALLBACK)
2649 rect.left += menucharsize.cx;
2650 rect.right -= menucharsize.cx;
2653 for (i = 0; item->text[i]; i++)
2654 if ((item->text[i] == '\t') || (item->text[i] == '\b'))
2655 break;
2657 if (item->fState & MF_GRAYED)
2659 if (!(item->fState & MF_HILITE) )
2661 ++rect.left; ++rect.top; ++rect.right; ++rect.bottom;
2662 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0xff, 0xff, 0xff), NULL );
2663 DrawTextW( hdc, item->text, i, &rect, format );
2664 --rect.left; --rect.top; --rect.right; --rect.bottom;
2666 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0x80, 0x80, 0x80), NULL );
2669 DrawTextW( hdc, item->text, i, &rect, format );
2671 /* paint the shortcut text */
2672 if (!menu_bar && item->text[i]) /* There's a tab or flush-right char */
2674 if (item->text[i] == '\t')
2676 rect.left = item->xTab;
2677 format = DT_LEFT | DT_VCENTER | DT_SINGLELINE;
2679 else
2681 rect.right = item->xTab;
2682 format = DT_RIGHT | DT_VCENTER | DT_SINGLELINE;
2685 if (item->fState & MF_GRAYED)
2687 if (!(item->fState & MF_HILITE) )
2689 ++rect.left; ++rect.top; ++rect.right; ++rect.bottom;
2690 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0xff, 0xff, 0xff), NULL );
2691 DrawTextW( hdc, item->text + i + 1, -1, &rect, format );
2692 --rect.left; --rect.top; --rect.right; --rect.bottom;
2694 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0x80, 0x80, 0x80), NULL );
2696 DrawTextW( hdc, item->text + i + 1, -1, &rect, format );
2699 if (prev_font) NtGdiSelectFont( hdc, prev_font );
2702 done:
2703 NtGdiExtSelectClipRgn( hdc, old_clip, RGN_COPY );
2704 if (old_clip) NtGdiDeleteObjectApp( old_clip );
2707 /***********************************************************************
2708 * NtUserDrawMenuBarTemp (win32u.@)
2710 DWORD WINAPI NtUserDrawMenuBarTemp( HWND hwnd, HDC hdc, RECT *rect, HMENU handle, HFONT font )
2712 BOOL flat_menu = FALSE;
2713 HFONT prev_font = 0;
2714 struct menu *menu;
2715 UINT i, retvalue;
2717 NtUserSystemParametersInfo( SPI_GETFLATMENU, 0, &flat_menu, 0 );
2719 if (!handle) handle = get_menu( hwnd );
2720 if (!font) font = get_menu_font(FALSE);
2722 menu = unsafe_menu_ptr( handle );
2723 if (!menu || !rect) return get_system_metrics( SM_CYMENU );
2725 TRACE( "(%p, %p, %p, %p, %p)\n", hwnd, hdc, rect, handle, font );
2727 prev_font = NtGdiSelectFont( hdc, font );
2729 if (!menu->Height) calc_menu_bar_size( hdc, rect, menu, hwnd );
2731 rect->bottom = rect->top + menu->Height;
2733 fill_rect( hdc, rect, get_sys_color_brush( flat_menu ? COLOR_MENUBAR : COLOR_MENU ));
2735 NtGdiSelectPen( hdc, get_sys_color_pen( COLOR_3DFACE ));
2736 NtGdiMoveTo( hdc, rect->left, rect->bottom, NULL );
2737 NtGdiLineTo( hdc, rect->right, rect->bottom );
2739 if (menu->nItems)
2741 for (i = 0; i < menu->nItems; i++)
2742 draw_menu_item( hwnd, menu, hwnd, hdc, &menu->items[i], TRUE, ODA_DRAWENTIRE );
2744 retvalue = menu->Height;
2746 else
2748 retvalue = get_system_metrics( SM_CYMENU );
2751 if (prev_font) NtGdiSelectFont( hdc, prev_font );
2752 return retvalue;
2755 static UINT get_scroll_arrow_height( const struct menu *menu )
2757 return menucharsize.cy + 4;
2760 static void draw_scroll_arrow( HDC hdc, int x, int top, int height, BOOL up, BOOL enabled )
2762 RECT rect, light_rect;
2763 HBRUSH brush = get_sys_color_brush( enabled ? COLOR_BTNTEXT : COLOR_BTNSHADOW );
2764 HBRUSH light = get_sys_color_brush( COLOR_3DLIGHT );
2766 if (!up)
2768 top = top + height;
2769 if (!enabled)
2771 SetRect( &rect, x + 1, top, x + 2, top + 1);
2772 fill_rect( hdc, &rect, light );
2774 top--;
2777 SetRect( &rect, x, top, x + 1, top + 1);
2778 while (height--)
2780 fill_rect( hdc, &rect, brush );
2781 if (!enabled && !up && height)
2783 SetRect( &light_rect, rect.right, rect.top, rect.right + 2, rect.bottom );
2784 fill_rect( hdc, &light_rect, light );
2786 InflateRect( &rect, 1, 0 );
2787 OffsetRect( &rect, 0, up ? 1 : -1 );
2790 if (!enabled && up)
2792 rect.left += 2;
2793 fill_rect( hdc, &rect, light );
2797 static void draw_scroll_arrows( const struct menu *menu, HDC hdc )
2799 UINT full_height = get_scroll_arrow_height( menu );
2800 UINT arrow_height = full_height / 3;
2801 BOOL at_end = menu->nScrollPos + menu->items_rect.bottom - menu->items_rect.top == menu->nTotalHeight;
2803 draw_scroll_arrow( hdc, menu->Width / 3, arrow_height, arrow_height,
2804 TRUE, menu->nScrollPos != 0);
2805 draw_scroll_arrow( hdc, menu->Width / 3, menu->Height - 2 * arrow_height, arrow_height,
2806 FALSE, !at_end );
2809 static int frame_rect( HDC hdc, const RECT *rect, HBRUSH hbrush )
2811 HBRUSH prev_brush;
2812 RECT r = *rect;
2814 if (IsRectEmpty(&r)) return 0;
2815 if (!(prev_brush = NtGdiSelectBrush( hdc, hbrush ))) return 0;
2817 NtGdiPatBlt( hdc, r.left, r.top, 1, r.bottom - r.top, PATCOPY );
2818 NtGdiPatBlt( hdc, r.right - 1, r.top, 1, r.bottom - r.top, PATCOPY );
2819 NtGdiPatBlt( hdc, r.left, r.top, r.right - r.left, 1, PATCOPY );
2820 NtGdiPatBlt( hdc, r.left, r.bottom - 1, r.right - r.left, 1, PATCOPY );
2822 NtGdiSelectBrush( hdc, prev_brush );
2823 return TRUE;
2826 static void draw_popup_menu( HWND hwnd, HDC hdc, HMENU hmenu )
2828 HBRUSH prev_hrush, brush = get_sys_color_brush( COLOR_MENU );
2829 struct menu *menu = unsafe_menu_ptr( hmenu );
2830 RECT rect;
2832 TRACE( "wnd=%p dc=%p menu=%p\n", hwnd, hdc, hmenu );
2834 get_client_rect( hwnd, &rect );
2836 if (menu && menu->hbrBack) brush = menu->hbrBack;
2837 if ((prev_hrush = NtGdiSelectBrush( hdc, brush ))
2838 && NtGdiSelectFont( hdc, get_menu_font( FALSE )))
2840 HPEN prev_pen;
2842 NtGdiRectangle( hdc, rect.left, rect.top, rect.right, rect.bottom );
2844 prev_pen = NtGdiSelectPen( hdc, GetStockObject( NULL_PEN ));
2845 if (prev_pen)
2847 BOOL flat_menu = FALSE;
2849 NtUserSystemParametersInfo( SPI_GETFLATMENU, 0, &flat_menu, 0 );
2850 if (flat_menu)
2851 frame_rect( hdc, &rect, get_sys_color_brush( COLOR_BTNSHADOW ));
2852 else
2853 draw_rect_edge( hdc, &rect, EDGE_RAISED, BF_RECT, 1 );
2855 if (menu)
2857 TRACE( "hmenu %p Style %08x\n", hmenu, menu->dwStyle );
2858 /* draw menu items */
2859 if (menu->nItems)
2861 struct menu_item *item;
2862 UINT u;
2864 item = menu->items;
2865 for (u = menu->nItems; u > 0; u--, item++)
2866 draw_menu_item( hwnd, menu, menu->hwndOwner, hdc,
2867 item, FALSE, ODA_DRAWENTIRE );
2869 if (menu->bScrolling) draw_scroll_arrows( menu, hdc );
2872 else
2874 NtGdiSelectBrush( hdc, prev_hrush );
2879 LRESULT popup_menu_window_proc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam )
2881 TRACE( "hwnd=%p msg=0x%04x wp=0x%04lx lp=0x%08lx\n", hwnd, message, wparam, lparam );
2883 switch(message)
2885 case WM_CREATE:
2887 CREATESTRUCTW *cs = (CREATESTRUCTW *)lparam;
2888 NtUserSetWindowLongPtr( hwnd, 0, (LONG_PTR)cs->lpCreateParams, FALSE );
2889 return 0;
2892 case WM_MOUSEACTIVATE: /* We don't want to be activated */
2893 return MA_NOACTIVATE;
2895 case WM_PAINT:
2897 PAINTSTRUCT ps;
2898 NtUserBeginPaint( hwnd, &ps );
2899 draw_popup_menu( hwnd, ps.hdc, (HMENU)get_window_long_ptr( hwnd, 0, FALSE ));
2900 NtUserEndPaint( hwnd, &ps );
2901 return 0;
2904 case WM_PRINTCLIENT:
2906 draw_popup_menu( hwnd, (HDC)wparam, (HMENU)get_window_long_ptr( hwnd, 0, FALSE ));
2907 return 0;
2910 case WM_ERASEBKGND:
2911 return 1;
2913 case WM_DESTROY:
2914 /* zero out global pointer in case resident popup window was destroyed. */
2915 if (hwnd == top_popup)
2917 top_popup = 0;
2918 top_popup_hmenu = NULL;
2920 break;
2922 case WM_SHOWWINDOW:
2923 if (wparam)
2925 if (!get_window_long_ptr( hwnd, 0, FALSE )) ERR( "no menu to display\n" );
2927 else
2928 NtUserSetWindowLongPtr( hwnd, 0, 0, FALSE );
2929 break;
2931 case MN_GETHMENU:
2932 return get_window_long_ptr( hwnd, 0, FALSE );
2934 default:
2935 return default_window_proc( hwnd, message, wparam, lparam, FALSE );
2937 return 0;
2940 HWND is_menu_active(void)
2942 return top_popup;
2945 /* Calculate the size of a popup menu */
2946 static void calc_popup_menu_size( struct menu *menu, UINT max_height )
2948 BOOL textandbmp = FALSE, multi_col = FALSE;
2949 int org_x, org_y, max_tab, max_tab_width;
2950 struct menu_item *item;
2951 UINT start, i;
2952 HDC hdc;
2954 menu->Width = menu->Height = 0;
2955 SetRectEmpty( &menu->items_rect );
2957 if (menu->nItems == 0) return;
2958 hdc = NtUserGetDCEx( 0, 0, DCX_CACHE | DCX_WINDOW );
2960 NtGdiSelectFont( hdc, get_menu_font( FALSE ));
2962 start = 0;
2963 menu->textOffset = 0;
2965 while (start < menu->nItems)
2967 item = &menu->items[start];
2968 org_x = menu->items_rect.right;
2969 if (item->fType & (MF_MENUBREAK | MF_MENUBARBREAK))
2970 org_x += MENU_COL_SPACE;
2971 org_y = menu->items_rect.top;
2973 max_tab = max_tab_width = 0;
2974 /* Parse items until column break or end of menu */
2975 for (i = start; i < menu->nItems; i++, item++)
2977 if (item->fType & (MF_MENUBREAK | MF_MENUBARBREAK))
2979 multi_col = TRUE;
2980 if (i != start) break;
2983 calc_menu_item_size( hdc, item, menu->hwndOwner, org_x, org_y, FALSE, menu );
2984 menu->items_rect.right = max( menu->items_rect.right, item->rect.right );
2985 org_y = item->rect.bottom;
2986 if (IS_STRING_ITEM( item->fType ) && item->xTab)
2988 max_tab = max( max_tab, item->xTab );
2989 max_tab_width = max( max_tab_width, item->rect.right-item->xTab );
2991 if (item->text && item->hbmpItem) textandbmp = TRUE;
2994 /* Finish the column (set all items to the largest width found) */
2995 menu->items_rect.right = max( menu->items_rect.right, max_tab + max_tab_width );
2996 for (item = &menu->items[start]; start < i; start++, item++)
2998 item->rect.right = menu->items_rect.right;
2999 if (IS_STRING_ITEM( item->fType ) && item->xTab)
3000 item->xTab = max_tab;
3002 menu->items_rect.bottom = max( menu->items_rect.bottom, org_y );
3005 /* If none of the items have both text and bitmap then
3006 * the text and bitmaps are all aligned on the left. If there is at
3007 * least one item with both text and bitmap then bitmaps are
3008 * on the left and texts left aligned with the right hand side
3009 * of the bitmaps */
3010 if (!textandbmp) menu->textOffset = 0;
3012 menu->nTotalHeight = menu->items_rect.bottom;
3014 /* space for the border */
3015 OffsetRect( &menu->items_rect, MENU_MARGIN, MENU_MARGIN );
3016 menu->Height = menu->items_rect.bottom + MENU_MARGIN;
3017 menu->Width = menu->items_rect.right + MENU_MARGIN;
3019 /* Adjust popup height if it exceeds maximum */
3020 if (menu->Height >= max_height)
3022 menu->Height = max_height;
3023 menu->bScrolling = !multi_col;
3024 /* When the scroll arrows are present, don't add the top/bottom margin as well */
3025 if (menu->bScrolling)
3027 menu->items_rect.top = get_scroll_arrow_height( menu );
3028 menu->items_rect.bottom = menu->Height - get_scroll_arrow_height( menu );
3031 else
3033 menu->bScrolling = FALSE;
3036 NtUserReleaseDC( 0, hdc );
3039 static BOOL show_popup( HWND owner, HMENU hmenu, UINT id, UINT flags,
3040 int x, int y, INT xanchor, INT yanchor )
3042 struct menu *menu;
3043 HMONITOR monitor;
3044 MONITORINFO info;
3045 UINT max_height;
3046 POINT pt;
3048 TRACE( "owner=%p hmenu=%p id=0x%04x x=0x%04x y=0x%04x xa=0x%04x ya=0x%04x\n",
3049 owner, hmenu, id, x, y, xanchor, yanchor );
3051 if (!(menu = unsafe_menu_ptr( hmenu ))) return FALSE;
3052 if (menu->FocusedItem != NO_SELECTED_ITEM)
3054 menu->items[menu->FocusedItem].fState &= ~(MF_HILITE|MF_MOUSESELECT);
3055 menu->FocusedItem = NO_SELECTED_ITEM;
3058 menu->nScrollPos = 0;
3060 /* FIXME: should use item rect */
3061 pt.x = x;
3062 pt.y = y;
3063 monitor = monitor_from_point( pt, MONITOR_DEFAULTTONEAREST, get_thread_dpi() );
3064 info.cbSize = sizeof(info);
3065 get_monitor_info( monitor, &info );
3067 max_height = info.rcWork.bottom - info.rcWork.top;
3068 if (menu->cyMax) max_height = min( max_height, menu->cyMax );
3069 calc_popup_menu_size( menu, max_height );
3071 /* adjust popup menu pos so that it fits within the desktop */
3072 if (flags & TPM_LAYOUTRTL) flags ^= TPM_RIGHTALIGN;
3074 if (flags & TPM_RIGHTALIGN) x -= menu->Width;
3075 if (flags & TPM_CENTERALIGN) x -= menu->Width / 2;
3077 if (flags & TPM_BOTTOMALIGN) y -= menu->Height;
3078 if (flags & TPM_VCENTERALIGN) y -= menu->Height / 2;
3080 if (x + menu->Width > info.rcWork.right)
3082 if (xanchor && x >= menu->Width - xanchor) x -= menu->Width - xanchor;
3083 if (x + menu->Width > info.rcWork.right) x = info.rcWork.right - menu->Width;
3085 if (x < info.rcWork.left) x = info.rcWork.left;
3087 if (y + menu->Height > info.rcWork.bottom)
3089 if (yanchor && y >= menu->Height + yanchor) y -= menu->Height + yanchor;
3090 if (y + menu->Height > info.rcWork.bottom) y = info.rcWork.bottom - menu->Height;
3092 if (y < info.rcWork.top) y = info.rcWork.top;
3094 if (!top_popup)
3096 top_popup = menu->hWnd;
3097 top_popup_hmenu = hmenu;
3100 /* Display the window */
3101 NtUserSetWindowPos( menu->hWnd, HWND_TOPMOST, x, y, menu->Width, menu->Height,
3102 SWP_SHOWWINDOW | SWP_NOACTIVATE );
3103 NtUserRedrawWindow( menu->hWnd, NULL, 0, RDW_UPDATENOW | RDW_ALLCHILDREN );
3104 return TRUE;
3107 static void ensure_menu_item_visible( struct menu *menu, UINT index, HDC hdc )
3109 if (menu->bScrolling)
3111 struct menu_item *item = &menu->items[index];
3112 UINT prev_pos = menu->nScrollPos;
3113 const RECT *rc = &menu->items_rect;
3114 UINT scroll_height = rc->bottom - rc->top;
3116 if (item->rect.bottom > menu->nScrollPos + scroll_height)
3118 menu->nScrollPos = item->rect.bottom - scroll_height;
3119 NtUserScrollWindowEx( menu->hWnd, 0, prev_pos - menu->nScrollPos, rc, rc, 0, NULL,
3120 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN | SW_NODCCACHE );
3122 else if (item->rect.top < menu->nScrollPos)
3124 menu->nScrollPos = item->rect.top;
3125 NtUserScrollWindowEx( menu->hWnd, 0, prev_pos - menu->nScrollPos, rc, rc, 0, NULL,
3126 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN | SW_NODCCACHE );
3129 /* Invalidate the scroll arrows if necessary */
3130 if (prev_pos != menu->nScrollPos)
3132 RECT arrow_rect = menu->items_rect;
3133 if (prev_pos == 0 || menu->nScrollPos == 0)
3135 arrow_rect.top = 0;
3136 arrow_rect.bottom = menu->items_rect.top;
3137 NtUserInvalidateRect( menu->hWnd, &arrow_rect, FALSE );
3139 if (prev_pos + scroll_height == menu->nTotalHeight ||
3140 menu->nScrollPos + scroll_height == menu->nTotalHeight)
3142 arrow_rect.top = menu->items_rect.bottom;
3143 arrow_rect.bottom = menu->Height;
3144 NtUserInvalidateRect( menu->hWnd, &arrow_rect, FALSE );
3150 static void select_item( HWND owner, HMENU hmenu, UINT index, BOOL send_select, HMENU topmenu )
3152 struct menu *menu;
3153 HDC hdc;
3155 TRACE( "owner %p menu %p index 0x%04x select 0x%04x\n", owner, hmenu, index, send_select );
3157 menu = unsafe_menu_ptr( hmenu );
3158 if (!menu || !menu->nItems || !menu->hWnd) return;
3160 if (menu->FocusedItem == index) return;
3161 if (menu->wFlags & MF_POPUP) hdc = NtUserGetDCEx( menu->hWnd, 0, DCX_USESTYLE );
3162 else hdc = NtUserGetDCEx( menu->hWnd, 0, DCX_CACHE | DCX_WINDOW);
3163 if (!top_popup)
3165 top_popup = menu->hWnd;
3166 top_popup_hmenu = hmenu;
3169 NtGdiSelectFont( hdc, get_menu_font( FALSE ));
3171 /* Clear previous highlighted item */
3172 if (menu->FocusedItem != NO_SELECTED_ITEM)
3174 menu->items[menu->FocusedItem].fState &= ~(MF_HILITE|MF_MOUSESELECT);
3175 draw_menu_item( menu->hWnd, menu, owner, hdc, &menu->items[menu->FocusedItem],
3176 !(menu->wFlags & MF_POPUP), ODA_SELECT );
3179 /* Highlight new item (if any) */
3180 menu->FocusedItem = index;
3181 if (menu->FocusedItem != NO_SELECTED_ITEM)
3183 if (!(menu->items[index].fType & MF_SEPARATOR))
3185 menu->items[index].fState |= MF_HILITE;
3186 ensure_menu_item_visible( menu, index, hdc );
3187 draw_menu_item( menu->hWnd, menu, owner, hdc, &menu->items[index],
3188 !(menu->wFlags & MF_POPUP), ODA_SELECT );
3190 if (send_select)
3192 struct menu_item *ip = &menu->items[menu->FocusedItem];
3193 send_message( owner, WM_MENUSELECT,
3194 MAKEWPARAM( ip->fType & MF_POPUP ? index: ip->wID,
3195 ip->fType | ip->fState | (menu->wFlags & MF_SYSMENU) ),
3196 (LPARAM)hmenu );
3199 else if (send_select)
3201 if (topmenu)
3203 int pos = find_submenu( &topmenu, hmenu );
3204 if (pos != NO_SELECTED_ITEM)
3206 struct menu *ptm = unsafe_menu_ptr( topmenu );
3207 struct menu_item *ip = &ptm->items[pos];
3208 send_message( owner, WM_MENUSELECT,
3209 MAKEWPARAM( pos, ip->fType | ip->fState | (ptm->wFlags & MF_SYSMENU) ),
3210 (LPARAM)topmenu );
3214 NtUserReleaseDC( menu->hWnd, hdc );
3217 /***********************************************************************
3218 * move_selection
3220 * Moves currently selected item according to the offset parameter.
3221 * If there is no selection then it should select the last item if
3222 * offset is ITEM_PREV or the first item if offset is ITEM_NEXT.
3224 static void move_selection( HWND owner, HMENU hmenu, INT offset )
3226 struct menu *menu;
3227 int i;
3229 TRACE( "hwnd %p hmenu %p off 0x%04x\n", owner, hmenu, offset );
3231 menu = unsafe_menu_ptr( hmenu );
3232 if (!menu || !menu->items) return;
3234 if (menu->FocusedItem != NO_SELECTED_ITEM)
3236 if (menu->nItems == 1) return;
3237 for (i = menu->FocusedItem + offset; i >= 0 && i < menu->nItems; i += offset)
3239 if (menu->items[i].fType & MF_SEPARATOR) continue;
3240 select_item( owner, hmenu, i, TRUE, 0 );
3241 return;
3245 for (i = (offset > 0) ? 0 : menu->nItems - 1; i >= 0 && i < menu->nItems; i += offset)
3247 if (menu->items[i].fType & MF_SEPARATOR) continue;
3248 select_item( owner, hmenu, i, TRUE, 0 );
3249 return;
3253 static void hide_sub_popups( HWND owner, HMENU hmenu, BOOL send_select, UINT flags )
3255 struct menu *menu = unsafe_menu_ptr( hmenu );
3257 TRACE( "owner=%p hmenu=%p 0x%04x\n", owner, hmenu, send_select );
3259 if (menu && top_popup)
3261 struct menu *submenu;
3262 struct menu_item *item;
3263 HMENU hsubmenu;
3265 if (menu->FocusedItem == NO_SELECTED_ITEM) return;
3267 item = &menu->items[menu->FocusedItem];
3268 if (!(item->fType & MF_POPUP) || !(item->fState & MF_MOUSESELECT)) return;
3269 item->fState &= ~MF_MOUSESELECT;
3270 hsubmenu = item->hSubMenu;
3272 if (!(submenu = unsafe_menu_ptr( hsubmenu ))) return;
3273 hide_sub_popups( owner, hsubmenu, FALSE, flags );
3274 select_item( owner, hsubmenu, NO_SELECTED_ITEM, send_select, 0 );
3275 NtUserDestroyWindow( submenu->hWnd );
3276 submenu->hWnd = 0;
3278 if (!(flags & TPM_NONOTIFY))
3279 send_message( owner, WM_UNINITMENUPOPUP, (WPARAM)hsubmenu,
3280 MAKELPARAM( 0, IS_SYSTEM_MENU( submenu )));
3284 static void init_sys_menu_popup( HMENU hmenu, DWORD style, DWORD class_style )
3286 BOOL gray;
3288 /* Grey the appropriate items in System menu */
3289 gray = !(style & WS_THICKFRAME) || (style & (WS_MAXIMIZE | WS_MINIMIZE));
3290 NtUserEnableMenuItem( hmenu, SC_SIZE, gray ? MF_GRAYED : MF_ENABLED );
3291 gray = ((style & WS_MAXIMIZE) != 0);
3292 NtUserEnableMenuItem( hmenu, SC_MOVE, gray ? MF_GRAYED : MF_ENABLED );
3293 gray = !(style & WS_MINIMIZEBOX) || (style & WS_MINIMIZE);
3294 NtUserEnableMenuItem( hmenu, SC_MINIMIZE, gray ? MF_GRAYED : MF_ENABLED );
3295 gray = !(style & WS_MAXIMIZEBOX) || (style & WS_MAXIMIZE);
3296 NtUserEnableMenuItem( hmenu, SC_MAXIMIZE, gray ? MF_GRAYED : MF_ENABLED );
3297 gray = !(style & (WS_MAXIMIZE | WS_MINIMIZE));
3298 NtUserEnableMenuItem( hmenu, SC_RESTORE, gray ? MF_GRAYED : MF_ENABLED );
3299 gray = (class_style & CS_NOCLOSE) != 0;
3301 /* The menu item must keep its state if it's disabled */
3302 if (gray) NtUserEnableMenuItem( hmenu, SC_CLOSE, MF_GRAYED );
3305 static BOOL init_popup( HWND owner, HMENU hmenu, UINT flags )
3307 UNICODE_STRING class_name = { .Buffer = MAKEINTRESOURCEW( POPUPMENU_CLASS_ATOM ) };
3308 DWORD ex_style = 0;
3309 struct menu *menu;
3311 TRACE( "owner %p hmenu %p\n", owner, hmenu );
3313 if (!(menu = unsafe_menu_ptr( hmenu ))) return FALSE;
3315 /* store the owner for DrawItem */
3316 if (!is_window( owner ))
3318 RtlSetLastWin32Error( ERROR_INVALID_WINDOW_HANDLE );
3319 return FALSE;
3321 menu->hwndOwner = owner;
3323 if (flags & TPM_LAYOUTRTL) ex_style = WS_EX_LAYOUTRTL;
3325 /* NOTE: In Windows, top menu popup is not owned. */
3326 menu->hWnd = NtUserCreateWindowEx( ex_style, &class_name, &class_name, NULL,
3327 WS_POPUP, 0, 0, 0, 0, owner, 0,
3328 (HINSTANCE)get_window_long_ptr( owner, GWLP_HINSTANCE, FALSE ),
3329 (void *)hmenu, 0, NULL, 0, FALSE );
3330 return !!menu->hWnd;
3334 /***********************************************************************
3335 * show_sub_popup
3337 * Display the sub-menu of the selected item of this menu.
3338 * Return the handle of the submenu, or hmenu if no submenu to display.
3340 static HMENU show_sub_popup( HWND owner, HMENU hmenu, BOOL select_first, UINT flags )
3342 struct menu *menu;
3343 struct menu_item *item;
3344 RECT rect;
3345 HDC hdc;
3347 TRACE( "owner %p hmenu %p 0x%04x\n", owner, hmenu, select_first );
3349 if (!(menu = unsafe_menu_ptr( hmenu ))) return hmenu;
3350 if (menu->FocusedItem == NO_SELECTED_ITEM) return hmenu;
3352 item = &menu->items[menu->FocusedItem];
3353 if (!(item->fType & MF_POPUP) || (item->fState & (MF_GRAYED | MF_DISABLED)))
3354 return hmenu;
3356 /* Send WM_INITMENUPOPUP message only if TPM_NONOTIFY flag is not specified */
3357 if (!(flags & TPM_NONOTIFY))
3358 send_message( owner, WM_INITMENUPOPUP, (WPARAM)item->hSubMenu,
3359 MAKELPARAM( menu->FocusedItem, IS_SYSTEM_MENU( menu )));
3361 item = &menu->items[menu->FocusedItem];
3362 rect = item->rect;
3364 /* correct item if modified as a reaction to WM_INITMENUPOPUP message */
3365 if (!(item->fState & MF_HILITE))
3367 if (menu->wFlags & MF_POPUP) hdc = NtUserGetDCEx( menu->hWnd, 0, DCX_USESTYLE );
3368 else hdc = NtUserGetDCEx( menu->hWnd, 0, DCX_CACHE | DCX_WINDOW );
3370 NtGdiSelectFont( hdc, get_menu_font( FALSE ));
3372 item->fState |= MF_HILITE;
3373 draw_menu_item( menu->hWnd, menu, owner, hdc, item, !(menu->wFlags & MF_POPUP), ODA_DRAWENTIRE );
3374 NtUserReleaseDC( menu->hWnd, hdc );
3376 if (!item->rect.top && !item->rect.left && !item->rect.bottom && !item->rect.right)
3377 item->rect = rect;
3379 item->fState |= MF_MOUSESELECT;
3381 if (IS_SYSTEM_MENU( menu ))
3383 init_sys_menu_popup( item->hSubMenu,
3384 get_window_long( menu->hWnd, GWL_STYLE ),
3385 get_class_long( menu->hWnd, GCL_STYLE, FALSE ));
3387 get_sys_popup_pos( menu->hWnd, &rect );
3388 if (flags & TPM_LAYOUTRTL) rect.left = rect.right;
3389 rect.top = rect.bottom;
3390 rect.right = get_system_metrics( SM_CXSIZE );
3391 rect.bottom = get_system_metrics( SM_CYSIZE );
3393 else
3395 RECT item_rect = item->rect;
3397 adjust_menu_item_rect( menu, &item_rect );
3398 get_window_rect( menu->hWnd, &rect, get_thread_dpi() );
3400 if (menu->wFlags & MF_POPUP)
3402 /* The first item in the popup menu has to be at the
3403 same y position as the focused menu item */
3404 if (flags & TPM_LAYOUTRTL)
3405 rect.left += get_system_metrics( SM_CXBORDER );
3406 else
3407 rect.left += item_rect.right - get_system_metrics( SM_CXBORDER );
3408 rect.top += item_rect.top - MENU_MARGIN;
3409 rect.right = item_rect.left - item_rect.right + get_system_metrics( SM_CXBORDER );
3410 rect.bottom = item_rect.top - item_rect.bottom - 2 * MENU_MARGIN;
3412 else
3414 if (flags & TPM_LAYOUTRTL)
3415 rect.left = rect.right - item_rect.left;
3416 else
3417 rect.left += item_rect.left;
3418 rect.top += item_rect.bottom;
3419 rect.right = item_rect.right - item_rect.left;
3420 rect.bottom = item_rect.bottom - item_rect.top;
3424 /* use default alignment for submenus */
3425 flags &= ~(TPM_CENTERALIGN | TPM_RIGHTALIGN | TPM_VCENTERALIGN | TPM_BOTTOMALIGN);
3426 init_popup( owner, item->hSubMenu, flags );
3427 show_popup( owner, item->hSubMenu, menu->FocusedItem, flags,
3428 rect.left, rect.top, rect.right, rect.bottom );
3429 if (select_first) move_selection( owner, item->hSubMenu, ITEM_NEXT );
3430 return item->hSubMenu;
3433 /***********************************************************************
3434 * exec_focused_item
3436 * Execute a menu item (for instance when user pressed Enter).
3437 * Return the wID of the executed item. Otherwise, -1 indicating
3438 * that no menu item was executed, -2 if a popup is shown;
3439 * Have to receive the flags for the NtUserTrackPopupMenuEx options to avoid
3440 * sending unwanted message.
3442 static INT exec_focused_item( MTRACKER *pmt, HMENU handle, UINT flags )
3444 struct menu_item *item;
3445 struct menu *menu = unsafe_menu_ptr( handle );
3447 TRACE( "%p hmenu=%p\n", pmt, handle );
3449 if (!menu || !menu->nItems || menu->FocusedItem == NO_SELECTED_ITEM) return -1;
3450 item = &menu->items[menu->FocusedItem];
3452 TRACE( "handle %p ID %08lx submenu %p type %04x\n", handle, item->wID,
3453 item->hSubMenu, item->fType );
3455 if ((item->fType & MF_POPUP))
3457 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, handle, TRUE, flags );
3458 return -2;
3461 if ((item->fState & (MF_GRAYED | MF_DISABLED)) || (item->fType & MF_SEPARATOR))
3462 return -1;
3464 /* If TPM_RETURNCMD is set you return the id, but
3465 do not send a message to the owner */
3466 if (!(flags & TPM_RETURNCMD))
3468 if (menu->wFlags & MF_SYSMENU)
3469 NtUserPostMessage( pmt->hOwnerWnd, WM_SYSCOMMAND, item->wID,
3470 MAKELPARAM( (INT16)pmt->pt.x, (INT16)pmt->pt.y ));
3471 else
3473 struct menu *topmenu = unsafe_menu_ptr( pmt->hTopMenu );
3474 DWORD style = menu->dwStyle | (topmenu ? topmenu->dwStyle : 0);
3476 if (style & MNS_NOTIFYBYPOS)
3477 NtUserPostMessage( pmt->hOwnerWnd, WM_MENUCOMMAND, menu->FocusedItem,
3478 (LPARAM)handle);
3479 else
3480 NtUserPostMessage( pmt->hOwnerWnd, WM_COMMAND, item->wID, 0 );
3484 return item->wID;
3487 /***********************************************************************
3488 * switch_tracking
3490 * Helper function for menu navigation routines.
3492 static void switch_tracking( MTRACKER *pmt, HMENU pt_menu, UINT id, UINT flags )
3494 struct menu *ptmenu = unsafe_menu_ptr( pt_menu );
3495 struct menu *topmenu = unsafe_menu_ptr( pmt->hTopMenu );
3497 TRACE( "%p hmenu=%p 0x%04x\n", pmt, pt_menu, id );
3499 if (pmt->hTopMenu != pt_menu && !((ptmenu->wFlags | topmenu->wFlags) & MF_POPUP))
3501 /* both are top level menus (system and menu-bar) */
3502 hide_sub_popups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, flags );
3503 select_item( pmt->hOwnerWnd, pmt->hTopMenu, NO_SELECTED_ITEM, FALSE, 0 );
3504 pmt->hTopMenu = pt_menu;
3506 else hide_sub_popups( pmt->hOwnerWnd, pt_menu, FALSE, flags );
3507 select_item( pmt->hOwnerWnd, pt_menu, id, TRUE, 0 );
3510 /***********************************************************************
3511 * menu_button_down
3513 * Return TRUE if we can go on with menu tracking.
3515 static BOOL menu_button_down( MTRACKER *pmt, UINT message, HMENU pt_menu, UINT flags )
3517 TRACE( "%p pt_menu=%p\n", pmt, pt_menu );
3519 if (pt_menu)
3521 struct menu *ptmenu = unsafe_menu_ptr( pt_menu );
3522 enum hittest ht = ht_item;
3523 UINT pos;
3525 if (IS_SYSTEM_MENU( ptmenu ))
3527 if (message == WM_LBUTTONDBLCLK) return FALSE;
3528 pos = 0;
3530 else
3531 ht = find_item_by_coords( ptmenu, pmt->pt, &pos );
3533 if (pos != NO_SELECTED_ITEM)
3535 if (ptmenu->FocusedItem != pos)
3536 switch_tracking( pmt, pt_menu, pos, flags );
3538 /* If the popup menu is not already "popped" */
3539 if (!(ptmenu->items[pos].fState & MF_MOUSESELECT))
3540 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pt_menu, FALSE, flags );
3543 /* A click on an item or anywhere on a popup keeps tracking going */
3544 if (ht == ht_item || ((ptmenu->wFlags & MF_POPUP) && ht != ht_nowhere))
3545 return TRUE;
3547 return FALSE;
3550 /***********************************************************************
3551 * menu_button_up
3553 * Return the value of exec_focused_item if
3554 * the selected item was not a popup. Else open the popup.
3555 * A -1 return value indicates that we go on with menu tracking.
3558 static INT menu_button_up( MTRACKER *pmt, HMENU pt_menu, UINT flags )
3560 TRACE( "%p hmenu=%p\n", pmt, pt_menu );
3562 if (pt_menu)
3564 struct menu *ptmenu = unsafe_menu_ptr( pt_menu );
3565 UINT pos;
3567 if (IS_SYSTEM_MENU( ptmenu ))
3568 pos = 0;
3569 else if (find_item_by_coords( ptmenu, pmt->pt, &pos ) != ht_item)
3570 pos = NO_SELECTED_ITEM;
3572 if (pos != NO_SELECTED_ITEM && (ptmenu->FocusedItem == pos))
3574 TRACE( "%s\n", debugstr_menuitem( &ptmenu->items[pos] ));
3576 if (!(ptmenu->items[pos].fType & MF_POPUP))
3578 INT executedMenuId = exec_focused_item( pmt, pt_menu, flags );
3579 if (executedMenuId == -1 || executedMenuId == -2) return -1;
3580 return executedMenuId;
3583 /* If we are dealing with the menu bar and this is a click on an
3584 * already "popped" item: Stop the menu tracking and close the
3585 * opened submenus */
3586 if(((pmt->hTopMenu == pt_menu) || IS_SYSTEM_MENU( ptmenu )) &&
3587 (pmt->trackFlags & TF_RCVD_BTN_UP))
3588 return 0;
3591 if (get_menu( ptmenu->hWnd ) == pt_menu || IS_SYSTEM_MENU( ptmenu ))
3593 if (pos == NO_SELECTED_ITEM) return 0;
3594 pmt->trackFlags |= TF_RCVD_BTN_UP;
3597 return -1;
3600 /***********************************************************************
3601 * end_menu
3603 * Call NtUserEndMenu() if the hwnd parameter belongs to the menu owner.
3605 void end_menu( HWND hwnd )
3607 struct menu *menu;
3608 BOOL call_end = FALSE;
3609 if (top_popup_hmenu && (menu = grab_menu_ptr( top_popup_hmenu )))
3611 call_end = hwnd == menu->hWnd || hwnd == menu->hwndOwner;
3612 release_menu_ptr( menu );
3614 if (call_end) NtUserEndMenu();
3617 /***********************************************************************
3618 * menu_mouse_move
3620 * Return TRUE if we can go on with menu tracking.
3622 static BOOL menu_mouse_move( MTRACKER* pmt, HMENU pt_menu, UINT flags )
3624 UINT id = NO_SELECTED_ITEM;
3625 struct menu *ptmenu = NULL;
3627 if (pt_menu)
3629 ptmenu = unsafe_menu_ptr( pt_menu );
3630 if (IS_SYSTEM_MENU( ptmenu ))
3631 id = 0;
3632 else if (find_item_by_coords( ptmenu, pmt->pt, &id ) != ht_item)
3633 id = NO_SELECTED_ITEM;
3636 if (id == NO_SELECTED_ITEM)
3638 select_item( pmt->hOwnerWnd, pmt->hCurrentMenu, NO_SELECTED_ITEM, TRUE, pmt->hTopMenu );
3640 else if (ptmenu->FocusedItem != id)
3642 switch_tracking( pmt, pt_menu, id, flags );
3643 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pt_menu, FALSE, flags );
3645 return TRUE;
3648 static LRESULT do_next_menu( MTRACKER *pmt, UINT vk, UINT flags )
3650 struct menu *menu = unsafe_menu_ptr( pmt->hTopMenu );
3651 BOOL at_end = FALSE;
3653 if (vk == VK_LEFT && menu->FocusedItem == 0)
3655 /* When skipping left, we need to do something special after the first menu */
3656 at_end = TRUE;
3658 else if (vk == VK_RIGHT && !IS_SYSTEM_MENU( menu ))
3660 /* When skipping right, for the non-system menu, we need to
3661 * handle the last non-special menu item (ie skip any window
3662 * icons such as MDI maximize, restore or close) */
3663 UINT i = menu->FocusedItem + 1;
3664 while (i < menu->nItems)
3666 if (menu->items[i].wID < SC_SIZE || menu->items[i].wID > SC_RESTORE) break;
3667 i++;
3669 if (i == menu->nItems) at_end = TRUE;
3671 else if (vk == VK_RIGHT && IS_SYSTEM_MENU( menu ))
3673 /* When skipping right, we need to cater for the system menu */
3674 if (menu->FocusedItem == menu->nItems - 1) at_end = TRUE;
3677 if (at_end)
3679 MDINEXTMENU next_menu;
3680 HMENU new_menu;
3681 HWND new_hwnd;
3682 UINT id = 0;
3684 next_menu.hmenuIn = (IS_SYSTEM_MENU( menu )) ? get_sub_menu( pmt->hTopMenu, 0 ) : pmt->hTopMenu;
3685 next_menu.hmenuNext = 0;
3686 next_menu.hwndNext = 0;
3687 send_message( pmt->hOwnerWnd, WM_NEXTMENU, vk, (LPARAM)&next_menu );
3689 TRACE( "%p [%p] -> %p [%p]\n", pmt->hCurrentMenu, pmt->hOwnerWnd, next_menu.hmenuNext,
3690 next_menu.hwndNext );
3692 if (!next_menu.hmenuNext || !next_menu.hwndNext)
3694 DWORD style = get_window_long( pmt->hOwnerWnd, GWL_STYLE );
3695 new_hwnd = pmt->hOwnerWnd;
3696 if (IS_SYSTEM_MENU( menu ))
3698 /* switch to the menu bar */
3699 if ((style & WS_CHILD) || !(new_menu = get_menu( new_hwnd ))) return FALSE;
3701 if (vk == VK_LEFT)
3703 menu = unsafe_menu_ptr( new_menu );
3704 id = menu->nItems - 1;
3706 /* Skip backwards over any system predefined icons,
3707 * eg. MDI close, restore etc icons */
3708 while (id > 0 &&
3709 menu->items[id].wID >= SC_SIZE && menu->items[id].wID <= SC_RESTORE)
3710 id--;
3713 else if (style & WS_SYSMENU)
3715 /* switch to the system menu */
3716 new_menu = get_win_sys_menu( new_hwnd );
3718 else return FALSE;
3720 else /* application returned a new menu to switch to */
3722 new_menu = next_menu.hmenuNext;
3723 new_hwnd = get_full_window_handle( next_menu.hwndNext );
3725 if (is_menu( new_menu ) && is_window( new_hwnd ))
3727 DWORD style = get_window_long( new_hwnd, GWL_STYLE );
3729 if (style & WS_SYSMENU && get_sub_menu(get_win_sys_menu( new_hwnd ), 0) == new_menu)
3731 /* get the real system menu */
3732 new_menu = get_win_sys_menu( new_hwnd );
3734 else if (style & WS_CHILD || get_menu( new_hwnd ) != new_menu )
3736 /* FIXME: what should we do? */
3737 TRACE( " -- got confused.\n" );
3738 return FALSE;
3741 else return FALSE;
3744 if (new_menu != pmt->hTopMenu)
3746 select_item( pmt->hOwnerWnd, pmt->hTopMenu, NO_SELECTED_ITEM, FALSE, 0 );
3747 if (pmt->hCurrentMenu != pmt->hTopMenu)
3748 hide_sub_popups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, flags );
3751 if (new_hwnd != pmt->hOwnerWnd)
3753 pmt->hOwnerWnd = new_hwnd;
3754 set_capture_window( pmt->hOwnerWnd, GUI_INMENUMODE, NULL );
3757 pmt->hTopMenu = pmt->hCurrentMenu = new_menu; /* all subpopups are hidden */
3758 select_item( pmt->hOwnerWnd, pmt->hTopMenu, id, TRUE, 0 );
3759 return TRUE;
3762 return FALSE;
3765 /***********************************************************************
3766 * get_sub_popup
3768 * Return the handle of the selected sub-popup menu (if any).
3770 static HMENU get_sub_popup( HMENU hmenu )
3772 struct menu *menu;
3773 struct menu_item *item;
3775 menu = unsafe_menu_ptr( hmenu );
3777 if (!menu || menu->FocusedItem == NO_SELECTED_ITEM) return 0;
3779 item = &menu->items[menu->FocusedItem];
3780 if ((item->fType & MF_POPUP) && (item->fState & MF_MOUSESELECT))
3781 return item->hSubMenu;
3782 return 0;
3785 /***********************************************************************
3786 * menu_key_escape
3788 * Handle a VK_ESCAPE key event in a menu.
3790 static BOOL menu_key_escape( MTRACKER *pmt, UINT flags )
3792 BOOL ret = TRUE;
3794 if (pmt->hCurrentMenu != pmt->hTopMenu)
3796 struct menu *menu = unsafe_menu_ptr( pmt->hCurrentMenu );
3798 if (menu->wFlags & MF_POPUP)
3800 HMENU top, prev_menu;
3802 prev_menu = top = pmt->hTopMenu;
3804 /* close topmost popup */
3805 while (top != pmt->hCurrentMenu)
3807 prev_menu = top;
3808 top = get_sub_popup( prev_menu );
3811 hide_sub_popups( pmt->hOwnerWnd, prev_menu, TRUE, flags );
3812 pmt->hCurrentMenu = prev_menu;
3813 ret = FALSE;
3817 return ret;
3820 static UINT get_start_of_next_column( HMENU handle )
3822 struct menu *menu = unsafe_menu_ptr( handle );
3823 UINT i;
3825 if (!menu) return NO_SELECTED_ITEM;
3827 i = menu->FocusedItem + 1;
3828 if (i == NO_SELECTED_ITEM) return i;
3830 while (i < menu->nItems)
3832 if (menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK))
3833 return i;
3834 i++;
3837 return NO_SELECTED_ITEM;
3840 static UINT get_start_of_prev_column( HMENU handle )
3842 struct menu *menu = unsafe_menu_ptr( handle );
3843 UINT i;
3845 if (!menu) return NO_SELECTED_ITEM;
3847 if (menu->FocusedItem == 0 || menu->FocusedItem == NO_SELECTED_ITEM)
3848 return NO_SELECTED_ITEM;
3850 /* Find the start of the column */
3851 i = menu->FocusedItem;
3852 while (i != 0 && !(menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK))) i--;
3853 if (i == 0) return NO_SELECTED_ITEM;
3855 for (--i; i != 0; --i)
3857 if (menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK))
3858 break;
3861 TRACE( "ret %d.\n", i );
3862 return i;
3865 /***********************************************************************
3866 * suspend_popup
3868 * Avoid showing the popup if the next input message is going to hide it anyway.
3870 static BOOL suspend_popup( MTRACKER *pmt, UINT message )
3872 MSG msg;
3874 msg.hwnd = pmt->hOwnerWnd;
3875 NtUserPeekMessage( &msg, 0, message, message, PM_NOYIELD | PM_REMOVE );
3876 pmt->trackFlags |= TF_SKIPREMOVE;
3878 switch (message)
3880 case WM_KEYDOWN:
3881 NtUserPeekMessage( &msg, 0, 0, 0, PM_NOYIELD | PM_NOREMOVE );
3882 if (msg.message == WM_KEYUP || msg.message == WM_PAINT)
3884 NtUserPeekMessage( &msg, 0, 0, 0, PM_NOYIELD | PM_REMOVE );
3885 NtUserPeekMessage( &msg, 0, 0, 0, PM_NOYIELD | PM_NOREMOVE );
3886 if (msg.message == WM_KEYDOWN && (msg.wParam == VK_LEFT || msg.wParam == VK_RIGHT))
3888 pmt->trackFlags |= TF_SUSPENDPOPUP;
3889 return TRUE;
3892 break;
3895 /* failures go through this */
3896 pmt->trackFlags &= ~TF_SUSPENDPOPUP;
3897 return FALSE;
3900 static void menu_key_left( MTRACKER *pmt, UINT flags, UINT msg )
3902 struct menu *menu;
3903 HMENU tmp_menu, prev_menu;
3904 UINT prevcol;
3906 prev_menu = tmp_menu = pmt->hTopMenu;
3907 menu = unsafe_menu_ptr( tmp_menu );
3909 /* Try to move 1 column left (if possible) */
3910 if ((prevcol = get_start_of_prev_column( pmt->hCurrentMenu )) != NO_SELECTED_ITEM)
3912 select_item( pmt->hOwnerWnd, pmt->hCurrentMenu, prevcol, TRUE, 0 );
3913 return;
3916 /* close topmost popup */
3917 while (tmp_menu != pmt->hCurrentMenu)
3919 prev_menu = tmp_menu;
3920 tmp_menu = get_sub_popup( prev_menu );
3923 hide_sub_popups( pmt->hOwnerWnd, prev_menu, TRUE, flags );
3924 pmt->hCurrentMenu = prev_menu;
3926 if ((prev_menu == pmt->hTopMenu) && !(menu->wFlags & MF_POPUP))
3928 /* move menu bar selection if no more popups are left */
3929 if (!do_next_menu( pmt, VK_LEFT, flags ))
3930 move_selection( pmt->hOwnerWnd, pmt->hTopMenu, ITEM_PREV );
3932 if (prev_menu != tmp_menu || pmt->trackFlags & TF_SUSPENDPOPUP)
3934 /* A sublevel menu was displayed - display the next one
3935 * unless there is another displacement coming up */
3936 if (!suspend_popup( pmt, msg ))
3937 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pmt->hTopMenu, TRUE, flags );
3942 static void menu_right_key( MTRACKER *pmt, UINT flags, UINT msg )
3944 struct menu *menu = unsafe_menu_ptr( pmt->hTopMenu );
3945 HMENU tmp_menu;
3946 UINT nextcol;
3948 TRACE( "menu_right_key called, cur %p (%s), top %p (%s).\n",
3949 pmt->hCurrentMenu, debugstr_w(unsafe_menu_ptr( pmt->hCurrentMenu )->items[0].text ),
3950 pmt->hTopMenu, debugstr_w( menu->items[0].text ));
3952 if ((menu->wFlags & MF_POPUP) || (pmt->hCurrentMenu != pmt->hTopMenu))
3954 /* If already displaying a popup, try to display sub-popup */
3955 tmp_menu = pmt->hCurrentMenu;
3956 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, tmp_menu, TRUE, flags );
3958 /* if subpopup was displayed then we are done */
3959 if (tmp_menu != pmt->hCurrentMenu) return;
3962 /* Check to see if there's another column */
3963 if ((nextcol = get_start_of_next_column( pmt->hCurrentMenu )) != NO_SELECTED_ITEM)
3965 TRACE( "Going to %d.\n", nextcol );
3966 select_item( pmt->hOwnerWnd, pmt->hCurrentMenu, nextcol, TRUE, 0 );
3967 return;
3970 if (!(menu->wFlags & MF_POPUP)) /* menu bar tracking */
3972 if (pmt->hCurrentMenu != pmt->hTopMenu)
3974 hide_sub_popups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, flags );
3975 tmp_menu = pmt->hCurrentMenu = pmt->hTopMenu;
3977 else tmp_menu = 0;
3979 /* try to move to the next item */
3980 if (!do_next_menu( pmt, VK_RIGHT, flags ))
3981 move_selection( pmt->hOwnerWnd, pmt->hTopMenu, ITEM_NEXT );
3983 if (tmp_menu || pmt->trackFlags & TF_SUSPENDPOPUP)
3984 if (!suspend_popup( pmt, msg ))
3985 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pmt->hTopMenu, TRUE, flags );
3989 /***********************************************************************
3990 * menu_from_point
3992 * Walks menu chain trying to find a menu pt maps to.
3994 static HMENU menu_from_point( HMENU handle, POINT pt )
3996 struct menu *menu = unsafe_menu_ptr( handle );
3997 UINT item = menu->FocusedItem;
3998 HMENU ret = 0;
4000 /* try subpopup first (if any) */
4001 if (item != NO_SELECTED_ITEM && (menu->items[item].fType & MF_POPUP) &&
4002 (menu->items[item].fState & MF_MOUSESELECT))
4003 ret = menu_from_point( menu->items[item].hSubMenu, pt );
4005 if (!ret) /* check the current window (avoiding WM_HITTEST) */
4007 INT ht = handle_nc_hit_test( menu->hWnd, pt );
4008 if (menu->wFlags & MF_POPUP)
4010 if (ht != HTNOWHERE && ht != HTERROR) ret = handle;
4012 else if (ht == HTSYSMENU)
4013 ret = get_win_sys_menu( menu->hWnd );
4014 else if (ht == HTMENU)
4015 ret = get_menu( menu->hWnd );
4017 return ret;
4020 /***********************************************************************
4021 * find_item_by_key
4023 * Find the menu item selected by a key press.
4024 * Return item id, -1 if none, -2 if we should close the menu.
4026 static UINT find_item_by_key( HWND owner, HMENU hmenu, WCHAR key, BOOL force_menu_char )
4028 TRACE( "\tlooking for '%c' (0x%02x) in [%p]\n", (char)key, key, hmenu );
4030 if (!is_menu( hmenu )) hmenu = get_sub_menu( get_win_sys_menu( owner ), 0 );
4032 if (hmenu)
4034 struct menu *menu = unsafe_menu_ptr( hmenu );
4035 struct menu_item *item = menu->items;
4036 LRESULT menuchar;
4038 if (!force_menu_char)
4040 BOOL cjk = get_system_metrics( SM_DBCSENABLED );
4041 UINT i;
4043 for (i = 0; i < menu->nItems; i++, item++)
4045 if (item->text)
4047 const WCHAR *p = item->text - 2;
4050 const WCHAR *q = p + 2;
4051 p = wcschr( q, '&' );
4052 if (!p && cjk) p = wcschr( q, '\036' ); /* Japanese Win16 */
4054 while (p && p[1] == '&');
4055 if (p && !wcsnicmp( &p[1], &key, 1)) return i;
4059 menuchar = send_message( owner, WM_MENUCHAR,
4060 MAKEWPARAM( key, menu->wFlags ), (LPARAM)hmenu );
4061 if (HIWORD(menuchar) == MNC_EXECUTE) return LOWORD( menuchar );
4062 if (HIWORD(menuchar) == MNC_CLOSE) return (UINT)-2;
4064 return -1;
4067 static BOOL track_menu( HMENU hmenu, UINT flags, int x, int y, HWND hwnd, const RECT *rect )
4069 BOOL enter_idle_sent = FALSE;
4070 int executed_menu_id = -1;
4071 HWND capture_win;
4072 struct menu *menu;
4073 BOOL remove;
4074 MTRACKER mt;
4075 MSG msg;
4077 mt.trackFlags = 0;
4078 mt.hCurrentMenu = hmenu;
4079 mt.hTopMenu = hmenu;
4080 mt.hOwnerWnd = get_full_window_handle( hwnd );
4081 mt.pt.x = x;
4082 mt.pt.y = y;
4084 TRACE( "hmenu=%p flags=0x%08x (%d,%d) hwnd=%p %s\n",
4085 hmenu, flags, x, y, hwnd, wine_dbgstr_rect( rect ));
4087 if (!(menu = unsafe_menu_ptr( hmenu )))
4089 WARN( "Invalid menu handle %p\n", hmenu );
4090 RtlSetLastWin32Error( ERROR_INVALID_MENU_HANDLE );
4091 return FALSE;
4094 if (flags & TPM_BUTTONDOWN)
4096 /* Get the result in order to start the tracking or not */
4097 remove = menu_button_down( &mt, WM_LBUTTONDOWN, hmenu, flags );
4098 exit_menu = !remove;
4101 if (flags & TF_ENDMENU) exit_menu = TRUE;
4103 /* owner may not be visible when tracking a popup, so use the menu itself */
4104 capture_win = (flags & TPM_POPUPMENU) ? menu->hWnd : mt.hOwnerWnd;
4105 set_capture_window( capture_win, GUI_INMENUMODE, NULL );
4107 if ((flags & TPM_POPUPMENU) && menu->nItems == 0)
4108 return FALSE;
4110 while (!exit_menu)
4112 if (!(menu = unsafe_menu_ptr( mt.hCurrentMenu ))) break;
4114 /* we have to keep the message in the queue until it's
4115 * clear that menu loop is not over yet. */
4116 for (;;)
4118 if (NtUserPeekMessage( &msg, 0, 0, 0, PM_NOREMOVE ))
4120 if (!NtUserCallMsgFilter( &msg, MSGF_MENU )) break;
4121 /* remove the message from the queue */
4122 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
4124 else
4126 if (!enter_idle_sent)
4128 HWND win = (menu->wFlags & MF_POPUP) ? menu->hWnd : 0;
4129 enter_idle_sent = TRUE;
4130 send_message( mt.hOwnerWnd, WM_ENTERIDLE, MSGF_MENU, (LPARAM)win );
4132 NtUserMsgWaitForMultipleObjectsEx( 0, NULL, INFINITE, QS_ALLINPUT, 0 );
4136 /* check if NtUserEndMenu() tried to cancel us, by posting this message */
4137 if (msg.message == WM_CANCELMODE)
4139 exit_menu = TRUE;
4140 /* remove the message from the queue */
4141 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
4142 break;
4145 mt.pt = msg.pt;
4146 if (msg.hwnd == menu->hWnd || msg.message != WM_TIMER) enter_idle_sent = FALSE;
4148 remove = FALSE;
4149 if (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST)
4152 * Use the mouse coordinates in lParam instead of those in the MSG
4153 * struct to properly handle synthetic messages. They are already
4154 * in screen coordinates.
4156 mt.pt.x = (short)LOWORD( msg.lParam );
4157 mt.pt.y = (short)HIWORD( msg.lParam );
4159 /* Find a menu for this mouse event */
4160 hmenu = menu_from_point( mt.hTopMenu, mt.pt );
4162 switch (msg.message)
4164 /* no WM_NC... messages in captured state */
4165 case WM_RBUTTONDBLCLK:
4166 case WM_RBUTTONDOWN:
4167 if (!(flags & TPM_RIGHTBUTTON)) break;
4168 /* fall through */
4169 case WM_LBUTTONDBLCLK:
4170 case WM_LBUTTONDOWN:
4171 /* If the message belongs to the menu, removes it from the queue
4172 * Else, end menu tracking */
4173 remove = menu_button_down( &mt, msg.message, hmenu, flags );
4174 exit_menu = !remove;
4175 break;
4177 case WM_RBUTTONUP:
4178 if (!(flags & TPM_RIGHTBUTTON)) break;
4179 /* fall through */
4180 case WM_LBUTTONUP:
4181 /* Check if a menu was selected by the mouse */
4182 if (hmenu)
4184 executed_menu_id = menu_button_up( &mt, hmenu, flags);
4185 TRACE( "executed_menu_id %d\n", executed_menu_id );
4187 /* End the loop if executed_menu_id is an item ID
4188 * or if the job was done (executed_menu_id = 0). */
4189 exit_menu = remove = executed_menu_id != -1;
4191 else
4192 /* No menu was selected by the mouse. If the function was called by
4193 * NtUserTrackPopupMenuEx, continue with the menu tracking. */
4194 exit_menu = !(flags & TPM_POPUPMENU);
4196 break;
4198 case WM_MOUSEMOVE:
4199 /* the selected menu item must be changed every time the mouse moves. */
4200 if (hmenu) exit_menu |= !menu_mouse_move( &mt, hmenu, flags );
4201 break;
4204 else if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
4206 remove = TRUE; /* Keyboard messages are always removed */
4207 switch (msg.message)
4209 case WM_KEYDOWN:
4210 case WM_SYSKEYDOWN:
4211 switch(msg.wParam)
4213 case VK_MENU:
4214 case VK_F10:
4215 exit_menu = TRUE;
4216 break;
4218 case VK_HOME:
4219 case VK_END:
4220 select_item( mt.hOwnerWnd, mt.hCurrentMenu, NO_SELECTED_ITEM, FALSE, 0 );
4221 move_selection( mt.hOwnerWnd, mt.hCurrentMenu,
4222 msg.wParam == VK_HOME ? ITEM_NEXT : ITEM_PREV );
4223 break;
4225 case VK_UP:
4226 case VK_DOWN: /* If on menu bar, pull-down the menu */
4227 menu = unsafe_menu_ptr( mt.hCurrentMenu );
4228 if (!(menu->wFlags & MF_POPUP))
4229 mt.hCurrentMenu = show_sub_popup( mt.hOwnerWnd, mt.hTopMenu, TRUE, flags );
4230 else /* otherwise try to move selection */
4231 move_selection( mt.hOwnerWnd, mt.hCurrentMenu,
4232 msg.wParam == VK_UP ? ITEM_PREV : ITEM_NEXT );
4233 break;
4235 case VK_LEFT:
4236 menu_key_left( &mt, flags, msg.message );
4237 break;
4239 case VK_RIGHT:
4240 menu_right_key( &mt, flags, msg.message );
4241 break;
4243 case VK_ESCAPE:
4244 exit_menu = menu_key_escape( &mt, flags );
4245 break;
4247 case VK_F1:
4249 HELPINFO hi;
4250 hi.cbSize = sizeof(HELPINFO);
4251 hi.iContextType = HELPINFO_MENUITEM;
4252 if (menu->FocusedItem == NO_SELECTED_ITEM)
4253 hi.iCtrlId = 0;
4254 else
4255 hi.iCtrlId = menu->items[menu->FocusedItem].wID;
4256 hi.hItemHandle = hmenu;
4257 hi.dwContextId = menu->dwContextHelpID;
4258 hi.MousePos = msg.pt;
4259 send_message( hwnd, WM_HELP, 0, (LPARAM)&hi );
4260 break;
4263 default:
4264 NtUserTranslateMessage( &msg, 0 );
4265 break;
4267 break; /* WM_KEYDOWN */
4269 case WM_CHAR:
4270 case WM_SYSCHAR:
4272 UINT pos;
4274 if (msg.wParam == '\r' || msg.wParam == ' ')
4276 executed_menu_id = exec_focused_item( &mt, mt.hCurrentMenu, flags );
4277 exit_menu = executed_menu_id != -2;
4278 break;
4281 /* Hack to avoid control chars... */
4282 if (msg.wParam < 32) break;
4284 pos = find_item_by_key( mt.hOwnerWnd, mt.hCurrentMenu,
4285 LOWORD( msg.wParam ), FALSE );
4286 if (pos == -2) exit_menu = TRUE;
4287 else if (pos == -1) message_beep( 0 );
4288 else
4290 select_item( mt.hOwnerWnd, mt.hCurrentMenu, pos, TRUE, 0 );
4291 executed_menu_id = exec_focused_item( &mt,mt.hCurrentMenu, flags );
4292 exit_menu = executed_menu_id != -2;
4295 break;
4298 else
4300 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
4301 NtUserDispatchMessage( &msg );
4302 continue;
4305 if (!exit_menu) remove = TRUE;
4307 /* finally remove message from the queue */
4308 if (remove && !(mt.trackFlags & TF_SKIPREMOVE))
4309 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
4310 else mt.trackFlags &= ~TF_SKIPREMOVE;
4313 set_capture_window( 0, GUI_INMENUMODE, NULL );
4315 /* If dropdown is still painted and the close box is clicked on
4316 * then the menu will be destroyed as part of the DispatchMessage above.
4317 * This will then invalidate the menu handle in mt.hTopMenu. We should
4318 * check for this first. */
4319 if (is_menu( mt.hTopMenu ))
4321 menu = unsafe_menu_ptr( mt.hTopMenu );
4323 if (is_window( mt.hOwnerWnd ))
4325 hide_sub_popups( mt.hOwnerWnd, mt.hTopMenu, FALSE, flags );
4327 if (menu && (menu->wFlags & MF_POPUP))
4329 NtUserDestroyWindow( menu->hWnd );
4330 menu->hWnd = 0;
4332 if (!(flags & TPM_NONOTIFY))
4333 send_message( mt.hOwnerWnd, WM_UNINITMENUPOPUP, (WPARAM)mt.hTopMenu,
4334 MAKELPARAM( 0, IS_SYSTEM_MENU( menu )));
4336 select_item( mt.hOwnerWnd, mt.hTopMenu, NO_SELECTED_ITEM, FALSE, 0 );
4337 send_message( mt.hOwnerWnd, WM_MENUSELECT, MAKEWPARAM( 0, 0xffff ), 0 );
4341 RtlSetLastWin32Error( ERROR_SUCCESS );
4342 /* The return value is only used by NtUserTrackPopupMenuEx */
4343 if (!(flags & TPM_RETURNCMD)) return TRUE;
4344 if (executed_menu_id == -1) executed_menu_id = 0;
4345 return executed_menu_id;
4348 static BOOL init_tracking( HWND hwnd, HMENU handle, BOOL is_popup, UINT flags )
4350 struct menu *menu;
4352 TRACE( "hwnd=%p hmenu=%p\n", hwnd, handle );
4354 NtUserHideCaret( 0 );
4356 if (!(menu = unsafe_menu_ptr( handle ))) return FALSE;
4358 /* This makes the menus of applications built with Delphi work.
4359 * It also enables menus to be displayed in more than one window,
4360 * but there are some bugs left that need to be fixed in this case.
4362 if (!is_popup) menu->hWnd = hwnd;
4363 if (!top_popup)
4365 top_popup = menu->hWnd;
4366 top_popup_hmenu = handle;
4369 exit_menu = FALSE;
4371 /* Send WM_ENTERMENULOOP and WM_INITMENU message only if TPM_NONOTIFY flag is not specified */
4372 if (!(flags & TPM_NONOTIFY))
4373 send_message( hwnd, WM_ENTERMENULOOP, is_popup, 0 );
4375 send_message( hwnd, WM_SETCURSOR, (WPARAM)hwnd, HTCAPTION );
4377 if (!(flags & TPM_NONOTIFY))
4379 send_message( hwnd, WM_INITMENU, (WPARAM)handle, 0 );
4380 /* If an app changed/recreated menu bar entries in WM_INITMENU
4381 * menu sizes will be recalculated once the menu created/shown. */
4384 return TRUE;
4387 static BOOL exit_tracking( HWND hwnd, BOOL is_popup )
4389 TRACE( "hwnd=%p\n", hwnd );
4391 send_message( hwnd, WM_EXITMENULOOP, is_popup, 0 );
4392 NtUserShowCaret( 0 );
4393 top_popup = 0;
4394 top_popup_hmenu = NULL;
4395 return TRUE;
4398 void track_mouse_menu_bar( HWND hwnd, INT ht, int x, int y )
4400 HMENU handle = ht == HTSYSMENU ? get_win_sys_menu( hwnd ) : get_menu( hwnd );
4401 UINT flags = TPM_BUTTONDOWN | TPM_LEFTALIGN | TPM_LEFTBUTTON;
4403 TRACE( "wnd=%p ht=0x%04x %d,%d\n", hwnd, ht, x, y );
4405 if (get_window_long( hwnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) flags |= TPM_LAYOUTRTL;
4406 if (is_menu( handle ))
4408 init_tracking( hwnd, handle, FALSE, flags );
4410 /* fetch the window menu again, it may have changed */
4411 handle = ht == HTSYSMENU ? get_win_sys_menu( hwnd ) : get_menu( hwnd );
4412 track_menu( handle, flags, x, y, hwnd, NULL );
4413 exit_tracking( hwnd, FALSE );
4417 void track_keyboard_menu_bar( HWND hwnd, UINT wparam, WCHAR ch )
4419 UINT flags = TPM_LEFTALIGN | TPM_LEFTBUTTON;
4420 UINT item = NO_SELECTED_ITEM;
4421 HMENU menu;
4423 TRACE( "hwnd %p wparam 0x%04x ch 0x%04x\n", hwnd, wparam, ch );
4425 /* find window that has a menu */
4426 while (is_win_menu_disallowed( hwnd ))
4427 if (!(hwnd = NtUserGetAncestor( hwnd, GA_PARENT ))) return;
4429 /* check if we have to track a system menu */
4430 menu = get_menu( hwnd );
4431 if (!menu || is_iconic( hwnd ) || ch == ' ')
4433 if (!(get_window_long( hwnd, GWL_STYLE ) & WS_SYSMENU)) return;
4434 menu = get_win_sys_menu( hwnd );
4435 item = 0;
4436 wparam |= HTSYSMENU; /* prevent item lookup */
4438 if (get_window_long( hwnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) flags |= TPM_LAYOUTRTL;
4440 if (!is_menu( menu )) return;
4442 init_tracking( hwnd, menu, FALSE, flags );
4444 /* fetch the window menu again, it may have changed */
4445 menu = (wparam & HTSYSMENU) ? get_win_sys_menu( hwnd ) : get_menu( hwnd );
4447 if (ch && ch != ' ')
4449 item = find_item_by_key( hwnd, menu, ch, wparam & HTSYSMENU );
4450 if (item >= -2)
4452 if (item == -1) message_beep( 0 );
4453 /* schedule end of menu tracking */
4454 flags |= TF_ENDMENU;
4455 goto track_menu;
4459 select_item( hwnd, menu, item, TRUE, 0 );
4461 if (!(wparam & HTSYSMENU) || ch == ' ')
4463 if( item == NO_SELECTED_ITEM )
4464 move_selection( hwnd, menu, ITEM_NEXT );
4465 else
4466 NtUserPostMessage( hwnd, WM_KEYDOWN, VK_RETURN, 0 );
4469 track_menu:
4470 track_menu( menu, flags, 0, 0, hwnd, NULL );
4471 exit_tracking( hwnd, FALSE );
4474 /**********************************************************************
4475 * NtUserTrackPopupMenuEx (win32u.@)
4477 BOOL WINAPI NtUserTrackPopupMenuEx( HMENU handle, UINT flags, INT x, INT y, HWND hwnd,
4478 TPMPARAMS *params )
4480 struct menu *menu;
4481 BOOL ret = FALSE;
4483 TRACE( "hmenu %p flags %04x (%d,%d) hwnd %p params %p rect %s\n",
4484 handle, flags, x, y, hwnd, params,
4485 params ? wine_dbgstr_rect( &params->rcExclude ) : "-" );
4487 if (!(menu = unsafe_menu_ptr( handle )))
4489 RtlSetLastWin32Error( ERROR_INVALID_MENU_HANDLE );
4490 return FALSE;
4493 if (is_window(menu->hWnd))
4495 RtlSetLastWin32Error( ERROR_POPUP_ALREADY_ACTIVE );
4496 return FALSE;
4499 if (init_popup( hwnd, handle, flags ))
4501 init_tracking( hwnd, handle, TRUE, flags );
4503 /* Send WM_INITMENUPOPUP message only if TPM_NONOTIFY flag is not specified */
4504 if (!(flags & TPM_NONOTIFY))
4505 send_message( hwnd, WM_INITMENUPOPUP, (WPARAM)handle, 0 );
4507 if (menu->wFlags & MF_SYSMENU)
4508 init_sys_menu_popup( handle, get_window_long( hwnd, GWL_STYLE ),
4509 get_class_long( hwnd, GCL_STYLE, FALSE ));
4511 if (show_popup( hwnd, handle, 0, flags, x, y, 0, 0 ))
4512 ret = track_menu( handle, flags | TPM_POPUPMENU, 0, 0, hwnd,
4513 params ? &params->rcExclude : NULL );
4514 exit_tracking( hwnd, TRUE );
4516 if (menu->hWnd)
4518 NtUserDestroyWindow( menu->hWnd );
4519 menu->hWnd = 0;
4521 if (!(flags & TPM_NONOTIFY))
4522 send_message( hwnd, WM_UNINITMENUPOPUP, (WPARAM)handle,
4523 MAKELPARAM( 0, IS_SYSTEM_MENU( menu )));
4525 RtlSetLastWin32Error( 0 );
4528 return ret;
4531 /**********************************************************************
4532 * NtUserHiliteMenuItem (win32u.@)
4534 BOOL WINAPI NtUserHiliteMenuItem( HWND hwnd, HMENU handle, UINT item, UINT hilite )
4536 HMENU handle_menu;
4537 UINT focused_item;
4538 struct menu *menu;
4539 UINT pos;
4541 TRACE( "(%p, %p, %04x, %04x);\n", hwnd, handle, item, hilite );
4543 if (!(menu = find_menu_item(handle, item, hilite, &pos))) return FALSE;
4544 handle_menu = menu->obj.handle;
4545 focused_item = menu->FocusedItem;
4546 release_menu_ptr(menu);
4548 if (focused_item != pos)
4550 hide_sub_popups( hwnd, handle_menu, FALSE, 0 );
4551 select_item( hwnd, handle_menu, pos, TRUE, 0 );
4554 return TRUE;
4557 /**********************************************************************
4558 * NtUserGetMenuBarInfo (win32u.@)
4560 BOOL WINAPI NtUserGetMenuBarInfo( HWND hwnd, LONG id, LONG item, MENUBARINFO *info )
4562 HMENU hmenu = NULL;
4563 struct menu *menu;
4564 ATOM class_atom;
4566 TRACE( "(%p,0x%08x,0x%08x,%p)\n", hwnd, id, item, info );
4568 switch (id)
4570 case OBJID_CLIENT:
4571 class_atom = get_class_long( hwnd, GCW_ATOM, FALSE );
4572 if (!class_atom)
4573 return FALSE;
4574 if (class_atom != POPUPMENU_CLASS_ATOM)
4576 WARN("called on invalid window: %d\n", class_atom);
4577 RtlSetLastWin32Error(ERROR_INVALID_MENU_HANDLE);
4578 return FALSE;
4581 hmenu = (HMENU)get_window_long_ptr( hwnd, 0, FALSE );
4582 break;
4583 case OBJID_MENU:
4584 hmenu = get_menu( hwnd );
4585 break;
4586 case OBJID_SYSMENU:
4587 hmenu = NtUserGetSystemMenu( hwnd, FALSE );
4588 break;
4589 default:
4590 return FALSE;
4593 if (!hmenu)
4594 return FALSE;
4596 if (info->cbSize != sizeof(MENUBARINFO))
4598 RtlSetLastWin32Error( ERROR_INVALID_PARAMETER );
4599 return FALSE;
4602 if (!(menu = grab_menu_ptr( hmenu ))) return FALSE;
4603 if (item < 0 || item > menu->nItems)
4605 release_menu_ptr( menu );
4606 return FALSE;
4609 if (!menu->Height)
4611 SetRectEmpty( &info->rcBar );
4613 else if (item == 0)
4615 NtUserGetMenuItemRect( hwnd, hmenu, 0, &info->rcBar );
4616 info->rcBar.right = info->rcBar.left + menu->Width;
4617 info->rcBar.bottom = info->rcBar.top + menu->Height;
4619 else
4621 NtUserGetMenuItemRect( hwnd, hmenu, item - 1, &info->rcBar );
4624 info->hMenu = hmenu;
4625 info->hwndMenu = NULL;
4626 info->fBarFocused = top_popup_hmenu == hmenu;
4627 if (item)
4629 info->fFocused = menu->FocusedItem == item - 1;
4630 if (info->fFocused && (menu->items[item - 1].fType & MF_POPUP))
4632 struct menu *hwnd_menu = grab_menu_ptr( menu->items[item - 1].hSubMenu );
4633 if (hwnd_menu)
4635 info->hwndMenu = hwnd_menu->hWnd;
4636 release_menu_ptr( hwnd_menu );
4640 else
4642 info->fFocused = info->fBarFocused;
4645 release_menu_ptr( menu );
4646 return TRUE;
4649 /***********************************************************************
4650 * NtUserEndMenu (win32u.@)
4652 BOOL WINAPI NtUserEndMenu(void)
4654 /* if we are in the menu code, and it is active, terminate the menu handling code */
4655 if (!exit_menu && top_popup)
4657 exit_menu = TRUE;
4659 /* needs to be posted to wakeup the internal menu handler
4660 * which will now terminate the menu, in the event that
4661 * the main window was minimized, or lost focus, so we
4662 * don't end up with an orphaned menu */
4663 NtUserPostMessage( top_popup, WM_CANCELMODE, 0, 0 );
4665 return exit_menu;