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