Create a context menu by R-click in 'free space'?

Some years ago I developed a tool which (more or less) worked in SU 2017 and 2018.

A R-click anywhere the drawing window (even in an empty part of it) would pop up a ‘context’ menu looking like this, from which to choose a softwood size before drawing a length of:
image

The content was defined in a method def getMenu(menu)....end using information from arrays defined earlier in the code.

It was called by a simple onRButtonDown method within the Tool code.

  def onRButtonDown flags, x, y, view
    ## Load plugin-specific R-click context menu
		getMenu()
	end

I’m working again with Steve Baumgartner (@slbaumgartner) to rework the code, and we are trying to replicate this behaviour in SU 2019.

Neither of us can work out why this worked at all - the getmenu() call has no menu parameter which the getMenu(menu) method requires, and in SU 2019 it immediately throws a mismatched number of arguments: 1 required, 0 supplied error (or words to that effect).

Somehow, SU2018 fills in a valid Menu object ID and the menu works fine. SU 2019 doesn’t, and the code errors.

Here’s a simplified version of the getMenu(menu) method, with fixed text replacing the array values, and just with a fixed item in the menu shown as MF_CHECKED.

 def getMenu(menu)
   puts "getMenu called #{menu.inspect}"
  menu.add_item("UK PAR/PSE") {} ## Displays type of timber (only PAR implemented so far)
	menu.add_item("Softwood size (nominal)") {}
	menu.add_separator
    item1 = menu.add_item("1\" x 1\"") {"do action 1"}
      menu.set_validation_proc(item1) { MF_UNCHECKED}
    item2 = menu.add_item("2\" x 1\"") {"do action 2"}
      menu.set_validation_proc(item2) { MF_CHECKED}
    item3 = menu.add_item("3\" x 1\"") {"do action 3"}
      menu.set_validation_proc(item3) {MF_UNCHECKED}
  menu.add_separator
  menu.add_item("Custom size (actual)") {}
  menu.add_separator
	item4 = menu.add_item("3/4\" x 1/2\"") {"do action 4"}
  menu.set_validation_proc(item4) {MF_UNCHECKED}
 end # def getMenu

Here’s what it pops up, when I replace the method in the tool with this simplified version.
image

QUESTIONS

  1. Why did it work at all, and where did SU 2018 get the menu object ID which it passes to the getMenu(menu) method? (We entered some test code to show that the receiving function was given a Menu object and ID.)

  2. Even if we don’t find out how it worked in SU 2018, how can we provide a menu object ID to the onRButtonDown (or …Up) event so the menu will work in SU 2019? Just use something like
    size_menu = UI.menu("")?
    At least that doesn’t error when I try it in the Ruby console, and returns:
    #<Sketchup::Menu:0x007f85491a2310>

  3. And anyway, how does a context menu get triggered when you click on empty space in the model window?

AFAIK, context menus usually need an object to click on, to provide a suitable context.

Look at my 2dTools.
The 2dHatching tool allows you to change the hatch pattern ‘on the fly’ by right-clicking in empty-space.
Opening a dialog to choose the texture for the hatching…

It doesn’t open the context-menu…
But it should be possible…

Because it was NOT your tool’s onRButtonDown callback that was calling getMenu(), … it was the SketchUp engine calling getMenu() and providing the object reference to an “on-the-fly” generated context menu object.

Since the API does not expose a menu object constructor, there is no way your tool code can safely and explicitly call the getMenu() callback (which is meant to be only called by the SketchUp engine.)

REF:

So having read the above thread and realizing that some mouse button callbacks never trigger on one or both platforms … you should understand that your onRButtonDown callback was likely not firing before v2019 and correctly it was the SketchUp engine that was calling getMenu().

Advise you re-read the callback method description for getMenu().

This should not work as it is the SketchUp engine that creates a context menu object as needed and passes it’s reference to the active tool’s getMenu callback IF such callback method exists. IF not, the engine shows the appropriate native context menu.

As said above, you don’t use any right mouse button callback to display the context menu, SketchUp always by default will seek to display the menu, and polls your tool object if see if it responds to getMenu() … then follows the 2 branch decision as explained above (custom menu if the method exists, native menu if not.)

