Disable default Right-Click Menu



  • Hey everyone,

    I am trying to use the right-click and middle-click function in my ASYNC-Dialog of a Command plugin.
    I can detect the clicks just fine, but I always get C4Ds basic behaviour as well. Is there a way to surpress the default context menu or the viewport toggle? I found that returning something invalid from Message() will stop the default behaviour and throw an error - but this can't be the solution, I think.
    Is there maybe a way to return a message with the c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSERIGHT, c4d.BFM_INPUT_VALUE changed to false so c4d won't try to act on the message?



  • Hi @unti

    From my side, I was also able to reproduce the Default right-click Menu Behavior in R20.
    So the main issue regarding that is because you do GUI operation (ShowPopuDialog) into Message function which indeed not thread-safe according to the received message (this was also true in previous versions).

    So to counter that, please check your message first and I would say listen to input only when Cinema 4D "invite you" to do it (aka during BFM_INTERACTSTART). Then make sure to call StopAllThread (Maybe another thread is also checking for input and want to display something?).

    Then finally regarding your freeze beside the threading issue that could cause some corruption of the event queue. you call ShowPopupDialog with None, while the host GeDialog must be passed. It's needed to ensure the event queue is correctly forwarded from the dialog to the host of the modal popup dialog).

    This way I was not anymore capable to make Cinema 4D freeze (you said crash, but personally I was never able to make it crash, only freeze, can you confirm? A crash is when C4D close, a freeze is when C4D hangs forever).

    Here a complete example that works nicely on the previous version (R19/R20) and R21.

    import c4d
    
    class TestDialog(c4d.gui.GeDialog):
        def __init__(self):
            self._showPopup = False, 0, 0
    
        def CreateLayout(self):
            self.GroupBegin(10002, c4d.BFH_SCALEFIT| c4d.BFH_SCALEFIT,  initw=100, inith=100)
            self.GroupEnd()
            return True
    
        def IsPositionInGuiArea(self, x, y):
            # Check click is within the GeDialog
            if not 0 < x < self._x:
                return False
    
            if not 0 < y < self._y:
                return False
    
            return True
    
        def Message(self, msg, result):
            if msg.GetId() == c4d.BFM_ADJUSTSIZE:
              self._x = msg[3] # Retrieve Y size of the GeDialog
              self._y = msg[4] # Retrieve Y size of the GeDialog
    
            # We are on the main thread here
            elif msg.GetId() == c4d.BFM_INTERACTSTART:
                c4d.StopAllThreads()
    
                state = c4d.BaseContainer()
                self.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSERIGHT, state)
    
                if state.GetInt32(c4d.BFM_INPUT_VALUE) == True:
    
                    x = state.GetInt32(c4d.BFM_INPUT_X)
                    y = state.GetInt32(c4d.BFM_INPUT_Y)
                    g2l  = self.Global2Local() 
                    x += g2l['x']  
                    y += g2l['y']
                    
                    if self.IsPositionInGuiArea(x, y):
                        IDM_MENU1 = c4d.FIRST_POPUP_ID
                        IDM_MENU2 = c4d.FIRST_POPUP_ID + 1
                        IDM_MENU3 = c4d.FIRST_POPUP_ID + 2
    
                        menu = c4d.BaseContainer()
                        menu.InsData(IDM_MENU1, 'Menu 1')
                        menu.InsData(IDM_MENU2, 'Menu 2')
                        menu.InsData(IDM_MENU3, 'Menu 3')
    
                        l2s = self.Local2Screen()
                        print str(x+l2s['x']) + " :: " + str(y+l2s['y'])
                        self.KillEvents()
                        res = c4d.gui.ShowPopupDialog(cd=self, bc=menu, x=x+l2s['x'], y=y+l2s['y'])
                        return True
    
            return c4d.gui.GeDialog.Message(self, msg, result)
    
    if __name__ == '__main__':
        global dlg
        dlg = TestDialog()
        dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC)
    

    Finally, there was some change in the UI in R21 regarding how events are handled, to be brief previously Cinema 4D consumes OS events, while now it only listens to them but Cinema 4D UI was and is always processed in the main Thread and was never thread-safe (that's why some operations are not possible in a threaded environment) because the c4d API ensure you are in the correct thread before doing anything GUI related stuff.

    Regarding your other issue about the console, as you may be aware R20 comes with a new Logger System.
    And now the console only reads content from their Logger System.
    The logger by itself is properly fed with data during a drag event (drag event = GUI interaction = main thread).
    But as you are now aware drawing is done also in the main thread so if you do a While XXX in the dragging process the main thread doesn't have room to jump to the console drawing part.
    Previously the logging (GePrint, Print) was also done in the main thread, so that means a redraw was done directly, but this slowdown a lot Cinema 4D UI.
    So in R21, the data are correctly added with the correct time in the logger but it's just not displayed because now print only adds data to the logger. And the logger is displayed when its draw request has time to be processed in the main thread.

    Not sure if it's clear, but just to recap, previously the console was refreshed in each print while now it requests redraw and redraw is done when the main thread got the time. (This is why previously printing 1000 value in the console was really slow and now pretty fast).

    EDIT 11/03/20:
    You could directly access the logger and passing the maxon.WRITEMETA.UI_SYNC_DRAW to force a redraw

    import maxon
    txt = "My Wonderfull Text"
    pythonLogger = maxon.Loggers.Python()
    pythonLogger.Write(maxon.TARGETAUDIENCE.ALL, txt, maxon.MAXON_SOURCE_LOCATION(1), maxon.WRITEMETA.UI_SYNC_DRAW)
    

    Cheers,
    Maxime.



  • Hi unti, thanks for reaching out us.

    With regard to your request, the solution to avoid Cinema 4D to deliver the expected "default" behaviors on RMB-click and MMB-click is to flush all events from the window message queue by using GeDialog.KillEvents().

    Best, R



  • Hey r_gigante,
    thank you for your reply, I think I am doing something wrong:

    I have this code in the Message function of my dialogwhere 10002 is the ID of a Group in my Layout:

    def Message(self, msg, result):
            # this will crash c4d with the commander window
            state = c4d.BaseContainer()
            self.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSERIGHT, state)
    
            if state.GetInt32(c4d.BFM_INPUT_VALUE) == True:
                x = state.GetInt32(c4d.BFM_INPUT_X)
                y = state.GetInt32(c4d.BFM_INPUT_Y)
                g2l  = self.Global2Local() 
                x += g2l['x']  
                y += g2l['y']
                print "Found rightclick in dialog"
                print "X: " +str(x)
                print "Y: " +str(y)
                print self.GetItemDim(10002)
                
                if self.GetPositionInGuiArea(10002, x, y):
    
                    IDM_MENU1 = c4d.FIRST_POPUP_ID
                    IDM_MENU2 = c4d.FIRST_POPUP_ID+1
                    IDM_MENU3 = c4d.FIRST_POPUP_ID+2
    
                    menu = c4d.BaseContainer()
                    menu.InsData(IDM_MENU1, 'Menu 1')
                    menu.InsData(IDM_MENU2, 'Menu 2')
                    menu.InsData(IDM_MENU3, 'Menu 3')
    
                    l2s = self.Local2Screen()
                    print str(x+l2s['x']) + " :: " + str(y+l2s['y'])
                    result = gui.ShowPopupDialog(cd=None, bc=menu, x=x+l2s['x'], y=y+l2s['y'])
    
                    self.KillEvents()
                    print result
        return gui.GeDialog.Message(self, msg, res)
    
    

    This code will still open the default popup menu - I tried with self as well as the basecall c4d.gui.GeDialog.KillEvents(). C4D will open my menu but it wil sometimes also open the default context menu after clicking something in the menu or canceling the rightclick with esc.
    Could you maybe provide an example?



  • Hi Unti, thanks for following up.

    The issue in the code you've reported is that rather than return the parent class' Message() result you need to simply return True.

    Best, R.



  • Hey,
    thanks, that worked great in R19.
    In R21, it will also work, but it will now crash Cinema4d when the area with the code above had focus when a user uses shift+c to launch the commander, insert a command and press enter.
    I learned that the GUI in R21 has been changed and is not thread-save anymore. Can I do anything to prevent the crash when using this code?



  • @unti said in Disable default Right-Click Menu:

    I learned that the GUI in R21 has been changed and is not thread-save anymore.

    Sorry, but where here did you "learn" that?



  • @s_bach
    I researched a "bug" with the new console not printing while dragging the mouse and got this answer.
    Is it wrong? Is there maybe another reason this crashes C4D that I can work around?



  • Hi @unti

    From my side, I was also able to reproduce the Default right-click Menu Behavior in R20.
    So the main issue regarding that is because you do GUI operation (ShowPopuDialog) into Message function which indeed not thread-safe according to the received message (this was also true in previous versions).

    So to counter that, please check your message first and I would say listen to input only when Cinema 4D "invite you" to do it (aka during BFM_INTERACTSTART). Then make sure to call StopAllThread (Maybe another thread is also checking for input and want to display something?).

    Then finally regarding your freeze beside the threading issue that could cause some corruption of the event queue. you call ShowPopupDialog with None, while the host GeDialog must be passed. It's needed to ensure the event queue is correctly forwarded from the dialog to the host of the modal popup dialog).

    This way I was not anymore capable to make Cinema 4D freeze (you said crash, but personally I was never able to make it crash, only freeze, can you confirm? A crash is when C4D close, a freeze is when C4D hangs forever).

    Here a complete example that works nicely on the previous version (R19/R20) and R21.

    import c4d
    
    class TestDialog(c4d.gui.GeDialog):
        def __init__(self):
            self._showPopup = False, 0, 0
    
        def CreateLayout(self):
            self.GroupBegin(10002, c4d.BFH_SCALEFIT| c4d.BFH_SCALEFIT,  initw=100, inith=100)
            self.GroupEnd()
            return True
    
        def IsPositionInGuiArea(self, x, y):
            # Check click is within the GeDialog
            if not 0 < x < self._x:
                return False
    
            if not 0 < y < self._y:
                return False
    
            return True
    
        def Message(self, msg, result):
            if msg.GetId() == c4d.BFM_ADJUSTSIZE:
              self._x = msg[3] # Retrieve Y size of the GeDialog
              self._y = msg[4] # Retrieve Y size of the GeDialog
    
            # We are on the main thread here
            elif msg.GetId() == c4d.BFM_INTERACTSTART:
                c4d.StopAllThreads()
    
                state = c4d.BaseContainer()
                self.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSERIGHT, state)
    
                if state.GetInt32(c4d.BFM_INPUT_VALUE) == True:
    
                    x = state.GetInt32(c4d.BFM_INPUT_X)
                    y = state.GetInt32(c4d.BFM_INPUT_Y)
                    g2l  = self.Global2Local() 
                    x += g2l['x']  
                    y += g2l['y']
                    
                    if self.IsPositionInGuiArea(x, y):
                        IDM_MENU1 = c4d.FIRST_POPUP_ID
                        IDM_MENU2 = c4d.FIRST_POPUP_ID + 1
                        IDM_MENU3 = c4d.FIRST_POPUP_ID + 2
    
                        menu = c4d.BaseContainer()
                        menu.InsData(IDM_MENU1, 'Menu 1')
                        menu.InsData(IDM_MENU2, 'Menu 2')
                        menu.InsData(IDM_MENU3, 'Menu 3')
    
                        l2s = self.Local2Screen()
                        print str(x+l2s['x']) + " :: " + str(y+l2s['y'])
                        self.KillEvents()
                        res = c4d.gui.ShowPopupDialog(cd=self, bc=menu, x=x+l2s['x'], y=y+l2s['y'])
                        return True
    
            return c4d.gui.GeDialog.Message(self, msg, result)
    
    if __name__ == '__main__':
        global dlg
        dlg = TestDialog()
        dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC)
    

    Finally, there was some change in the UI in R21 regarding how events are handled, to be brief previously Cinema 4D consumes OS events, while now it only listens to them but Cinema 4D UI was and is always processed in the main Thread and was never thread-safe (that's why some operations are not possible in a threaded environment) because the c4d API ensure you are in the correct thread before doing anything GUI related stuff.

    Regarding your other issue about the console, as you may be aware R20 comes with a new Logger System.
    And now the console only reads content from their Logger System.
    The logger by itself is properly fed with data during a drag event (drag event = GUI interaction = main thread).
    But as you are now aware drawing is done also in the main thread so if you do a While XXX in the dragging process the main thread doesn't have room to jump to the console drawing part.
    Previously the logging (GePrint, Print) was also done in the main thread, so that means a redraw was done directly, but this slowdown a lot Cinema 4D UI.
    So in R21, the data are correctly added with the correct time in the logger but it's just not displayed because now print only adds data to the logger. And the logger is displayed when its draw request has time to be processed in the main thread.

    Not sure if it's clear, but just to recap, previously the console was refreshed in each print while now it requests redraw and redraw is done when the main thread got the time. (This is why previously printing 1000 value in the console was really slow and now pretty fast).

    EDIT 11/03/20:
    You could directly access the logger and passing the maxon.WRITEMETA.UI_SYNC_DRAW to force a redraw

    import maxon
    txt = "My Wonderfull Text"
    pythonLogger = maxon.Loggers.Python()
    pythonLogger.Write(maxon.TARGETAUDIENCE.ALL, txt, maxon.MAXON_SOURCE_LOCATION(1), maxon.WRITEMETA.UI_SYNC_DRAW)
    

    Cheers,
    Maxime.



  • @m_adam
    Thanks!
    Thats a perfect explanation that solves many of my issues - even in some of my other Plugins.
    I am sorry, I did in fact refer to c4d freezing when I said crashing. Sorry for the confusion.