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
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);
#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);
}