Again, SketchUp reacts to the right mouse button click and follows code decisions based upon whether a native tool or a custom Ruby tool is active. Ruby tool as discussed above.

There is a native “sparse” context menu for nothing selected. Items can be added to this as well … in all situations, only when nothing is selected or only when something(s) are selected. (This is using the context menu handler interface independent of Tools.)

Just looked through the past 3 major version API release notes and see nothing mentioned regarding fixes to getMenu() … nor onRButtonDown … so I think that the difference (you see) here is likely the result of a combination of OSX version and Ruby version (2.5.x vs 2.2.x.)

@DanRathbun you put me to shame (but thank you)! I should know that getMenu is a Tool protocol callback, not something that extension code should call on its own! Somehow, even looking at the API docs I missed that because I was focused on onRButtonDown, which prior to 2019 often wouldn’t even fire on Mac, pre-empted by the context menu system.

Mystery solved.

1 Like

@thomthom Was there an explicit fix that has not been mentioned in the release notes ?

No, I have no idea how calling getMenu from another event would ever work. Certainly not a designed behaviour, nor part of the API contract.

Only related change I can think of was that in SU2015 there was an optional signature added to getMenu allowing the event to also receive mouse position and flags:

Seems to me that’s the variant you want to be using: def getMenu(menu, flags, x, y, view)

1 Like

Curioser and curioser.

A version of it still works, now, in 2019.

def getMenu(menu)
    DrawFramingTool.profiles.getMenu(menu)
end

No longer quite so mysterious, perhaps, when called inside a Tool class

It also worked, before @slbaumgartner tidied it up, as

def onRButtonDown
  getMenu(UI.menu("Size menu"))
end

where the “Size menu” is an arbitrary string, not connected to anything else.

The getMenu callback method defined a context menu which popped up on R-click (down or up worked similarly).

Doesn’t seem to need flags, x, y, view and I certainly wouldn’t know what to put there, or how to use them if I did.

NO it did not work. (Reasons as explained above.)

Proof …

