Solved CommandData with Options Dialog - Docked command button

Hi,

I have a CommandData plugin which opens an asynchronous dialog and also supports an options dialog (PLUGINFLAG_COMMAND_OPTION_DIALOG).

Everything is nice and dandy, until I dock the command into a palette in C4D's layout. In this case I get a shiny button with a cogwheel on the right side. Nice.
The problem is, if the command has been called to open the dialog (and thus is enabled, i.e. shows highlighted in menu), then the cogwheel part of the command button does no longer work. Instead of calling ExecuteOptionID() it then calls the normal Execute(), which is obviously not what I would expect. From menu, this works as expected, regardless of the command's state, clicking the cogwheel in menu calls ExecuteOptionID(), but not so from a button palette.

Is this intended operation or maybe a bug?

I have tested the behavior in R21 and S24.

Cheers,
Andreas

Edit: I forgot to mention, all my test were done on Win 10.

Hello Andreas,

thank you for reaching out to us. And special thank you for making the effort of describing and testing bugs in such a detailed manner as you do and have done in the past, much appreciated.

With that being said, I struggle a bit with reproducing your problem. I might be misunderstanding something here.

  1. You talk about a CommandData being highlighted in the menu when the dialog is opened. But that is not the case for CommandData in my experience. Other than for tool plugins, e.g., ToolData in Python, their icon is not being highlighted "when they are running". I double checked in S24 and R21 and could not reproduce this. The icon of a CommandData is only being highlighted when hovered. It can be enabled or disabled via overwriting CommandData.GetState, but that has nothing to do with an execution state or however wants to call this.
  2. Regarding the reported mixup of Execute() and ExecuteOptionID(), I can unfortunately also not reproduce this. I opened the option dialog of the example plugin shown at the end of the posting both in R21 and S24. Then I added the plugin as an icon to a palette and invoked the major and minor/option gadget of that icon while dialog was still opened. In all versions Cinema did execute the correct method.

So, I would have to ask you to clarify where I have misunderstood you or share some code, so, that we can to the bottom of why this is happening for you.

Cheers,
Ferdinand

What I did (in S24):
demo.gif

The code:

"""Example for executing an options dialog.

As discussed in:
    https://plugincafe.maxon.net/topic/13407/
"""
import c4d

class PluginDialog (c4d.gui.GeDialog):
    """A dialog for a CommandData plugin.
    """
    ID_INT_VALUE = 1000

    def CreateLayout(self):
        """Adds gadgets to the dialog.
        """
        self.SetTitle("Some Dialog")
        self.AddEditSlider(
            id=PluginDialog.ID_INT_VALUE,
            flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT)
        return True

    def InitValues(self):
        """Initialize the dialog values.
        """
        self.SetInt32(PluginDialog.ID_INT_VALUE, 42)
        return True


class PC13407(c4d.plugins.CommandData):
    """A CommandData plugin with a dialog.
    """
    ID_PLUGIN = 1057432
    _pluginDialog = None

    @classmethod
    def GetPluginDialog(cls):
        """Returns the class bound instance of the plugin dialog.
        """
        if cls._pluginDialog is None:
            cls._pluginDialog = PluginDialog()
        return cls._pluginDialog

    def Execute(self, doc):
        """
        """
        print("Running {}.Execute()".format(self))
        return True

    def ExecuteOptionID(self, doc, plugid, subid):
        """Opens the option dialog.
        """
        print("Running {}.ExecuteOptionID()".format(self))
        dialog = PC13407.GetPluginDialog()
        result = True
        if not dialog.IsOpen():
            result = dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC,
                                 pluginid=PC13407.ID_PLUGIN)

        return result

    def RestoreLayout(self, secret):
        """Restores the dialog on layout changes.
        """
        dialog = PC13407.GetPluginDialog()
        return dialog.Restore(PC13407.ID_PLUGIN, secret)


if __name__ == '__main__':
    c4d.plugins.RegisterCommandPlugin(
        id=PC13407.ID_PLUGIN,
        str="Options Test",
        info=c4d.PLUGINFLAG_COMMAND_OPTION_DIALOG,
        icon=None,
        help="",
        dat=PC13407())

MAXON SDK Specialist
developers.maxon.net

Hi Ferdinand,

thanks for your quick reply.
Please find a small plugin to reproduce below.
Looks like you thanked me too early for giving detailed bug reports, because obviously I did not mention an important detail (GetState())...

As far as I know, every manager dialog in C4D gets opened via CommandData, don't they? And all of these represent their open state by highlighting/checking their command using GetState(). And this is what I'd like to reproduce, except that my manager not only has a main dialog, but also makes use of an additional "options command".

