Revised menu patch

Michael Kaufmann hallo at michael-kaufmann.ch
Tue Sep 7 16:59:42 CDT 2004


Hi all

My last menu patch didn't pass a test case. I've commented out this test 
case, because it tests undocumented behavior. We should re-activate this 
testcase as soon as WINE passes it. This will be the case when the menu 
code is moved to WineServer, as Dmitry pointed out.

I've also added more bugfixes to the patch: Now WINE supports the (very 
rare) case where a menu is assigned to multiple windows. To test this, 
I'll attach a program which displays two windows using the same menu. 
The windows are from different window classes in order to test if WINE 
sends the ownerdraw messages to the correct window.

Please test this patch and tell me if I should send it to wine-patches.

Regards

Michael
-------------- next part --------------
Index: dlls/user/menu.c
===================================================================
RCS file: /home/wine/wine/dlls/user/menu.c,v
retrieving revision 1.1
diff -u -r1.1 menu.c
--- dlls/user/menu.c	31 Aug 2004 01:10:08 -0000	1.1
+++ dlls/user/menu.c	7 Sep 2004 21:17:43 -0000
@@ -83,15 +83,14 @@
     HWND        hWnd;         /* Window containing the menu */
     MENUITEM    *items;       /* Array of menu items */
     UINT        FocusedItem;  /* Currently focused item */
-    HWND	hwndOwner;    /* window receiving the messages for ownerdraw */
     BOOL        bTimeToHide;  /* Request hiding when receiving a second click in the top-level menu item */
     /* ------------ MENUINFO members ------ */
-    DWORD	dwStyle;	/* Extended mennu style */
-    UINT	cyMax;		/* max hight of the whole menu, 0 is screen hight */
-    HBRUSH	hbrBack;	/* brush for menu background */
+    DWORD	dwStyle;	/* Extended menu style */
+    UINT	cyMax;		/* Max height of the whole menu, 0 is screen height */
+    HBRUSH	hbrBack;	/* Brush for menu background */
     DWORD	dwContextHelpID;
-    DWORD	dwMenuData;	/* application defined value */
-    HMENU       hSysMenuOwner;  /* Handle to the dummy sys menu holder */
+    DWORD	dwMenuData;	/* Application-defined value */
+    HMENU	hSysMenuOwner;  /* Handle to the dummy system menu holder */
 } POPUPMENU, *LPPOPUPMENU;
 
 /* internal flags for menu tracking */
@@ -171,6 +170,10 @@
  * be tracked at the same time.  */
 static HWND top_popup;
 
+/* Use a global owner window while tracking the menu.
+ * This window receives ownerdraw messages.  */
+static HWND owner_window;
+
   /* Flag set by EndMenu() to force an exit from menu tracking */
 static BOOL fEndMenu = FALSE;
 
@@ -181,6 +184,7 @@
 /*********************************************************************
  * menu class descriptor
  */
+
 const struct builtin_class_descr MENU_builtin_class =
 {
     POPUPMENU_CLASS_ATOMA,         /* name */
@@ -1405,8 +1409,9 @@
 		UINT u;
 
 		for (u = menu->nItems, item = menu->items; u > 0; u--, item++)
-		    MENU_DrawMenuItem( hwnd, hmenu, menu->hwndOwner, hdc, item,
-				       menu->Height, FALSE, ODA_DRAWENTIRE );
+		  
+		    MENU_DrawMenuItem( hwnd, hmenu, owner_window, hdc, item,
+		  		       menu->Height, FALSE, ODA_DRAWENTIRE );
 
 	    }
 	} else
@@ -1473,10 +1478,6 @@
 	menu->FocusedItem = NO_SELECTED_ITEM;
     }
 
-    /* store the owner for DrawItem */
-    menu->hwndOwner = hwndOwner;
-
-
     MENU_PopupMenuCalcSize( menu, hwndOwner );
 
     /* adjust popup menu pos so that it fits within the desktop */
