user: Fix and test behavior when selecting disabled menu items, and fix GetMenuItemRect()

tkho at ucla.edu tkho at ucla.edu
Fri Apr 21 18:23:59 CDT 2006


Hello,

This patch 1) changes the behavior of selecting disabled menu items, 2)
recognizes VK_LMENU and VK_RMENU messages to the menu WndProc and 3) fixes
GetMenuItemRect().

Some details:
The menu should close if a disabled menu item is selected via keyboard and stay
open if the menu item is selected via mouse. Wine currently keeps the menu open
in both cases.

GetMenuItemRect() was previously using window-relative positions for top-level
menus instead of client-area relative positions.

Thanks,

Thomas Kho

2006-04-21 Thomas Kho <tkho at ucla.edu>

	* dlls/user/defwnd.c, dlls/user/menu.c, dlls/user/tests/menu.c:
	user: Fix and test behavior when selecting disabled menu items, and fix
	GetMenuItemRect().

 defwnd.c     |    7 +-
 menu.c       |   32 +++++++---
 tests/menu.c |  186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 212 insertions(+), 13 deletions(-)

Signed-off-by: Thomas Kho <tkho at ucla.edu>
Index: dlls/user/defwnd.c
===================================================================
RCS file: /home/wine/wine/dlls/user/defwnd.c,v
retrieving revision 1.8
diff -u -r1.8 defwnd.c
--- dlls/user/defwnd.c	24 Feb 2006 20:31:12 -0000	1.8
+++ dlls/user/defwnd.c	21 Apr 2006 23:01:25 -0000
@@ -527,7 +527,8 @@
         if( HIWORD(lParam) & KEYDATA_ALT )
         {
             /* if( HIWORD(lParam) & ~KEYDATA_PREVSTATE ) */
-              if( wParam == VK_MENU && !iMenuSysKey )
+              if ( (wParam == VK_MENU || wParam == VK_LMENU
+                    || wParam == VK_RMENU) && !iMenuSysKey )
                 iMenuSysKey = 1;
               else
                 iMenuSysKey = 0;
@@ -550,8 +551,8 @@
     case WM_KEYUP:
     case WM_SYSKEYUP:
         /* Press and release F10 or ALT */
-        if (((wParam == VK_MENU) && iMenuSysKey) ||
-            ((wParam == VK_F10) && iF10Key))
+        if (((wParam == VK_MENU || wParam == VK_LMENU || wParam == VK_RMENU)
+             && iMenuSysKey) || ((wParam == VK_F10) && iF10Key))
               SendMessageW( GetAncestor( hwnd, GA_ROOT ), WM_SYSCOMMAND, SC_KEYMENU, 0L );
         iMenuSysKey = iF10Key = 0;
         break;
Index: dlls/user/menu.c
===================================================================
RCS file: /home/wine/wine/dlls/user/menu.c,v
retrieving revision 1.60
diff -u -r1.60 menu.c
--- dlls/user/menu.c	19 Apr 2006 11:04:53 -0000	1.60
+++ dlls/user/menu.c	21 Apr 2006 23:01:25 -0000
@@ -2407,7 +2407,7 @@
  *
  * Execute a menu item (for instance when user pressed Enter).
  * Return the wID of the executed item. Otherwise, -1 indicating
- * that no menu item was executed;
+ * that no menu item was executed, -2 if a popup is shown;
  * Have to receive the flags for the TrackPopupMenu options to avoid
  * sending unwanted message.
  *
@@ -2444,7 +2444,10 @@
 	}
     }
     else
+    {
 	pmt->hCurrentMenu = MENU_ShowSubPopup(pmt->hOwnerWnd, hMenu, TRUE, wFlags);
+	return -2;
+    }
 
     return -1;
 }
@@ -2538,7 +2541,10 @@
 	if( item && (ptmenu->FocusedItem == id ))
 	{
 	    if( !(item->fType & MF_POPUP) )
-		return MENU_ExecFocusedItem( pmt, hPtMenu, wFlags);
+	    {
+	        INT executedMenuId = MENU_ExecFocusedItem( pmt, hPtMenu, wFlags);
+	        return (executedMenuId < 0) ? -1 : executedMenuId;
+	    }
 
 	    /* If we are dealing with the top-level menu            */
 	    /* and this is a click on an already "popped" item:     */
@@ -3113,7 +3119,7 @@
 		    if (msg.wParam == '\r' || msg.wParam == ' ')
 		    {
                         executedMenuId = MENU_ExecFocusedItem(&mt,mt.hCurrentMenu, wFlags);
-                        fEndMenu = (executedMenuId != -1);
+                        fEndMenu = (executedMenuId != -2);
 
 			break;
 		    }
@@ -3131,7 +3137,7 @@
 			MENU_SelectItem( mt.hOwnerWnd, mt.hCurrentMenu, pos,
                                 TRUE, 0 );
                         executedMenuId = MENU_ExecFocusedItem(&mt,mt.hCurrentMenu, wFlags);
-                        fEndMenu = (executedMenuId != -1);
+                        fEndMenu = (executedMenuId != -2);
 		    }
 		}
 		break;
