There is also another problem. The value of a menu handle is so little (about 0x80 - according to what I saw) that it’s very possible that a submenu has the same ID as a command item for a submenu’s ID equals to the handle of its submenu by default. Applications like Adobe Audition that use GetMenuItemInfo to retrieve the information of a command item will run into trouble. The conflict seldom or never happens on Windows, because the value there often exceeds 0xFFFF. I wonder that if we can solve it by XORing a constant value to the handle.
ChangeLog controls/menu.c, dlls/user/tests/Makefile.in, dlls/user/tests/menu.c - Make GetMenuItemInfo by ID return the data of a command item if the item and the submenu that the item belongs to have the same ID. - Add a test for menus.
Index: controls/menu.c =================================================================== RCS file: /home/wine/wine/controls/menu.c,v retrieving revision 1.168 diff -u -r1.168 menu.c --- controls/menu.c 17 Sep 2003 04:28:29 -0000 1.168 +++ controls/menu.c 8 Nov 2003 05:53:16 -0000 @@ -565,12 +565,13 @@ MENUITEM *item = menu->items; for (i = 0; i < menu->nItems; i++, item++) { - if (item->wID == *nPos) - { - *nPos = i; - return item; + if (item->wID == *nPos && (item->fType & MF_POPUP) != MF_POPUP) + {/* If we find a command item, return it */ + *nPos = i; + return item; } - else if (item->fType & MF_POPUP) + /* else search the submenu */ + if (item->fType & MF_POPUP) { HMENU hsubmenu = item->hSubMenu; MENUITEM *subitem = MENU_FindItem( &hsubmenu, nPos, wFlags ); @@ -578,6 +579,11 @@ { *hmenu = hsubmenu; return subitem; + } + if (item->wID == *nPos) + { + *nPos = i; + return item; } } } Index: dlls/user/tests/Makefile.in =================================================================== RCS file: /home/wine/wine/dlls/user/tests/Makefile.in,v retrieving revision 1.6 diff -u -r1.6 Makefile.in --- dlls/user/tests/Makefile.in 28 Oct 2003 00:18:40 -0000 1.6 +++ dlls/user/tests/Makefile.in 8 Nov 2003 05:53:54 -0000 @@ -10,6 +10,7 @@ generated.c \ input.c \ listbox.c \ + menu.c \ msg.c \ sysparams.c \ win.c \ --- /dev/null 2002-08-31 07:31:37.000000000 +0800 +++ dlls/user/tests/menu.c 2003-11-08 13:17:31.000000000 +0800 @@ -0,0 +1,422 @@ +/* + * Copyright (C) the Wine project + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <assert.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdio.h> + +#include "wine/test.h" +#include "windef.h" +#include "winbase.h" +#include "winuser.h" +#include "winnls.h" + + +#ifndef array_count +#define array_count(array) (sizeof(array) / sizeof((array)[0])) +#endif +#define MAX_MENU_ITEM_NAME 16 + +typedef struct MY_MENUITEMTEMPLATE { + WORD mtOption; + WORD mtID; + char mtString[MAX_MENU_ITEM_NAME]; +}MY_MENUITEMTEMPLATE; + +const static MY_MENUITEMTEMPLATE theLMI[] = {/* for LoadMenuIndirect */ + {MF_POPUP, 0, "Popup"}, + {MF_END, 40001, "Submenu Item"}, + {MF_POPUP | MF_END, 0, "Popup2"}, + {MF_END, 40002, "End"} +}; + +typedef struct GMBIT_DATA {/* for GetMenuInfo by ID test */ + const MY_MENUITEMTEMPLATE *pItems;/* menu to test */ + int nCount; /* count of menuitems */ + + DWORD dwItemID; /* which item to get */ + BOOL fSubMenu; /* expect a submenu or command item */ + char szExpected[MAX_MENU_ITEM_NAME]; /* item's name returned by GetMenuInfo */ +}GMBIT_DATA; + +/* For testing GetMenuItemInfo by ID */ +const static MY_MENUITEMTEMPLATE theGMI0[] = { + {MF_SEPARATOR, 0, ""}, + {0, 40001, "A-0"}, + {MF_SEPARATOR, 0, ""}, + {MF_POPUP, 0, "A-1"}, + {0, 40001, "A-1-0"}, + {MF_END, 40001, "A-1-1"}, + {MF_POPUP, 0, "A-2"}, + {0, 40001, "A-2-0"}, + {MF_END, 40001, "A-2-1"}, + {MF_SEPARATOR, 0, ""}, + {MF_END, 40001, "A-3"} +}; + +const static MY_MENUITEMTEMPLATE theGMI1[] = { + {0, 40002, "B-0"}, + {MF_SEPARATOR, 0, ""}, + {MF_POPUP, 0, "B-1"}, + {0, 40002, "B-1-0"}, + {MF_END, 40002, "B-1-1"}, + {MF_POPUP, 0, "B-2"}, + {0, 40002, "B-2-0"}, + {MF_END, 40001, "B-2-1"}, + {MF_SEPARATOR, 0, ""}, + {MF_END, 40001, "B-3"} +}; + +const static MY_MENUITEMTEMPLATE theGMI2[] = { + {MF_POPUP, 40003, "C-0"}, + {MF_POPUP, 40003, "C-0-0"}, + {0, 40002, "C-0-0-0"}, + {0, 40001, "C-0-0-1"}, + {MF_END, 40003, "C-0-0-2"}, + {0, 40002, "C-0-1"}, + {MF_END, 40001, "C-0-2"}, + {MF_POPUP, 40003, "C-1"}, + {0, 40001, "C-1-0"}, + {MF_END, 40001, "C-1-1"}, + {MF_END, 40001, "C-2"} +}; + +const static MY_MENUITEMTEMPLATE theGMI3[] = { + {MF_POPUP, 40003, "D-0"}, + {0, 40002, "D-0-0"}, + {MF_POPUP, 40003, "D-0-1"}, + {0, 40002, "D-0-1-0"}, + {MF_END, 40001, "D-0-1-1"}, + {MF_END, 40001, "D-0-2"}, + {MF_POPUP, 40003, "D-1"}, + {0, 40001, "D-1-0"}, + {MF_END, 40001, "D-1-1"}, + {0, 40001, "D-2"}, + {MF_END, 40003, "D-3"} +}; + +const static GMBIT_DATA theGMBITData[] = { + {theGMI0, array_count(theGMI0), 40001, FALSE, "A-0"}, + {theGMI1, array_count(theGMI1), 40001, FALSE, "B-2-1"}, + {theGMI2, array_count(theGMI2), 40001, FALSE, "C-0-0-1"}, + {theGMI2, array_count(theGMI2), 40003, FALSE, "C-0-0-2"}, + {theGMI3, array_count(theGMI3), 40003, TRUE, "D-0-1"} +}; + + +LPCSTR GetLastErrorMsg() +{ + static char szMessage[1024] = {""}; + FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + szMessage, + 1024, + NULL + ); + return szMessage; +} + +/* + * MENU hMenu: Handle of the menu to dump. + * UINT nTabs: How many '\t' chars to insert at the beginning of the line. + * Useful for distincting the menu items of different levels. + */ +void DumpMenu_(HMENU hMenu, int nTabs) +{ + BOOL fRet; + int i, j, nCount; + char szName[MAX_MENU_ITEM_NAME]; + MENUITEMINFOA mii; + + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_SUBMENU; + + nCount = GetMenuItemCount(hMenu); + for (i = 0; i < nCount; i++) { + mii.dwTypeData = szName; + mii.cch = array_count(szName); + *szName = '\0'; + fRet = GetMenuItemInfoA(hMenu, i, TRUE, &mii); + ok (fRet, "GetMenuItemInfoA: %s.\n", GetLastErrorMsg()); + + for (j = 0; j < nTabs; j++) + printf("\t"); + + if (NULL == mii.hSubMenu) { + printf("MENUITEM \"%s\", %u\n", szName, mii.wID); + } + else {/* dump the submenu */ + printf("POPUP \"%s\", %u\n", szName, mii.wID); + for (j = 0; j < nTabs; j++) + printf("\t"); + printf("{\n"); + DumpMenu_(mii.hSubMenu, nTabs + 1); + for (j = 0; j < nTabs; j++) + printf("\t"); + printf("}\n"); + } + } +} + +__inline void DumpMenu(HMENU hMenu) +{ + printf("%p MENU\n{\n", hMenu); + DumpMenu_(hMenu, 1); + printf("}\n"); +} + + +LPBYTE CreateMenuTemplate(const MY_MENUITEMTEMPLATE *pItems, int nCount) +{ + MENUITEMTEMPLATEHEADER *hdr; + + int nLen; + LPBYTE pBuffer, pTemplate; + int nItemSize; + nItemSize = (sizeof(WORD) * 2 + sizeof(WCHAR) * MAX_MENU_ITEM_NAME); + pBuffer = (LPBYTE)malloc(sizeof(*hdr) + nItemSize * nCount); + pTemplate = pBuffer; + if (NULL == pBuffer) { + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + + /* Setting Header ... */ + hdr = (MENUITEMTEMPLATEHEADER*)pBuffer; + hdr->versionNumber = 0; + hdr->offset = 0; + pBuffer += sizeof(*hdr); + + /* Adding Menu items ... */ + for ( ; nCount--; pItems++) { + *(WORD*)pBuffer = pItems->mtOption; + pBuffer += sizeof(WORD); + if ((pItems->mtOption & MF_POPUP) != MF_POPUP) { + *(WORD*)pBuffer = pItems->mtID; + pBuffer += sizeof(WORD); + } + /* else Nothing to do ... for a popup doesn't have mtID!!! */ + nLen = MultiByteToWideChar(CP_ACP, 0, pItems->mtString, -1, + (LPWSTR)pBuffer, MAX_MENU_ITEM_NAME); + ok (0 != nLen, "MultiByteToWideChar: %s.\n", GetLastErrorMsg()); + pBuffer += nLen * sizeof(WCHAR); + } + return pTemplate; +} + +/* returns how many items (including items in submenus and submenus themself) + * in the hMenu */ +int SetSubMenusID(HMENU hMenu, const MY_MENUITEMTEMPLATE *pItems) +{ + BOOL fRet; + int i, nCount, nRet; + char szName[MAX_MENU_ITEM_NAME]; + MENUITEMINFOA mii; + const MY_MENUITEMTEMPLATE *pData = pItems; + + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + + nCount = GetMenuItemCount(hMenu); + for (i = 0; i < nCount; i++, pData++) { + mii.fMask = MIIM_SUBMENU | MIIM_TYPE; + mii.dwTypeData = szName; + mii.cch = array_count(szName); + *szName = '\0'; + fRet = GetMenuItemInfoA(hMenu, i, TRUE, &mii); + ok (fRet, "GetMenuItemInfoA: %s.\n", GetLastErrorMsg()); + + if (NULL != mii.hSubMenu) { + mii.fMask = MIIM_ID; + mii.wID = pData->mtID; + if (mii.wID != 0 && mii.wID != 0xFFFF) { + fRet = SetMenuItemInfo(hMenu, i, TRUE, &mii); + ok(fRet, "SetMenuItemInfo: %s.\n", GetLastErrorMsg()); + } + nRet = SetSubMenusID(mii.hSubMenu, pData + 1); + ok (nRet > 0 && (pData[nRet].mtOption & MF_END) != 0, "Invalid parameters.\n"); + pData += nRet; + } + } + return pData - pItems; +} + +HMENU MyLoadMenuIndirect(const MY_MENUITEMTEMPLATE *pItems, int nCount) +{ + HMENU hMenu = NULL; + LPBYTE pBuffer = NULL; + + pBuffer = CreateMenuTemplate(pItems, nCount); + if (NULL != pBuffer) { + hMenu = LoadMenuIndirect(pBuffer); + free(pBuffer); + if (hMenu != NULL) { + SetSubMenusID(hMenu, pItems); + } + } + return hMenu; +} + +/* A popup menu's ID equals to the handle of its submenu by default */ +void SubMenuIDTest(HMENU hMenu) +{ + BOOL fRet; + int i, nCount; + char szName[MAX_MENU_ITEM_NAME]; + MENUITEMINFOA mii; + + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_SUBMENU; + + nCount = GetMenuItemCount(hMenu); + for ( i = 0; i < nCount; i++) { + mii.dwTypeData = szName; + mii.cch = array_count(szName); + mii.hSubMenu = NULL; + *szName = '\0'; + fRet = GetMenuItemInfoA(hMenu, i, TRUE, &mii); + ok (fRet, "GetMenuItemInfoA: %s.\n", GetLastErrorMsg()); + if (NULL != mii.hSubMenu) { + ok (mii.wID == (UINT)mii.hSubMenu, "Popup menu's ID differs from its submenu's handle.(Name: \"%s\")\n", szName); + SubMenuIDTest(mii.hSubMenu); + } + } +} + +BOOL LoadMenuIndirectTest() +{ + BOOL fRet; + HMENU hMenu; + char szName[MAX_MENU_ITEM_NAME]; + MENUITEMINFOA mii; + + hMenu = MyLoadMenuIndirect(theLMI, array_count(theLMI)); + ok (NULL != hMenu, "MyLoadMenuIndirect: %s.\n", GetLastErrorMsg()); + if (NULL != hMenu) { + /* Check IDs ... */ + SubMenuIDTest(hMenu); + + /* Check menu names */ + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_TYPE; + mii.dwTypeData = szName; + mii.cch = array_count(szName); + *szName = '\0'; + fRet = GetMenuItemInfoA(hMenu, 1, TRUE, &mii); + ok (fRet, "GetMenuItemInfoA: %s.\n", GetLastErrorMsg()); + ok (strcmp(theLMI[2].mtString, szName) == 0, + "LoadMenuIndirect: Wrong name. Before load:\"%s\", after load:\"%s\"", + theLMI[2].mtString, + szName + ); + /* DumpMenu(hMenu); */ + fRet = DestroyMenu(hMenu); + ok (fRet, "DestroyMenu: %s.\n", GetLastErrorMsg()); + + return TRUE; + } + + return FALSE; +} + +void GetMenuItemByIDTest(const GMBIT_DATA *pData) +{ + BOOL fRet; + HMENU hMenu; + BOOL fSubMenu; + char szName[MAX_MENU_ITEM_NAME]; + MENUITEMINFOA mii; + + /* Create a menu */ + hMenu = MyLoadMenuIndirect(pData->pItems, pData->nCount); + ok (NULL != hMenu, "MyLoadMenuIndirect: %s.\n", GetLastErrorMsg()); + if (NULL == hMenu) { + return; + } + + /* What will we get? */ + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_TYPE | MIIM_SUBMENU; + mii.dwTypeData = szName; + mii.cch = array_count(szName); + *szName = '\0'; + fRet = GetMenuItemInfoA(hMenu, pData->dwItemID, FALSE, &mii); + ok (fRet, "GetMenuItemInfoA: %s.\n", GetLastErrorMsg()); + + /* Is it right? */ + ok (strcmp(pData->szExpected, szName) == 0, + "\"%s\" was expected, but got \"%s\".\n", + pData->szExpected, + szName + ); + fSubMenu = pData->fSubMenu; + ok ((fSubMenu && NULL != mii.hSubMenu) || (!fSubMenu && NULL == mii.hSubMenu), + fSubMenu ? "A submenu(\"%s\") was expected, but got a command item(\"%s\").\n" + : "A command item(\"%s\") was expected, but got a submenu(\"%s\").\n", + pData->szExpected, + szName + ); + + DestroyMenu(hMenu); +} + +void MenuIDTest() +{ + int i, nCount; + const GMBIT_DATA *pData; + + srand(GetTickCount()); + + pData = theGMBITData; + nCount = array_count(theGMBITData); + for (i = 0; i < nCount; i++, pData++) { + GetMenuItemByIDTest(pData); + } +} + +START_TEST(menu) +{ +#if 0/* Don't know how to write a unit test for InsertMenu ... */ + BOOL fRet; + HMENU hMenu = NULL; + + /* InsertMenu */ + hMenu = MyLoadMenuIndirect(theGMI2, array_count(theGMI2)); + printf("Insert menu item (name=\"Inserted item\") " + "before menu item (id = 40003).\n"); + fRet = InsertMenuA(hMenu, 40003, MF_BYCOMMAND, 40010, "Inserted item"); + ok (fRet, "InsertMenuA: %s.\n", GetLastErrorMsg() ); + DumpMenu(hMenu); + + DestroyMenu(hMenu); +#endif + + if (!LoadMenuIndirectTest()) { + return; + } + + MenuIDTest(); +}