So in order to reproduce, use below plugin.
Then:

  1. Customize a palette and drag the "Test Command Button" into it.
  2. You should get a button with a cogwheel.
  3. Click the cogwheel to see "ExecuteOptionID" being printed to the console.
  4. Click the left/main section of the button to have it open its dialog (now, thinking about it, the dialog is redundant here, any state change would do... anyway too lazy to change the example code again...)
  5. Now, the palette button is highlighted.
  6. Press the cogwheel again.
  7. See "Execute" being printed to the console, instead of the expected "ExecuteOptionID".

Thanks for looking into it.

Cheers,
Andreas

import c4d

PLUGIN_ID = 1234567 # MAKE SURE TO USE A UNIQUE ID FROM Plugin Café
PLUGIN_NAME = 'Test Command Button'
PLUGIN_TOOLTIP = 'Dock command to layout palette'

class DialogMain(c4d.gui.GeDialog):

    def CreateLayout(self):
        self.SetTitle('{0}'.format(PLUGIN_NAME))

        self.AddDlgGroup(c4d.DLG_OK)

        return True


    def Command(self, id, msg):
        if id == c4d.DLG_OK:
            self.Close()

        return True


class CommandDataTest(c4d.plugins.CommandData):
    _dlgMain = None


    def Execute(self, doc):
        print('Execute')

        if self._dlgMain is None:
            self._dlgMain = DialogMain()

        if self._dlgMain.IsOpen():
            self._dlgMain.Close()
            return True

        self._dlgMain.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, xpos=-1, ypos=-1, defaultw=300, defaulth=0)

        return True


    def ExecuteOptionID(self, doc, plugid, subid):
        print('ExecuteOptionID')

        return True


    def GetState(self, doc):
        state = c4d.CMD_ENABLED

        if self._dlgMain is not None and self._dlgMain.IsOpen():
            state |= c4d.CMD_VALUE

        return state


if __name__ == '__main__':
    c4d.plugins.RegisterCommandPlugin(PLUGIN_ID, PLUGIN_NAME,
                                      c4d.PLUGINFLAG_COMMAND_OPTION_DIALOG, None, PLUGIN_TOOLTIP, CommandDataTest())

Hello Andreas,

thank you for the clarification, CMD_VALUE was indeed the part that was not obvious to me. I can confirm the behavior and would also consider it to be a bug, especially since there has been a bug in 18.04 which has been fixed and was similar in nature.

There is unfortunately not much what you can do currently regarding workarounds. At least I do not see one. This is because the root of the problem lies within the handling of palettes (and might be related to/ a side effect of the fix of the 18.04 issue) and therefore far outside of the wiggle room of the public API/SDK. I have reached out to the dev who fixed the older bug, and we will see if we want to consider this to be a bug or an accepted limitation.

I will give you here a status update on what we will do once I know it.

Cheers,
Ferdinand

The pruned plugin example to reproduce this:

"""Example for buggy ExecuteOptionID behavior when a CommandData state is 
c4d.CMD_ENABLED | c4d.CMD_VALUE.

As discussed in:
    https://plugincafe.maxon.net/topic/13407/
"""

class PC13407(c4d.plugins.CommandData):
    """A CommandData plugin implementing GetState and being registered with
    PLUGINFLAG_COMMAND_OPTION_DIALOG.

    The CommandData has an internal toggle to either set it as checked, i.e., 
    c4d.CMD_VALUE, or not. Which is here being toggled in Execute() and then 
    reflected in GetState().

    The problem which then arises is that when the plugin icon is docked into
    a palette, and brought into the c4d.CMD_VALUE state, that Cinema will not
    distinguish properly anymore between clicks onto the major gadget, the 
    icon, and the cogwheel next to it. The user looses then in this state the
    ability to invoke ExecuteOptionID() via clicking onto the cogwheel. 
    Clicking onto the cogwheel will then invoke Execute() instead.

    When invoking the plugin from a menu, e.g., the Extensions menu, this does
    not happen.
    """
    ID_PLUGIN = 1057432
    _isActive = False

    def Execute(self, doc):
        PC13407._isActive = not PC13407._isActive
        print(f'Execute(),  _isActive: {PC13407._isActive}')
        return True

    def ExecuteOptionID(self, doc, plugid, subid):
        print('ExecuteOptionID()')
        return True

    def GetState(self, doc):
        return (c4d.CMD_ENABLED 
                if not PC13407._isActive else
                c4d.CMD_ENABLED | c4d.CMD_VALUE)


if __name__ == '__main__':
    c4d.plugins.RegisterCommandPlugin(
        id=PC13407.ID_PLUGIN,
        str="Options Test",
        info=c4d.PLUGINFLAG_COMMAND_OPTION_DIALOG,
        icon=None,
        help="",
        dat=PC13407())

MAXON SDK Specialist
developers.maxon.net

Hi Ferdinand,

thanks for the confirmation and looking into it.

While it's a bit unfortunate, I do not consider this a major road block. From my side this thread can be considered close.

Cheers