From: Vladislav Timonin timoninvlad@yandex.ru
--- dlls/user32/tests/Makefile.in | 2 +- dlls/user32/tests/menu.c | 169 ++++++++++++++++++++++++++++++ dlls/user32/tests/resource.rc | 3 + dlls/user32/tests/user32.manifest | 16 +++ dlls/win32u/menu.c | 4 + 5 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 dlls/user32/tests/user32.manifest
diff --git a/dlls/user32/tests/Makefile.in b/dlls/user32/tests/Makefile.in index 33c7f13f12d..4bf8e8dad4b 100644 --- a/dlls/user32/tests/Makefile.in +++ b/dlls/user32/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = user32.dll -IMPORTS = user32 gdi32 advapi32 hid imm32 setupapi +IMPORTS = user32 gdi32 advapi32 hid imm32 setupapi comctl32
C_SRCS = \ broadcast.c \ diff --git a/dlls/user32/tests/menu.c b/dlls/user32/tests/menu.c index 9cd24141ad5..95894efc3d2 100644 --- a/dlls/user32/tests/menu.c +++ b/dlls/user32/tests/menu.c @@ -30,6 +30,7 @@ #include "winbase.h" #include "wingdi.h" #include "winuser.h" +#include "commctrl.h"
#include "wine/test.h"
@@ -2454,6 +2455,10 @@ static void test_menu_input(void) { while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg); } SetCursorPos(orig_pos.x, orig_pos.y); + + DestroyMenu(hMenus[3]); + DestroyMenu(hMenus[2]); + DestroyMenu(hMenus[1]); DestroyWindow(hWnd); }
@@ -2614,6 +2619,23 @@ static void test_menu_hilitemenuitem( void ) ok(!(GetMenuState(hPopupMenu, 2, MF_BYPOSITION) & MF_HILITE), "HiliteMenuItem: Item 3 is hilited\n");
+ /* deleting an off-screen menu item doesn't reset hilite */ + + SetLastError(0xdeadbeef); + ok(HiliteMenuItem(hWnd, hPopupMenu, 0, MF_HILITE | MF_BYPOSITION), + "HiliteMenuItem: call should not have failed.\n"); + ok(GetLastError() == 0xdeadbeef, + "HiliteMenuItem: expected error 0xdeadbeef, got: %ld\n", GetLastError()); + ok(GetMenuState(hPopupMenu, 0, MF_BYPOSITION) & MF_HILITE, + "HiliteMenuItem: Item 1 is not hilited\n"); + + ok(DeleteMenu(hPopupMenu, 2, MF_BYPOSITION), + "DeleteMenu: call should have succeeded.\n"); + ok(GetMenuState(hPopupMenu, 0, MF_BYPOSITION) & MF_HILITE, + "HiliteMenuItem: Item 1 is not hilited\n"); + ok(!(GetMenuState(hPopupMenu, 1, MF_BYPOSITION) & MF_HILITE), + "HiliteMenuItem: Item 2 is hilited\n"); + DestroyWindow(hWnd); }
@@ -4221,6 +4243,152 @@ if (0) /* FIXME: uncomment once Wine is fixed */ { DeleteObject(hbmp); }
+static void flush_events(void) +{ + MSG msg; + int diff = 200; + int min_timeout = 100; + DWORD time = GetTickCount() + diff; + + while (diff > 0) + { + if (MsgWaitForMultipleObjects( 0, NULL, FALSE, min_timeout, QS_ALLINPUT ) == WAIT_TIMEOUT) break; + while (PeekMessageA( &msg, 0, 0, 0, PM_REMOVE )) DispatchMessageA( &msg ); + diff = time - GetTickCount(); + } +} + +static DWORD WINAPI test_menu_mutation_thread(LPVOID lpParameter) +{ + HANDLE hWnd = lpParameter; + HMENU popup_menu = hMenus[2]; + int elapsed; + + /* add an item to menu and select it */ + AppendMenuA(popup_menu, MF_STRING, 0, "Item 1"); + PostMessageA(hWnd, WM_USER, 0, 0); /* set focus to split button */ + send_key(VK_DOWN); /* open the menu */ + send_key(VK_DOWN); /* hilite the 1st item */ + send_key(VK_RETURN); /* select it */ + flush_events(); + + /* remove selected item and open the empty menu */ + RemoveMenu(popup_menu, 0, MF_BYPOSITION); + PostMessageA(hWnd, WM_USER, 0, 0); + send_key(VK_DOWN); + send_key(VK_ESCAPE); + flush_events(); + ok(!bMenuVisible, "menu is open\n"); + + /* add item back and make sure menu opens again */ + AppendMenuA(popup_menu, MF_STRING, 0, "Item 1"); + PostMessageA(hWnd, WM_USER, 0, 0); + send_key(VK_DOWN); + /* wait for the menu to show up */ + elapsed = 0; + while (!bMenuVisible) + { + if (elapsed > 200) + break; + elapsed += 20; + Sleep(20); + } + ok(bMenuVisible, "menu did not open\n"); + send_key(VK_ESCAPE); + + return 0; +} + +static LRESULT CALLBACK menu_mutation_wndproc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) +{ + + switch (msg) { + case WM_NOTIFY: + { + NMHDR *nmhdr = (NMHDR *)lParam; + if (nmhdr && nmhdr->code == BCN_DROPDOWN) + { + NMBCDROPDOWN* dropdown = (NMBCDROPDOWN *)lParam; + HMENU menu; + POINT pt = { .x = dropdown->rcButton.left, + .y = dropdown->rcButton.bottom, }; + + menu = (HMENU)GetWindowLongPtrA(dropdown->hdr.hwndFrom, GWLP_USERDATA); + ClientToScreen(dropdown->hdr.hwndFrom, &pt); + TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, hWnd, NULL); + return 1; + } + + break; + } + case WM_ENTERMENULOOP: + bMenuVisible = TRUE; + break; + case WM_EXITMENULOOP: + bMenuVisible = FALSE; + break; + } + + return DefWindowProcA(hWnd, msg, wParam, lParam); +} + +static void test_menu_mutation(void) { + MSG msg; + WNDCLASSA wclass; + HINSTANCE hInstance = GetModuleHandleA( NULL ); + HANDLE hThread, hWnd, split_button; + DWORD tid; + ATOM aclass; + + wclass.lpszClassName = "MenuMutationTestClass"; + wclass.style = CS_HREDRAW | CS_VREDRAW; + wclass.lpfnWndProc = menu_mutation_wndproc; + wclass.hInstance = hInstance; + wclass.hIcon = LoadIconA( 0, (LPCSTR)IDI_APPLICATION ); + wclass.hCursor = LoadCursorA( 0, (LPCSTR)IDC_ARROW ); + wclass.hbrBackground = (HBRUSH)( COLOR_WINDOW + 1 ); + wclass.lpszMenuName = 0; + wclass.cbClsExtra = 0; + wclass.cbWndExtra = 0; + aclass = RegisterClassA( &wclass ); + ok (aclass, "MenuMutationTest class not created\n"); + if (!aclass) return; + hWnd = CreateWindowA( wclass.lpszClassName, "MenuMutationTest", + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, + 400, 200, NULL, NULL, hInstance, NULL); + ok (hWnd != NULL, "MenuMutationTest window not created\n"); + if (!hWnd) return; + + hMenus[2] = CreatePopupMenu(); + + split_button = CreateWindowA(WC_BUTTONA, " ", + WS_CHILD | WS_VISIBLE | BS_SPLITBUTTON, + 0, 0, 50, 30, + hWnd, 0, GetModuleHandleA( NULL ), NULL); + SetWindowLongPtrA(split_button, GWLP_USERDATA, (LONG_PTR)hMenus[2]); + + ShowWindow(hWnd, SW_SHOW); + UpdateWindow(hWnd); + + hThread = CreateThread(NULL, 0, test_menu_mutation_thread, hWnd, 0, &tid); + while (1) + { + if (WAIT_TIMEOUT != WaitForSingleObject(hThread, 50)) + break; + + while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) { + if (msg.message == WM_USER && msg.hwnd == hWnd) + SetFocus(split_button); + + DispatchMessageA(&msg); + } + } + + DestroyMenu(hMenus[2]); + DestroyWindow(split_button); + DestroyWindow(hWnd); +} + START_TEST(menu) { register_menu_check_class(); @@ -4253,4 +4421,5 @@ START_TEST(menu) test_menu_circref(); test_emptypopup(); test_AppendMenu(); + test_menu_mutation(); } diff --git a/dlls/user32/tests/resource.rc b/dlls/user32/tests/resource.rc index a957e50689d..d379738c1f2 100644 --- a/dlls/user32/tests/resource.rc +++ b/dlls/user32/tests/resource.rc @@ -286,3 +286,6 @@ BEGIN DEFPUSHBUTTON "OK",IDOK,129,7,50,14 PUSHBUTTON "Cancel",IDCANCEL,129,24,50,14 END + +/* @makedep: user32.manifest */ +1 RT_MANIFEST user32.manifest diff --git a/dlls/user32/tests/user32.manifest b/dlls/user32/tests/user32.manifest new file mode 100644 index 00000000000..0d2ee44e178 --- /dev/null +++ b/dlls/user32/tests/user32.manifest @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity type="win32" name="Wine.user32.Test" version="0.0.0.0"/> +<dependency> + <dependentAssembly> + <assemblyIdentity + type="win32" + name="Microsoft.Windows.Common-Controls" + version="6.0.0.0" + processorArchitecture="*" + publicKeyToken="6595b64144ccf1df" + language="*" + /> + </dependentAssembly> +</dependency> +</assembly> diff --git a/dlls/win32u/menu.c b/dlls/win32u/menu.c index 50ff451bf3e..1b98f6b8de0 100644 --- a/dlls/win32u/menu.c +++ b/dlls/win32u/menu.c @@ -1370,11 +1370,15 @@ BOOL WINAPI NtUserRemoveMenu( HMENU handle, UINT id, UINT flags ) { free( menu->items ); menu->items = NULL; + menu->FocusedItem = NO_SELECTED_ITEM; } else { struct menu_item *new_items, *item = &menu->items[pos];
+ if (menu->FocusedItem >= menu->nItems) + menu->FocusedItem = NO_SELECTED_ITEM; + while (pos < menu->nItems) { *item = item[1];