GetMenuItemInfo by ID returns the data of a command item if the item belongs to a submenu that has the same ID as the item on windows. Our function will return the information of a submenu in such case. My patch will fix it.
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();
+}