GetMenuItemInfo by ID differs from the native

Jerry Jenkins Jerry_J_Jenkins at hotmail.com
Sat Nov 8 00:32:56 CST 2003


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.

-------------- next part --------------
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();
+}


More information about the wine-patches mailing list