Solved Adding icon to submenu

Hi again!

I'm generating a dynamic menu and was playing around with icons. Since you have to register seperate icon ids this is probably too complicated. I had an interesting thing happen though I wanted to ask about.
I don't think it is documented, but I saw on old forum post where someone was saying you could add an icon to a submenu by naming the entry [name]&i[iconID]&. When looping through menus, giving one of the entries an icon like that the subsequent entries also get that icon, even though they are named the same way the first ones were.
0_1548062281609_dropdown_icon.png
Entry seven was given the icon submenu.SetString(entryID, name + "&i" + str(iconID) + "&"),
all the others are named with identical code: submenu.SetString(entryID, name).

Anyone got any insight into what's happening here or how to use this technique correctly?

Hi @Boony2000 thanks a lot for the code sample I can indeed confirm the issue. I will forward this bug to our development team.

Here a workaround by registering an empty icon.

import c4d

TMP_PLUGIN_ID = 1000001

def enhanceMainMenu():
    mainMenu = c4d.gui.GetMenuResource("M_EDITOR")
    pluginsMenu = c4d.gui.SearchPluginMenuResource()

    menu = c4d.BaseContainer()
    menu.InsData(c4d.MENURESOURCE_SUBTITLE, "Test Menu")
    menu.InsData(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_" + str(TMP_PLUGIN_ID))

    mainMenu.InsDataAfter(c4d.MENURESOURCE_STRING, menu, pluginsMenu)

def PluginMessage(id, data):
    print "Pluginmessage"
    if id==c4d.C4DPL_BUILDMENU:
        enhanceMainMenu()

class MenuTest(c4d.plugins.CommandData):

    def CreateEmptyBmp(self):
        # Create a BaseBitmap
        bmp = c4d.bitmaps.BaseBitmap()
        
        # Init this Bitmap with 1 pixel and as 1bit
        if bmp.Init(1, 1, 1, c4d.INITBITMAPFLAGS_NONE) != c4d.IMAGERESULT_OK:
            return None

        # add alpha channel
        alphaChannel = bmp.AddChannel(True, False)

        # Set alpha
        bmp.SetAlphaPixel(alphaChannel, 1, 1, 0)

        return bmp

    def GetSubContainer(self, doc, submenu) :
        # if we don't have already registered an "empty" icon do it
        # This work because, the CommandData is not register with an icon, otherwise use another unique ID
        if c4d.gui.GetIcon(TMP_PLUGIN_ID) is None:
            bmp = self.CreateEmptyBmp()
            if not bmp: return True

            c4d.gui.RegisterIcon(TMP_PLUGIN_ID, bmp)

        menuItems = [["name1"], ["name2", "hasIcon"], ["name3"]]

        for i, data in enumerate(menuItems) :
            if len(data) > 1:
                submenu.SetString(i+1000, data[0] + "&i" + str(c4d.IDM_KEY_NEXT) + "&")
            else:
                submenu.SetString(i+1000, data[0] + "&i" + str(TMP_PLUGIN_ID) + "&")

        return True

if __name__ == "__main__":
    c4d.plugins.RegisterCommandPlugin(
        TMP_PLUGIN_ID,
        "MENU TEST",
        c4d.PLUGINFLAG_HIDEPLUGINMENU,
        None,
        None,
        MenuTest())

Hi @Boony2000, just to inform you this is a known bug normally fixed in R20.

I know you tagged your post as R20. But from my test, this bug is fixed in R20 SP1 (the one currently available to everyone), so may I ask you if your code was really executed in R20? If yes could you provide us with more information (like the current context, if it's called from a GeDialog or ShowPopupDialog.)

Nevertheless, as a workaround, you can register an icon without any opacity and use this one for entries without icons.

Documentation will be improved in this regards.

Cheers,
Maxime.

Hi Maxime!

I just updated to R20.030 and the issue is still there.
The screenshot is from the main editor window. I pretty much do what is described in the doc to enhanceMainMenu().
Within that I insert my commandDataPlugin

menu.InsData(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_" + str(res.ID_MENUTEST))

and in GetSubContainer() I iterate through a list and call

submenu.SetString(entryID, name)

or

submenu.SetString(entryID, name + "&i" + str(iconID) + "&")

Hi from my test here is it's working nicely either reacting to C4DPL_BUILDMENU or by overriding GetSubContainer in a CommandData plugin.

Could you please share with us some code that doesn't work and in which context exactly?

Thanks a lot.
Cheers,
Maxime.

Hi Maxime!

I reduced the code down to a plugin that just adds the menu and uses an existing icon and the error still persists:

import c4d

TMP_PLUGIN_ID = 1000001

def enhanceMainMenu():
    mainMenu = c4d.gui.GetMenuResource("M_EDITOR")
    pluginsMenu = c4d.gui.SearchPluginMenuResource()

    menu = c4d.BaseContainer()
    menu.InsData(c4d.MENURESOURCE_SUBTITLE, "Test Menu")
    menu.InsData(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_" + str(TMP_PLUGIN_ID))

    mainMenu.InsDataAfter(c4d.MENURESOURCE_STRING, menu, pluginsMenu)

def PluginMessage(id, data):
    print "Pluginmessage"
    if id==c4d.C4DPL_BUILDMENU:
        enhanceMainMenu()
        c4d.gui.UpdateMenus()

class MenuTest(c4d.plugins.CommandData):
    def GetSubContainer(self, doc, submenu) :
        menuItems = [["name1"], ["name2", "hasIcon"], ["name3"]]

        for i, data in enumerate(menuItems) :
            if len(data) > 1:
                submenu.SetString(i+1000, data[0] + "&i" + str(c4d.IDM_KEY_NEXT) + "&")
            else:
                submenu.SetString(i+1000, data[0])
        return True

if __name__ == "__main__":
    c4d.plugins.RegisterCommandPlugin(
        TMP_PLUGIN_ID,
        "MENU TEST",
        c4d.PLUGINFLAG_HIDEPLUGINMENU,
        None,
        None,
        MenuTest())

Hi @Boony2000 thanks a lot for the code sample I can indeed confirm the issue. I will forward this bug to our development team.

Here a workaround by registering an empty icon.

import c4d

TMP_PLUGIN_ID = 1000001

def enhanceMainMenu():
    mainMenu = c4d.gui.GetMenuResource("M_EDITOR")
    pluginsMenu = c4d.gui.SearchPluginMenuResource()

    menu = c4d.BaseContainer()
    menu.InsData(c4d.MENURESOURCE_SUBTITLE, "Test Menu")
    menu.InsData(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_" + str(TMP_PLUGIN_ID))

    mainMenu.InsDataAfter(c4d.MENURESOURCE_STRING, menu, pluginsMenu)

def PluginMessage(id, data):
    print "Pluginmessage"
    if id==c4d.C4DPL_BUILDMENU:
        enhanceMainMenu()

class MenuTest(c4d.plugins.CommandData):

    def CreateEmptyBmp(self):
        # Create a BaseBitmap
        bmp = c4d.bitmaps.BaseBitmap()
        
        # Init this Bitmap with 1 pixel and as 1bit
        if bmp.Init(1, 1, 1, c4d.INITBITMAPFLAGS_NONE) != c4d.IMAGERESULT_OK:
            return None

        # add alpha channel
        alphaChannel = bmp.AddChannel(True, False)

        # Set alpha
        bmp.SetAlphaPixel(alphaChannel, 1, 1, 0)

        return bmp

    def GetSubContainer(self, doc, submenu) :
        # if we don't have already registered an "empty" icon do it
        # This work because, the CommandData is not register with an icon, otherwise use another unique ID
        if c4d.gui.GetIcon(TMP_PLUGIN_ID) is None:
            bmp = self.CreateEmptyBmp()
            if not bmp: return True

            c4d.gui.RegisterIcon(TMP_PLUGIN_ID, bmp)

        menuItems = [["name1"], ["name2", "hasIcon"], ["name3"]]

        for i, data in enumerate(menuItems) :
            if len(data) > 1:
                submenu.SetString(i+1000, data[0] + "&i" + str(c4d.IDM_KEY_NEXT) + "&")
            else:
                submenu.SetString(i+1000, data[0] + "&i" + str(TMP_PLUGIN_ID) + "&")

        return True

if __name__ == "__main__":
    c4d.plugins.RegisterCommandPlugin(
        TMP_PLUGIN_ID,
        "MENU TEST",
        c4d.PLUGINFLAG_HIDEPLUGINMENU,
        None,
        None,
        MenuTest())

Thanks, Maxime!

I will use that in the meantime.

Edit: Why can't I flag your posts as the correct answer? I can only flag my own.