UI.menu("Size menu").inspect
Error: #<ArgumentError: Unknown menu size menu>
<main>:in `menu'
<main>:in `<main>'
SketchUp:1:in `eval'

This proves as I explained above, that your onRButtonDown callback methods were not getting called because you were not seeing these silly errors.

It is the SketchUp engine that is designed to and was directly calling the getMenu callback. (This is the basic premise of a “callback method”. Ie that a coder does not explicitly call these methods, they are called by the system being event driven.)

It MUST be called by the system from within an abstract Tool object which is the active tool. Otherwise it does not get called, and calling it directly will have no effect (ie, the system would not be in the context menu building subroutine.)

The example above (if within a tool object) just simply passes the system generated menu object out to a class method that appears to do the job of building the context menu items.

More like dazed and confused :wink:

Again, a tool or any other code does not call the getMenu callback method. You do not explicitly call this method from any other method especially mouseclick callback methods.

The SketchUp engine calls it when the context mouse button is clicked if the getMenu method is defined.

If a coder would want something other than a context menu to happen upon right mouse click, then they’d need to (1) define an empty getMenu method so that no context menu appears. (Remember that if you do not define the getMenu method, then the SketchUp engine will display the native context menu.)
Then (2) they’d define a onRButtonUp callback method that does something else. (Use the 'up" event because historically the onRButtonDown didn’t fire on some platforms or OSes.)

If a coder would want to always do some task before displaying a custom context menu, then they’d call a custom task method at the beginning of the getMenu callback. (This task would get done even if no menu item was chosen and the user ESCaped from the menu.)

To do tasks only after menu items were chosen, then task method calls would need to be inserted into the menu item command blocks wherever desired.

I do not know if the SketchUp engine would both display the native context menu (in the absence of a getMenu method,) and also process a right mouse click callback. You’d need to test.
A question might be, which happens first the context menu display or the right mouse click callback ?

I’m afraid I am, but thank you for your explanations, Dan.

I will re-read several times, and try to understand then what is happening in my code. Because it IS raising a menu on R-click, specified in the Tool’s getMenu method, and triggered by a R-click (onRButtonDown at the moment, though I do understand that onRButtonUp might well be better).

John,

have a play with this… tooltests.rb (3.9 KB)

use load "<path to file>" or just copy/paste in RC…

john

I think, @DanRathbun, that I’ve finally ‘got it’ from this sentence. If I have, that would imply that we need neither onRButtonDown nor onRButtonUp methods in the tool to show the menu defined in def getMenu(menu) on a right button click.

And that when either of those methods IS defined, and getMenu(menu) is also defined, the onRButton... methods in the Tool are ignored because the Sketchup engine ‘gets there first’ and activates getMenu(menu)?

Is that right? If so, then I have ‘got it’ at last. Sorry to have been obtuse earlier.

:notes: :fireworks: :sparkler: :sparkles: :tada: :confetti_ball: :partying_face::boom: :clap: :family_man_woman_girl_boy: (the crowd cheers)

The getMenu method documentation clearly states …

The #getMenu method is called by SketchUp

… so it is not implied, … the fact is explicit in the text.

Also, please realize that ALL the documented instance methods of the abstract Tool class, are (every one of them) event driven callback methods that are meant to be called by the SketchUp core.

(This is why when I write tools, I put them all in a curly bracketed collapsible group labeled as “callbacks” and have my other internal methods in a separate group.)

Well I think that your main confusion was caused by the bug that onRButtomDown does not (or did not) fire on Mac. (I know there are also some key down callbacks that never fire on MS Windows.)

You’d have to test if onRButtonUp also fires, and whether it fires before or after the context menu displays. (Also keep in mind that the context menu code appears to act in an asynchronous manner. In actuality, the #getMenu method returns and then the context menu is displayed by the SketchUp core.)

The main reason that right mouse button callbacks are there is for coders to use them for other than a context menu. (Ie, it’d be somewhat weird to have code that uses both.)

EDIT: I just tested this, and in SU2018 using core context menus (ie, undefined or empty #getMenu method,) the onRButtonUp only fires if clicked on empty space. Otherwise, if clicked on an entity, or the axis the appropriate core context menu is displayed and no right mouse button callback is called.
The only way I can see getting both to fire would be for a custom context menu (on SU2019 or better,) and the in #getMenu method you pass along the flags, x, y, view parameters and directly call the onRButtonUp callback. But this is kinda silly because at that point it doesn’t matter what the name of the method is that processes the flags, x, y, view parameters, so the processing method should likely (for coding style sake) be a custom internal method of another name, rather than one of the core callback method names. (This should be done because months or years later you or someone else is likely not to remember why things where written the way they were, and you end up in this confused state, thinking that tool callbacks operate in a way other than how they were designed.)

Your tool will use a standard context menu by default if you do not implement this method. Implement this method if you want a context-click to display something other than this default context menu.

I just tested an empty #getMenu callback that had nothing but nil in it, and the SketchUp core treats it as if the method is undefined, meaning it defaults to using the core context menu system (which reacts to what is under the cursor,… axis, instance, nothing, etc.)

Thank you again Dan for your patience! I’m not a good or knowledgeable ruby coder, so I learn mainly by example. And when I find examples that seem to do what I want, more or less, I don’t always understand why or even if they should work!

EDIT: I just tested this, and in SU2018 using core context menus (ie, undefined or empty #getMenu method,) the onRButtonUp only fires if clicked on empty space. Otherwise, if clicked on an entity, or the axis the appropriate core context menu is displayed and no right mouse button callback is called.

The only way I can see getting both to fire would be for a custom context menu (on SU2019 or better,) and the in #getMenu method you pass along the flags, x, y, view parameters and directly call the onRButtonUp callback. But this is kinda silly because at that point it doesn’t matter what the name of the method is that processes the flags, x, y, view parameters, so the processing method should likely (for coding style sake) be a custom internal method of another name, rather than one of the core callback method names. (This should be done because months or years later you or someone else is likely not to remember why things where written the way they were, and you end up in this confused state, thinking that tool callbacks operate in a way other than how they were designed.)