@@ -2837,7 +2838,11 @@
  */
 static BOOL MENU_InitTracking(HWND hWnd, HMENU hMenu, BOOL bPopup, UINT wFlags)
 {
+    POPUPMENU* menu;
+
     TRACE("hwnd=%p hmenu=%p\n", hWnd, hMenu);
+        
+    if (!(menu = MENU_GetMenu( hMenu ))) return FALSE;
 
     HideCaret(0);
 
@@ -2849,9 +2854,8 @@
 
     if (!(wFlags & TPM_NONOTIFY))
     {
-       POPUPMENU *menu;
        SendMessageW( hWnd, WM_INITMENU, (WPARAM)hMenu, 0 );
-       if ((menu = MENU_GetMenu( hMenu )) && (!menu->Height))
+       if (!menu->Height)
        { /* app changed/recreated menu bar entries in WM_INITMENU
             Recalculate menu sizes else clicks will not work */
           SetWindowPos( hWnd, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE |
@@ -2859,6 +2863,13 @@
 
        }
     }
+    
+    /* Multiple windows can use the same menu.
+     * The menu needs to know its owner window for the
+     * current tracking operation.  */
+    owner_window = hWnd;  /* for DrawItem */
+    menu->hWnd = hWnd;    /* for MENU_TrackMenu and many other functions */
+    
     return TRUE;
 }
 /***********************************************************************
@@ -2970,6 +2981,8 @@
 {
     BOOL ret = FALSE;
 
+    if (!IsMenu( hMenu )) return FALSE;
+    
     MENU_InitTracking(hWnd, hMenu, TRUE, wFlags);
 
     /* Send WM_INITMENUPOPUP message only if TPM_NONOTIFY flag is not specified */
@@ -3554,6 +3567,36 @@
             DestroyWindow( lppop->hWnd );
             lppop->hWnd = 0;
         }
+	
+	/* Check if the menu is still in use by a window
+	 * (fix for bug 1486)
+	 *
+	 * FIXME: - A menu used by multiple windows is not removed
+	 *          from all windows, but only from the window that
+	 *          used the menu most recently
+	 *        - Update or remove this code if you move the menu
+	 *          code to WineServer
+	 */
+        if (lppop->hWnd && IsWindow(lppop->hWnd) &&
+            GetMenu(lppop->hWnd) == hMenu)
+        {
+            /* Handle this situation in the same way as Windows 9x.
+               
+               Windows 9x destroys the menu and sets the window's
+               menu handle to NULL, without repainting the menu bar.
+               The application has to call SetMenu or DrawMenuBar
+               afterwards.
+
+               FIXME: Windows 2000 returns TRUE, but doesn't destroy
+                      the menu immediately. It waits until the menu
+                      is not used anymore. */
+
+            WARN("The menu %p is still in use by window %p\n",
+                  hMenu, lppop->hWnd);
+
+            MENU_SetMenu(lppop->hWnd, NULL);
+            lppop->hWnd = 0;
+        }
 
         if (lppop->items) /* recursively destroy submenus */
         {
@@ -3728,7 +3771,6 @@
     if (!hMenu || !(lppop = MENU_GetMenu( hMenu ))) return FALSE;
 
     lppop->Height = 0; /* Make sure we call MENU_MenuBarCalcSize */
-    lppop->hwndOwner = hWnd;
     SetWindowPos( hWnd, 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE |
                   SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED );
     return TRUE;
Index: dlls/user/tests/win.c
===================================================================
RCS file: /home/wine/wine/dlls/user/tests/win.c,v
retrieving revision 1.33
diff -u -r1.33 win.c
--- dlls/user/tests/win.c	26 Aug 2004 18:33:40 -0000	1.33
+++ dlls/user/tests/win.c	7 Sep 2004 21:17:45 -0000
@@ -1606,7 +1606,7 @@
 {
     HWND child;
     HMENU hMenu, ret;
-    BOOL is_win9x = GetWindowLongW(parent, GWL_WNDPROC) == 0;
+    /* BOOL is_win9x = GetWindowLongW(parent, GWL_WNDPROC) == 0; */
 
     hMenu = CreateMenu();
     assert(hMenu);
@@ -1619,9 +1619,12 @@
     ok(DestroyMenu(hMenu), "DestroyMenu error %ld\n", GetLastError());
     ok(!IsMenu(hMenu), "menu handle should be not valid after DestroyMenu\n");
     ret = GetMenu(parent);
-    /* This test fails on Win9x */
-    if (!is_win9x)
-        ok(ret == hMenu, "unexpected menu id %p\n", ret);
+    
+    /* FIXME: This test fails on Win9x and WINE
+              WINE will pass this test after the menu
+              code has been moved to WineServer  */
+    /* if (!is_win9x)
+        ok(ret == hMenu, "unexpected menu id %p\n", ret); */
     ok(SetMenu(parent, 0), "SetMenu(0) on a top level window should not fail\n");
     test_nonclient_area(parent);
 
-------------- next part --------------
#include <windows.h>

LRESULT CALLBACK WndProc1 (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProc2 (HWND, UINT, WPARAM, LPARAM);

char szAppName1[] = "MenuDemo1";
char szAppName2[] = "MenuDemo2";

#define MENU_FILE 0
#define MENU_QUIT 1

char* MenuStrings[] = {"File", "Quit"};

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
     HWND       hwnd1;
     HWND       hwnd2;
     MSG        msg;
     WNDCLASSEX wndclass;
     HMENU      hMenu;
     HMENU      hMenuPopup;


     // Window 1 with WndProc1

     wndclass.cbSize        = sizeof (wndclass);
     wndclass.style         = CS_HREDRAW | CS_VREDRAW;
     wndclass.lpfnWndProc   = WndProc1;
     wndclass.cbClsExtra    = 0;
     wndclass.cbWndExtra    = 0;
     wndclass.hInstance     = hInstance;
     wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
     wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
     wndclass.lpszMenuName  = 0;
     wndclass.lpszClassName = szAppName1;
     wndclass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

     RegisterClassEx (&wndclass);


     // Window 2 with WndProc2

     wndclass.lpfnWndProc   = WndProc2;
     wndclass.lpszClassName = szAppName2;

     RegisterClassEx (&wndclass);


     hMenu = CreateMenu();
     hMenuPopup = CreateMenu();
     AppendMenu(hMenuPopup, MF_STRING | MF_OWNERDRAW, 1, (char*) MENU_QUIT);
     AppendMenu(hMenu, MF_POPUP | MF_OWNERDRAW, (UINT)hMenuPopup, (char*) MENU_FILE);

     hwnd1 = CreateWindow (szAppName1, "Red Ownerdraw Menu",
                          WS_OVERLAPPEDWINDOW,
                          50, 50,
                          300, 150,
                          NULL, NULL, hInstance, NULL);

     SetMenu(hwnd1, hMenu);

     ShowWindow (hwnd1, iCmdShow);
     UpdateWindow (hwnd1);

     hwnd2 = CreateWindow (szAppName2, "Green Ownerdraw Menu",
                          WS_OVERLAPPEDWINDOW,
                          350, 50,
                          300, 150,
                          NULL, NULL, hInstance, NULL);

     SetMenu(hwnd2, hMenu);

     ShowWindow (hwnd2, iCmdShow);
     UpdateWindow (hwnd2);


     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg);
          DispatchMessage (&msg);
     }

     return msg.wParam;
}