@@ -3182,7 +3188,7 @@
 
     /* The return value is only used by TrackPopupMenu */
     if (!(wFlags & TPM_RETURNCMD)) return TRUE;
-    if (executedMenuId == -1) executedMenuId = 0;
+    if (executedMenuId < 0) executedMenuId = 0;
     return executedMenuId;
 }
 
@@ -4823,9 +4829,9 @@
      item = MENU_FindItem (&hMenu, &uItem, MF_BYPOSITION);
      referenceHwnd = hwnd;
 
+     itemMenu = MENU_GetMenu(hMenu);
      if(!hwnd)
      {
-	 itemMenu = MENU_GetMenu(hMenu);
 	 if (itemMenu == NULL)
 	     return FALSE;
 
@@ -4839,7 +4845,19 @@
 
      *rect = item->rect;
 
-     MapWindowPoints(referenceHwnd, 0, (LPPOINT)rect, 2);
+     if (itemMenu && !(itemMenu->wFlags & MF_POPUP))
+     {
+         /* Perhaps this special case should be eliminated and the coordinates
+          * should be relative to client area instead of window frame */
+         RECT rectWnd;
+         GetWindowRect(referenceHwnd, &rectWnd);
+         rect->top += rectWnd.top;
+         rect->bottom += rectWnd.top;
+         rect->left += rectWnd.left;
+         rect->right += rectWnd.left;
+     }
+     else
+         MapWindowPoints(referenceHwnd, 0, (LPPOINT)rect, 2);
 
      return TRUE;
 }
Index: dlls/user/tests/menu.c
===================================================================
RCS file: /home/wine/wine/dlls/user/tests/menu.c,v
retrieving revision 1.20
diff -u -r1.20 menu.c
--- dlls/user/tests/menu.c	19 Apr 2006 11:04:53 -0000	1.20
+++ dlls/user/tests/menu.c	21 Apr 2006 23:01:25 -0000
@@ -18,9 +18,6 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-#define NONAMELESSUNION
-#define NONAMELESSSTRUCT
-
 #include <stdlib.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -54,6 +51,9 @@
 
 /* globals to communicate between test and wndproc */
 
+static BOOL bMenuVisible;
+static HMENU hMenus[4];
+
 #define MOD_SIZE 10
 #define MOD_NRMENUS 8
 
@@ -1475,6 +1475,185 @@
     DestroyMenu(hmenuSub);
 }
 
