Solved Get the Button GadgetID Directly Under the Mouse?

Hi,

Is there a command to get the Button Gadget ID directly under the mouse?
I understand that you can get the GadgetID by clicking the button but I'm using the Message() rather than Command().

In overview, I have several buttons and when you RMB them, they have there own dedicated pop-up commands.
It works when I have them typed separately but I'm having a problem with making them dynamic through a class.

You can see the illustration here:
https://www.dropbox.com/s/k5d38cctk4hgtz7/c4d206_get_gadgetid_under_mouse.jpg?dl=0

You can check the WIP code here:

import c4d

class ColorButton(object):

    def __init__(self):
        self.width = None
        self.height = None
        self.color = None
        self.color = None
        self.btn_id = None
        self.menu_list = None


    def create(self, dlg, w, h, color, btn_id):

        self.width = w
        self.height = h
        self.color = color
        self.btn_id = btn_id

        bmp_color = c4d.bitmaps.BaseBitmap()
        bmp_color.Init(w, h)

        for y in xrange(w):
            for x in xrange(h):
                bmp_color.SetPixel(x, y, color[0], color[1], color[2])

        bcBitmapButton = c4d.BaseContainer()
        bcBitmapButton[c4d.BITMAPBUTTON_BUTTON] = True

        bmp_btn = dlg.AddCustomGui(self.btn_id, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_CENTER | c4d.BFV_CENTER, w, h, bcBitmapButton)

        bmp_btn.SetImage(bmp_color, True)

    def menu(self):

        self.menu = c4d.BaseContainer()

        for menu_item in self.menu_list: 
            counter = 0
            IDM_MENU = c4d.FIRST_POPUP_ID + counter
            self.menu.InsData(IDM_MENU, menu_item)

            counter += 1

class MyDialog(c4d.gui.GeDialog):

    def __init__(self):
        self.btn_id_list = []
        self.class_btn_id_dict = {}

    def CreateLayout(self):

        red_button = ColorButton()
        red_button.create(self, w=50,h=50,color=(255,0,0), btn_id=6000)
        red_button.menu_list = ['Menu1', 'Menu2', 'Menu3']
        self.btn_id_list.append(red_button.btn_id)
        self.class_btn_id_dict[6000] = red_button

        blue_button = ColorButton()
        blue_button.create(self, w=50,h=50,color=(0,0,255), btn_id=7000)
        blue_button.menu_list = ['Menu4', 'Menu5', 'Menu6', 'Menu7']
        self.btn_id_list.append(blue_button.btn_id)
        self.class_btn_id_dict[7000] = blue_button


        green_button = ColorButton()
        green_button.create(self, w=50,h=50,color=(0,0,255), btn_id=7000)
        green_button.menu_list = ['Menu8', 'Menu9']
        self.btn_id_list.append(blue_button.btn_id)
        self.class_btn_id_dict[8000] = green_button

        return True

    def IsPositionOnGadget(self, gadgetId, x, y):
        # Be sure that the windows is opened,
        # in our case since we call it in BFM_INTERACTSTART it's ok
        buttonData = self.GetItemDim(gadgetId)
        
        if not buttonData["x"] < x < buttonData["x"] + buttonData["w"]:
            return False

        if not buttonData["y"] < y < buttonData["y"] + buttonData["h"]:
            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']

                gadgetId = function_to_determine_gadgetId_under_mouse_cursor()

                if gadgetId in self.btn_id_list:

                     if self.IsPositionOnGadget(gadgetId=gadgetId, x=x, y=y):

                          button_class = self.class_btn_id_dict[gadgetId] 
                          button_class.menu()


                          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)

        return True
if __name__ == "__main__":
    dlg = MyDialog()
    dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=20304050)

Hi,

I think you you mean the gadget under the mouse, do you?

Oh sorry for the confusion. Yes, I meant under the mouse. I was thinking of hover.

Hi @bentraje there is no builtin way for doing it, you will have to tests each Gadget to determine which one match with the function posted in Right Click Contextual Menu on a Button?.

Final note you can retrieve the current mouse position in the message function with

        if msg.GetId() == c4d.BFM_GETCURSORINFO:
            x = msg.GetInt32(c4d.BFM_DRAG_SCREENX)
            y = msg.GetInt32(c4d.BFM_DRAG_SCREENY)

