win32u: Move menu tracking implementation from user32.
[wine.git] / dlls / win32u / menu.c
blob6a4640474277a00c5fc530cb1cf204e431c2eb1f
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);
35 /* the accelerator user object */
36 struct accelerator
38 struct user_object obj;
39 unsigned int count;
40 ACCEL table[1];
43 enum hittest
45 ht_nowhere, /* outside the menu */
46 ht_border, /* anywhere that's not an item or a scroll arrow */
47 ht_item, /* a menu item */
48 ht_scroll_up, /* scroll up arrow */
49 ht_scroll_down /* scroll down arrow */
52 typedef struct
54 UINT trackFlags;
55 HMENU hCurrentMenu; /* current submenu (can be equal to hTopMenu)*/
56 HMENU hTopMenu; /* initial menu */
57 HWND hOwnerWnd; /* where notifications are sent */
58 POINT pt;
59 } MTRACKER;
61 /* maximum allowed depth of any branch in the menu tree.
62 * This value is slightly larger than in windows (25) to
63 * stay on the safe side. */
64 #define MAXMENUDEPTH 30
66 /* (other menu->FocusedItem values give the position of the focused item) */
67 #define NO_SELECTED_ITEM 0xffff
69 /* internal flags for menu tracking */
70 #define TF_ENDMENU 0x10000
71 #define TF_SUSPENDPOPUP 0x20000
72 #define TF_SKIPREMOVE 0x40000
73 #define TF_RCVD_BTN_UP 0x80000
75 /* Internal track_menu() flags */
76 #define TPM_INTERNAL 0xf0000000
77 #define TPM_BUTTONDOWN 0x40000000 /* menu was clicked before tracking */
78 #define TPM_POPUPMENU 0x20000000 /* menu is a popup menu */
80 /* Space between 2 columns */
81 #define MENU_COL_SPACE 4
83 /* Margins for popup menus */
84 #define MENU_MARGIN 3
86 #define ITEM_PREV -1
87 #define ITEM_NEXT 1
89 #define MENU_ITEM_TYPE(flags) \
90 ((flags) & (MF_STRING | MF_BITMAP | MF_OWNERDRAW | MF_SEPARATOR))
92 /* macro to test that flags do not indicate bitmap, ownerdraw or separator */
93 #define IS_STRING_ITEM(flags) (MENU_ITEM_TYPE ((flags)) == MF_STRING)
94 #define IS_MAGIC_BITMAP(id) ((id) && ((INT_PTR)(id) < 12) && ((INT_PTR)(id) >= -1))
96 #define IS_SYSTEM_MENU(menu) \
97 (!((menu)->wFlags & MF_POPUP) && ((menu)->wFlags & MF_SYSMENU))
99 #define MENUITEMINFO_TYPE_MASK \
100 (MFT_STRING | MFT_BITMAP | MFT_OWNERDRAW | MFT_SEPARATOR | \
101 MFT_MENUBARBREAK | MFT_MENUBREAK | MFT_RADIOCHECK | \
102 MFT_RIGHTORDER | MFT_RIGHTJUSTIFY /* same as MF_HELP */ )
103 #define TYPE_MASK (MENUITEMINFO_TYPE_MASK | MF_POPUP | MF_SYSMENU)
104 #define STATE_MASK (~TYPE_MASK)
105 #define MENUITEMINFO_STATE_MASK (STATE_MASK & ~(MF_BYPOSITION | MF_MOUSESELECT))
108 /* Use global popup window because there's no way 2 menus can
109 * be tracked at the same time. */
110 static HWND top_popup;
111 static HMENU top_popup_hmenu;
113 /* Flag set by NtUserEndMenu() to force an exit from menu tracking */
114 static BOOL exit_menu = FALSE;
116 static SIZE menucharsize;
117 static UINT od_item_hight; /* default owner drawn item height */
119 /**********************************************************************
120 * NtUserCopyAcceleratorTable (win32u.@)
122 INT WINAPI NtUserCopyAcceleratorTable( HACCEL src, ACCEL *dst, INT count )
124 struct accelerator *accel;
125 int i;
127 if (!(accel = get_user_handle_ptr( src, NTUSER_OBJ_ACCEL ))) return 0;
128 if (accel == OBJ_OTHER_PROCESS)
130 FIXME_(accel)( "other process handle %p?\n", src );
131 return 0;
133 if (dst)
135 if (count > accel->count) count = accel->count;
136 for (i = 0; i < count; i++)
138 dst[i].fVirt = accel->table[i].fVirt & 0x7f;
139 dst[i].key = accel->table[i].key;
140 dst[i].cmd = accel->table[i].cmd;
143 else count = accel->count;
144 release_user_handle_ptr( accel );
145 return count;
148 /*********************************************************************
149 * NtUserCreateAcceleratorTable (win32u.@)
151 HACCEL WINAPI NtUserCreateAcceleratorTable( ACCEL *table, INT count )
153 struct accelerator *accel;
154 HACCEL handle;
156 if (count < 1)
158 SetLastError( ERROR_INVALID_PARAMETER );
159 return 0;
161 accel = malloc( FIELD_OFFSET( struct accelerator, table[count] ));
162 if (!accel) return 0;
163 accel->count = count;
164 memcpy( accel->table, table, count * sizeof(*table) );
166 if (!(handle = alloc_user_handle( &accel->obj, NTUSER_OBJ_ACCEL ))) free( accel );
167 TRACE_(accel)("returning %p\n", handle );
168 return handle;
171 /******************************************************************************
172 * NtUserDestroyAcceleratorTable (win32u.@)
174 BOOL WINAPI NtUserDestroyAcceleratorTable( HACCEL handle )
176 struct accelerator *accel;
178 if (!(accel = free_user_handle( handle, NTUSER_OBJ_ACCEL ))) return FALSE;
179 if (accel == OBJ_OTHER_PROCESS)
181 FIXME_(accel)( "other process handle %p\n", accel );
182 return FALSE;
184 free( accel );
185 return TRUE;
188 #define MENUFLAG(bit,text) \
189 do { \
190 if (flags & (bit)) { flags &= ~(bit); strcat(buf, (text)); } \
191 } while (0)
193 static const char *debugstr_menuitem( const MENUITEM *item )
195 static const char *const hbmmenus[] = { "HBMMENU_CALLBACK", "", "HBMMENU_SYSTEM",
196 "HBMMENU_MBAR_RESTORE", "HBMMENU_MBAR_MINIMIZE", "UNKNOWN BITMAP", "HBMMENU_MBAR_CLOSE",
197 "HBMMENU_MBAR_CLOSE_D", "HBMMENU_MBAR_MINIMIZE_D", "HBMMENU_POPUP_CLOSE",
198 "HBMMENU_POPUP_RESTORE", "HBMMENU_POPUP_MAXIMIZE", "HBMMENU_POPUP_MINIMIZE" };
199 char buf[256];
200 UINT flags;
202 if (!item) return "NULL";
204 sprintf( buf, "{ ID=0x%lx", item->wID );
205 if (item->hSubMenu) sprintf( buf + strlen(buf), ", Sub=%p", item->hSubMenu );
207 flags = item->fType;
208 if (flags)
210 strcat( buf, ", fType=" );
211 MENUFLAG( MFT_SEPARATOR, "sep" );
212 MENUFLAG( MFT_OWNERDRAW, "own" );
213 MENUFLAG( MFT_BITMAP, "bit" );
214 MENUFLAG( MF_POPUP, "pop" );
215 MENUFLAG( MFT_MENUBARBREAK, "barbrk" );
216 MENUFLAG( MFT_MENUBREAK, "brk");
217 MENUFLAG( MFT_RADIOCHECK, "radio" );
218 MENUFLAG( MFT_RIGHTORDER, "rorder" );
219 MENUFLAG( MF_SYSMENU, "sys" );
220 MENUFLAG( MFT_RIGHTJUSTIFY, "right" ); /* same as MF_HELP */
221 if (flags) sprintf( buf + strlen(buf), "+0x%x", flags );
224 flags = item->fState;
225 if (flags)
227 strcat( buf, ", State=" );
228 MENUFLAG( MFS_GRAYED, "grey" );
229 MENUFLAG( MFS_DEFAULT, "default" );
230 MENUFLAG( MFS_DISABLED, "dis" );
231 MENUFLAG( MFS_CHECKED, "check" );
232 MENUFLAG( MFS_HILITE, "hi" );
233 MENUFLAG( MF_USECHECKBITMAPS, "usebit" );
234 MENUFLAG( MF_MOUSESELECT, "mouse" );
235 if (flags) sprintf( buf + strlen(buf), "+0x%x", flags );
238 if (item->hCheckBit) sprintf( buf + strlen(buf), ", Chk=%p", item->hCheckBit );
239 if (item->hUnCheckBit) sprintf( buf + strlen(buf), ", Unc=%p", item->hUnCheckBit );
240 if (item->text) sprintf( buf + strlen(buf), ", Text=%s", debugstr_w(item->text) );
241 if (item->dwItemData) sprintf( buf + strlen(buf), ", ItemData=0x%08lx", item->dwItemData );
243 if (item->hbmpItem)
245 if (IS_MAGIC_BITMAP( item->hbmpItem ))
246 sprintf( buf + strlen(buf), ", hbitmap=%s", hbmmenus[(INT_PTR)item->hbmpItem + 1] );
247 else
248 sprintf( buf + strlen(buf), ", hbitmap=%p", item->hbmpItem );
250 return wine_dbg_sprintf( "%s }", buf );
253 #undef MENUFLAG
255 static POPUPMENU *grab_menu_ptr( HMENU handle )
257 POPUPMENU *menu = get_user_handle_ptr( handle, NTUSER_OBJ_MENU );
259 if (menu == OBJ_OTHER_PROCESS)
261 WARN( "other process menu %p\n", handle );
262 return NULL;
265 if (menu)
266 menu->refcount++;
267 else
268 WARN( "invalid menu handle=%p\n", handle );
269 return menu;
272 static void release_menu_ptr( POPUPMENU *menu )
274 if (menu)
276 menu->refcount--;
277 release_user_handle_ptr( menu );
282 * Validate the given menu handle and returns the menu structure pointer.
283 * FIXME: this is unsafe, we should use a better mechanism instead.
285 static POPUPMENU *unsafe_menu_ptr( HMENU handle )
287 POPUPMENU *menu = grab_menu_ptr( handle );
288 if (menu) release_menu_ptr( menu );
289 return menu;
292 /* see IsMenu */
293 BOOL is_menu( HMENU handle )
295 POPUPMENU *menu;
296 BOOL is_menu;
298 menu = grab_menu_ptr( handle );
299 is_menu = menu != NULL;
300 release_menu_ptr( menu );
302 if (!is_menu) SetLastError( ERROR_INVALID_MENU_HANDLE );
303 return is_menu;
306 /***********************************************************************
307 * get_win_sys_menu
309 * Get the system menu of a window
311 static HMENU get_win_sys_menu( HWND hwnd )
313 HMENU ret = 0;
314 WND *win = get_win_ptr( hwnd );
315 if (win && win != WND_OTHER_PROCESS && win != WND_DESKTOP)
317 ret = win->hSysMenu;
318 release_win_ptr( win );
320 return ret;
323 static POPUPMENU *find_menu_item( HMENU handle, UINT id, UINT flags, UINT *pos )
325 UINT fallback_pos = ~0u, i;
326 POPUPMENU *menu;
328 menu = grab_menu_ptr( handle );
329 if (!menu)
330 return NULL;
332 if (flags & MF_BYPOSITION)
334 if (id >= menu->nItems)
336 release_menu_ptr( menu );
337 return NULL;
340 if (pos) *pos = id;
341 return menu;
343 else
345 MENUITEM *item = menu->items;
346 for (i = 0; i < menu->nItems; i++, item++)
348 if (item->fType & MF_POPUP)
350 POPUPMENU *submenu = find_menu_item( item->hSubMenu, id, flags, pos );
352 if (submenu)
354 release_menu_ptr( menu );
355 return submenu;
357 else if (item->wID == id)
359 /* fallback to this item if nothing else found */
360 fallback_pos = i;
363 else if (item->wID == id)
365 if (pos) *pos = i;
366 return menu;
371 if (fallback_pos != ~0u)
372 *pos = fallback_pos;
373 else
375 release_menu_ptr( menu );
376 menu = NULL;
379 return menu;
382 static POPUPMENU *insert_menu_item( HMENU handle, UINT id, UINT flags, UINT *ret_pos )
384 MENUITEM *new_items;
385 POPUPMENU *menu;
386 UINT pos = id;
388 /* Find where to insert new item */
389 if (!(menu = find_menu_item(handle, id, flags, &pos)))
391 if (!(menu = grab_menu_ptr(handle)))
392 return NULL;
393 pos = menu->nItems;
396 /* Make sure that MDI system buttons stay on the right side.
397 * Note: XP treats only bitmap handles 1 - 6 as "magic" ones
398 * regardless of their id.
400 while (pos > 0 && (INT_PTR)menu->items[pos - 1].hbmpItem >= (INT_PTR)HBMMENU_SYSTEM &&
401 (INT_PTR)menu->items[pos - 1].hbmpItem <= (INT_PTR)HBMMENU_MBAR_CLOSE_D)
402 pos--;
404 TRACE( "inserting at %u flags %x\n", pos, flags );
406 new_items = malloc( sizeof(MENUITEM) * (menu->nItems + 1) );
407 if (!new_items)
409 release_menu_ptr( menu );
410 return NULL;
412 if (menu->nItems > 0)
414 /* Copy the old array into the new one */
415 if (pos > 0) memcpy( new_items, menu->items, pos * sizeof(MENUITEM) );
416 if (pos < menu->nItems) memcpy( &new_items[pos + 1], &menu->items[pos],
417 (menu->nItems - pos) * sizeof(MENUITEM) );
418 free( menu->items );
420 menu->items = new_items;
421 menu->nItems++;
422 memset( &new_items[pos], 0, sizeof(*new_items) );
423 menu->Height = 0; /* force size recalculate */
425 *ret_pos = pos;
426 return menu;
429 static BOOL is_win_menu_disallowed( HWND hwnd )
431 return (get_window_long(hwnd, GWL_STYLE) & (WS_CHILD | WS_POPUP)) == WS_CHILD;
434 /***********************************************************************
435 * find_submenu
437 * Find a Sub menu. Return the position of the submenu, and modifies
438 * *hmenu in case it is found in another sub-menu.
439 * If the submenu cannot be found, NO_SELECTED_ITEM is returned.
441 static UINT find_submenu( HMENU *handle_ptr, HMENU target )
443 POPUPMENU *menu;
444 MENUITEM *item;
445 UINT i;
447 if (*handle_ptr == (HMENU)0xffff || !(menu = grab_menu_ptr( *handle_ptr )))
448 return NO_SELECTED_ITEM;
450 item = menu->items;
451 for (i = 0; i < menu->nItems; i++, item++)
453 if(!(item->fType & MF_POPUP)) continue;
454 if (item->hSubMenu == target)
456 release_menu_ptr( menu );
457 return i;
459 else
461 HMENU hsubmenu = item->hSubMenu;
462 UINT pos = find_submenu( &hsubmenu, target );
463 if (pos != NO_SELECTED_ITEM)
465 *handle_ptr = hsubmenu;
466 release_menu_ptr( menu );
467 return pos;
472 release_menu_ptr( menu );
473 return NO_SELECTED_ITEM;
476 /* Adjust menu item rectangle according to scrolling state */
477 static void adjust_menu_item_rect( const POPUPMENU *menu, RECT *rect )
479 INT scroll_offset = menu->bScrolling ? menu->nScrollPos : 0;
480 OffsetRect( rect, menu->items_rect.left, menu->items_rect.top - scroll_offset );
483 /***********************************************************************
484 * find_item_by_coords
486 * Find the item at the specified coordinates (screen coords). Does
487 * not work for child windows and therefore should not be called for
488 * an arbitrary system menu.
490 * Returns a hittest code. *pos will contain the position of the
491 * item or NO_SELECTED_ITEM. If the hittest code is ht_scroll_up
492 * or ht_scroll_down then *pos will contain the position of the
493 * item that's just outside the items_rect - ie, the one that would
494 * be scrolled completely into view.
496 static enum hittest find_item_by_coords( const POPUPMENU *menu, POINT pt, UINT *pos )
498 enum hittest ht = ht_border;
499 MENUITEM *item;
500 RECT rect;
501 UINT i;
503 *pos = NO_SELECTED_ITEM;
505 if (!get_window_rect( menu->hWnd, &rect, get_thread_dpi() ) || !PtInRect( &rect, pt ))
506 return ht_nowhere;
508 if (get_window_long( menu->hWnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) pt.x = rect.right - 1 - pt.x;
509 else pt.x -= rect.left;
510 pt.y -= rect.top;
512 if (!PtInRect( &menu->items_rect, pt ))
514 if (!menu->bScrolling || pt.x < menu->items_rect.left || pt.x >= menu->items_rect.right)
515 return ht_border;
517 /* On a scroll arrow. Update pt so that it points to the item just outside items_rect */
518 if (pt.y < menu->items_rect.top)
520 ht = ht_scroll_up;
521 pt.y = menu->items_rect.top - 1;
523 else
525 ht = ht_scroll_down;
526 pt.y = menu->items_rect.bottom;
530 item = menu->items;
531 for (i = 0; i < menu->nItems; i++, item++)
533 rect = item->rect;
534 adjust_menu_item_rect( menu, &rect );
535 if (PtInRect( &rect, pt ))
537 *pos = i;
538 if (ht != ht_scroll_up && ht != ht_scroll_down) ht = ht_item;
539 break;
543 return ht;
546 /* see GetMenu */
547 HMENU get_menu( HWND hwnd )
549 return UlongToHandle( get_window_long( hwnd, GWLP_ID ));
552 /* see CreateMenu and CreatePopupMenu */
553 HMENU create_menu( BOOL is_popup )
555 POPUPMENU *menu;
556 HMENU handle;
558 if (!(menu = calloc( 1, sizeof(*menu) ))) return 0;
559 menu->FocusedItem = NO_SELECTED_ITEM;
560 menu->refcount = 1;
561 if (is_popup) menu->wFlags |= MF_POPUP;
563 if (!(handle = alloc_user_handle( &menu->obj, NTUSER_OBJ_MENU ))) free( menu );
565 TRACE( "return %p\n", handle );
566 return handle;
569 /**********************************************************************
570 * NtUserDestroyMenu (win32u.@)
572 BOOL WINAPI NtUserDestroyMenu( HMENU handle )
574 POPUPMENU *menu;
576 TRACE( "(%p)\n", handle );
578 if (!(menu = free_user_handle( handle, NTUSER_OBJ_MENU ))) return FALSE;
579 if (menu == OBJ_OTHER_PROCESS) return FALSE;
581 /* DestroyMenu should not destroy system menu popup owner */
582 if ((menu->wFlags & (MF_POPUP | MF_SYSMENU)) == MF_POPUP && menu->hWnd)
584 NtUserDestroyWindow( menu->hWnd );
585 menu->hWnd = 0;
588 /* recursively destroy submenus */
589 if (menu->items)
591 MENUITEM *item = menu->items;
592 int i;
594 for (i = menu->nItems; i > 0; i--, item++)
596 if (item->fType & MF_POPUP) NtUserDestroyMenu( item->hSubMenu );
597 free( item->text );
599 free( menu->items );
602 free( menu );
603 return TRUE;
606 /*******************************************************************
607 * set_window_menu
609 * Helper for NtUserSetMenu that does not call NtUserSetWindowPos.
611 BOOL set_window_menu( HWND hwnd, HMENU handle )
613 TRACE( "(%p, %p);\n", hwnd, handle );
615 if (handle && !is_menu( handle ))
617 WARN( "%p is not a menu handle\n", handle );
618 return FALSE;
621 if (is_win_menu_disallowed( hwnd ))
622 return FALSE;
624 hwnd = get_full_window_handle( hwnd );
625 if (get_capture() == hwnd)
626 set_capture_window( 0, GUI_INMENUMODE, NULL ); /* release the capture */
628 if (handle)
630 POPUPMENU *menu;
632 if (!(menu = grab_menu_ptr( handle ))) return FALSE;
633 menu->hWnd = hwnd;
634 menu->Height = 0; /* Make sure we recalculate the size */
635 release_menu_ptr(menu);
638 NtUserSetWindowLong( hwnd, GWLP_ID, (LONG_PTR)handle, FALSE );
639 return TRUE;
642 /**********************************************************************
643 * NtUserSetMenu (win32u.@)
645 BOOL WINAPI NtUserSetMenu( HWND hwnd, HMENU menu )
647 if (!set_window_menu( hwnd, menu ))
648 return FALSE;
650 NtUserSetWindowPos( hwnd, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE |
651 SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED );
652 return TRUE;
655 /*******************************************************************
656 * NtUserCheckMenuItem (win32u.@)
658 DWORD WINAPI NtUserCheckMenuItem( HMENU handle, UINT id, UINT flags )
660 POPUPMENU *menu;
661 MENUITEM *item;
662 DWORD ret;
663 UINT pos;
665 if (!(menu = find_menu_item(handle, id, flags, &pos)))
666 return -1;
667 item = &menu->items[pos];
669 ret = item->fState & MF_CHECKED;
670 if (flags & MF_CHECKED) item->fState |= MF_CHECKED;
671 else item->fState &= ~MF_CHECKED;
672 release_menu_ptr(menu);
673 return ret;
676 /**********************************************************************
677 * NtUserEnableMenuItem (win32u.@)
679 BOOL WINAPI NtUserEnableMenuItem( HMENU handle, UINT id, UINT flags )
681 UINT oldflags, pos;
682 POPUPMENU *menu;
683 MENUITEM *item;
685 TRACE( "(%p, %04x, %04x)\n", handle, id, flags );
687 /* Get the Popupmenu to access the owner menu */
688 if (!(menu = find_menu_item( handle, id, flags, &pos )))
689 return ~0u;
691 item = &menu->items[pos];
692 oldflags = item->fState & (MF_GRAYED | MF_DISABLED);
693 item->fState ^= (oldflags ^ flags) & (MF_GRAYED | MF_DISABLED);
695 /* If the close item in the system menu change update the close button */
696 if (item->wID == SC_CLOSE && oldflags != flags && menu->hSysMenuOwner)
698 POPUPMENU *parent_menu;
699 RECT rc;
700 HWND hwnd;
702 /* Get the parent menu to access */
703 parent_menu = grab_menu_ptr( menu->hSysMenuOwner );
704 release_menu_ptr( menu );
705 if (!parent_menu)
706 return ~0u;
708 hwnd = parent_menu->hWnd;
709 release_menu_ptr( parent_menu );
711 /* Refresh the frame to reflect the change */
712 get_window_rects( hwnd, COORDS_CLIENT, &rc, NULL, get_thread_dpi() );
713 rc.bottom = 0;
714 NtUserRedrawWindow( hwnd, &rc, 0, RDW_FRAME | RDW_INVALIDATE | RDW_NOCHILDREN );
716 else
717 release_menu_ptr( menu );
719 return oldflags;
722 /* see DrawMenuBar */
723 BOOL draw_menu_bar( HWND hwnd )
725 HMENU handle;
727 if (!is_window( hwnd )) return FALSE;
728 if (is_win_menu_disallowed( hwnd )) return TRUE;
730 if ((handle = get_menu( hwnd )))
732 POPUPMENU *menu = grab_menu_ptr( handle );
733 if (menu)
735 menu->Height = 0; /* Make sure we call MENU_MenuBarCalcSize */
736 menu->hwndOwner = hwnd;
737 release_menu_ptr( menu );
741 return NtUserSetWindowPos( hwnd, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE |
742 SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED );
745 /**********************************************************************
746 * NtUserGetMenuItemRect (win32u.@)
748 BOOL WINAPI NtUserGetMenuItemRect( HWND hwnd, HMENU handle, UINT item, RECT *rect )
750 POPUPMENU *menu;
751 UINT pos;
752 RECT window_rect;
754 TRACE( "(%p,%p,%d,%p)\n", hwnd, handle, item, rect );
756 if (!rect)
757 return FALSE;
759 if (!(menu = find_menu_item( handle, item, MF_BYPOSITION, &pos )))
760 return FALSE;
762 if (!hwnd) hwnd = menu->hWnd;
763 if (!hwnd)
765 release_menu_ptr( menu );
766 return FALSE;
769 *rect = menu->items[pos].rect;
770 OffsetRect( rect, menu->items_rect.left, menu->items_rect.top );
772 /* Popup menu item draws in the client area */
773 if (menu->wFlags & MF_POPUP) map_window_points( hwnd, 0, (POINT *)rect, 2, get_thread_dpi() );
774 else
776 /* Sysmenu draws in the non-client area */
777 get_window_rect( hwnd, &window_rect, get_thread_dpi() );
778 OffsetRect( rect, window_rect.left, window_rect.top );
781 release_menu_ptr(menu);
782 return TRUE;
785 static BOOL set_menu_info( HMENU handle, const MENUINFO *info )
787 POPUPMENU *menu;
789 if (!(menu = grab_menu_ptr( handle ))) return FALSE;
791 if (info->fMask & MIM_BACKGROUND) menu->hbrBack = info->hbrBack;
792 if (info->fMask & MIM_HELPID) menu->dwContextHelpID = info->dwContextHelpID;
793 if (info->fMask & MIM_MAXHEIGHT) menu->cyMax = info->cyMax;
794 if (info->fMask & MIM_MENUDATA) menu->dwMenuData = info->dwMenuData;
795 if (info->fMask & MIM_STYLE) menu->dwStyle = info->dwStyle;
797 if (info->fMask & MIM_APPLYTOSUBMENUS)
799 int i;
800 MENUITEM *item = menu->items;
801 for (i = menu->nItems; i; i--, item++)
802 if (item->fType & MF_POPUP)
803 set_menu_info( item->hSubMenu, info);
806 release_menu_ptr( menu );
807 return TRUE;
810 /**********************************************************************
811 * NtUserThunkedMenuInfo (win32u.@)
813 BOOL WINAPI NtUserThunkedMenuInfo( HMENU menu, const MENUINFO *info )
815 TRACE( "(%p %p)\n", menu, info );
817 if (!info)
819 SetLastError( ERROR_NOACCESS );
820 return FALSE;
823 if (!set_menu_info( menu, info ))
825 SetLastError( ERROR_INVALID_MENU_HANDLE );
826 return FALSE;
829 if (info->fMask & MIM_STYLE)
831 if (info->dwStyle & MNS_AUTODISMISS) FIXME("MNS_AUTODISMISS unimplemented\n");
832 if (info->dwStyle & MNS_DRAGDROP) FIXME("MNS_DRAGDROP unimplemented\n");
833 if (info->dwStyle & MNS_MODELESS) FIXME("MNS_MODELESS unimplemented\n");
835 return TRUE;
838 /* see GetMenuInfo */
839 BOOL get_menu_info( HMENU handle, MENUINFO *info )
841 POPUPMENU *menu;
843 TRACE( "(%p %p)\n", handle, info );
845 if (!info || info->cbSize != sizeof(MENUINFO) || !(menu = grab_menu_ptr( handle )))
847 SetLastError( ERROR_INVALID_PARAMETER);
848 return FALSE;
851 if (info->fMask & MIM_BACKGROUND) info->hbrBack = menu->hbrBack;
852 if (info->fMask & MIM_HELPID) info->dwContextHelpID = menu->dwContextHelpID;
853 if (info->fMask & MIM_MAXHEIGHT) info->cyMax = menu->cyMax;
854 if (info->fMask & MIM_MENUDATA) info->dwMenuData = menu->dwMenuData;
855 if (info->fMask & MIM_STYLE) info->dwStyle = menu->dwStyle;
857 release_menu_ptr(menu);
858 return TRUE;
861 /**********************************************************************
862 * menu_depth
864 * detect if there are loops in the menu tree (or the depth is too large)
866 static int menu_depth( POPUPMENU *pmenu, int depth)
868 int i, subdepth;
869 MENUITEM *item;
871 if (++depth > MAXMENUDEPTH) return depth;
872 item = pmenu->items;
873 subdepth = depth;
874 for (i = 0; i < pmenu->nItems && subdepth <= MAXMENUDEPTH; i++, item++)
876 POPUPMENU *submenu = item->hSubMenu ? grab_menu_ptr( item->hSubMenu ) : NULL;
877 if (submenu)
879 int bdepth = menu_depth( submenu, depth);
880 if (bdepth > subdepth) subdepth = bdepth;
881 release_menu_ptr( submenu );
883 if (subdepth > MAXMENUDEPTH)
884 TRACE( "<- hmenu %p\n", item->hSubMenu );
887 return subdepth;
890 static BOOL set_menu_item_info( MENUITEM *menu, const MENUITEMINFOW *info )
892 if (!menu) return FALSE;
894 TRACE( "%s\n", debugstr_menuitem( menu ));
896 if (info->fMask & MIIM_FTYPE )
898 menu->fType &= ~MENUITEMINFO_TYPE_MASK;
899 menu->fType |= info->fType & MENUITEMINFO_TYPE_MASK;
901 if (info->fMask & MIIM_STRING )
903 const WCHAR *text = info->dwTypeData;
904 /* free the string when used */
905 free( menu->text );
906 if (!text)
907 menu->text = NULL;
908 else if ((menu->text = malloc( (lstrlenW(text) + 1) * sizeof(WCHAR) )))
909 lstrcpyW( menu->text, text );
912 if (info->fMask & MIIM_STATE)
913 /* Other menu items having MFS_DEFAULT are not converted
914 to normal items */
915 menu->fState = info->fState & MENUITEMINFO_STATE_MASK;
917 if (info->fMask & MIIM_ID)
918 menu->wID = info->wID;
920 if (info->fMask & MIIM_SUBMENU)
922 menu->hSubMenu = info->hSubMenu;
923 if (menu->hSubMenu)
925 POPUPMENU *submenu = grab_menu_ptr( menu->hSubMenu );
926 if (!submenu)
928 SetLastError( ERROR_INVALID_PARAMETER);
929 return FALSE;
931 if (menu_depth( submenu, 0 ) > MAXMENUDEPTH)
933 ERR( "Loop detected in menu hierarchy or maximum menu depth exceeded\n" );
934 menu->hSubMenu = 0;
935 release_menu_ptr( submenu );
936 return FALSE;
938 submenu->wFlags |= MF_POPUP;
939 menu->fType |= MF_POPUP;
940 release_menu_ptr( submenu );
942 else
943 menu->fType &= ~MF_POPUP;
946 if (info->fMask & MIIM_CHECKMARKS)
948 menu->hCheckBit = info->hbmpChecked;
949 menu->hUnCheckBit = info->hbmpUnchecked;
951 if (info->fMask & MIIM_DATA)
952 menu->dwItemData = info->dwItemData;
954 if (info->fMask & MIIM_BITMAP)
955 menu->hbmpItem = info->hbmpItem;
957 if (!menu->text && !(menu->fType & MFT_OWNERDRAW) && !menu->hbmpItem)
958 menu->fType |= MFT_SEPARATOR;
960 TRACE( "to: %s\n", debugstr_menuitem( menu ));
961 return TRUE;
964 /* see GetMenuState */
965 UINT get_menu_state( HMENU handle, UINT item_id, UINT flags )
967 POPUPMENU *menu;
968 UINT state, pos;
969 MENUITEM *item;
971 TRACE( "(menu=%p, id=%04x, flags=%04x);\n", handle, item_id, flags );
973 if (!(menu = find_menu_item( handle, item_id, flags, &pos )))
974 return -1;
976 item = &menu->items[pos];
977 TRACE( " item: %s\n", debugstr_menuitem( item ));
978 if (item->fType & MF_POPUP)
980 POPUPMENU *submenu = grab_menu_ptr( item->hSubMenu );
981 if (submenu)
982 state = (submenu->nItems << 8) | ((item->fState | item->fType) & 0xff);
983 else
984 state = -1;
985 release_menu_ptr( submenu );
987 else
989 state = item->fType | item->fState;
991 release_menu_ptr(menu);
992 return state;
995 /**********************************************************************
996 * NtUserThunkedMenuItemInfo (win32u.@)
998 UINT WINAPI NtUserThunkedMenuItemInfo( HMENU handle, UINT pos, UINT flags, UINT method,
999 MENUITEMINFOW *info, UNICODE_STRING *str )
1001 POPUPMENU *menu;
1002 UINT i;
1003 BOOL ret;
1005 switch (method)
1007 case NtUserInsertMenuItem:
1008 if (!info || info->cbSize != sizeof(*info))
1010 SetLastError( ERROR_INVALID_PARAMETER );
1011 return FALSE;
1014 if (!(menu = insert_menu_item( handle, pos, flags, &i )))
1016 /* workaround for Word 95: pretend that SC_TASKLIST item exists */
1017 if (pos == SC_TASKLIST && !(flags & MF_BYPOSITION)) return TRUE;
1018 return FALSE;
1021 ret = set_menu_item_info( &menu->items[i], info );
1022 if (!ret) NtUserRemoveMenu( handle, pos, flags );
1023 release_menu_ptr(menu);
1024 break;
1026 case NtUserSetMenuItemInfo:
1027 if (!info || info->cbSize != sizeof(*info))
1029 SetLastError( ERROR_INVALID_PARAMETER );
1030 return FALSE;
1033 if (!(menu = find_menu_item( handle, pos, flags, &i )))
1035 /* workaround for Word 95: pretend that SC_TASKLIST item exists */
1036 if (pos == SC_TASKLIST && !(flags & MF_BYPOSITION)) return TRUE;
1037 return FALSE;
1040 ret = set_menu_item_info( &menu->items[i], info );
1041 if (ret) menu->Height = 0; /* force size recalculate */
1042 release_menu_ptr(menu);
1043 break;
1045 case NtUserGetMenuState:
1046 return get_menu_state( handle, pos, flags );
1048 default:
1049 FIXME( "unsupported method %u\n", method );
1050 return FALSE;
1053 return ret;
1056 /* see GetMenuItemCount */
1057 INT get_menu_item_count( HMENU handle )
1059 POPUPMENU *menu;
1060 INT count;
1062 if (!(menu = grab_menu_ptr( handle ))) return -1;
1063 count = menu->nItems;
1064 release_menu_ptr(menu);
1066 TRACE( "(%p) returning %d\n", handle, count );
1067 return count;
1070 /**********************************************************************
1071 * NtUserRemoveMenu (win32u.@)
1073 BOOL WINAPI NtUserRemoveMenu( HMENU handle, UINT id, UINT flags )
1075 POPUPMENU *menu;
1076 UINT pos;
1078 TRACE( "(menu=%p id=%#x flags=%04x)\n", handle, id, flags );
1080 if (!(menu = find_menu_item( handle, id, flags, &pos )))
1081 return FALSE;
1083 /* Remove item */
1084 free( menu->items[pos].text );
1086 if (--menu->nItems == 0)
1088 free( menu->items );
1089 menu->items = NULL;
1091 else
1093 MENUITEM *new_items, *item = &menu->items[pos];
1095 while (pos < menu->nItems)
1097 *item = item[1];
1098 item++;
1099 pos++;
1101 new_items = realloc( menu->items, menu->nItems * sizeof(MENUITEM) );
1102 if (new_items) menu->items = new_items;
1105 release_menu_ptr(menu);
1106 return TRUE;
1109 /**********************************************************************
1110 * NtUserDeleteMenu (win32u.@)
1112 BOOL WINAPI NtUserDeleteMenu( HMENU handle, UINT id, UINT flags )
1114 POPUPMENU *menu;
1115 UINT pos;
1117 if (!(menu = find_menu_item( handle, id, flags, &pos )))
1118 return FALSE;
1120 if (menu->items[pos].fType & MF_POPUP)
1121 NtUserDestroyMenu( menu->items[pos].hSubMenu );
1123 NtUserRemoveMenu( menu->obj.handle, pos, flags | MF_BYPOSITION );
1124 release_menu_ptr( menu );
1125 return TRUE;
1128 /**********************************************************************
1129 * NtUserSetMenuContextHelpId (win32u.@)
1131 BOOL WINAPI NtUserSetMenuContextHelpId( HMENU handle, DWORD id )
1133 POPUPMENU *menu;
1135 TRACE( "(%p 0x%08x)\n", handle, id );
1137 if (!(menu = grab_menu_ptr( handle ))) return FALSE;
1138 menu->dwContextHelpID = id;
1139 release_menu_ptr( menu );
1140 return TRUE;
1143 /* see GetSubMenu */
1144 static HMENU get_sub_menu( HMENU handle, INT pos )
1146 POPUPMENU *menu;
1147 HMENU submenu;
1148 UINT i;
1150 if (!(menu = find_menu_item( handle, pos, MF_BYPOSITION, &i )))
1151 return 0;
1153 if (menu->items[i].fType & MF_POPUP)
1154 submenu = menu->items[i].hSubMenu;
1155 else
1156 submenu = 0;
1158 release_menu_ptr(menu);
1159 return submenu;
1162 /**********************************************************************
1163 * NtUserMenuItemFromPoint (win32u.@)
1165 INT WINAPI NtUserMenuItemFromPoint( HWND hwnd, HMENU handle, int x, int y )
1167 POINT pt = { .x = x, .y = y };
1168 POPUPMENU *menu;
1169 UINT pos;
1171 if (!(menu = grab_menu_ptr(handle))) return -1;
1172 if (find_item_by_coords( menu, pt, &pos ) != ht_item) pos = -1;
1173 release_menu_ptr(menu);
1174 return pos;
1177 /**********************************************************************
1178 * NtUserGetSystemMenu (win32u.@)
1180 HMENU WINAPI NtUserGetSystemMenu( HWND hwnd, BOOL revert )
1182 WND *win = get_win_ptr( hwnd );
1183 HMENU retvalue = 0;
1185 if (win == WND_DESKTOP || !win) return 0;
1186 if (win == WND_OTHER_PROCESS)
1188 if (is_window( hwnd )) FIXME( "not supported on other process window %p\n", hwnd );
1189 return 0;
1192 if (win->hSysMenu && revert)
1194 NtUserDestroyMenu( win->hSysMenu );
1195 win->hSysMenu = 0;
1198 if (!win->hSysMenu && (win->dwStyle & WS_SYSMENU) && user_callbacks)
1199 win->hSysMenu = user_callbacks->get_sys_menu( hwnd, 0 );
1201 if (win->hSysMenu)
1203 POPUPMENU *menu;
1204 retvalue = get_sub_menu( win->hSysMenu, 0 );
1206 /* Store the dummy sysmenu handle to facilitate the refresh */
1207 /* of the close button if the SC_CLOSE item change */
1208 menu = grab_menu_ptr( retvalue );
1209 if (menu)
1211 menu->hSysMenuOwner = win->hSysMenu;
1212 release_menu_ptr( menu );
1216 release_win_ptr( win );
1217 return revert ? 0 : retvalue;
1220 /**********************************************************************
1221 * NtUserSetSystemMenu (win32u.@)
1223 BOOL WINAPI NtUserSetSystemMenu( HWND hwnd, HMENU menu )
1225 WND *win = get_win_ptr( hwnd );
1227 if (!win || win == WND_OTHER_PROCESS || win == WND_DESKTOP) return FALSE;
1229 if (win->hSysMenu) NtUserDestroyMenu( win->hSysMenu );
1230 win->hSysMenu = user_callbacks ? user_callbacks->get_sys_menu( hwnd, menu ) : NULL;
1231 release_win_ptr( win );
1232 return TRUE;
1235 /**********************************************************************
1236 * NtUserSetMenuDefaultItem (win32u.@)
1238 BOOL WINAPI NtUserSetMenuDefaultItem( HMENU handle, UINT item, UINT bypos )
1240 MENUITEM *menu_item;
1241 POPUPMENU *menu;
1242 unsigned int i;
1243 BOOL ret = FALSE;
1245 TRACE( "(%p,%d,%d)\n", handle, item, bypos );
1247 if (!(menu = grab_menu_ptr( handle ))) return FALSE;
1249 /* reset all default-item flags */
1250 menu_item = menu->items;
1251 for (i = 0; i < menu->nItems; i++, menu_item++)
1253 menu_item->fState &= ~MFS_DEFAULT;
1256 if (item != -1)
1258 menu_item = menu->items;
1260 if (bypos)
1262 ret = item < menu->nItems;
1263 if (ret) menu->items[item].fState |= MFS_DEFAULT;
1265 else
1267 for (i = 0; i < menu->nItems; i++)
1269 if (menu->items[i].wID == item)
1271 menu->items[i].fState |= MFS_DEFAULT;
1272 ret = TRUE;
1277 else ret = TRUE;
1279 release_menu_ptr( menu );
1280 return ret;
1283 /**********************************************************************
1284 * translate_accelerator
1286 static BOOL translate_accelerator( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam,
1287 BYTE virt, WORD key, WORD cmd )
1289 INT mask = 0;
1290 UINT msg = 0;
1292 if (wparam != key) return FALSE;
1294 if (NtUserGetKeyState( VK_CONTROL ) & 0x8000) mask |= FCONTROL;
1295 if (NtUserGetKeyState( VK_MENU ) & 0x8000) mask |= FALT;
1296 if (NtUserGetKeyState( VK_SHIFT ) & 0x8000) mask |= FSHIFT;
1298 if (message == WM_CHAR || message == WM_SYSCHAR)
1300 if (!(virt & FVIRTKEY) && (mask & FALT) == (virt & FALT))
1302 TRACE_(accel)( "found accel for WM_CHAR: ('%c')\n", LOWORD(wparam) & 0xff );
1303 goto found;
1306 else
1308 if (virt & FVIRTKEY)
1310 TRACE_(accel)( "found accel for virt_key %04lx (scan %04x)\n",
1311 wparam, 0xff & HIWORD(lparam) );
1313 if (mask == (virt & (FSHIFT | FCONTROL | FALT))) goto found;
1314 TRACE_(accel)( ", but incorrect SHIFT/CTRL/ALT-state\n" );
1316 else
1318 if (!(lparam & 0x01000000)) /* no special_key */
1320 if ((virt & FALT) && (lparam & 0x20000000)) /* ALT pressed */
1322 TRACE_(accel)( "found accel for Alt-%c\n", LOWORD(wparam) & 0xff );
1323 goto found;
1328 return FALSE;
1330 found:
1331 if (message == WM_KEYUP || message == WM_SYSKEYUP)
1332 msg = 1;
1333 else
1335 HMENU menu_handle, submenu, sys_menu;
1336 UINT sys_stat = ~0u, stat = ~0u, pos;
1337 POPUPMENU *menu;
1339 menu_handle = (get_window_long( hwnd, GWL_STYLE ) & WS_CHILD) ? 0 : get_menu(hwnd);
1340 sys_menu = get_win_sys_menu( hwnd );
1342 /* find menu item and ask application to initialize it */
1343 /* 1. in the system menu */
1344 if ((menu = find_menu_item( sys_menu, cmd, MF_BYCOMMAND, NULL )))
1346 submenu = menu->obj.handle;
1347 release_menu_ptr( menu );
1349 if (get_capture())
1350 msg = 2;
1351 if (!is_window_enabled( hwnd ))
1352 msg = 3;
1353 else
1355 send_message( hwnd, WM_INITMENU, (WPARAM)sys_menu, 0 );
1356 if (submenu != sys_menu)
1358 pos = find_submenu( &sys_menu, submenu );
1359 TRACE_(accel)( "sys_menu = %p, submenu = %p, pos = %d\n",
1360 sys_menu, submenu, pos );
1361 send_message( hwnd, WM_INITMENUPOPUP, (WPARAM)submenu, MAKELPARAM(pos, TRUE) );
1363 sys_stat = get_menu_state( get_sub_menu( sys_menu, 0 ), cmd, MF_BYCOMMAND );
1366 else /* 2. in the window's menu */
1368 if ((menu = find_menu_item( menu_handle, cmd, MF_BYCOMMAND, NULL )))
1370 submenu = menu->obj.handle;
1371 release_menu_ptr( menu );
1373 if (get_capture())
1374 msg = 2;
1375 if (!is_window_enabled( hwnd ))
1376 msg = 3;
1377 else
1379 send_message( hwnd, WM_INITMENU, (WPARAM)menu_handle, 0 );
1380 if(submenu != menu_handle)
1382 pos = find_submenu( &menu_handle, submenu );
1383 TRACE_(accel)( "menu_handle = %p, submenu = %p, pos = %d\n",
1384 menu_handle, submenu, pos );
1385 send_message( hwnd, WM_INITMENUPOPUP, (WPARAM)submenu,
1386 MAKELPARAM(pos, FALSE) );
1388 stat = get_menu_state( menu_handle, cmd, MF_BYCOMMAND );
1393 if (msg == 0)
1395 if (sys_stat != ~0u)
1397 if (sys_stat & (MF_DISABLED|MF_GRAYED))
1398 msg = 4;
1399 else
1400 msg = WM_SYSCOMMAND;
1402 else
1404 if (stat != ~0u)
1406 if (is_iconic( hwnd ))
1407 msg = 5;
1408 else
1410 if (stat & (MF_DISABLED|MF_GRAYED))
1411 msg = 6;
1412 else
1413 msg = WM_COMMAND;
1416 else
1417 msg = WM_COMMAND;
1422 if (msg == WM_COMMAND)
1424 TRACE_(accel)( ", sending WM_COMMAND, wparam=%0x\n", 0x10000 | cmd );
1425 send_message( hwnd, msg, 0x10000 | cmd, 0 );
1427 else if (msg == WM_SYSCOMMAND)
1429 TRACE_(accel)( ", sending WM_SYSCOMMAND, wparam=%0x\n", cmd );
1430 send_message( hwnd, msg, cmd, 0x00010000 );
1432 else
1434 /* some reasons for NOT sending the WM_{SYS}COMMAND message:
1435 * #0: unknown (please report!)
1436 * #1: for WM_KEYUP,WM_SYSKEYUP
1437 * #2: mouse is captured
1438 * #3: window is disabled
1439 * #4: it's a disabled system menu option
1440 * #5: it's a menu option, but window is iconic
1441 * #6: it's a menu option, but disabled
1443 TRACE_(accel)( ", but won't send WM_{SYS}COMMAND, reason is #%d\n", msg );
1444 if (!msg) ERR_(accel)( " unknown reason\n" );
1446 return TRUE;
1449 /**********************************************************************
1450 * NtUserTranslateAccelerator (win32u.@)
1452 INT WINAPI NtUserTranslateAccelerator( HWND hwnd, HACCEL accel, MSG *msg )
1454 ACCEL data[32], *ptr = data;
1455 int i, count;
1457 if (!hwnd) return 0;
1459 if (msg->message != WM_KEYDOWN &&
1460 msg->message != WM_SYSKEYDOWN &&
1461 msg->message != WM_CHAR &&
1462 msg->message != WM_SYSCHAR)
1463 return 0;
1465 TRACE_(accel)("accel %p, hwnd %p, msg->hwnd %p, msg->message %04x, wParam %08lx, lParam %08lx\n",
1466 accel,hwnd,msg->hwnd,msg->message,msg->wParam,msg->lParam);
1468 if (!(count = NtUserCopyAcceleratorTable( accel, NULL, 0 ))) return 0;
1469 if (count > ARRAY_SIZE( data ))
1471 if (!(ptr = malloc( count * sizeof(*ptr) ))) return 0;
1473 count = NtUserCopyAcceleratorTable( accel, ptr, count );
1474 for (i = 0; i < count; i++)
1476 if (translate_accelerator( hwnd, msg->message, msg->wParam, msg->lParam,
1477 ptr[i].fVirt, ptr[i].key, ptr[i].cmd))
1478 break;
1480 if (ptr != data) free( ptr );
1481 return (i < count);
1484 static HFONT get_menu_font( BOOL bold )
1486 static HFONT menu_font, menu_font_bold;
1488 HFONT ret = bold ? menu_font_bold : menu_font;
1490 if (!ret)
1492 NONCLIENTMETRICSW ncm;
1493 HFONT prev;
1495 ncm.cbSize = sizeof(NONCLIENTMETRICSW);
1496 NtUserSystemParametersInfo( SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICSW), &ncm, 0 );
1498 if (bold)
1500 ncm.lfMenuFont.lfWeight += 300;
1501 if (ncm.lfMenuFont.lfWeight > 1000) ncm.lfMenuFont.lfWeight = 1000;
1503 if (!(ret = NtGdiHfontCreate( &ncm.lfMenuFont, sizeof(ncm.lfMenuFont), 0, 0, NULL )))
1504 return 0;
1505 prev = InterlockedCompareExchangePointer( (void **)(bold ? &menu_font_bold : &menu_font),
1506 ret, NULL );
1507 if (prev)
1509 /* another thread beat us to it */
1510 NtGdiDeleteObjectApp( ret );
1511 ret = prev;
1514 return ret;
1517 static HBITMAP get_arrow_bitmap(void)
1519 static HBITMAP arrow_bitmap;
1521 if (!arrow_bitmap)
1522 arrow_bitmap = LoadImageW( 0, MAKEINTRESOURCEW(OBM_MNARROW), IMAGE_BITMAP, 0, 0, 0 );
1523 return arrow_bitmap;
1526 /* Get the size of a bitmap item */
1527 static void get_bitmap_item_size( MENUITEM *item, SIZE *size, HWND owner )
1529 HBITMAP bmp = item->hbmpItem;
1530 BITMAP bm;
1532 size->cx = size->cy = 0;
1534 /* check if there is a magic menu item associated with this item */
1535 switch ((INT_PTR)bmp)
1537 case (INT_PTR)HBMMENU_CALLBACK:
1539 MEASUREITEMSTRUCT meas_item;
1540 meas_item.CtlType = ODT_MENU;
1541 meas_item.CtlID = 0;
1542 meas_item.itemID = item->wID;
1543 meas_item.itemWidth = item->rect.right - item->rect.left;
1544 meas_item.itemHeight = item->rect.bottom - item->rect.top;
1545 meas_item.itemData = item->dwItemData;
1546 send_message( owner, WM_MEASUREITEM, 0, (LPARAM)&meas_item );
1547 size->cx = meas_item.itemWidth;
1548 size->cy = meas_item.itemHeight;
1549 return;
1551 break;
1552 case (INT_PTR)HBMMENU_SYSTEM:
1553 if (item->dwItemData)
1555 bmp = (HBITMAP)item->dwItemData;
1556 break;
1558 /* fall through */
1559 case (INT_PTR)HBMMENU_MBAR_RESTORE:
1560 case (INT_PTR)HBMMENU_MBAR_MINIMIZE:
1561 case (INT_PTR)HBMMENU_MBAR_MINIMIZE_D:
1562 case (INT_PTR)HBMMENU_MBAR_CLOSE:
1563 case (INT_PTR)HBMMENU_MBAR_CLOSE_D:
1564 size->cx = get_system_metrics( SM_CYMENU ) - 4;
1565 size->cy = size->cx;
1566 return;
1567 case (INT_PTR)HBMMENU_POPUP_CLOSE:
1568 case (INT_PTR)HBMMENU_POPUP_RESTORE:
1569 case (INT_PTR)HBMMENU_POPUP_MAXIMIZE:
1570 case (INT_PTR)HBMMENU_POPUP_MINIMIZE:
1571 size->cx = get_system_metrics( SM_CXMENUSIZE );
1572 size->cy = get_system_metrics( SM_CYMENUSIZE );
1573 return;
1575 if (NtGdiExtGetObjectW( bmp, sizeof(bm), &bm ))
1577 size->cx = bm.bmWidth;
1578 size->cy = bm.bmHeight;
1582 /* Calculate the size of the menu item and store it in item->rect */
1583 static void calc_menu_item_size( HDC hdc, MENUITEM *item, HWND owner, INT org_x, INT org_y,
1584 BOOL menu_bar, POPUPMENU *menu )
1586 UINT check_bitmap_width = get_system_metrics( SM_CXMENUCHECK );
1587 UINT arrow_bitmap_width;
1588 INT item_height;
1589 BITMAP bm;
1590 WCHAR *p;
1592 TRACE( "dc=%p owner=%p (%d,%d) item %s\n", hdc, owner, org_x, org_y, debugstr_menuitem( item ));
1594 NtGdiExtGetObjectW( get_arrow_bitmap(), sizeof(bm), &bm );
1595 arrow_bitmap_width = bm.bmWidth;
1597 if (!menucharsize.cx)
1599 menucharsize.cx = get_char_dimensions( hdc, NULL, &menucharsize.cy );
1600 /* Win95/98/ME will use menucharsize.cy here. Testing is possible
1601 * but it is unlikely an application will depend on that */
1602 od_item_hight = HIWORD( get_dialog_base_units() );
1605 SetRect( &item->rect, org_x, org_y, org_x, org_y );
1607 if (item->fType & MF_OWNERDRAW)
1609 MEASUREITEMSTRUCT mis;
1610 mis.CtlType = ODT_MENU;
1611 mis.CtlID = 0;
1612 mis.itemID = item->wID;
1613 mis.itemData = item->dwItemData;
1614 mis.itemHeight = od_item_hight;
1615 mis.itemWidth = 0;
1616 send_message( owner, WM_MEASUREITEM, 0, (LPARAM)&mis );
1617 /* Tests reveal that Windows ( Win95 through WinXP) adds twice the average
1618 * width of a menufont character to the width of an owner-drawn menu. */
1619 item->rect.right += mis.itemWidth + 2 * menucharsize.cx;
1620 if (menu_bar)
1622 /* Under at least win95 you seem to be given a standard
1623 * height for the menu and the height value is ignored. */
1624 item->rect.bottom += get_system_metrics( SM_CYMENUSIZE );
1626 else
1627 item->rect.bottom += mis.itemHeight;
1629 TRACE( "id=%04lx size=%dx%d\n", item->wID, item->rect.right-item->rect.left,
1630 item->rect.bottom-item->rect.top );
1631 return;
1634 if (item->fType & MF_SEPARATOR)
1636 item->rect.bottom += get_system_metrics( SM_CYMENUSIZE ) / 2;
1637 if (!menu_bar) item->rect.right += arrow_bitmap_width + menucharsize.cx;
1638 return;
1641 item_height = 0;
1642 item->xTab = 0;
1644 if (!menu_bar)
1646 if (item->hbmpItem)
1648 SIZE size;
1650 get_bitmap_item_size( item, &size, owner );
1651 /* Keep the size of the bitmap in callback mode to be able
1652 * to draw it correctly */
1653 item->bmpsize = size;
1654 menu->textOffset = max( menu->textOffset, size.cx );
1655 item->rect.right += size.cx + 2;
1656 item_height = size.cy + 2;
1658 if (!(menu->dwStyle & MNS_NOCHECK)) item->rect.right += check_bitmap_width;
1659 item->rect.right += 4 + menucharsize.cx;
1660 item->xTab = item->rect.right;
1661 item->rect.right += arrow_bitmap_width;
1663 else if (item->hbmpItem) /* menu_bar */
1665 SIZE size;
1667 get_bitmap_item_size( item, &size, owner );
1668 item->bmpsize = size;
1669 item->rect.right += size.cx;
1670 if (item->text) item->rect.right += 2;
1671 item_height = size.cy;
1674 /* it must be a text item - unless it's the system menu */
1675 if (!(item->fType & MF_SYSMENU) && item->text)
1677 LONG txt_height, txt_width;
1678 HFONT prev_font = NULL;
1679 RECT rc = item->rect;
1681 if (item->fState & MFS_DEFAULT)
1682 prev_font = NtGdiSelectFont( hdc, get_menu_font(TRUE) );
1684 if (menu_bar)
1686 txt_height = DrawTextW( hdc, item->text, -1, &rc, DT_SINGLELINE | DT_CALCRECT );
1687 item->rect.right += rc.right - rc.left;
1688 item_height = max( max( item_height, txt_height ),
1689 get_system_metrics( SM_CYMENU ) - 1 );
1690 item->rect.right += 2 * menucharsize.cx;
1692 else
1694 if ((p = wcschr( item->text, '\t' )))
1696 RECT r = rc;
1697 int h, n = (int)(p - item->text);
1699 /* Item contains a tab (only meaningful in popup menus) */
1700 /* get text size before the tab */
1701 txt_height = DrawTextW( hdc, item->text, n, &rc, DT_SINGLELINE | DT_CALCRECT );
1702 txt_width = rc.right - rc.left;
1703 p += 1; /* advance past the Tab */
1704 /* get text size after the tab */
1705 h = DrawTextW( hdc, p, -1, &r, DT_SINGLELINE | DT_CALCRECT );
1706 item->xTab += txt_width;
1707 txt_height = max( txt_height, h );
1708 /* space for the tab and the short cut */
1709 txt_width += menucharsize.cx + r.right - r.left;
1711 else
1713 txt_height = DrawTextW( hdc, item->text, -1, &rc, DT_SINGLELINE | DT_CALCRECT );
1714 txt_width = rc.right - rc.left;
1715 item->xTab += txt_width;
1717 item->rect.right += 2 + txt_width;
1718 item_height = max( item_height, max( txt_height + 2, menucharsize.cy + 4 ));
1720 if (prev_font) NtGdiSelectFont( hdc, prev_font );
1722 else if (menu_bar)
1724 item_height = max( item_height, get_system_metrics( SM_CYMENU ) - 1 );
1726 item->rect.bottom += item_height;
1727 TRACE( "%s\n", wine_dbgstr_rect( &item->rect ));
1730 /* Calculate the size of the menu bar */
1731 static void calc_menu_bar_size( HDC hdc, RECT *rect, POPUPMENU *menu, HWND owner )
1733 UINT start, i, help_pos;
1734 int org_x, org_y;
1735 MENUITEM *item;
1737 if (!rect || !menu || !menu->nItems) return;
1739 TRACE( "rect %p %s\n", rect, wine_dbgstr_rect( rect ));
1740 /* Start with a 1 pixel top border.
1741 This corresponds to the difference between SM_CYMENU and SM_CYMENUSIZE. */
1742 SetRect( &menu->items_rect, 0, 0, rect->right - rect->left, 1 );
1743 start = 0;
1744 help_pos = ~0u;
1745 menu->textOffset = 0;
1746 while (start < menu->nItems)
1748 item = &menu->items[start];
1749 org_x = menu->items_rect.left;
1750 org_y = menu->items_rect.bottom;
1752 /* Parse items until line break or end of menu */
1753 for (i = start; i < menu->nItems; i++, item++)
1755 if (help_pos == ~0u && (item->fType & MF_RIGHTJUSTIFY)) help_pos = i;
1756 if (i != start && (item->fType & (MF_MENUBREAK | MF_MENUBARBREAK))) break;
1758 TRACE("item org=(%d, %d) %s\n", org_x, org_y, debugstr_menuitem( item ));
1759 calc_menu_item_size( hdc, item, owner, org_x, org_y, TRUE, menu );
1761 if (item->rect.right > menu->items_rect.right)
1763 if (i != start) break;
1764 else item->rect.right = menu->items_rect.right;
1766 menu->items_rect.bottom = max( menu->items_rect.bottom, item->rect.bottom );
1767 org_x = item->rect.right;
1770 /* Finish the line (set all items to the largest height found) */
1771 while (start < i) menu->items[start++].rect.bottom = menu->items_rect.bottom;
1774 OffsetRect( &menu->items_rect, rect->left, rect->top );
1775 menu->Width = menu->items_rect.right - menu->items_rect.left;
1776 menu->Height = menu->items_rect.bottom - menu->items_rect.top;
1777 rect->bottom = menu->items_rect.bottom;
1779 /* Flush right all items between the MF_RIGHTJUSTIFY and */
1780 /* the last item (if several lines, only move the last line) */
1781 if (help_pos == ~0u) return;
1782 item = &menu->items[menu->nItems-1];
1783 org_y = item->rect.top;
1784 org_x = rect->right - rect->left;
1785 for (i = menu->nItems - 1; i >= help_pos; i--, item--)
1787 if (item->rect.top != org_y) break; /* other line */
1788 if (item->rect.right >= org_x) break; /* too far right already */
1789 item->rect.left += org_x - item->rect.right;
1790 item->rect.right = org_x;
1791 org_x = item->rect.left;
1795 UINT get_menu_bar_height( HWND hwnd, UINT width, INT org_x, INT org_y )
1797 POPUPMENU *menu;
1798 RECT rect_bar;
1799 HDC hdc;
1801 TRACE( "hwnd %p, width %d, at (%d, %d).\n", hwnd, width, org_x, org_y );
1803 if (!(menu = unsafe_menu_ptr( get_menu( hwnd )))) return 0;
1805 hdc = NtUserGetDCEx( hwnd, 0, DCX_CACHE | DCX_WINDOW );
1806 NtGdiSelectFont( hdc, get_menu_font(FALSE));
1807 SetRect( &rect_bar, org_x, org_y, org_x + width, org_y + get_system_metrics( SM_CYMENU ));
1808 calc_menu_bar_size( hdc, &rect_bar, menu, hwnd );
1809 NtUserReleaseDC( hwnd, hdc );
1810 return menu->Height;
1813 static void draw_popup_arrow( HDC hdc, RECT rect, UINT arrow_width, UINT arrow_height )
1815 HDC mem_hdc = NtGdiCreateCompatibleDC( hdc );
1816 HBITMAP prev_bitmap;
1818 prev_bitmap = NtGdiSelectBitmap( mem_hdc, get_arrow_bitmap() );
1819 NtGdiBitBlt( hdc, rect.right - arrow_width - 1,
1820 (rect.top + rect.bottom - arrow_height) / 2,
1821 arrow_width, arrow_height, mem_hdc, 0, 0, SRCCOPY, 0, 0 );
1822 NtGdiSelectBitmap( mem_hdc, prev_bitmap );
1823 NtGdiDeleteObjectApp( mem_hdc );
1826 static void draw_bitmap_item( HDC hdc, MENUITEM *item, const RECT *rect,
1827 POPUPMENU *menu, HWND owner, UINT odaction )
1829 int w = rect->right - rect->left;
1830 int h = rect->bottom - rect->top;
1831 int bmp_xoffset = 0, left, top;
1832 HBITMAP bmp_to_draw = item->hbmpItem;
1833 HBITMAP bmp = bmp_to_draw;
1834 BITMAP bm;
1835 DWORD rop;
1836 HDC mem_hdc;
1838 /* Check if there is a magic menu item associated with this item */
1839 if (IS_MAGIC_BITMAP( bmp_to_draw ))
1841 UINT flags = 0;
1842 WCHAR bmchr = 0;
1843 RECT r;
1845 switch ((INT_PTR)bmp_to_draw)
1847 case (INT_PTR)HBMMENU_SYSTEM:
1848 if (item->dwItemData)
1850 bmp = (HBITMAP)item->dwItemData;
1851 if (!NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return;
1853 else
1855 static HBITMAP sys_menu_bmp;
1857 if (!sys_menu_bmp)
1858 sys_menu_bmp = LoadImageW( 0, MAKEINTRESOURCEW(OBM_CLOSE), IMAGE_BITMAP, 0, 0, 0 );
1859 bmp = sys_menu_bmp;
1860 if (!NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return;
1861 /* only use right half of the bitmap */
1862 bmp_xoffset = bm.bmWidth / 2;
1863 bm.bmWidth -= bmp_xoffset;
1865 goto got_bitmap;
1866 case (INT_PTR)HBMMENU_MBAR_RESTORE:
1867 flags = DFCS_CAPTIONRESTORE;
1868 break;
1869 case (INT_PTR)HBMMENU_MBAR_MINIMIZE:
1870 flags = DFCS_CAPTIONMIN;
1871 break;
1872 case (INT_PTR)HBMMENU_MBAR_MINIMIZE_D:
1873 flags = DFCS_CAPTIONMIN | DFCS_INACTIVE;
1874 break;
1875 case (INT_PTR)HBMMENU_MBAR_CLOSE:
1876 flags = DFCS_CAPTIONCLOSE;
1877 break;
1878 case (INT_PTR)HBMMENU_MBAR_CLOSE_D:
1879 flags = DFCS_CAPTIONCLOSE | DFCS_INACTIVE;
1880 break;
1881 case (INT_PTR)HBMMENU_CALLBACK:
1883 DRAWITEMSTRUCT drawItem;
1884 drawItem.CtlType = ODT_MENU;
1885 drawItem.CtlID = 0;
1886 drawItem.itemID = item->wID;
1887 drawItem.itemAction = odaction;
1888 drawItem.itemState = 0;
1889 if (item->fState & MF_CHECKED) drawItem.itemState |= ODS_CHECKED;
1890 if (item->fState & MF_DEFAULT) drawItem.itemState |= ODS_DEFAULT;
1891 if (item->fState & MF_DISABLED) drawItem.itemState |= ODS_DISABLED;
1892 if (item->fState & MF_GRAYED) drawItem.itemState |= ODS_GRAYED|ODS_DISABLED;
1893 if (item->fState & MF_HILITE) drawItem.itemState |= ODS_SELECTED;
1894 drawItem.hwndItem = (HWND)menu->obj.handle;
1895 drawItem.hDC = hdc;
1896 drawItem.itemData = item->dwItemData;
1897 drawItem.rcItem = *rect;
1898 send_message( owner, WM_DRAWITEM, 0, (LPARAM)&drawItem );
1899 return;
1901 break;
1902 case (INT_PTR)HBMMENU_POPUP_CLOSE:
1903 bmchr = 0x72;
1904 break;
1905 case (INT_PTR)HBMMENU_POPUP_RESTORE:
1906 bmchr = 0x32;
1907 break;
1908 case (INT_PTR)HBMMENU_POPUP_MAXIMIZE:
1909 bmchr = 0x31;
1910 break;
1911 case (INT_PTR)HBMMENU_POPUP_MINIMIZE:
1912 bmchr = 0x30;
1913 break;
1914 default:
1915 FIXME( "Magic %p not implemented\n", bmp_to_draw );
1916 return;
1919 if (bmchr)
1921 /* draw the magic bitmaps using marlett font characters */
1922 /* FIXME: fontsize and the position (x,y) could probably be better */
1923 HFONT hfont, prev_font;
1924 LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
1925 {'M','a','r','l','e','t','t'}};
1926 logfont.lfHeight = min( h, w) - 5 ;
1927 TRACE( " height %d rect %s\n", logfont.lfHeight, wine_dbgstr_rect( rect ));
1928 hfont = NtGdiHfontCreate( &logfont, sizeof(logfont), 0, 0, NULL );
1929 prev_font = NtGdiSelectFont( hdc, hfont );
1930 NtGdiExtTextOutW( hdc, rect->left, rect->top + 2, 0, NULL, &bmchr, 1, NULL, 0 );
1931 NtGdiSelectFont( hdc, prev_font );
1932 NtGdiDeleteObjectApp( hfont );
1934 else
1936 r = *rect;
1937 InflateRect( &r, -1, -1 );
1938 if (item->fState & MF_HILITE) flags |= DFCS_PUSHED;
1939 draw_frame_caption( hdc, &r, flags );
1941 return;
1944 if (!bmp || !NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return;
1946 got_bitmap:
1947 mem_hdc = NtGdiCreateCompatibleDC( hdc );
1948 NtGdiSelectBitmap( mem_hdc, bmp );
1950 /* handle fontsize > bitmap_height */
1951 top = (h>bm.bmHeight) ? rect->top + (h - bm.bmHeight) / 2 : rect->top;
1952 left=rect->left;
1953 rop= ((item->fState & MF_HILITE) && !IS_MAGIC_BITMAP(bmp_to_draw)) ? NOTSRCCOPY : SRCCOPY;
1954 if ((item->fState & MF_HILITE) && item->hbmpItem)
1955 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color( COLOR_HIGHLIGHT ), NULL );
1956 NtGdiBitBlt( hdc, left, top, w, h, mem_hdc, bmp_xoffset, 0, rop, 0, 0 );
1957 NtGdiDeleteObjectApp( mem_hdc );
1960 /* Draw a single menu item */
1961 static void draw_menu_item( HWND hwnd, POPUPMENU *menu, HWND owner, HDC hdc,
1962 MENUITEM *item, BOOL menu_bar, UINT odaction )
1964 UINT arrow_width = 0, arrow_height = 0;
1965 HRGN old_clip = NULL, clip;
1966 BOOL flat_menu = FALSE;
1967 RECT rect, bmprc;
1968 int bkgnd;
1970 TRACE( "%s\n", debugstr_menuitem( item ));
1972 if (!menu_bar)
1974 BITMAP bmp;
1975 NtGdiExtGetObjectW( get_arrow_bitmap(), sizeof(bmp), &bmp );
1976 arrow_width = bmp.bmWidth;
1977 arrow_height = bmp.bmHeight;
1980 if (item->fType & MF_SYSMENU)
1982 if (!is_iconic( hwnd ))
1983 draw_nc_sys_button( hwnd, hdc, item->fState & (MF_HILITE | MF_MOUSESELECT) );
1984 return;
1987 TRACE( "rect=%s\n", wine_dbgstr_rect( &item->rect ));
1988 rect = item->rect;
1989 adjust_menu_item_rect( menu, &rect );
1990 if (!intersect_rect( &bmprc, &rect, &menu->items_rect )) /* bmprc is used as a dummy */
1991 return;
1993 NtUserSystemParametersInfo( SPI_GETFLATMENU, 0, &flat_menu, 0 );
1994 bkgnd = (menu_bar && flat_menu) ? COLOR_MENUBAR : COLOR_MENU;
1996 /* Setup colors */
1997 if (item->fState & MF_HILITE)
1999 if (menu_bar && !flat_menu)
2001 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color(COLOR_MENUTEXT), NULL );
2002 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color(COLOR_MENU), NULL );
2004 else
2006 if (item->fState & MF_GRAYED)
2007 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_GRAYTEXT ), NULL );
2008 else
2009 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_HIGHLIGHTTEXT ), NULL );
2010 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color( COLOR_HIGHLIGHT ), NULL );
2013 else
2015 if (item->fState & MF_GRAYED)
2016 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_GRAYTEXT ), NULL );
2017 else
2018 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, get_sys_color( COLOR_MENUTEXT ), NULL );
2019 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, get_sys_color( bkgnd ), NULL );
2022 old_clip = NtGdiCreateRectRgn( 0, 0, 0, 0 );
2023 if (NtGdiGetRandomRgn( hdc, old_clip, NTGDI_RGN_MIRROR_RTL | 1 ) <= 0)
2025 NtGdiDeleteObjectApp( old_clip );
2026 old_clip = NULL;
2028 clip = NtGdiCreateRectRgn( menu->items_rect.left, menu->items_rect.top,
2029 menu->items_rect.right, menu->items_rect.bottom );
2030 NtGdiExtSelectClipRgn( hdc, clip, RGN_AND );
2031 NtGdiDeleteObjectApp( clip );
2033 if (item->fType & MF_OWNERDRAW)
2036 * Experimentation under Windows reveals that an owner-drawn
2037 * menu is given the rectangle which includes the space it requested
2038 * in its response to WM_MEASUREITEM _plus_ width for a checkmark
2039 * and a popup-menu arrow. This is the value of item->rect.
2040 * Windows will leave all drawing to the application except for
2041 * the popup-menu arrow. Windows always draws that itself, after
2042 * the menu owner has finished drawing.
2044 DRAWITEMSTRUCT dis;
2045 DWORD old_bk, old_text;
2047 dis.CtlType = ODT_MENU;
2048 dis.CtlID = 0;
2049 dis.itemID = item->wID;
2050 dis.itemData = item->dwItemData;
2051 dis.itemState = 0;
2052 if (item->fState & MF_CHECKED) dis.itemState |= ODS_CHECKED;
2053 if (item->fState & MF_GRAYED) dis.itemState |= ODS_GRAYED|ODS_DISABLED;
2054 if (item->fState & MF_HILITE) dis.itemState |= ODS_SELECTED;
2055 dis.itemAction = odaction; /* ODA_DRAWENTIRE | ODA_SELECT | ODA_FOCUS; */
2056 dis.hwndItem = (HWND)menu->obj.handle;
2057 dis.hDC = hdc;
2058 dis.rcItem = rect;
2059 TRACE( "Ownerdraw: owner=%p itemID=%d, itemState=%d, itemAction=%d, "
2060 "hwndItem=%p, hdc=%p, rcItem=%s\n", owner,
2061 dis.itemID, dis.itemState, dis.itemAction, dis.hwndItem,
2062 dis.hDC, wine_dbgstr_rect( &dis.rcItem ));
2063 NtGdiGetDCDword( hdc, NtGdiGetBkColor, &old_bk );
2064 NtGdiGetDCDword( hdc, NtGdiGetTextColor, &old_text );
2065 send_message( owner, WM_DRAWITEM, 0, (LPARAM)&dis );
2066 /* Draw the popup-menu arrow */
2067 NtGdiGetAndSetDCDword( hdc, NtGdiGetBkColor, old_bk, NULL );
2068 NtGdiGetAndSetDCDword( hdc, NtGdiGetTextColor, old_text, NULL );
2069 if (item->fType & MF_POPUP)
2070 draw_popup_arrow( hdc, rect, arrow_width, arrow_height );
2071 goto done;
2074 if (menu_bar && (item->fType & MF_SEPARATOR)) goto done;
2076 if (item->fState & MF_HILITE)
2078 if (flat_menu)
2080 InflateRect (&rect, -1, -1);
2081 fill_rect( hdc, &rect, get_sys_color_brush( COLOR_MENUHILIGHT ));
2082 InflateRect (&rect, 1, 1);
2083 fill_rect( hdc, &rect, get_sys_color_brush( COLOR_HIGHLIGHT ));
2085 else
2087 if (menu_bar)
2088 draw_rect_edge( hdc, &rect, BDR_SUNKENOUTER, BF_RECT, 1 );
2089 else
2090 fill_rect( hdc, &rect, get_sys_color_brush( COLOR_HIGHLIGHT ));
2093 else
2094 fill_rect( hdc, &rect, get_sys_color_brush(bkgnd) );
2096 NtGdiGetAndSetDCDword( hdc, NtGdiSetBkMode, TRANSPARENT, NULL );
2098 /* vertical separator */
2099 if (!menu_bar && (item->fType & MF_MENUBARBREAK))
2101 HPEN oldPen;
2102 RECT rc = rect;
2104 rc.left -= MENU_COL_SPACE / 2 + 1;
2105 rc.top = 3;
2106 rc.bottom = menu->Height - 3;
2107 if (flat_menu)
2109 oldPen = NtGdiSelectPen( hdc, get_sys_color_pen( COLOR_BTNSHADOW ));
2110 NtGdiMoveTo( hdc, rc.left, rc.top, NULL );
2111 NtGdiLineTo( hdc, rc.left, rc.bottom );
2112 NtGdiSelectPen( hdc, oldPen );
2114 else
2115 draw_rect_edge( hdc, &rc, EDGE_ETCHED, BF_LEFT, 1 );
2118 /* horizontal separator */
2119 if (item->fType & MF_SEPARATOR)
2121 HPEN oldPen;
2122 RECT rc = rect;
2124 InflateRect( &rc, -1, 0 );
2125 rc.top = ( rc.top + rc.bottom) / 2;
2126 if (flat_menu)
2128 oldPen = NtGdiSelectPen( hdc, get_sys_color_pen( COLOR_BTNSHADOW ));
2129 NtGdiMoveTo( hdc, rc.left, rc.top, NULL );
2130 NtGdiLineTo( hdc, rc.right, rc.top );
2131 NtGdiSelectPen( hdc, oldPen );
2133 else
2134 draw_rect_edge( hdc, &rc, EDGE_ETCHED, BF_TOP, 1 );
2135 goto done;
2138 if (item->hbmpItem)
2140 /* calculate the bitmap rectangle in coordinates relative
2141 * to the item rectangle */
2142 if (menu_bar)
2144 if (item->hbmpItem == HBMMENU_CALLBACK)
2145 bmprc.left = 3;
2146 else
2147 bmprc.left = item->text ? menucharsize.cx : 0;
2149 else if (menu->dwStyle & MNS_NOCHECK)
2150 bmprc.left = 4;
2151 else if (menu->dwStyle & MNS_CHECKORBMP)
2152 bmprc.left = 2;
2153 else
2154 bmprc.left = 4 + get_system_metrics( SM_CXMENUCHECK );
2155 bmprc.right = bmprc.left + item->bmpsize.cx;
2156 if (menu_bar && !(item->hbmpItem == HBMMENU_CALLBACK))
2157 bmprc.top = 0;
2158 else
2159 bmprc.top = (rect.bottom - rect.top - item->bmpsize.cy) / 2;
2160 bmprc.bottom = bmprc.top + item->bmpsize.cy;
2163 if (!menu_bar)
2165 HBITMAP bm;
2166 INT y = rect.top + rect.bottom;
2167 BOOL checked = FALSE;
2168 UINT check_bitmap_width = get_system_metrics( SM_CXMENUCHECK );
2169 UINT check_bitmap_height = get_system_metrics( SM_CYMENUCHECK );
2171 /* Draw the check mark */
2172 if (!(menu->dwStyle & MNS_NOCHECK))
2174 bm = (item->fState & MF_CHECKED) ? item->hCheckBit :
2175 item->hUnCheckBit;
2176 if (bm) /* we have a custom bitmap */
2178 HDC mem_hdc = NtGdiCreateCompatibleDC( hdc );
2180 NtGdiSelectBitmap( mem_hdc, bm );
2181 NtGdiBitBlt( hdc, rect.left, (y - check_bitmap_height) / 2,
2182 check_bitmap_width, check_bitmap_height,
2183 mem_hdc, 0, 0, SRCCOPY, 0, 0 );
2184 NtGdiDeleteObjectApp( mem_hdc );
2185 checked = TRUE;
2187 else if (item->fState & MF_CHECKED) /* standard bitmaps */
2189 RECT r;
2190 HBITMAP bm = NtGdiCreateBitmap( check_bitmap_width,
2191 check_bitmap_height, 1, 1, NULL );
2192 HDC mem_hdc = NtGdiCreateCompatibleDC( hdc );
2194 NtGdiSelectBitmap( mem_hdc, bm );
2195 SetRect( &r, 0, 0, check_bitmap_width, check_bitmap_height);
2196 draw_frame_menu( mem_hdc, &r,
2197 (item->fType & MFT_RADIOCHECK) ? DFCS_MENUBULLET : DFCS_MENUCHECK );
2198 NtGdiBitBlt( hdc, rect.left, (y - r.bottom) / 2, r.right, r.bottom,
2199 mem_hdc, 0, 0, SRCCOPY, 0, 0 );
2200 NtGdiDeleteObjectApp( mem_hdc );
2201 NtGdiDeleteObjectApp( bm );
2202 checked = TRUE;
2205 if (item->hbmpItem && !(checked && (menu->dwStyle & MNS_CHECKORBMP)))
2207 POINT origorg;
2208 /* some applications make this assumption on the DC's origin */
2209 set_viewport_org( hdc, rect.left, rect.top, &origorg );
2210 draw_bitmap_item( hdc, item, &bmprc, menu, owner, odaction );
2211 set_viewport_org( hdc, origorg.x, origorg.y, NULL );
2213 /* Draw the popup-menu arrow */
2214 if (item->fType & MF_POPUP)
2215 draw_popup_arrow( hdc, rect, arrow_width, arrow_height);
2216 rect.left += 4;
2217 if (!(menu->dwStyle & MNS_NOCHECK))
2218 rect.left += check_bitmap_width;
2219 rect.right -= arrow_width;
2221 else if (item->hbmpItem)
2222 { /* Draw the bitmap */
2223 POINT origorg;
2225 set_viewport_org( hdc, rect.left, rect.top, &origorg);
2226 draw_bitmap_item( hdc, item, &bmprc, menu, owner, odaction );
2227 set_viewport_org( hdc, origorg.x, origorg.y, NULL);
2229 /* process text if present */
2230 if (item->text)
2232 int i;
2233 HFONT prev_font = 0;
2234 UINT format = menu_bar ?
2235 DT_CENTER | DT_VCENTER | DT_SINGLELINE :
2236 DT_LEFT | DT_VCENTER | DT_SINGLELINE;
2238 if (!(menu->dwStyle & MNS_CHECKORBMP))
2239 rect.left += menu->textOffset;
2241 if (item->fState & MFS_DEFAULT)
2243 prev_font = NtGdiSelectFont(hdc, get_menu_font( TRUE ));
2246 if (menu_bar)
2248 if (item->hbmpItem)
2249 rect.left += item->bmpsize.cx;
2250 if (item->hbmpItem != HBMMENU_CALLBACK)
2251 rect.left += menucharsize.cx;
2252 rect.right -= menucharsize.cx;
2255 for (i = 0; item->text[i]; i++)
2256 if ((item->text[i] == '\t') || (item->text[i] == '\b'))
2257 break;
2259 if (item->fState & MF_GRAYED)
2261 if (!(item->fState & MF_HILITE) )
2263 ++rect.left; ++rect.top; ++rect.right; ++rect.bottom;
2264 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0xff, 0xff, 0xff), NULL );
2265 DrawTextW( hdc, item->text, i, &rect, format );
2266 --rect.left; --rect.top; --rect.right; --rect.bottom;
2268 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0x80, 0x80, 0x80), NULL );
2271 DrawTextW( hdc, item->text, i, &rect, format );
2273 /* paint the shortcut text */
2274 if (!menu_bar && item->text[i]) /* There's a tab or flush-right char */
2276 if (item->text[i] == '\t')
2278 rect.left = item->xTab;
2279 format = DT_LEFT | DT_VCENTER | DT_SINGLELINE;
2281 else
2283 rect.right = item->xTab;
2284 format = DT_RIGHT | DT_VCENTER | DT_SINGLELINE;
2287 if (item->fState & MF_GRAYED)
2289 if (!(item->fState & MF_HILITE) )
2291 ++rect.left; ++rect.top; ++rect.right; ++rect.bottom;
2292 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0xff, 0xff, 0xff), NULL );
2293 DrawTextW( hdc, item->text + i + 1, -1, &rect, format );
2294 --rect.left; --rect.top; --rect.right; --rect.bottom;
2296 NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0x80, 0x80, 0x80), NULL );
2298 DrawTextW( hdc, item->text + i + 1, -1, &rect, format );
2301 if (prev_font) NtGdiSelectFont( hdc, prev_font );
2304 done:
2305 NtGdiExtSelectClipRgn( hdc, old_clip, RGN_COPY );
2306 if (old_clip) NtGdiDeleteObjectApp( old_clip );
2309 /***********************************************************************
2310 * NtUserDrawMenuBarTemp (win32u.@)
2312 DWORD WINAPI NtUserDrawMenuBarTemp( HWND hwnd, HDC hdc, RECT *rect, HMENU handle, HFONT font )
2314 BOOL flat_menu = FALSE;
2315 HFONT prev_font = 0;
2316 POPUPMENU *menu;
2317 UINT i, retvalue;
2319 NtUserSystemParametersInfo( SPI_GETFLATMENU, 0, &flat_menu, 0 );
2321 if (!handle) handle = get_menu( hwnd );
2322 if (!font) font = get_menu_font(FALSE);
2324 menu = unsafe_menu_ptr( handle );
2325 if (!menu || !rect) return get_system_metrics( SM_CYMENU );
2327 TRACE( "(%p, %p, %p, %p, %p)\n", hwnd, hdc, rect, handle, font );
2329 prev_font = NtGdiSelectFont( hdc, font );
2331 if (!menu->Height) calc_menu_bar_size( hdc, rect, menu, hwnd );
2333 rect->bottom = rect->top + menu->Height;
2335 fill_rect( hdc, rect, get_sys_color_brush( flat_menu ? COLOR_MENUBAR : COLOR_MENU ));
2337 NtGdiSelectPen( hdc, get_sys_color_pen( COLOR_3DFACE ));
2338 NtGdiMoveTo( hdc, rect->left, rect->bottom, NULL );
2339 NtGdiLineTo( hdc, rect->right, rect->bottom );
2341 if (menu->nItems)
2343 for (i = 0; i < menu->nItems; i++)
2344 draw_menu_item( hwnd, menu, hwnd, hdc, &menu->items[i], TRUE, ODA_DRAWENTIRE );
2346 retvalue = menu->Height;
2348 else
2350 retvalue = get_system_metrics( SM_CYMENU );
2353 if (prev_font) NtGdiSelectFont( hdc, prev_font );
2354 return retvalue;
2357 static UINT get_scroll_arrow_height( const POPUPMENU *menu )
2359 return menucharsize.cy + 4;
2362 static void draw_scroll_arrow( HDC hdc, int x, int top, int height, BOOL up, BOOL enabled )
2364 RECT rect, light_rect;
2365 HBRUSH brush = get_sys_color_brush( enabled ? COLOR_BTNTEXT : COLOR_BTNSHADOW );
2366 HBRUSH light = get_sys_color_brush( COLOR_3DLIGHT );
2368 if (!up)
2370 top = top + height;
2371 if (!enabled)
2373 SetRect( &rect, x + 1, top, x + 2, top + 1);
2374 fill_rect( hdc, &rect, light );
2376 top--;
2379 SetRect( &rect, x, top, x + 1, top + 1);
2380 while (height--)
2382 fill_rect( hdc, &rect, brush );
2383 if (!enabled && !up && height)
2385 SetRect( &light_rect, rect.right, rect.top, rect.right + 2, rect.bottom );
2386 fill_rect( hdc, &light_rect, light );
2388 InflateRect( &rect, 1, 0 );
2389 OffsetRect( &rect, 0, up ? 1 : -1 );
2392 if (!enabled && up)
2394 rect.left += 2;
2395 fill_rect( hdc, &rect, light );
2399 static void draw_scroll_arrows( const POPUPMENU *menu, HDC hdc )
2401 UINT full_height = get_scroll_arrow_height( menu );
2402 UINT arrow_height = full_height / 3;
2403 BOOL at_end = menu->nScrollPos + menu->items_rect.bottom - menu->items_rect.top == menu->nTotalHeight;
2405 draw_scroll_arrow( hdc, menu->Width / 3, arrow_height, arrow_height,
2406 TRUE, menu->nScrollPos != 0);
2407 draw_scroll_arrow( hdc, menu->Width / 3, menu->Height - 2 * arrow_height, arrow_height,
2408 FALSE, !at_end );
2411 static int frame_rect( HDC hdc, const RECT *rect, HBRUSH hbrush )
2413 HBRUSH prev_brush;
2414 RECT r = *rect;
2416 if (IsRectEmpty(&r)) return 0;
2417 if (!(prev_brush = NtGdiSelectBrush( hdc, hbrush ))) return 0;
2419 NtGdiPatBlt( hdc, r.left, r.top, 1, r.bottom - r.top, PATCOPY );
2420 NtGdiPatBlt( hdc, r.right - 1, r.top, 1, r.bottom - r.top, PATCOPY );
2421 NtGdiPatBlt( hdc, r.left, r.top, r.right - r.left, 1, PATCOPY );
2422 NtGdiPatBlt( hdc, r.left, r.bottom - 1, r.right - r.left, 1, PATCOPY );
2424 NtGdiSelectBrush( hdc, prev_brush );
2425 return TRUE;
2428 static void draw_popup_menu( HWND hwnd, HDC hdc, HMENU hmenu )
2430 HBRUSH prev_hrush, brush = get_sys_color_brush( COLOR_MENU );
2431 POPUPMENU *menu = unsafe_menu_ptr( hmenu );
2432 RECT rect;
2434 TRACE( "wnd=%p dc=%p menu=%p\n", hwnd, hdc, hmenu );
2436 get_client_rect( hwnd, &rect );
2438 if (menu && menu->hbrBack) brush = menu->hbrBack;
2439 if ((prev_hrush = NtGdiSelectBrush( hdc, brush ))
2440 && NtGdiSelectFont( hdc, get_menu_font( FALSE )))
2442 HPEN prev_pen;
2444 NtGdiRectangle( hdc, rect.left, rect.top, rect.right, rect.bottom );
2446 prev_pen = NtGdiSelectPen( hdc, GetStockObject( NULL_PEN ));
2447 if (prev_pen)
2449 BOOL flat_menu = FALSE;
2451 NtUserSystemParametersInfo( SPI_GETFLATMENU, 0, &flat_menu, 0 );
2452 if (flat_menu)
2453 frame_rect( hdc, &rect, get_sys_color_brush( COLOR_BTNSHADOW ));
2454 else
2455 draw_rect_edge( hdc, &rect, EDGE_RAISED, BF_RECT, 1 );
2457 if (menu)
2459 TRACE( "hmenu %p Style %08x\n", hmenu, menu->dwStyle );
2460 /* draw menu items */
2461 if (menu->nItems)
2463 MENUITEM *item;
2464 UINT u;
2466 item = menu->items;
2467 for (u = menu->nItems; u > 0; u--, item++)
2468 draw_menu_item( hwnd, menu, menu->hwndOwner, hdc,
2469 item, FALSE, ODA_DRAWENTIRE );
2471 if (menu->bScrolling) draw_scroll_arrows( menu, hdc );
2474 else
2476 NtGdiSelectBrush( hdc, prev_hrush );
2481 LRESULT popup_menu_window_proc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam )
2483 TRACE( "hwnd=%p msg=0x%04x wp=0x%04lx lp=0x%08lx\n", hwnd, message, wparam, lparam );
2485 switch(message)
2487 case WM_CREATE:
2489 CREATESTRUCTW *cs = (CREATESTRUCTW *)lparam;
2490 NtUserSetWindowLongPtr( hwnd, 0, (LONG_PTR)cs->lpCreateParams, FALSE );
2491 return 0;
2494 case WM_MOUSEACTIVATE: /* We don't want to be activated */
2495 return MA_NOACTIVATE;
2497 case WM_PAINT:
2499 PAINTSTRUCT ps;
2500 NtUserBeginPaint( hwnd, &ps );
2501 draw_popup_menu( hwnd, ps.hdc, (HMENU)get_window_long_ptr( hwnd, 0, FALSE ));
2502 NtUserEndPaint( hwnd, &ps );
2503 return 0;
2506 case WM_PRINTCLIENT:
2508 draw_popup_menu( hwnd, (HDC)wparam, (HMENU)get_window_long_ptr( hwnd, 0, FALSE ));
2509 return 0;
2512 case WM_ERASEBKGND:
2513 return 1;
2515 case WM_DESTROY:
2516 /* zero out global pointer in case resident popup window was destroyed. */
2517 if (hwnd == top_popup)
2519 top_popup = 0;
2520 top_popup_hmenu = NULL;
2522 break;
2524 case WM_SHOWWINDOW:
2525 if (wparam)
2527 if (!get_window_long_ptr( hwnd, 0, FALSE )) ERR( "no menu to display\n" );
2529 else
2530 NtUserSetWindowLongPtr( hwnd, 0, 0, FALSE );
2531 break;
2533 case MN_GETHMENU:
2534 return get_window_long_ptr( hwnd, 0, FALSE );
2536 default:
2537 return default_window_proc( hwnd, message, wparam, lparam, FALSE );
2539 return 0;
2542 HWND is_menu_active(void)
2544 return top_popup;
2547 /* Calculate the size of a popup menu */
2548 static void calc_popup_menu_size( POPUPMENU *menu, UINT max_height )
2550 BOOL textandbmp = FALSE, multi_col = FALSE;
2551 int org_x, org_y, max_tab, max_tab_width;
2552 MENUITEM *item;
2553 UINT start, i;
2554 HDC hdc;
2556 menu->Width = menu->Height = 0;
2557 SetRectEmpty( &menu->items_rect );
2559 if (menu->nItems == 0) return;
2560 hdc = NtUserGetDCEx( 0, 0, DCX_CACHE | DCX_WINDOW );
2562 NtGdiSelectFont( hdc, get_menu_font( FALSE ));
2564 start = 0;
2565 menu->textOffset = 0;
2567 while (start < menu->nItems)
2569 item = &menu->items[start];
2570 org_x = menu->items_rect.right;
2571 if (item->fType & (MF_MENUBREAK | MF_MENUBARBREAK))
2572 org_x += MENU_COL_SPACE;
2573 org_y = menu->items_rect.top;
2575 max_tab = max_tab_width = 0;
2576 /* Parse items until column break or end of menu */
2577 for (i = start; i < menu->nItems; i++, item++)
2579 if (item->fType & (MF_MENUBREAK | MF_MENUBARBREAK))
2581 multi_col = TRUE;
2582 if (i != start) break;
2585 calc_menu_item_size( hdc, item, menu->hwndOwner, org_x, org_y, FALSE, menu );
2586 menu->items_rect.right = max( menu->items_rect.right, item->rect.right );
2587 org_y = item->rect.bottom;
2588 if (IS_STRING_ITEM( item->fType ) && item->xTab)
2590 max_tab = max( max_tab, item->xTab );
2591 max_tab_width = max( max_tab_width, item->rect.right-item->xTab );
2593 if (item->text && item->hbmpItem) textandbmp = TRUE;
2596 /* Finish the column (set all items to the largest width found) */
2597 menu->items_rect.right = max( menu->items_rect.right, max_tab + max_tab_width );
2598 for (item = &menu->items[start]; start < i; start++, item++)
2600 item->rect.right = menu->items_rect.right;
2601 if (IS_STRING_ITEM( item->fType ) && item->xTab)
2602 item->xTab = max_tab;
2604 menu->items_rect.bottom = max( menu->items_rect.bottom, org_y );
2607 /* If none of the items have both text and bitmap then
2608 * the text and bitmaps are all aligned on the left. If there is at
2609 * least one item with both text and bitmap then bitmaps are
2610 * on the left and texts left aligned with the right hand side
2611 * of the bitmaps */
2612 if (!textandbmp) menu->textOffset = 0;
2614 menu->nTotalHeight = menu->items_rect.bottom;
2616 /* space for the border */
2617 OffsetRect( &menu->items_rect, MENU_MARGIN, MENU_MARGIN );
2618 menu->Height = menu->items_rect.bottom + MENU_MARGIN;
2619 menu->Width = menu->items_rect.right + MENU_MARGIN;
2621 /* Adjust popup height if it exceeds maximum */
2622 if (menu->Height >= max_height)
2624 menu->Height = max_height;
2625 menu->bScrolling = !multi_col;
2626 /* When the scroll arrows are present, don't add the top/bottom margin as well */
2627 if (menu->bScrolling)
2629 menu->items_rect.top = get_scroll_arrow_height( menu );
2630 menu->items_rect.bottom = menu->Height - get_scroll_arrow_height( menu );
2633 else
2635 menu->bScrolling = FALSE;
2638 NtUserReleaseDC( 0, hdc );
2641 static BOOL show_popup( HWND owner, HMENU hmenu, UINT id, UINT flags,
2642 int x, int y, INT xanchor, INT yanchor )
2644 POPUPMENU *menu;
2645 HMONITOR monitor;
2646 MONITORINFO info;
2647 UINT max_height;
2648 POINT pt;
2650 TRACE( "owner=%p hmenu=%p id=0x%04x x=0x%04x y=0x%04x xa=0x%04x ya=0x%04x\n",
2651 owner, hmenu, id, x, y, xanchor, yanchor );
2653 if (!(menu = unsafe_menu_ptr( hmenu ))) return FALSE;
2654 if (menu->FocusedItem != NO_SELECTED_ITEM)
2656 menu->items[menu->FocusedItem].fState &= ~(MF_HILITE|MF_MOUSESELECT);
2657 menu->FocusedItem = NO_SELECTED_ITEM;
2660 menu->nScrollPos = 0;
2662 /* FIXME: should use item rect */
2663 pt.x = x;
2664 pt.y = y;
2665 monitor = monitor_from_point( pt, MONITOR_DEFAULTTONEAREST, get_thread_dpi() );
2666 info.cbSize = sizeof(info);
2667 get_monitor_info( monitor, &info );
2669 max_height = info.rcWork.bottom - info.rcWork.top;
2670 if (menu->cyMax) max_height = min( max_height, menu->cyMax );
2671 calc_popup_menu_size( menu, max_height );
2673 /* adjust popup menu pos so that it fits within the desktop */
2674 if (flags & TPM_LAYOUTRTL) flags ^= TPM_RIGHTALIGN;
2676 if (flags & TPM_RIGHTALIGN) x -= menu->Width;
2677 if (flags & TPM_CENTERALIGN) x -= menu->Width / 2;
2679 if (flags & TPM_BOTTOMALIGN) y -= menu->Height;
2680 if (flags & TPM_VCENTERALIGN) y -= menu->Height / 2;
2682 if (x + menu->Width > info.rcWork.right)
2684 if (xanchor && x >= menu->Width - xanchor) x -= menu->Width - xanchor;
2685 if (x + menu->Width > info.rcWork.right) x = info.rcWork.right - menu->Width;
2687 if (x < info.rcWork.left) x = info.rcWork.left;
2689 if (y + menu->Height > info.rcWork.bottom)
2691 if (yanchor && y >= menu->Height + yanchor) y -= menu->Height + yanchor;
2692 if (y + menu->Height > info.rcWork.bottom) y = info.rcWork.bottom - menu->Height;
2694 if (y < info.rcWork.top) y = info.rcWork.top;
2696 if (!top_popup)
2698 top_popup = menu->hWnd;
2699 top_popup_hmenu = hmenu;
2702 /* Display the window */
2703 NtUserSetWindowPos( menu->hWnd, HWND_TOPMOST, x, y, menu->Width, menu->Height,
2704 SWP_SHOWWINDOW | SWP_NOACTIVATE );
2705 NtUserRedrawWindow( menu->hWnd, NULL, 0, RDW_UPDATENOW | RDW_ALLCHILDREN );
2706 return TRUE;
2709 static void ensure_menu_item_visible( POPUPMENU *menu, UINT index, HDC hdc )
2711 if (menu->bScrolling)
2713 MENUITEM *item = &menu->items[index];
2714 UINT prev_pos = menu->nScrollPos;
2715 const RECT *rc = &menu->items_rect;
2716 UINT scroll_height = rc->bottom - rc->top;
2718 if (item->rect.bottom > menu->nScrollPos + scroll_height)
2720 menu->nScrollPos = item->rect.bottom - scroll_height;
2721 NtUserScrollWindowEx( menu->hWnd, 0, prev_pos - menu->nScrollPos, rc, rc, 0, NULL,
2722 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN | SW_NODCCACHE );
2724 else if (item->rect.top < menu->nScrollPos)
2726 menu->nScrollPos = item->rect.top;
2727 NtUserScrollWindowEx( menu->hWnd, 0, prev_pos - menu->nScrollPos, rc, rc, 0, NULL,
2728 SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN | SW_NODCCACHE );
2731 /* Invalidate the scroll arrows if necessary */
2732 if (prev_pos != menu->nScrollPos)
2734 RECT arrow_rect = menu->items_rect;
2735 if (prev_pos == 0 || menu->nScrollPos == 0)
2737 arrow_rect.top = 0;
2738 arrow_rect.bottom = menu->items_rect.top;
2739 NtUserInvalidateRect( menu->hWnd, &arrow_rect, FALSE );
2741 if (prev_pos + scroll_height == menu->nTotalHeight ||
2742 menu->nScrollPos + scroll_height == menu->nTotalHeight)
2744 arrow_rect.top = menu->items_rect.bottom;
2745 arrow_rect.bottom = menu->Height;
2746 NtUserInvalidateRect( menu->hWnd, &arrow_rect, FALSE );
2752 static void select_item( HWND owner, HMENU hmenu, UINT index, BOOL send_select, HMENU topmenu )
2754 POPUPMENU *menu;
2755 HDC hdc;
2757 TRACE( "owner %p menu %p index 0x%04x select 0x%04x\n", owner, hmenu, index, send_select );
2759 menu = unsafe_menu_ptr( hmenu );
2760 if (!menu || !menu->nItems || !menu->hWnd) return;
2762 if (menu->FocusedItem == index) return;
2763 if (menu->wFlags & MF_POPUP) hdc = NtUserGetDCEx( menu->hWnd, 0, DCX_USESTYLE );
2764 else hdc = NtUserGetDCEx( menu->hWnd, 0, DCX_CACHE | DCX_WINDOW);
2765 if (!top_popup)
2767 top_popup = menu->hWnd;
2768 top_popup_hmenu = hmenu;
2771 NtGdiSelectFont( hdc, get_menu_font( FALSE ));
2773 /* Clear previous highlighted item */
2774 if (menu->FocusedItem != NO_SELECTED_ITEM)
2776 menu->items[menu->FocusedItem].fState &= ~(MF_HILITE|MF_MOUSESELECT);
2777 draw_menu_item( menu->hWnd, menu, owner, hdc, &menu->items[menu->FocusedItem],
2778 !(menu->wFlags & MF_POPUP), ODA_SELECT );
2781 /* Highlight new item (if any) */
2782 menu->FocusedItem = index;
2783 if (menu->FocusedItem != NO_SELECTED_ITEM)
2785 if (!(menu->items[index].fType & MF_SEPARATOR))
2787 menu->items[index].fState |= MF_HILITE;
2788 ensure_menu_item_visible( menu, index, hdc );
2789 draw_menu_item( menu->hWnd, menu, owner, hdc, &menu->items[index],
2790 !(menu->wFlags & MF_POPUP), ODA_SELECT );
2792 if (send_select)
2794 MENUITEM *ip = &menu->items[menu->FocusedItem];
2795 send_message( owner, WM_MENUSELECT,
2796 MAKEWPARAM( ip->fType & MF_POPUP ? index: ip->wID,
2797 ip->fType | ip->fState | (menu->wFlags & MF_SYSMENU) ),
2798 (LPARAM)hmenu );
2801 else if (send_select)
2803 if (topmenu)
2805 int pos = find_submenu( &topmenu, hmenu );
2806 if (pos != NO_SELECTED_ITEM)
2808 POPUPMENU *ptm = unsafe_menu_ptr( topmenu );
2809 MENUITEM *ip = &ptm->items[pos];
2810 send_message( owner, WM_MENUSELECT,
2811 MAKEWPARAM( pos, ip->fType | ip->fState | (ptm->wFlags & MF_SYSMENU) ),
2812 (LPARAM)topmenu );
2816 NtUserReleaseDC( menu->hWnd, hdc );
2819 /***********************************************************************
2820 * move_selection
2822 * Moves currently selected item according to the offset parameter.
2823 * If there is no selection then it should select the last item if
2824 * offset is ITEM_PREV or the first item if offset is ITEM_NEXT.
2826 static void move_selection( HWND owner, HMENU hmenu, INT offset )
2828 POPUPMENU *menu;
2829 int i;
2831 TRACE( "hwnd %p hmenu %p off 0x%04x\n", owner, hmenu, offset );
2833 menu = unsafe_menu_ptr( hmenu );
2834 if (!menu || !menu->items) return;
2836 if (menu->FocusedItem != NO_SELECTED_ITEM)
2838 if (menu->nItems == 1) return;
2839 for (i = menu->FocusedItem + offset; i >= 0 && i < menu->nItems; i += offset)
2841 if (menu->items[i].fType & MF_SEPARATOR) continue;
2842 select_item( owner, hmenu, i, TRUE, 0 );
2843 return;
2847 for (i = (offset > 0) ? 0 : menu->nItems - 1; i >= 0 && i < menu->nItems; i += offset)
2849 if (menu->items[i].fType & MF_SEPARATOR) continue;
2850 select_item( owner, hmenu, i, TRUE, 0 );
2851 return;
2855 static void hide_sub_popups( HWND owner, HMENU hmenu, BOOL send_select, UINT flags )
2857 POPUPMENU *menu = unsafe_menu_ptr( hmenu );
2859 TRACE( "owner=%p hmenu=%p 0x%04x\n", owner, hmenu, send_select );
2861 if (menu && top_popup)
2863 POPUPMENU *submenu;
2864 MENUITEM *item;
2865 HMENU hsubmenu;
2867 if (menu->FocusedItem == NO_SELECTED_ITEM) return;
2869 item = &menu->items[menu->FocusedItem];
2870 if (!(item->fType & MF_POPUP) || !(item->fState & MF_MOUSESELECT)) return;
2871 item->fState &= ~MF_MOUSESELECT;
2872 hsubmenu = item->hSubMenu;
2874 if (!(submenu = unsafe_menu_ptr( hsubmenu ))) return;
2875 hide_sub_popups( owner, hsubmenu, FALSE, flags );
2876 select_item( owner, hsubmenu, NO_SELECTED_ITEM, send_select, 0 );
2877 NtUserDestroyWindow( submenu->hWnd );
2878 submenu->hWnd = 0;
2880 if (!(flags & TPM_NONOTIFY))
2881 send_message( owner, WM_UNINITMENUPOPUP, (WPARAM)hsubmenu,
2882 MAKELPARAM( 0, IS_SYSTEM_MENU( submenu )));
2886 static void init_sys_menu_popup( HMENU hmenu, DWORD style, DWORD class_style )
2888 BOOL gray;
2890 /* Grey the appropriate items in System menu */
2891 gray = !(style & WS_THICKFRAME) || (style & (WS_MAXIMIZE | WS_MINIMIZE));
2892 NtUserEnableMenuItem( hmenu, SC_SIZE, gray ? MF_GRAYED : MF_ENABLED );
2893 gray = ((style & WS_MAXIMIZE) != 0);
2894 NtUserEnableMenuItem( hmenu, SC_MOVE, gray ? MF_GRAYED : MF_ENABLED );
2895 gray = !(style & WS_MINIMIZEBOX) || (style & WS_MINIMIZE);
2896 NtUserEnableMenuItem( hmenu, SC_MINIMIZE, gray ? MF_GRAYED : MF_ENABLED );
2897 gray = !(style & WS_MAXIMIZEBOX) || (style & WS_MAXIMIZE);
2898 NtUserEnableMenuItem( hmenu, SC_MAXIMIZE, gray ? MF_GRAYED : MF_ENABLED );
2899 gray = !(style & (WS_MAXIMIZE | WS_MINIMIZE));
2900 NtUserEnableMenuItem( hmenu, SC_RESTORE, gray ? MF_GRAYED : MF_ENABLED );
2901 gray = (class_style & CS_NOCLOSE) != 0;
2903 /* The menu item must keep its state if it's disabled */
2904 if (gray) NtUserEnableMenuItem( hmenu, SC_CLOSE, MF_GRAYED );
2907 static BOOL init_popup( HWND owner, HMENU hmenu, UINT flags )
2909 UNICODE_STRING class_name = { .Buffer = MAKEINTRESOURCEW( POPUPMENU_CLASS_ATOM ) };
2910 DWORD ex_style = 0;
2911 POPUPMENU *menu;
2913 TRACE( "owner %p hmenu %p\n", owner, hmenu );
2915 if (!(menu = unsafe_menu_ptr( hmenu ))) return FALSE;
2917 /* store the owner for DrawItem */
2918 if (!is_window( owner ))
2920 SetLastError( ERROR_INVALID_WINDOW_HANDLE );
2921 return FALSE;
2923 menu->hwndOwner = owner;
2925 if (flags & TPM_LAYOUTRTL) ex_style = WS_EX_LAYOUTRTL;
2927 /* NOTE: In Windows, top menu popup is not owned. */
2928 menu->hWnd = NtUserCreateWindowEx( ex_style, &class_name, &class_name, NULL,
2929 WS_POPUP, 0, 0, 0, 0, owner, 0,
2930 (HINSTANCE)get_window_long_ptr( owner, GWLP_HINSTANCE, FALSE ),
2931 (void *)hmenu, 0, NULL, 0, FALSE );
2932 return !!menu->hWnd;
2936 /***********************************************************************
2937 * show_sub_popup
2939 * Display the sub-menu of the selected item of this menu.
2940 * Return the handle of the submenu, or hmenu if no submenu to display.
2942 static HMENU show_sub_popup( HWND owner, HMENU hmenu, BOOL select_first, UINT flags )
2944 POPUPMENU *menu;
2945 MENUITEM *item;
2946 RECT rect;
2947 HDC hdc;
2949 TRACE( "owner %p hmenu %p 0x%04x\n", owner, hmenu, select_first );
2951 if (!(menu = unsafe_menu_ptr( hmenu ))) return hmenu;
2952 if (menu->FocusedItem == NO_SELECTED_ITEM) return hmenu;
2954 item = &menu->items[menu->FocusedItem];
2955 if (!(item->fType & MF_POPUP) || (item->fState & (MF_GRAYED | MF_DISABLED)))
2956 return hmenu;
2958 /* Send WM_INITMENUPOPUP message only if TPM_NONOTIFY flag is not specified */
2959 if (!(flags & TPM_NONOTIFY))
2960 send_message( owner, WM_INITMENUPOPUP, (WPARAM)item->hSubMenu,
2961 MAKELPARAM( menu->FocusedItem, IS_SYSTEM_MENU( menu )));
2963 item = &menu->items[menu->FocusedItem];
2964 rect = item->rect;
2966 /* correct item if modified as a reaction to WM_INITMENUPOPUP message */
2967 if (!(item->fState & MF_HILITE))
2969 if (menu->wFlags & MF_POPUP) hdc = NtUserGetDCEx( menu->hWnd, 0, DCX_USESTYLE );
2970 else hdc = NtUserGetDCEx( menu->hWnd, 0, DCX_CACHE | DCX_WINDOW );
2972 NtGdiSelectFont( hdc, get_menu_font( FALSE ));
2974 item->fState |= MF_HILITE;
2975 draw_menu_item( menu->hWnd, menu, owner, hdc, item, !(menu->wFlags & MF_POPUP), ODA_DRAWENTIRE );
2976 NtUserReleaseDC( menu->hWnd, hdc );
2978 if (!item->rect.top && !item->rect.left && !item->rect.bottom && !item->rect.right)
2979 item->rect = rect;
2981 item->fState |= MF_MOUSESELECT;
2983 if (IS_SYSTEM_MENU( menu ))
2985 init_sys_menu_popup( item->hSubMenu,
2986 get_window_long( menu->hWnd, GWL_STYLE ),
2987 get_class_long( menu->hWnd, GCL_STYLE, FALSE ));
2989 get_sys_popup_pos( menu->hWnd, &rect );
2990 if (flags & TPM_LAYOUTRTL) rect.left = rect.right;
2991 rect.top = rect.bottom;
2992 rect.right = get_system_metrics( SM_CXSIZE );
2993 rect.bottom = get_system_metrics( SM_CYSIZE );
2995 else
2997 RECT item_rect = item->rect;
2999 adjust_menu_item_rect( menu, &item_rect );
3000 get_window_rect( menu->hWnd, &rect, get_thread_dpi() );
3002 if (menu->wFlags & MF_POPUP)
3004 /* The first item in the popup menu has to be at the
3005 same y position as the focused menu item */
3006 if (flags & TPM_LAYOUTRTL)
3007 rect.left += get_system_metrics( SM_CXBORDER );
3008 else
3009 rect.left += item_rect.right - get_system_metrics( SM_CXBORDER );
3010 rect.top += item_rect.top - MENU_MARGIN;
3011 rect.right = item_rect.left - item_rect.right + get_system_metrics( SM_CXBORDER );
3012 rect.bottom = item_rect.top - item_rect.bottom - 2 * MENU_MARGIN;
3014 else
3016 if (flags & TPM_LAYOUTRTL)
3017 rect.left = rect.right - item_rect.left;
3018 else
3019 rect.left += item_rect.left;
3020 rect.top += item_rect.bottom;
3021 rect.right = item_rect.right - item_rect.left;
3022 rect.bottom = item_rect.bottom - item_rect.top;
3026 /* use default alignment for submenus */
3027 flags &= ~(TPM_CENTERALIGN | TPM_RIGHTALIGN | TPM_VCENTERALIGN | TPM_BOTTOMALIGN);
3028 init_popup( owner, item->hSubMenu, flags );
3029 show_popup( owner, item->hSubMenu, menu->FocusedItem, flags,
3030 rect.left, rect.top, rect.right, rect.bottom );
3031 if (select_first) move_selection( owner, item->hSubMenu, ITEM_NEXT );
3032 return item->hSubMenu;
3035 /***********************************************************************
3036 * exec_focused_item
3038 * Execute a menu item (for instance when user pressed Enter).
3039 * Return the wID of the executed item. Otherwise, -1 indicating
3040 * that no menu item was executed, -2 if a popup is shown;
3041 * Have to receive the flags for the NtUserTrackPopupMenuEx options to avoid
3042 * sending unwanted message.
3044 static INT exec_focused_item( MTRACKER *pmt, HMENU handle, UINT flags )
3046 MENUITEM *item;
3047 POPUPMENU *menu = unsafe_menu_ptr( handle );
3049 TRACE( "%p hmenu=%p\n", pmt, handle );
3051 if (!menu || !menu->nItems || menu->FocusedItem == NO_SELECTED_ITEM) return -1;
3052 item = &menu->items[menu->FocusedItem];
3054 TRACE( "handle %p ID %08lx submenu %p type %04x\n", handle, item->wID,
3055 item->hSubMenu, item->fType );
3057 if ((item->fType & MF_POPUP))
3059 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, handle, TRUE, flags );
3060 return -2;
3063 if ((item->fState & (MF_GRAYED | MF_DISABLED)) || (item->fType & MF_SEPARATOR))
3064 return -1;
3066 /* If TPM_RETURNCMD is set you return the id, but
3067 do not send a message to the owner */
3068 if (!(flags & TPM_RETURNCMD))
3070 if (menu->wFlags & MF_SYSMENU)
3071 NtUserPostMessage( pmt->hOwnerWnd, WM_SYSCOMMAND, item->wID,
3072 MAKELPARAM( (INT16)pmt->pt.x, (INT16)pmt->pt.y ));
3073 else
3075 POPUPMENU *topmenu = unsafe_menu_ptr( pmt->hTopMenu );
3076 DWORD style = menu->dwStyle | (topmenu ? topmenu->dwStyle : 0);
3078 if (style & MNS_NOTIFYBYPOS)
3079 NtUserPostMessage( pmt->hOwnerWnd, WM_MENUCOMMAND, menu->FocusedItem,
3080 (LPARAM)handle);
3081 else
3082 NtUserPostMessage( pmt->hOwnerWnd, WM_COMMAND, item->wID, 0 );
3086 return item->wID;
3089 /***********************************************************************
3090 * switch_tracking
3092 * Helper function for menu navigation routines.
3094 static void switch_tracking( MTRACKER *pmt, HMENU pt_menu, UINT id, UINT flags )
3096 POPUPMENU *ptmenu = unsafe_menu_ptr( pt_menu );
3097 POPUPMENU *topmenu = unsafe_menu_ptr( pmt->hTopMenu );
3099 TRACE( "%p hmenu=%p 0x%04x\n", pmt, pt_menu, id );
3101 if (pmt->hTopMenu != pt_menu && !((ptmenu->wFlags | topmenu->wFlags) & MF_POPUP))
3103 /* both are top level menus (system and menu-bar) */
3104 hide_sub_popups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, flags );
3105 select_item( pmt->hOwnerWnd, pmt->hTopMenu, NO_SELECTED_ITEM, FALSE, 0 );
3106 pmt->hTopMenu = pt_menu;
3108 else hide_sub_popups( pmt->hOwnerWnd, pt_menu, FALSE, flags );
3109 select_item( pmt->hOwnerWnd, pt_menu, id, TRUE, 0 );
3112 /***********************************************************************
3113 * menu_button_down
3115 * Return TRUE if we can go on with menu tracking.
3117 static BOOL menu_button_down( MTRACKER *pmt, UINT message, HMENU pt_menu, UINT flags )
3119 TRACE( "%p pt_menu=%p\n", pmt, pt_menu );
3121 if (pt_menu)
3123 POPUPMENU *ptmenu = unsafe_menu_ptr( pt_menu );
3124 enum hittest ht = ht_item;
3125 UINT pos;
3127 if (IS_SYSTEM_MENU( ptmenu ))
3129 if (message == WM_LBUTTONDBLCLK) return FALSE;
3130 pos = 0;
3132 else
3133 ht = find_item_by_coords( ptmenu, pmt->pt, &pos );
3135 if (pos != NO_SELECTED_ITEM)
3137 if (ptmenu->FocusedItem != pos)
3138 switch_tracking( pmt, pt_menu, pos, flags );
3140 /* If the popup menu is not already "popped" */
3141 if (!(ptmenu->items[pos].fState & MF_MOUSESELECT))
3142 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pt_menu, FALSE, flags );
3145 /* A click on an item or anywhere on a popup keeps tracking going */
3146 if (ht == ht_item || ((ptmenu->wFlags & MF_POPUP) && ht != ht_nowhere))
3147 return TRUE;
3149 return FALSE;
3152 /***********************************************************************
3153 * menu_button_up
3155 * Return the value of exec_focused_item if
3156 * the selected item was not a popup. Else open the popup.
3157 * A -1 return value indicates that we go on with menu tracking.
3160 static INT menu_button_up( MTRACKER *pmt, HMENU pt_menu, UINT flags )
3162 TRACE( "%p hmenu=%p\n", pmt, pt_menu );
3164 if (pt_menu)
3166 POPUPMENU *ptmenu = unsafe_menu_ptr( pt_menu );
3167 UINT pos;
3169 if (IS_SYSTEM_MENU( ptmenu ))
3170 pos = 0;
3171 else if (find_item_by_coords( ptmenu, pmt->pt, &pos ) != ht_item)
3172 pos = NO_SELECTED_ITEM;
3174 if (pos != NO_SELECTED_ITEM && (ptmenu->FocusedItem == pos))
3176 TRACE( "%s\n", debugstr_menuitem( &ptmenu->items[pos] ));
3178 if (!(ptmenu->items[pos].fType & MF_POPUP))
3180 INT executedMenuId = exec_focused_item( pmt, pt_menu, flags );
3181 if (executedMenuId == -1 || executedMenuId == -2) return -1;
3182 return executedMenuId;
3185 /* If we are dealing with the menu bar and this is a click on an
3186 * already "popped" item: Stop the menu tracking and close the
3187 * opened submenus */
3188 if(((pmt->hTopMenu == pt_menu) || IS_SYSTEM_MENU( ptmenu )) &&
3189 (pmt->trackFlags & TF_RCVD_BTN_UP))
3190 return 0;
3193 if (get_menu( ptmenu->hWnd ) == pt_menu || IS_SYSTEM_MENU( ptmenu ))
3195 if (pos == NO_SELECTED_ITEM) return 0;
3196 pmt->trackFlags |= TF_RCVD_BTN_UP;
3199 return -1;
3202 /***********************************************************************
3203 * end_menu
3205 * Call NtUserEndMenu() if the hwnd parameter belongs to the menu owner.
3207 void end_menu( HWND hwnd )
3209 POPUPMENU *menu;
3210 BOOL call_end = FALSE;
3211 if (top_popup_hmenu && (menu = grab_menu_ptr( top_popup_hmenu )))
3213 call_end = hwnd == menu->hWnd || hwnd == menu->hwndOwner;
3214 release_menu_ptr( menu );
3216 if (call_end) NtUserEndMenu();
3219 /***********************************************************************
3220 * menu_mouse_move
3222 * Return TRUE if we can go on with menu tracking.
3224 static BOOL menu_mouse_move( MTRACKER* pmt, HMENU pt_menu, UINT flags )
3226 UINT id = NO_SELECTED_ITEM;
3227 POPUPMENU *ptmenu = NULL;
3229 if (pt_menu)
3231 ptmenu = unsafe_menu_ptr( pt_menu );
3232 if (IS_SYSTEM_MENU( ptmenu ))
3233 id = 0;
3234 else if (find_item_by_coords( ptmenu, pmt->pt, &id ) != ht_item)
3235 id = NO_SELECTED_ITEM;
3238 if (id == NO_SELECTED_ITEM)
3240 select_item( pmt->hOwnerWnd, pmt->hCurrentMenu, NO_SELECTED_ITEM, TRUE, pmt->hTopMenu );
3242 else if (ptmenu->FocusedItem != id)
3244 switch_tracking( pmt, pt_menu, id, flags );
3245 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pt_menu, FALSE, flags );
3247 return TRUE;
3250 static LRESULT do_next_menu( MTRACKER *pmt, UINT vk, UINT flags )
3252 POPUPMENU *menu = unsafe_menu_ptr( pmt->hTopMenu );
3253 BOOL at_end = FALSE;
3255 if (vk == VK_LEFT && menu->FocusedItem == 0)
3257 /* When skipping left, we need to do something special after the first menu */
3258 at_end = TRUE;
3260 else if (vk == VK_RIGHT && !IS_SYSTEM_MENU( menu ))
3262 /* When skipping right, for the non-system menu, we need to
3263 * handle the last non-special menu item (ie skip any window
3264 * icons such as MDI maximize, restore or close) */
3265 UINT i = menu->FocusedItem + 1;
3266 while (i < menu->nItems)
3268 if (menu->items[i].wID < SC_SIZE || menu->items[i].wID > SC_RESTORE) break;
3269 i++;
3271 if (i == menu->nItems) at_end = TRUE;
3273 else if (vk == VK_RIGHT && IS_SYSTEM_MENU( menu ))
3275 /* When skipping right, we need to cater for the system menu */
3276 if (menu->FocusedItem == menu->nItems - 1) at_end = TRUE;
3279 if (at_end)
3281 MDINEXTMENU next_menu;
3282 HMENU new_menu;
3283 HWND new_hwnd;
3284 UINT id = 0;
3286 next_menu.hmenuIn = (IS_SYSTEM_MENU( menu )) ? get_sub_menu( pmt->hTopMenu, 0 ) : pmt->hTopMenu;
3287 next_menu.hmenuNext = 0;
3288 next_menu.hwndNext = 0;
3289 send_message( pmt->hOwnerWnd, WM_NEXTMENU, vk, (LPARAM)&next_menu );
3291 TRACE( "%p [%p] -> %p [%p]\n", pmt->hCurrentMenu, pmt->hOwnerWnd, next_menu.hmenuNext,
3292 next_menu.hwndNext );
3294 if (!next_menu.hmenuNext || !next_menu.hwndNext)
3296 DWORD style = get_window_long( pmt->hOwnerWnd, GWL_STYLE );
3297 new_hwnd = pmt->hOwnerWnd;
3298 if (IS_SYSTEM_MENU( menu ))
3300 /* switch to the menu bar */
3301 if ((style & WS_CHILD) || !(new_menu = get_menu( new_hwnd ))) return FALSE;
3303 if (vk == VK_LEFT)
3305 menu = unsafe_menu_ptr( new_menu );
3306 id = menu->nItems - 1;
3308 /* Skip backwards over any system predefined icons,
3309 * eg. MDI close, restore etc icons */
3310 while (id > 0 &&
3311 menu->items[id].wID >= SC_SIZE && menu->items[id].wID <= SC_RESTORE)
3312 id--;
3315 else if (style & WS_SYSMENU)
3317 /* switch to the system menu */
3318 new_menu = get_win_sys_menu( new_hwnd );
3320 else return FALSE;
3322 else /* application returned a new menu to switch to */
3324 new_menu = next_menu.hmenuNext;
3325 new_hwnd = get_full_window_handle( next_menu.hwndNext );
3327 if (is_menu( new_menu ) && is_window( new_hwnd ))
3329 DWORD style = get_window_long( new_hwnd, GWL_STYLE );
3331 if (style & WS_SYSMENU && get_sub_menu(get_win_sys_menu( new_hwnd ), 0) == new_menu)
3333 /* get the real system menu */
3334 new_menu = get_win_sys_menu( new_hwnd );
3336 else if (style & WS_CHILD || get_menu( new_hwnd ) != new_menu )
3338 /* FIXME: what should we do? */
3339 TRACE( " -- got confused.\n" );
3340 return FALSE;
3343 else return FALSE;
3346 if (new_menu != pmt->hTopMenu)
3348 select_item( pmt->hOwnerWnd, pmt->hTopMenu, NO_SELECTED_ITEM, FALSE, 0 );
3349 if (pmt->hCurrentMenu != pmt->hTopMenu)
3350 hide_sub_popups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, flags );
3353 if (new_hwnd != pmt->hOwnerWnd)
3355 pmt->hOwnerWnd = new_hwnd;
3356 set_capture_window( pmt->hOwnerWnd, GUI_INMENUMODE, NULL );
3359 pmt->hTopMenu = pmt->hCurrentMenu = new_menu; /* all subpopups are hidden */
3360 select_item( pmt->hOwnerWnd, pmt->hTopMenu, id, TRUE, 0 );
3361 return TRUE;
3364 return FALSE;
3367 /***********************************************************************
3368 * get_sub_popup
3370 * Return the handle of the selected sub-popup menu (if any).
3372 static HMENU get_sub_popup( HMENU hmenu )
3374 POPUPMENU *menu;
3375 MENUITEM *item;
3377 menu = unsafe_menu_ptr( hmenu );
3379 if (!menu || menu->FocusedItem == NO_SELECTED_ITEM) return 0;
3381 item = &menu->items[menu->FocusedItem];
3382 if ((item->fType & MF_POPUP) && (item->fState & MF_MOUSESELECT))
3383 return item->hSubMenu;
3384 return 0;
3387 /***********************************************************************
3388 * menu_key_escape
3390 * Handle a VK_ESCAPE key event in a menu.
3392 static BOOL menu_key_escape( MTRACKER *pmt, UINT flags )
3394 BOOL ret = TRUE;
3396 if (pmt->hCurrentMenu != pmt->hTopMenu)
3398 POPUPMENU *menu = unsafe_menu_ptr( pmt->hCurrentMenu );
3400 if (menu->wFlags & MF_POPUP)
3402 HMENU top, prev_menu;
3404 prev_menu = top = pmt->hTopMenu;
3406 /* close topmost popup */
3407 while (top != pmt->hCurrentMenu)
3409 prev_menu = top;
3410 top = get_sub_popup( prev_menu );
3413 hide_sub_popups( pmt->hOwnerWnd, prev_menu, TRUE, flags );
3414 pmt->hCurrentMenu = prev_menu;
3415 ret = FALSE;
3419 return ret;
3422 static UINT get_start_of_next_column( HMENU handle )
3424 POPUPMENU *menu = unsafe_menu_ptr( handle );
3425 UINT i;
3427 if (!menu) return NO_SELECTED_ITEM;
3429 i = menu->FocusedItem + 1;
3430 if (i == NO_SELECTED_ITEM) return i;
3432 while (i < menu->nItems)
3434 if (menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK))
3435 return i;
3436 i++;
3439 return NO_SELECTED_ITEM;
3442 static UINT get_start_of_prev_column( HMENU handle )
3444 POPUPMENU *menu = unsafe_menu_ptr( handle );
3445 UINT i;
3447 if (!menu) return NO_SELECTED_ITEM;
3449 if (menu->FocusedItem == 0 || menu->FocusedItem == NO_SELECTED_ITEM)
3450 return NO_SELECTED_ITEM;
3452 /* Find the start of the column */
3453 i = menu->FocusedItem;
3454 while (i != 0 && !(menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK))) i--;
3455 if (i == 0) return NO_SELECTED_ITEM;
3457 for (--i; i != 0; --i)
3459 if (menu->items[i].fType & (MF_MENUBREAK | MF_MENUBARBREAK))
3460 break;
3463 TRACE( "ret %d.\n", i );
3464 return i;
3467 /***********************************************************************
3468 * suspend_popup
3470 * Avoid showing the popup if the next input message is going to hide it anyway.
3472 static BOOL suspend_popup( MTRACKER *pmt, UINT message )
3474 MSG msg;
3476 msg.hwnd = pmt->hOwnerWnd;
3477 NtUserPeekMessage( &msg, 0, message, message, PM_NOYIELD | PM_REMOVE );
3478 pmt->trackFlags |= TF_SKIPREMOVE;
3480 switch (message)
3482 case WM_KEYDOWN:
3483 NtUserPeekMessage( &msg, 0, 0, 0, PM_NOYIELD | PM_NOREMOVE );
3484 if (msg.message == WM_KEYUP || msg.message == WM_PAINT)
3486 NtUserPeekMessage( &msg, 0, 0, 0, PM_NOYIELD | PM_REMOVE );
3487 NtUserPeekMessage( &msg, 0, 0, 0, PM_NOYIELD | PM_NOREMOVE );
3488 if (msg.message == WM_KEYDOWN && (msg.wParam == VK_LEFT || msg.wParam == VK_RIGHT))
3490 pmt->trackFlags |= TF_SUSPENDPOPUP;
3491 return TRUE;
3494 break;
3497 /* failures go through this */
3498 pmt->trackFlags &= ~TF_SUSPENDPOPUP;
3499 return FALSE;
3502 static void menu_key_left( MTRACKER *pmt, UINT flags, UINT msg )
3504 POPUPMENU *menu;
3505 HMENU tmp_menu, prev_menu;
3506 UINT prevcol;
3508 prev_menu = tmp_menu = pmt->hTopMenu;
3509 menu = unsafe_menu_ptr( tmp_menu );
3511 /* Try to move 1 column left (if possible) */
3512 if ((prevcol = get_start_of_prev_column( pmt->hCurrentMenu )) != NO_SELECTED_ITEM)
3514 select_item( pmt->hOwnerWnd, pmt->hCurrentMenu, prevcol, TRUE, 0 );
3515 return;
3518 /* close topmost popup */
3519 while (tmp_menu != pmt->hCurrentMenu)
3521 prev_menu = tmp_menu;
3522 tmp_menu = get_sub_popup( prev_menu );
3525 hide_sub_popups( pmt->hOwnerWnd, prev_menu, TRUE, flags );
3526 pmt->hCurrentMenu = prev_menu;
3528 if ((prev_menu == pmt->hTopMenu) && !(menu->wFlags & MF_POPUP))
3530 /* move menu bar selection if no more popups are left */
3531 if (!do_next_menu( pmt, VK_LEFT, flags ))
3532 move_selection( pmt->hOwnerWnd, pmt->hTopMenu, ITEM_PREV );
3534 if (prev_menu != tmp_menu || pmt->trackFlags & TF_SUSPENDPOPUP)
3536 /* A sublevel menu was displayed - display the next one
3537 * unless there is another displacement coming up */
3538 if (!suspend_popup( pmt, msg ))
3539 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pmt->hTopMenu, TRUE, flags );
3544 static void menu_right_key( MTRACKER *pmt, UINT flags, UINT msg )
3546 POPUPMENU *menu = unsafe_menu_ptr( pmt->hTopMenu );
3547 HMENU tmp_menu;
3548 UINT nextcol;
3550 TRACE( "menu_right_key called, cur %p (%s), top %p (%s).\n",
3551 pmt->hCurrentMenu, debugstr_w(unsafe_menu_ptr( pmt->hCurrentMenu )->items[0].text ),
3552 pmt->hTopMenu, debugstr_w( menu->items[0].text ));
3554 if ((menu->wFlags & MF_POPUP) || (pmt->hCurrentMenu != pmt->hTopMenu))
3556 /* If already displaying a popup, try to display sub-popup */
3557 tmp_menu = pmt->hCurrentMenu;
3558 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, tmp_menu, TRUE, flags );
3560 /* if subpopup was displayed then we are done */
3561 if (tmp_menu != pmt->hCurrentMenu) return;
3564 /* Check to see if there's another column */
3565 if ((nextcol = get_start_of_next_column( pmt->hCurrentMenu )) != NO_SELECTED_ITEM)
3567 TRACE( "Going to %d.\n", nextcol );
3568 select_item( pmt->hOwnerWnd, pmt->hCurrentMenu, nextcol, TRUE, 0 );
3569 return;
3572 if (!(menu->wFlags & MF_POPUP)) /* menu bar tracking */
3574 if (pmt->hCurrentMenu != pmt->hTopMenu)
3576 hide_sub_popups( pmt->hOwnerWnd, pmt->hTopMenu, FALSE, flags );
3577 tmp_menu = pmt->hCurrentMenu = pmt->hTopMenu;
3579 else tmp_menu = 0;
3581 /* try to move to the next item */
3582 if (!do_next_menu( pmt, VK_RIGHT, flags ))
3583 move_selection( pmt->hOwnerWnd, pmt->hTopMenu, ITEM_NEXT );
3585 if (tmp_menu || pmt->trackFlags & TF_SUSPENDPOPUP)
3586 if (!suspend_popup( pmt, msg ))
3587 pmt->hCurrentMenu = show_sub_popup( pmt->hOwnerWnd, pmt->hTopMenu, TRUE, flags );
3591 /***********************************************************************
3592 * menu_from_point
3594 * Walks menu chain trying to find a menu pt maps to.
3596 static HMENU menu_from_point( HMENU handle, POINT pt )
3598 POPUPMENU *menu = unsafe_menu_ptr( handle );
3599 UINT item = menu->FocusedItem;
3600 HMENU ret = 0;
3602 /* try subpopup first (if any) */
3603 if (item != NO_SELECTED_ITEM && (menu->items[item].fType & MF_POPUP) &&
3604 (menu->items[item].fState & MF_MOUSESELECT))
3605 ret = menu_from_point( menu->items[item].hSubMenu, pt );
3607 if (!ret) /* check the current window (avoiding WM_HITTEST) */
3609 INT ht = handle_nc_hit_test( menu->hWnd, pt );
3610 if (menu->wFlags & MF_POPUP)
3612 if (ht != HTNOWHERE && ht != HTERROR) ret = handle;
3614 else if (ht == HTSYSMENU)
3615 ret = get_win_sys_menu( menu->hWnd );
3616 else if (ht == HTMENU)
3617 ret = get_menu( menu->hWnd );
3619 return ret;
3622 /***********************************************************************
3623 * find_item_by_key
3625 * Find the menu item selected by a key press.
3626 * Return item id, -1 if none, -2 if we should close the menu.
3628 static UINT find_item_by_key( HWND owner, HMENU hmenu, WCHAR key, BOOL force_menu_char )
3630 TRACE( "\tlooking for '%c' (0x%02x) in [%p]\n", (char)key, key, hmenu );
3632 if (!is_menu( hmenu )) hmenu = get_sub_menu( get_win_sys_menu( owner ), 0 );
3634 if (hmenu)
3636 POPUPMENU *menu = unsafe_menu_ptr( hmenu );
3637 MENUITEM *item = menu->items;
3638 LRESULT menuchar;
3640 if (!force_menu_char)
3642 BOOL cjk = get_system_metrics( SM_DBCSENABLED );
3643 UINT i;
3645 for (i = 0; i < menu->nItems; i++, item++)
3647 if (item->text)
3649 const WCHAR *p = item->text - 2;
3652 const WCHAR *q = p + 2;
3653 p = wcschr( q, '&' );
3654 if (!p && cjk) p = wcschr( q, '\036' ); /* Japanese Win16 */
3656 while (p && p[1] == '&');
3657 if (p && !wcsnicmp( &p[1], &key, 1)) return i;
3661 menuchar = send_message( owner, WM_MENUCHAR,
3662 MAKEWPARAM( key, menu->wFlags ), (LPARAM)hmenu );
3663 if (HIWORD(menuchar) == MNC_EXECUTE) return LOWORD( menuchar );
3664 if (HIWORD(menuchar) == MNC_CLOSE) return (UINT)-2;
3666 return -1;
3669 static BOOL seh_release_capture;
3671 static void CALLBACK finally_release_capture( BOOL __normal )
3673 if (seh_release_capture) set_capture_window( 0, GUI_INMENUMODE, NULL );
3676 static BOOL track_menu_impl( HMENU hmenu, UINT flags, int x, int y, HWND hwnd, const RECT *rect )
3678 BOOL enter_idle_sent = FALSE;
3679 int executed_menu_id = -1;
3680 HWND capture_win;
3681 POPUPMENU *menu;
3682 BOOL remove;
3683 MTRACKER mt;
3684 MSG msg;
3686 mt.trackFlags = 0;
3687 mt.hCurrentMenu = hmenu;
3688 mt.hTopMenu = hmenu;
3689 mt.hOwnerWnd = get_full_window_handle( hwnd );
3690 mt.pt.x = x;
3691 mt.pt.y = y;
3693 TRACE( "hmenu=%p flags=0x%08x (%d,%d) hwnd=%p %s\n",
3694 hmenu, flags, x, y, hwnd, wine_dbgstr_rect( rect ));
3696 if (!(menu = unsafe_menu_ptr( hmenu )))
3698 WARN( "Invalid menu handle %p\n", hmenu );
3699 SetLastError( ERROR_INVALID_MENU_HANDLE );
3700 return FALSE;
3703 if (flags & TPM_BUTTONDOWN)
3705 /* Get the result in order to start the tracking or not */
3706 remove = menu_button_down( &mt, WM_LBUTTONDOWN, hmenu, flags );
3707 exit_menu = !remove;
3710 if (flags & TF_ENDMENU) exit_menu = TRUE;
3712 /* owner may not be visible when tracking a popup, so use the menu itself */
3713 capture_win = (flags & TPM_POPUPMENU) ? menu->hWnd : mt.hOwnerWnd;
3714 set_capture_window( capture_win, GUI_INMENUMODE, NULL );
3716 if ((flags & TPM_POPUPMENU) && menu->nItems == 0)
3717 return FALSE;
3719 seh_release_capture = TRUE;
3721 while (!exit_menu)
3723 if (!(menu = unsafe_menu_ptr( mt.hCurrentMenu ))) break;
3725 /* we have to keep the message in the queue until it's
3726 * clear that menu loop is not over yet. */
3727 for (;;)
3729 if (NtUserPeekMessage( &msg, 0, 0, 0, PM_NOREMOVE ))
3731 if (!NtUserCallMsgFilter( &msg, MSGF_MENU )) break;
3732 /* remove the message from the queue */
3733 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
3735 else
3737 if (!enter_idle_sent)
3739 HWND win = (menu->wFlags & MF_POPUP) ? menu->hWnd : 0;
3740 enter_idle_sent = TRUE;
3741 send_message( mt.hOwnerWnd, WM_ENTERIDLE, MSGF_MENU, (LPARAM)win );
3743 NtUserMsgWaitForMultipleObjectsEx( 0, NULL, INFINITE, QS_ALLINPUT, 0 );
3747 /* check if NtUserEndMenu() tried to cancel us, by posting this message */
3748 if (msg.message == WM_CANCELMODE)
3750 exit_menu = TRUE;
3751 /* remove the message from the queue */
3752 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
3753 break;
3756 mt.pt = msg.pt;
3757 if (msg.hwnd == menu->hWnd || msg.message != WM_TIMER) enter_idle_sent = FALSE;
3759 remove = FALSE;
3760 if (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST)
3763 * Use the mouse coordinates in lParam instead of those in the MSG
3764 * struct to properly handle synthetic messages. They are already
3765 * in screen coordinates.
3767 mt.pt.x = (short)LOWORD( msg.lParam );
3768 mt.pt.y = (short)HIWORD( msg.lParam );
3770 /* Find a menu for this mouse event */
3771 hmenu = menu_from_point( mt.hTopMenu, mt.pt );
3773 switch (msg.message)
3775 /* no WM_NC... messages in captured state */
3776 case WM_RBUTTONDBLCLK:
3777 case WM_RBUTTONDOWN:
3778 if (!(flags & TPM_RIGHTBUTTON)) break;
3779 /* fall through */
3780 case WM_LBUTTONDBLCLK:
3781 case WM_LBUTTONDOWN:
3782 /* If the message belongs to the menu, removes it from the queue
3783 * Else, end menu tracking */
3784 remove = menu_button_down( &mt, msg.message, hmenu, flags );
3785 exit_menu = !remove;
3786 break;
3788 case WM_RBUTTONUP:
3789 if (!(flags & TPM_RIGHTBUTTON)) break;
3790 /* fall through */
3791 case WM_LBUTTONUP:
3792 /* Check if a menu was selected by the mouse */
3793 if (hmenu)
3795 executed_menu_id = menu_button_up( &mt, hmenu, flags);
3796 TRACE( "executed_menu_id %d\n", executed_menu_id );
3798 /* End the loop if executed_menu_id is an item ID
3799 * or if the job was done (executed_menu_id = 0). */
3800 exit_menu = remove = executed_menu_id != -1;
3802 else
3803 /* No menu was selected by the mouse. If the function was called by
3804 * NtUserTrackPopupMenuEx, continue with the menu tracking. */
3805 exit_menu = !(flags & TPM_POPUPMENU);
3807 break;
3809 case WM_MOUSEMOVE:
3810 /* the selected menu item must be changed every time the mouse moves. */
3811 if (hmenu) exit_menu |= !menu_mouse_move( &mt, hmenu, flags );
3812 break;
3815 else if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
3817 remove = TRUE; /* Keyboard messages are always removed */
3818 switch (msg.message)
3820 case WM_KEYDOWN:
3821 case WM_SYSKEYDOWN:
3822 switch(msg.wParam)
3824 case VK_MENU:
3825 case VK_F10:
3826 exit_menu = TRUE;
3827 break;
3829 case VK_HOME:
3830 case VK_END:
3831 select_item( mt.hOwnerWnd, mt.hCurrentMenu, NO_SELECTED_ITEM, FALSE, 0 );
3832 move_selection( mt.hOwnerWnd, mt.hCurrentMenu,
3833 msg.wParam == VK_HOME ? ITEM_NEXT : ITEM_PREV );
3834 break;
3836 case VK_UP:
3837 case VK_DOWN: /* If on menu bar, pull-down the menu */
3838 menu = unsafe_menu_ptr( mt.hCurrentMenu );
3839 if (!(menu->wFlags & MF_POPUP))
3840 mt.hCurrentMenu = show_sub_popup( mt.hOwnerWnd, mt.hTopMenu, TRUE, flags );
3841 else /* otherwise try to move selection */
3842 move_selection( mt.hOwnerWnd, mt.hCurrentMenu,
3843 msg.wParam == VK_UP ? ITEM_PREV : ITEM_NEXT );
3844 break;
3846 case VK_LEFT:
3847 menu_key_left( &mt, flags, msg.message );
3848 break;
3850 case VK_RIGHT:
3851 menu_right_key( &mt, flags, msg.message );
3852 break;
3854 case VK_ESCAPE:
3855 exit_menu = menu_key_escape( &mt, flags );
3856 break;
3858 case VK_F1:
3860 HELPINFO hi;
3861 hi.cbSize = sizeof(HELPINFO);
3862 hi.iContextType = HELPINFO_MENUITEM;
3863 if (menu->FocusedItem == NO_SELECTED_ITEM)
3864 hi.iCtrlId = 0;
3865 else
3866 hi.iCtrlId = menu->items[menu->FocusedItem].wID;
3867 hi.hItemHandle = hmenu;
3868 hi.dwContextId = menu->dwContextHelpID;
3869 hi.MousePos = msg.pt;
3870 send_message( hwnd, WM_HELP, 0, (LPARAM)&hi );
3871 break;
3874 default:
3875 NtUserTranslateMessage( &msg, 0 );
3876 break;
3878 break; /* WM_KEYDOWN */
3880 case WM_CHAR:
3881 case WM_SYSCHAR:
3883 UINT pos;
3885 if (msg.wParam == '\r' || msg.wParam == ' ')
3887 executed_menu_id = exec_focused_item( &mt, mt.hCurrentMenu, flags );
3888 exit_menu = executed_menu_id != -2;
3889 break;
3892 /* Hack to avoid control chars... */
3893 if (msg.wParam < 32) break;
3895 pos = find_item_by_key( mt.hOwnerWnd, mt.hCurrentMenu,
3896 LOWORD( msg.wParam ), FALSE );
3897 if (pos == -2) exit_menu = TRUE;
3898 else if (pos == -1) message_beep( 0 );
3899 else
3901 select_item( mt.hOwnerWnd, mt.hCurrentMenu, pos, TRUE, 0 );
3902 executed_menu_id = exec_focused_item( &mt,mt.hCurrentMenu, flags );
3903 exit_menu = executed_menu_id != -2;
3906 break;
3909 else
3911 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
3912 NtUserDispatchMessage( &msg );
3913 continue;
3916 if (!exit_menu) remove = TRUE;
3918 /* finally remove message from the queue */
3919 if (remove && !(mt.trackFlags & TF_SKIPREMOVE))
3920 NtUserPeekMessage( &msg, 0, msg.message, msg.message, PM_REMOVE );
3921 else mt.trackFlags &= ~TF_SKIPREMOVE;
3924 seh_release_capture = FALSE;
3925 set_capture_window( 0, GUI_INMENUMODE, NULL );
3927 /* If dropdown is still painted and the close box is clicked on
3928 * then the menu will be destroyed as part of the DispatchMessage above.
3929 * This will then invalidate the menu handle in mt.hTopMenu. We should
3930 * check for this first. */
3931 if (is_menu( mt.hTopMenu ))
3933 menu = unsafe_menu_ptr( mt.hTopMenu );
3935 if (is_window( mt.hOwnerWnd ))
3937 hide_sub_popups( mt.hOwnerWnd, mt.hTopMenu, FALSE, flags );
3939 if (menu && (menu->wFlags & MF_POPUP))
3941 NtUserDestroyWindow( menu->hWnd );
3942 menu->hWnd = 0;
3944 if (!(flags & TPM_NONOTIFY))
3945 send_message( mt.hOwnerWnd, WM_UNINITMENUPOPUP, (WPARAM)mt.hTopMenu,
3946 MAKELPARAM( 0, IS_SYSTEM_MENU( menu )));
3948 select_item( mt.hOwnerWnd, mt.hTopMenu, NO_SELECTED_ITEM, FALSE, 0 );
3949 send_message( mt.hOwnerWnd, WM_MENUSELECT, MAKEWPARAM( 0, 0xffff ), 0 );
3953 SetLastError( ERROR_SUCCESS );
3954 /* The return value is only used by NtUserTrackPopupMenuEx */
3955 if (!(flags & TPM_RETURNCMD)) return TRUE;
3956 if (executed_menu_id == -1) executed_menu_id = 0;
3957 return executed_menu_id;
3960 /* FIXME: this is an ugly hack to work around unixlib exceptions limitations.
3961 * For this to work properly we need recursive exception handlers capable of
3962 * catching exceptions from client callbacks. We probably need to actually
3963 * run on Unix stack first, so we need a hack for now. */
3964 struct track_menu_params
3966 HMENU handle;
3967 UINT flags;
3968 int x;
3969 int y;
3970 HWND hwnd;
3971 const RECT *rect;
3974 static NTSTATUS CDECL track_menu_proc( void *arg )
3976 struct track_menu_params *params = arg;
3977 return track_menu_impl( params->handle, params->flags, params->x, params->y,
3978 params->hwnd, params->rect );
3981 static BOOL track_menu( HMENU handle, UINT flags, int x, int y, HWND hwnd, const RECT *rect )
3983 struct track_menu_params params =
3984 { .handle = handle, .flags = flags, .x = x, .y = y, .hwnd = hwnd, .rect = rect };
3985 if (!user_callbacks)
3986 return track_menu_impl( handle, flags, x, y, hwnd, rect );
3987 return user_callbacks->try_finally( track_menu_proc, &params, finally_release_capture );
3990 static BOOL init_tracking( HWND hwnd, HMENU handle, BOOL is_popup, UINT flags )
3992 POPUPMENU *menu;
3994 TRACE( "hwnd=%p hmenu=%p\n", hwnd, handle );
3996 NtUserHideCaret( 0 );
3998 if (!(menu = unsafe_menu_ptr( handle ))) return FALSE;
4000 /* This makes the menus of applications built with Delphi work.
4001 * It also enables menus to be displayed in more than one window,
4002 * but there are some bugs left that need to be fixed in this case.
4004 if (!is_popup) menu->hWnd = hwnd;
4005 if (!top_popup)
4007 top_popup = menu->hWnd;
4008 top_popup_hmenu = handle;
4011 exit_menu = FALSE;
4013 /* Send WM_ENTERMENULOOP and WM_INITMENU message only if TPM_NONOTIFY flag is not specified */
4014 if (!(flags & TPM_NONOTIFY))
4015 send_message( hwnd, WM_ENTERMENULOOP, is_popup, 0 );
4017 send_message( hwnd, WM_SETCURSOR, (WPARAM)hwnd, HTCAPTION );
4019 if (!(flags & TPM_NONOTIFY))
4021 send_message( hwnd, WM_INITMENU, (WPARAM)handle, 0 );
4022 /* If an app changed/recreated menu bar entries in WM_INITMENU
4023 * menu sizes will be recalculated once the menu created/shown. */
4026 return TRUE;
4029 static BOOL exit_tracking( HWND hwnd, BOOL is_popup )
4031 TRACE( "hwnd=%p\n", hwnd );
4033 send_message( hwnd, WM_EXITMENULOOP, is_popup, 0 );
4034 NtUserShowCaret( 0 );
4035 top_popup = 0;
4036 top_popup_hmenu = NULL;
4037 return TRUE;
4040 void track_mouse_menu_bar( HWND hwnd, INT ht, int x, int y )
4042 HMENU handle = ht == HTSYSMENU ? get_win_sys_menu( hwnd ) : get_menu( hwnd );
4043 UINT flags = TPM_BUTTONDOWN | TPM_LEFTALIGN | TPM_LEFTBUTTON;
4045 TRACE( "wnd=%p ht=0x%04x %d,%d\n", hwnd, ht, x, y );
4047 if (get_window_long( hwnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) flags |= TPM_LAYOUTRTL;
4048 if (is_menu( handle ))
4050 init_tracking( hwnd, handle, FALSE, flags );
4052 /* fetch the window menu again, it may have changed */
4053 handle = ht == HTSYSMENU ? get_win_sys_menu( hwnd ) : get_menu( hwnd );
4054 track_menu( handle, flags, x, y, hwnd, NULL );
4055 exit_tracking( hwnd, FALSE );
4059 void track_keyboard_menu_bar( HWND hwnd, UINT wparam, WCHAR ch )
4061 UINT flags = TPM_LEFTALIGN | TPM_LEFTBUTTON;
4062 UINT item = NO_SELECTED_ITEM;
4063 HMENU menu;
4065 TRACE( "hwnd %p wparam 0x%04x ch 0x%04x\n", hwnd, wparam, ch );
4067 /* find window that has a menu */
4068 while (is_win_menu_disallowed( hwnd ))
4069 if (!(hwnd = NtUserGetAncestor( hwnd, GA_PARENT ))) return;
4071 /* check if we have to track a system menu */
4072 menu = get_menu( hwnd );
4073 if (!menu || is_iconic( hwnd ) || ch == ' ')
4075 if (!(get_window_long( hwnd, GWL_STYLE ) & WS_SYSMENU)) return;
4076 menu = get_win_sys_menu( hwnd );
4077 item = 0;
4078 wparam |= HTSYSMENU; /* prevent item lookup */
4080 if (get_window_long( hwnd, GWL_EXSTYLE ) & WS_EX_LAYOUTRTL) flags |= TPM_LAYOUTRTL;
4082 if (!is_menu( menu )) return;
4084 init_tracking( hwnd, menu, FALSE, flags );
4086 /* fetch the window menu again, it may have changed */
4087 menu = (wparam & HTSYSMENU) ? get_win_sys_menu( hwnd ) : get_menu( hwnd );
4089 if (ch && ch != ' ')
4091 item = find_item_by_key( hwnd, menu, ch, wparam & HTSYSMENU );
4092 if (item >= -2)
4094 if (item == -1) message_beep( 0 );
4095 /* schedule end of menu tracking */
4096 flags |= TF_ENDMENU;
4097 goto track_menu;
4101 select_item( hwnd, menu, item, TRUE, 0 );
4103 if (!(wparam & HTSYSMENU) || ch == ' ')
4105 if( item == NO_SELECTED_ITEM )
4106 move_selection( hwnd, menu, ITEM_NEXT );
4107 else
4108 NtUserPostMessage( hwnd, WM_KEYDOWN, VK_RETURN, 0 );
4111 track_menu:
4112 track_menu( menu, flags, 0, 0, hwnd, NULL );
4113 exit_tracking( hwnd, FALSE );
4116 /**********************************************************************
4117 * NtUserTrackPopupMenuEx (win32u.@)
4119 BOOL WINAPI NtUserTrackPopupMenuEx( HMENU handle, UINT flags, INT x, INT y, HWND hwnd,
4120 TPMPARAMS *params )
4122 POPUPMENU *menu;
4123 BOOL ret = FALSE;
4125 TRACE( "hmenu %p flags %04x (%d,%d) hwnd %p params %p rect %s\n",
4126 handle, flags, x, y, hwnd, params,
4127 params ? wine_dbgstr_rect( &params->rcExclude ) : "-" );
4129 if (!(menu = unsafe_menu_ptr( handle )))
4131 SetLastError( ERROR_INVALID_MENU_HANDLE );
4132 return FALSE;
4135 if (is_window(menu->hWnd))
4137 SetLastError( ERROR_POPUP_ALREADY_ACTIVE );
4138 return FALSE;
4141 if (init_popup( hwnd, handle, flags ))
4143 init_tracking( hwnd, handle, TRUE, flags );
4145 /* Send WM_INITMENUPOPUP message only if TPM_NONOTIFY flag is not specified */
4146 if (!(flags & TPM_NONOTIFY))
4147 send_message( hwnd, WM_INITMENUPOPUP, (WPARAM)handle, 0 );
4149 if (menu->wFlags & MF_SYSMENU)
4150 init_sys_menu_popup( handle, get_window_long( hwnd, GWL_STYLE ),
4151 get_class_long( hwnd, GCL_STYLE, FALSE ));
4153 if (show_popup( hwnd, handle, 0, flags, x, y, 0, 0 ))
4154 ret = track_menu( handle, flags | TPM_POPUPMENU, 0, 0, hwnd,
4155 params ? &params->rcExclude : NULL );
4156 exit_tracking( hwnd, TRUE );
4158 if (menu->hWnd)
4160 NtUserDestroyWindow( menu->hWnd );
4161 menu->hWnd = 0;
4163 if (!(flags & TPM_NONOTIFY))
4164 send_message( hwnd, WM_UNINITMENUPOPUP, (WPARAM)handle,
4165 MAKELPARAM( 0, IS_SYSTEM_MENU( menu )));
4167 SetLastError( 0 );
4170 return ret;
4173 /**********************************************************************
4174 * NtUserHiliteMenuItem (win32u.@)
4176 BOOL WINAPI NtUserHiliteMenuItem( HWND hwnd, HMENU handle, UINT item, UINT hilite )
4178 HMENU handle_menu;
4179 UINT focused_item;
4180 POPUPMENU *menu;
4181 UINT pos;
4183 TRACE( "(%p, %p, %04x, %04x);\n", hwnd, handle, item, hilite );
4185 if (!(menu = find_menu_item(handle, item, hilite, &pos))) return FALSE;
4186 handle_menu = menu->obj.handle;
4187 focused_item = menu->FocusedItem;
4188 release_menu_ptr(menu);
4190 if (focused_item != pos)
4192 hide_sub_popups( hwnd, handle_menu, FALSE, 0 );
4193 select_item( hwnd, handle_menu, pos, TRUE, 0 );
4196 return TRUE;
4199 /**********************************************************************
4200 * NtUserGetMenuBarInfo (win32u.@)
4202 BOOL WINAPI NtUserGetMenuBarInfo( HWND hwnd, LONG id, LONG item, MENUBARINFO *info )
4204 HMENU hmenu = NULL;
4205 POPUPMENU *menu;
4206 ATOM class_atom;
4208 TRACE( "(%p,0x%08x,0x%08x,%p)\n", hwnd, id, item, info );
4210 switch (id)
4212 case OBJID_CLIENT:
4213 class_atom = get_class_long( hwnd, GCW_ATOM, FALSE );
4214 if (!class_atom)
4215 return FALSE;
4216 if (class_atom != POPUPMENU_CLASS_ATOM)
4218 WARN("called on invalid window: %d\n", class_atom);
4219 SetLastError(ERROR_INVALID_MENU_HANDLE);
4220 return FALSE;
4223 hmenu = (HMENU)get_window_long_ptr( hwnd, 0, FALSE );
4224 break;
4225 case OBJID_MENU:
4226 hmenu = get_menu( hwnd );
4227 break;
4228 case OBJID_SYSMENU:
4229 hmenu = NtUserGetSystemMenu( hwnd, FALSE );
4230 break;
4231 default:
4232 return FALSE;
4235 if (!hmenu)
4236 return FALSE;
4238 if (info->cbSize != sizeof(MENUBARINFO))
4240 SetLastError( ERROR_INVALID_PARAMETER );
4241 return FALSE;
4244 if (!(menu = grab_menu_ptr( hmenu ))) return FALSE;
4245 if (item < 0 || item > menu->nItems)
4247 release_menu_ptr( menu );
4248 return FALSE;
4251 if (!menu->Height)
4253 SetRectEmpty( &info->rcBar );
4255 else if (item == 0)
4257 NtUserGetMenuItemRect( hwnd, hmenu, 0, &info->rcBar );
4258 info->rcBar.right = info->rcBar.left + menu->Width;
4259 info->rcBar.bottom = info->rcBar.top + menu->Height;
4261 else
4263 NtUserGetMenuItemRect( hwnd, hmenu, item - 1, &info->rcBar );
4266 info->hMenu = hmenu;
4267 info->hwndMenu = NULL;
4268 info->fBarFocused = top_popup_hmenu == hmenu;
4269 if (item)
4271 info->fFocused = menu->FocusedItem == item - 1;
4272 if (info->fFocused && (menu->items[item - 1].fType & MF_POPUP))
4274 POPUPMENU *hwnd_menu = grab_menu_ptr( menu->items[item - 1].hSubMenu );
4275 if (hwnd_menu)
4277 info->hwndMenu = hwnd_menu->hWnd;
4278 release_menu_ptr( hwnd_menu );
4282 else
4284 info->fFocused = info->fBarFocused;
4287 release_menu_ptr( menu );
4288 return TRUE;
4291 /***********************************************************************
4292 * NtUserEndMenu (win32u.@)
4294 BOOL WINAPI NtUserEndMenu(void)
4296 /* if we are in the menu code, and it is active, terminate the menu handling code */
4297 if (!exit_menu && top_popup)
4299 exit_menu = TRUE;
4301 /* needs to be posted to wakeup the internal menu handler
4302 * which will now terminate the menu, in the event that
4303 * the main window was minimized, or lost focus, so we
4304 * don't end up with an orphaned menu */
4305 NtUserPostMessage( top_popup, WM_CANCELMODE, 0, 0 );
4307 return exit_menu;