+struct menu_item_pair_s {
+    UINT uMenu; /* 1 - top level menu, [0-Menu 1-Enabled 2-Disabled]
+                 * 2 - 2nd level menu, [0-Popup 1-Enabled 2-Disabled]
+                 * 3 - 3rd level menu, [0-Enabled 1-Disabled] */
+    UINT uItem;
+};
+
+static struct menu_mouse_tests_s {
+    DWORD type;
+    struct menu_item_pair_s menu_item_pairs[10]; /* for mousing */
+    WORD wVk[10]; /* keys */
+    BOOL bMenuVisible;
+} menu_tests[] = {
+    /* for each test, send keys or clicks and check for menu visibility */
+    { INPUT_KEYBOARD, {{0}}, {VK_LMENU, 0}, TRUE }, /* test 0 */
+    { INPUT_KEYBOARD, {{0}}, {VK_ESCAPE, 0}, FALSE },
+    { INPUT_KEYBOARD, {{0}}, {VK_LMENU, 0}, TRUE },
+    { INPUT_KEYBOARD, {{0}}, {'D', 0}, FALSE },
+    { INPUT_KEYBOARD, {{0}}, {VK_LMENU, 0}, TRUE },
+    { INPUT_KEYBOARD, {{0}}, {'E', 0}, FALSE },
+    { INPUT_KEYBOARD, {{0}}, {VK_LMENU, 'M', 0}, TRUE },
+    { INPUT_KEYBOARD, {{0}}, {VK_ESCAPE, VK_ESCAPE, 0}, FALSE },
+    { INPUT_KEYBOARD, {{0}}, {VK_LMENU, 'M', VK_ESCAPE, 0}, TRUE },
+    { INPUT_KEYBOARD, {{0}}, {VK_ESCAPE, 0}, FALSE },
+    { INPUT_KEYBOARD, {{0}}, {VK_LMENU, 'M', 0}, TRUE },
+    { INPUT_KEYBOARD, {{0}}, {'D', 0}, FALSE },
+    { INPUT_KEYBOARD, {{0}}, {VK_LMENU, 'M', 0}, TRUE },
+    { INPUT_KEYBOARD, {{0}}, {'E', 0}, FALSE },
+    { INPUT_KEYBOARD, {{0}}, {VK_LMENU, 'M', 'P', 0}, TRUE },
+    { INPUT_KEYBOARD, {{0}}, {'D', 0}, FALSE },
+    { INPUT_KEYBOARD, {{0}}, {VK_LMENU, 'M', 'P', 0}, TRUE },
+    { INPUT_KEYBOARD, {{0}}, {'E', 0}, FALSE },
+
+    { INPUT_MOUSE, {{1, 2}, {0}}, {0}, TRUE }, /* test 18 */
+    { INPUT_MOUSE, {{1, 1}, {0}}, {0}, FALSE },
+    { INPUT_MOUSE, {{1, 0}, {0}}, {0}, TRUE },
+    { INPUT_MOUSE, {{1, 1}, {0}}, {0}, FALSE },
+    { INPUT_MOUSE, {{1, 0}, {2, 2}, {0}}, {0}, TRUE },
+    { INPUT_MOUSE, {{2, 1}, {0}}, {0}, FALSE },
+    { INPUT_MOUSE, {{1, 0}, {2, 0}, {0}}, {0}, TRUE },
+    { INPUT_MOUSE, {{3, 0}, {0}}, {0}, FALSE },
+    { INPUT_MOUSE, {{1, 0}, {2, 0}, {0}}, {0}, TRUE },
+    { INPUT_MOUSE, {{3, 1}, {0}}, {0}, TRUE },
+    { INPUT_MOUSE, {{1, 1}, {0}}, {0}, FALSE },
+    { -1 }
+};
+
+static void send_key(WORD wVk)
+{
+    INPUT i[2];
+    memset(&i, 0, 2*sizeof(INPUT));
+    i[0].type = i[1].type = INPUT_KEYBOARD;
+    i[0].ki.wVk = i[1].ki.wVk = wVk;
+    i[1].ki.dwFlags = KEYEVENTF_KEYUP;
+    SendInput(2, i, sizeof(INPUT));
+}
+
+static void click_menu(HANDLE hWnd, struct menu_item_pair_s *mi)
+{
+    HMENU hMenu = hMenus[mi->uMenu];
+    INPUT i[3];
+    MSG msg;
+    RECT r;
+    int screen_w = GetSystemMetrics(SM_CXSCREEN);
+    int screen_h = GetSystemMetrics(SM_CYSCREEN);
+
+    GetMenuItemRect(mi->uMenu > 2 ? NULL : hWnd, hMenu, mi->uItem, &r);
+
+    memset(&i, 0, 3*sizeof(INPUT));
+    i[0].type = i[1].type = i[2].type = INPUT_MOUSE;
+    i[0].mi.dx = i[1].mi.dx = i[2].mi.dx = ((r.left + 5) * 65535) / screen_w;
+    i[0].mi.dy = i[1].mi.dy = i[2].mi.dy = ((r.top + 5) * 65535) / screen_h;
+    i[0].mi.dwFlags = i[1].mi.dwFlags = i[2].mi.dwFlags = MOUSEEVENTF_ABSOLUTE;
+    i[0].mi.dwFlags |= MOUSEEVENTF_MOVE;
+    i[1].mi.dwFlags |= MOUSEEVENTF_LEFTDOWN;
+    i[2].mi.dwFlags |= MOUSEEVENTF_LEFTUP;
+    SendInput(3, i, sizeof(INPUT));
+
+    /* hack to prevent mouse message buildup in Wine */
+    while (PeekMessage( &msg, 0, 0, 0, PM_REMOVE )) DispatchMessageA( &msg );
+}
+
+static DWORD WINAPI test_menu_input_thread(LPVOID lpParameter)
+{
+    int i, j;
+    HANDLE hWnd = lpParameter;
+
+    Sleep(500);
+    /* mixed keyboard/mouse test */
+    for (i = 0; menu_tests[i].type != -1; i++)
+    {
+        int elapsed = 0;
+
+        if (menu_tests[i].type == INPUT_KEYBOARD)
+            for (j = 0; menu_tests[i].wVk[j] != 0; j++)
+                send_key(menu_tests[i].wVk[j]);
+        else
+            for (j = 0; menu_tests[i].menu_item_pairs[j].uMenu != 0; j++)
+                click_menu(hWnd, &menu_tests[i].menu_item_pairs[j]);
+
+        while (menu_tests[i].bMenuVisible != bMenuVisible)
+        {
+            if (elapsed > 500)
+                break;
+            elapsed += 100;
+            Sleep(100);
+        }
+
+        ok(menu_tests[i].bMenuVisible == bMenuVisible, "test %d\n", i);
+    }
+    return 0;
+}
+
+static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam,
+        LPARAM lParam)
+{
+    switch (msg) {
+        case WM_ENTERMENULOOP:
+            bMenuVisible = TRUE;
+            break;
+        case WM_EXITMENULOOP:
+            bMenuVisible = FALSE;
+            break;
+        default:
+            return( DefWindowProcA( hWnd, msg, wParam, lParam ) );
+    }
+    return 0;
+}
+
+static void test_menu_input(void) {
+    MSG msg;
+    WNDCLASSA  wclass;
+    HINSTANCE hInstance = GetModuleHandleA( NULL );
+    HANDLE hThread, hWnd;
+
+    wclass.lpszClassName = "MenuTestClass";
+    wclass.style         = CS_HREDRAW | CS_VREDRAW;
+    wclass.lpfnWndProc   = WndProc;
+    wclass.hInstance     = hInstance;
+    wclass.hIcon         = LoadIconA( 0, (LPSTR)IDI_APPLICATION );
+    wclass.hCursor       = LoadCursorA( NULL, (LPSTR)IDC_ARROW);
+    wclass.hbrBackground = (HBRUSH)( COLOR_WINDOW + 1);
+    wclass.lpszMenuName  = 0;
+    wclass.cbClsExtra    = 0;
+    wclass.cbWndExtra    = 0;
+    assert (RegisterClassA( &wclass ));
+    assert (hWnd = CreateWindowA( wclass.lpszClassName, "MenuTest",
+                                  WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0,
+                                  400, 200, NULL, NULL, hInstance, NULL) );
+
+    /* fixed menus */
+    hMenus[3] = CreatePopupMenu();
+    AppendMenu(hMenus[3], MF_STRING, 0, "&Enabled");
+    AppendMenu(hMenus[3], MF_STRING|MF_DISABLED, 0, "&Disabled");
+
+    hMenus[2] = CreatePopupMenu();
+    AppendMenu(hMenus[2], MF_STRING|MF_POPUP, (UINT_PTR) hMenus[3], "&Popup");
+    AppendMenu(hMenus[2], MF_STRING, 0, "&Enabled");
+    AppendMenu(hMenus[2], MF_STRING|MF_DISABLED, 0, "&Disabled");
+
+    hMenus[1] = CreateMenu();
+    AppendMenu(hMenus[1], MF_STRING|MF_POPUP, (UINT_PTR) hMenus[2], "&Menu");
+    AppendMenu(hMenus[1], MF_STRING, 0, "&Enabled");
+    AppendMenu(hMenus[1], MF_STRING|MF_DISABLED, 0, "&Disabled");
+
+    SetMenu(hWnd, hMenus[1]);
+    ShowWindow(hWnd, SW_SHOW);
+    UpdateWindow(hWnd);
+
+    hThread = CreateThread(NULL, 0, test_menu_input_thread, hWnd, 0, NULL);
+    while(1)
+    {
+        if (WAIT_TIMEOUT != WaitForSingleObject(hThread, 50))
+            break;
+        while (PeekMessage( &msg, 0, 0, 0, PM_REMOVE )) DispatchMessageA( &msg );
+    }
+    DestroyWindow(hWnd);
+}
+
 START_TEST(menu)
 {
     pSetMenuInfo =
@@ -1490,4 +1669,5 @@
     test_menu_iteminfo();
     test_menu_search_bycommand();
     test_menu_bmp_and_string();
+    test_menu_input();
 }



More information about the wine-patches mailing list