Right-mouse click on dialog tab
-
Hi folks,
is it possible to weave a way to catch a right-mouse click on a group of dialog tabs? C4D seems to have one when you click on tabs and other dialog-related areas. I'm referring to the menu that has "Undock" etc in it.
This menu doesn't appear in my dialog, so I'm hoping it means there's space for me to make one. I'd like to initiate a pop-up menu when the user right-clicks on one of the tabs.
WP.
-
Hello @wickedp,
Thank you for reaching out to us. While I do understand your question, an important detail remains ambiguous for me; what do you mean with 'a group of dialog tabs'?
- A tab group opened with
GeDialog::TabGroupBegin
or the equivalent markup, - or the tab header of a docked dialog?
For option one you will be able to retrieve command messages in
GeDialog::Command
when an action has occurred for the tab gadget, this should also include a right click on one of its tabs. If this fails, you could try to resort toBFM_GETCURSORINFO
, but I would first try the first option, as it seems likely that it will work.If you were talking about the second case, this is unfortunately not possible, at least I would say so. The only way to find out for sure, would be to listen to the message stream of your dialog if a message is being sent to one of the methods when the user right clicks on the dialog tab. But even if it were, you could not really open a menu, as there is already the context menu of Cinema 4D.
Cheers,
Ferdinand - A tab group opened with
-
Hi @ferdinand,
yep, option 1 was what I was looking at. I have a dynamic series of TabGroupBegin() tabs, and would like to open a context menu on a right-click on them.
I'd tried catching it in Command(), but it seems you can't test for mouse input there? Doing this inside the Command():
if(GetInputState(BFM_INPUT_MOUSE,BFM_INPUT_MOUSERIGHT,MB)) { if(MB.GetLong(BFM_INPUT_VALUE) == 1) { GePrint("Right-click menu found.."); } else { GePrint("NOT found."); } }
consistently returns "NOT found". I tried sending a Message() from inside Command() to the same dialog but that doesn't do it either. BFM_GETCURSORINFO will occasionally catch it, but you have to fiddle with the mouse to get the right catch.
Update edit: as an additional attempt, I tried GetItemDim() on the tab, but that returns the entire tab area, not the tab 'button' itself.
Seems like this is not really possible?
WP.
-
Hey @wickedp,
So, I spent some time on this but unfortunately there are also a few other things on my plate, so I did not have too much time to invest on it. Here are my findings for now:
- You are right,
::Command
is not the right way to go here, since you are effectively unable to distinguish there between right and left mouse button clicks, as the input event already has been consumed inside that method (you have to actually release the mouse button to trigger::Command
). - You then must go with the second approach I proposed, although poorly explained as I now see. You must instead move to
GeDialog::Message
and do the handling manually (that was what I meant withBFM_GETCURSORINFO
). - I tried that, only to find out that in the context of
BFM_GETCURSORINFO
you are unable to poll the mouse inside::Message
, when you poll for the right mouse button, it will always return0
, although the button is pressed. - But you are able to do it in the context of
BFM_INTERACTSTART
. - There is still quite a bit to be fleshed out here, the restriction part to the actual tab header thingy, for example, but this will at least open a popup menu when RMB is being pressed (without doing the evaluation all the time for that). I cannot tell you exactly how much of the remaining way is possible, I would have to poke a bit myself.
Find the example code in Python below (sorry, I know that you are on C++, but I really did not have much time today). Just tell me when you want this to be explored further, I will then invest some time next week.
Cheers,
FerdinandThe code:
"""Opens a popup menu over a GeDialog when the right mouse button is being pressed. This can be run in the Script Manger. """ import c4d class TabDialog (c4d.gui.GeDialog): """Provides a dialog with two tabs. """ ID_GRP_MAIN: int = 1000 ID_GRP_TABS: int = 2000 ID_GRP_TAB_FOO: int = 3000 ID_GRP_TAB_BAR: int = 3001 def CreateLayout(self) -> bool: """Adds the gadgets to the dialog. """ flags: int = c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT self.GroupBegin(TabDialog.ID_GRP_MAIN, flags, 1) self.TabGroupBegin(TabDialog.ID_GRP_TABS, flags, c4d.TAB_TABS) self.GroupBegin(TabDialog.ID_GRP_TAB_FOO, flags, 1, 0, "Foo") self.GroupEnd() # ID_GRP_TAB_FOO self.GroupBegin(TabDialog.ID_GRP_TAB_BAR, flags, 1, 0, "Bar") self.GroupEnd() # ID_GRP_TAB_BAR self.GroupEnd() # ID_GRP_TABS self.GroupEnd() # ID_GRP_MAIN return super().CreateLayout() def Message(self, msg: c4d.BaseContainer, result: c4d.BaseContainer) -> int: """Opens the popup menu. """ # Bail when this not the start of a GUI interaction. if msg.GetId() != c4d.BFM_INTERACTSTART: return super().Message(msg, result) # Poll for the right mouse button being pressed. state: c4d.BaseContainer = c4d.BaseContainer() if not c4d.gui.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSERIGHT, state): raise RuntimeError("Could not poll input state.") # Bail when RMB is not being pressed. if state[c4d.BFM_INPUT_VALUE] != 1: return super().Message(msg, result) # Open a popup menu at the cursor. x: int = int(state[c4d.BFM_INPUT_X]) y: int = int(state[c4d.BFM_INPUT_Y]) bc: c4d.BaseContainer = c4d.BaseContainer() bc.InsData(c4d.FIRST_POPUP_ID, f"Cube&i{c4d.Ocube}&") bc.InsData(c4d.FIRST_POPUP_ID + 1, f"Sphere&i{c4d.Osphere}&") res = c4d.gui.ShowPopupDialog(self, bc, x, y) return super().Message(msg, result) if __name__ == '__main__': dialog: TabDialog = TabDialog() dialog.Open(c4d.DLG_TYPE_MODAL_RESIZEABLE, defaultw=500, defaulth=500)
- You are right,
-
Thanks for your efforts @ferdinand, and no probs with the python code, I can interpret (I see a lot of that here now!).
I've tried all sorts of things (including some things from @ferdinand python code) but just can't seem to get a clean example going. So what I've done in the meantime, is to create a bunch of BitmapButtonCustomGui buttons and replace the TabGroupsBegin() with a normal GroupBegin() and just hide/unhide elements myself. It's not really ideal, as I've had to add a bunch of extra code to make this work, but it does kind of work the way I'm after. A right-click on the project 1 button brings up the project 1 settings menu:
@ferdinand not sure what you want to do with this topic. I'm happy for you to close it if it's easier. But if there is a solution that anyone does have using the standard TabGroupBegin() layout, I'd be really keen on seeing it.
WP.
-
Hey @wickedp,
well, it depends on how you view the subject. If it is a case of "it would be interesting to see", this would reduce the priority of it for me. The general idea would be to use groups to mask out gadgets, so that you can evaluate where the mouse cursor is hovering over. For the tabs itself, you probably will have to get hacky and divide the width of the tab container by the number of tabs in it or something like this.
Regarding your solution, I thought this form of tabs gadget was a requirement. If it is not, I would recommend having a look at the QuickTabCustomGui. It is basically what you are trying to do with the
BitmapButtonCustomGui
. It will give you a row of buttons (of which only one can be active) and you must do the actual showing and hiding of the tab body yourself. It is what the Attribute Manger uses for example for its tabs. So, for clarity:QuickTabCustomGui
and aTabGroup
are not the same and both can be added to a dialog. UsingQuickTabCustomGui
, you can probably get rid of at least some of the boilerplate code of yourBitmapButtonCustomGui
solution.PS: Unless you say differently, I will set your own answer as the solution for this thread.
Cheers,
Ferdinand -
Hi @ferdinand,
I was wondering about dividing by the count as well. But not sure if that would work for tabs where there are different widths?
I'll have a think about the gui design and layout. It probably doesn't matter how it looks, so long as it works. Thanks for the QuickTabCustomGui tip as well, I had completely forgotten they existed!
I can probably live with the DIY bitmapbutton approach. It's still a tab system I guess, just not out of the box. But I can get a custom context menu for each 'tab' this way, so it kind of provides me with a solution, albeit with a bit more coding effort on my part.
You can set answer and mark as solved. If I have any follow-up questions I'll pop back in.
WP.
-
Hey,
You can set answer and mark as solved. If I have any follow-up questions I'll pop back in.
Thanks for the reply!
I was wondering about dividing by the count as well. But not sure if that would work for tabs where there are different widths?
Right, I admittedly did not think of that, but what you could do is measure the width of each tab string with
GeClipMap::
orGeUserArea::GetTextWidth
and then normalize these values. After that you can apply that to the tab widths you want to calculate. As pseudo-code:GetItemDim(ID_MY_TABGADGET, ..., gadgetWidth) averageTabWidth = gadgetWidth / tabCount titleWidths = (clipmap.GetTextWidth(s) for s in tabTitles) maxTitleWidth = max(titleWidths) normalizedTitleWidths = (t / maxTitleWidth for t in titleWidths) absoluteTabWidths = (averageTabWidth * ntw for ntw in normalizedTitleWidths)
This would not be pixel-perfect and way hackier than at least I would be comfortable with. But if you are stubborn enough, you can make everything work
Cheers,
Ferdinand