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