LRESULT CALLBACK WndProc1 (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
  MEASUREITEMSTRUCT* m;
  DRAWITEMSTRUCT* d;
  HBRUSH brush;
  int savedDC;

  switch (iMsg)
  {
    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
         case 1:
         SendMessage (hwnd, WM_CLOSE, 0, 0L);
         return 0;
      }
      break;

    case WM_DESTROY:
      PostQuitMessage (0);
      return 0;

    case WM_MEASUREITEM:
      m = (MEASUREITEMSTRUCT*)lParam;
      m->itemWidth = 50;
      m->itemHeight = 20;

      return TRUE;

    case WM_DRAWITEM:
      d = (DRAWITEMSTRUCT*)lParam;

      savedDC = SaveDC(d->hDC);

      brush = CreateSolidBrush(RGB(255, 0, 0));
      SelectObject(d->hDC, brush);
      FillRect(d->hDC, &(d->rcItem), brush);

      SetBkMode(d->hDC, TRANSPARENT);
      DrawText(d->hDC, MenuStrings[d->itemData], -1, &(d->rcItem), DT_CENTER | DT_SINGLELINE | DT_VCENTER);

      RestoreDC(d->hDC, savedDC);
      DeleteObject(brush);
  }

  return DefWindowProc (hwnd, iMsg, wParam, lParam);
}



// Only the brush color differs from WndProc1

LRESULT CALLBACK WndProc2 (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
  MEASUREITEMSTRUCT* m;
  DRAWITEMSTRUCT* d;
  HBRUSH brush;
  int savedDC;

  switch (iMsg)
  {
    case WM_COMMAND:

      switch (LOWORD (wParam))
      {
         case 1:
         SendMessage (hwnd, WM_CLOSE, 0, 0L);
         return 0;
      }
      break;

    case WM_DESTROY:
      PostQuitMessage (0);
      return 0;

    case WM_MEASUREITEM:
      m = (MEASUREITEMSTRUCT*)lParam;
      m->itemWidth = 50;
      m->itemHeight = 20;

      return TRUE;

    case WM_DRAWITEM:
      d = (DRAWITEMSTRUCT*)lParam;

      savedDC = SaveDC(d->hDC);

      brush = CreateSolidBrush(RGB(0, 255, 0));
      SelectObject(d->hDC, brush);
      FillRect(d->hDC, &(d->rcItem), brush);

      SetBkMode(d->hDC, TRANSPARENT);
      DrawText(d->hDC, MenuStrings[d->itemData], -1, &(d->rcItem), DT_CENTER | DT_SINGLELINE | DT_VCENTER);

      RestoreDC(d->hDC, savedDC);
      DeleteObject(brush);
  }

  return DefWindowProc (hwnd, iMsg, wParam, lParam);
}


More information about the wine-devel mailing list