Cheers,
Maxime.

@m_adam

Thanks for the response. Apologies for the late response. I was trying to figure out this one: you will have to tests each Gadget

I tried a for loop but the Message function does not seem to work with it.

Is there a way around this?

You can check the illustration here:
https://www.dropbox.com/s/inizedq3rt3660f/c4d212_dynamic_rmb_context_menu.mp4?dl=0

You can check the sample file here:
https://www.dropbox.com/s/hvh759wyjfiw7o7/c4d212_dynamic_rmb_context_menu.rar?dl=0

Its seems that GetItemDim is not properly supported by baseCustomGui ( I've to check if its a Python issue or not)
And BaseCustomGui.GetWidth seems to also not work correctly (same things I have to check if it's a Python issue).

So, unfortunately, I guess in your case, the best way is to either replace the CustomGui by a GeUserArea or store the gadgetId with its coordinate + size information during the creation.

Cheers,
Maxime.

@m_adam

Thanks for the response. I'm trying the later solution since I'm not that familiar with the GeUserArea

RE: store the gadgetId with its coordinate + size information during the creation.
By this, I assume you mean moving the GeDialog.GetItemDim(id) to the CreateLayout function.

If so, I did with this file but it has the same result as above. The dialog does not pop up and also no error.
https://www.dropbox.com/s/slyc9irbyfjj6u7/c4d212_dynamic_rmb_context_menu02.rar?dl=0

@bentraje said in Get the Button GadgetID Directly Under the Mouse?:

RE: store the gadgetId with its coordinate + size information during the creation.
By this, I assume you mean moving the GeDialog.GetItemDim(id) to the CreateLayout function.

@m_adam said in Get the Button GadgetID Directly Under the Mouse?:

Its seems that GetItemDim is not properly supported by baseCustomGui ( I've to check if its a Python issue or not)

No, but since you input a size/height to draw the bitmapbutton and you store theses information in self.width and self.height you can already do the checking yourself.

@m_adam

I'm sorry but I'm having a hard time following.
Isn't the self.width and self.height a bit irrelevant since it doesn't contain the coordinates of the button relative to the whole dialog? Or are you referring to a different thing.

RE:you can already do the checking yourself.
I believe this satisfies the checking
if self.IsPositionOnGadget(gadgetId=button_id, buttonData = self.button_data_list[idx], buttonx=x, y=y):

It works on a standalone but it doesn't with a for loop

First of all sorry for the confusion I forget GetItemDim should be called within BFM_INTERACTSTART so that's why I didn't success previously to get it working.

Here is the method function_to_determine_gadgetId_under_mouse_cursor.

    def function_to_determine_gadgetId_under_mouse_cursor(self, x, y):
        for gadgetId in self.btn_id_list:
            if self.IsPositionOnGadget(gadgetId, x, y):
                return gadgetId

Now there are several issues in your code, and while I understand you may be a beginner remember we are not here to debug your code or even develop for you so please next time try to double-check your code.

@bentraje said in Get the Button GadgetID Directly Under the Mouse?:

  def menu(self):

      self.menu = c4d.BaseContainer()

      for menu_item in self.menu_list:
          counter = 0
          IDM_MENU = c4d.FIRST_POPUP_ID + counter
          self.menu.InsData(IDM_MENU, menu_item)

          counter += 1

Call this method another name than a member variable name, otherwise, Python has no way to know which one is the good one (or I would say, as soon as you do self.menu = Something you override the value).
Keep in mind in Python everything is an object (variable, function everything) and everything can be overridden (almost). So please rename this function or your variable.

@bentraje said in Get the Button GadgetID Directly Under the Mouse?:

    def CreateLayout(self):
        green_button = ColorButton()
        green_button.create(self, w=50,h=50,color=(0,0,255), btn_id=7000)
        green_button.menu_list = ['Menu8', 'Menu9']
        self.btn_id_list.append(blue_button.btn_id)
        self.class_btn_id_dict[8000] = green_button

You passed wrong instance and an already used Id.

green_button = ColorButton()
green_button.create(self, w=50, h=50, color=(0, 0, 255), btn_id=7000)
green_button.menu_list = ['Menu8', 'Menu9']
self.btn_id_list.append(green_button.btn_id)
self.class_btn_id_dict[8000] = green_button

@bentraje said in Get the Button GadgetID Directly Under the Mouse?:

        res = c4d.gui.ShowPopupDialog(cd=self, bc=menu, x=x+l2s['x'], y=y+l2s['y'])

This should be bc=button_class.menu.

Cheers,
Maxime.

Hi @m_adam

Thanks for the patience. Your suggestions and reminders works are greatly appreciated.

I guess the confusion stems mainly on my part because I posted slightly two different codes. You were responding to my initial post but I was thinking with the code from the succeeding post(the one in the rar file). Totally my bad.

Anyhow, here is the working code (using the initial post) which works as I expected:

import c4d

class ColorButton(object):

    def __init__(self):
        self.width = None
        self.height = None
        self.color = None
        self.color = None
        self.btn_id = None
        self.menu_list = None


    def create(self, dlg, w, h, color, btn_id):

        self.width = w
        self.height = h
        self.color = color
        self.btn_id = btn_id

        bmp_color = c4d.bitmaps.BaseBitmap()
        bmp_color.Init(w, h)

        for y in xrange(w):
            for x in xrange(h):
                bmp_color.SetPixel(x, y, color[0], color[1], color[2])

        bcBitmapButton = c4d.BaseContainer()
        bcBitmapButton[c4d.BITMAPBUTTON_BUTTON] = True

        bmp_btn = dlg.AddCustomGui(self.btn_id, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_CENTER | c4d.BFV_CENTER, w, h, bcBitmapButton)

        bmp_btn.SetImage(bmp_color, True)

    def create_menu(self):

        self.menu = c4d.BaseContainer()

        for menu_item in self.menu_list: 
            counter = 0
            IDM_MENU = c4d.FIRST_POPUP_ID + counter
            self.menu.InsData(IDM_MENU, menu_item)

            counter += 1

class MyDialog(c4d.gui.GeDialog):

    def __init__(self):
        self.btn_id_list = []
        self.class_btn_id_dict = {}

    def CreateLayout(self):

        red_button = ColorButton()
        red_button.create(self, w=50,h=50,color=(255,0,0), btn_id=6000)
        red_button.menu_list = ['Menu1', 'Menu2', 'Menu3']
        self.btn_id_list.append(red_button.btn_id)
        self.class_btn_id_dict[6000] = red_button

        blue_button = ColorButton()
        blue_button.create(self, w=50,h=50,color=(0,0,255), btn_id=7000)
        blue_button.menu_list = ['Menu4', 'Menu5', 'Menu6', 'Menu7']
        self.btn_id_list.append(blue_button.btn_id)
        self.class_btn_id_dict[7000] = blue_button


        green_button = ColorButton()
        green_button.create(self, w=50,h=50,color=(0,255,0), btn_id=8000)
        green_button.menu_list = ['Menu8', 'Menu9']
        self.btn_id_list.append(green_button.btn_id)
        self.class_btn_id_dict[8000] = green_button

        return True

    def IsPositionOnGadget(self, gadgetId, x, y):
        # Be sure that the windows is opened,
        # in our case since we call it in BFM_INTERACTSTART it's ok
        buttonData = self.GetItemDim(gadgetId)
        
        if not buttonData["x"] < x < buttonData["x"] + buttonData["w"]:
            return False

        if not buttonData["y"] < y < buttonData["y"] + buttonData["h"]:
            return False

        return True
    
    def function_to_determine_gadgetId_under_mouse_cursor(self, x, y):
        for gadgetId in self.btn_id_list:
            if self.IsPositionOnGadget(gadgetId, x, y):
                return gadgetId
            
    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']

                gadgetId = self.function_to_determine_gadgetId_under_mouse_cursor(x=x,y=y)

                if gadgetId in self.btn_id_list:

                     if self.IsPositionOnGadget(gadgetId=gadgetId, x=x, y=y):

                          button_class = self.class_btn_id_dict[gadgetId] 
                          button_class.create_menu()


                          l2s = self.Local2Screen()
                          print str(x+l2s['x']) + " :: " + str(y+l2s['y'])
                          self.KillEvents()
                          res = c4d.gui.ShowPopupDialog(cd=self, bc=button_class.menu, x=x+l2s['x'], y=y+l2s['y'])
                          return True

        return c4d.gui.GeDialog.Message(self, msg, result)

if __name__ == "__main__":
    dlg = MyDialog()
    dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=20304050)