Change a menu item


#1

Is there a way to change a menu item. A menu item performs a function. Once that function is performed it would not be used again on the same model. Rather than add another menu item to perform a different function I would like to reuse that menu slot. I want to relabel it and assign a different function to that menu item. Is this possible?

I know I could make the initial function branch to do this or that depending on the model state but was hoping I could swap labels and functions.


#2

From recent experience I believe that once an item is added to a menu there is relatively little you can change about it (you can enable/disable, e.g.). In particular you can’t change the text or assign a different command, though your command can vary what it does.


#3

An added menu item is not undo-able.
Although it could be seen BUT ‘disabled’, given certain circumstances.

However, you can add a context-menu item which is set only to be displayed when certain factors prevail [or don’t prevail !].
So you could have a context-menu item which is only available if you have not yet done something [i.e. a reference is ‘false’], then when you do that something it resets the reference to be ‘true’ - which then means that the context-menu item is no longer displayed…


#4

I’ve opted for something like this.

mnuItem = mnuMenu.add_item('My Menu Item') {
  if $condition then
    do
      something
    end
  else
    do
      something else
    end
  end
}

#5

I’d suggest that you don’t use a global variable $condition, but otherwise that’s the idea for a fixed (non-context) menu. What you can’t change is the name ‘My Menu Item’.

Note that when your Tool is active, SketchUp invokes the #getMenu callback when the user right-clicks. Because the context menu is always dynamic, your code can choose what item(s) you want to add to the context menu based on conditions, and they can be different each time.


#6

But of course, assuming it’s recast as @condition or @@condition, then once that is set and the menu is loaded, then that menu item becomes non-hide-able - although you can still ‘disable’ [gray] it…
With a context-menu you can elect to show/hide it and/or enable/disable it…


#7

Which will be rejected by the Extension Warehouse (even though they still have many example and extensions that are creating globals.)

Basically there is no reason for a extension to have any code running outside their own namespace modules. Therefore module and class variables are all that is needed.

Also, the doend blocks as well as the then are frivolous in Ruby.


Back to the basic of coding this scenario.

mnuItem = mnuMenu.add_item('My Menu Item') {
  if @@condition then
    do
      something
    end
  else
    do
      something else
    end
  end
}

This has a drawback in that you cannot dynamically change the block of code. It can only be assigned to the UI menu item once.

I usually do this instead:

def something()
  # code to do something
end

def something_else()
  # code to do something else
end

def decide_what_to_do()
  @@condition ? something() : something_else()
end

mnuItem = mnuMenu.add_item('My Menu Item') {
  decide_what_to_do()
}

Now anyone of those methods can be redefined at any time since Ruby is a dynamic language, either via meta-programming or by simply loading a new rb file that overrides the method(s).


#8

Dan,

Thank you for your recommendations.


#9

Dan,

Your suggestion to use @@condition implies this is done within a class. But how do you retain this class from menu? The code below always creates a new instance unless myTest is global $myTest.

begin
  mnuMenu = UI.menu('Extensions')
  mnuItem = mnuMenu.add_item('Test') {
    unless defined? myTest
      myTest = MyTest.new
      puts myTest
    else
      puts "already there"
    end
  }
rescue => except
  puts except.backtrace
end

#10

Not really. As in Ruby, class Class is a direct child subclass of class Module (and therefore inherits all of class Module’s functionality, and then gets it’s own instancing function,) … they both can use @@ variables.

So, these @@ variables are called both class variables, and module variables, depending upon which they are defined within.

The rule of whether you should be using a class or a module, is if you need multiple copies (instances) of the code, then a class and instancing it is in order. If you only need one instance of the code, the a module will do.
Ie, a module is an instance of class Module. You may not realize that using the module SomeNameend syntax is translated by the interpreter into SomeName = Module::new { block of code }

Regardless of this (above) ALL of your extensions need to be wrapped within your OWN unique toplevel namespace module. Then each of your various extensions / plugins should be wrapped within their OWN sub-namespace module (so that each of them does not clash with your other extensions.)

Not really because that is just a snippet pulled out of it’s namespacing.

You need to learn the basics (101) of Ruby. (Which is not taught by the API, nor very well by the SketchUp examples.)

So as said so many places and times here and at SketchUcation, your code should be following this basic format:

module TheWiz
  module SomePlugin

    # Constants:
    MENUTEXT ||= "Test"

    # Classes used only by this plugin here:
    class MyTestDialog
      def initialize(*args)
        # code that inits the new instance
        # (This method is called by ::new
        #   just after it creates the new instance.)
      end
      #
      # ... other method definitions ...
      #
     end

    # Module Variables:
    @@loaded ||= false
    @@myTest ||= nil

    # Make methods available to each other,
    # by extending this module with itself.
    extend self

    # Methods:
    def run_test
      @@myTest = MyTestDialog.new
      puts @@myTest
    rescue => except
      puts except.inspect
      puts except.backtrace
    end

    # Run Once block:
    if not @@loaded
      mnuMenu = UI.menu('Extensions')
      mnuItem = mnuMenu.add_item(MENUTEXT) { run_test() }
      @@loaded = true
    end

  end
end

As I show in the above example, everything is defined WITHIN the TheWiz::SomePlugin module namespace, so that when SketchUp’s Sketchup::Menu.add_item() API method
receives the block instance, all the scope and current state is wrapped up in the block (by Ruby) and passed as an argument into the method. So the item, on the C++ side, has a fully qualified path to run_test() like TheWiz::SomePlugin::run_test().

To understand this, you need to read about the creation of Ruby blocks and Procs.


You are not following my basic oft given advice,… learn Ruby, read the ol’ Pick-Axe book (old version are free online, I’ve posted them myself.) The longer you ignore this advice,… the longer you are going to be frustrated by this simple basic Ruby stuff.

So, I point you (and any other Ruby learner) to my exhaustive Ruby Learning Resources wikilist, which is pinned here at the top of this forum.

It is full of links to free resources and has links to template examples I myself have posted.


I’ve just spent another hour or so, reexplaining these things that I and others have explained just too many times already. I’m sorry I cannot keep doing it.


Ruby program hanging SU on test - is there a log file?
#11

Dan,

If my question seems repetitive, I apologize. In the context of what I am trying to achieve with Sketchup the previous explanations just didn’t seem to work. I am trying to figure out how to make one event interact with other events. My difficulty is in trying to get Sketchup to remember what was done in the previous events. All of the examples and tools I’ve seen so far are one time events.

Your expose on Ruby Modules as more than just a namespace is very informative and I will try and see if that can accomplish my objective. Your experience with Ruby and Sketchup is vastly superior to mine and I hope you will continue to view my posts. Thanks you for all your support.


#12

The idea is that the first event’s callback sets a value in something that will persist until the next event’s callback. Certainly a global variable will do that, but as Dan has written, globals have no protection against colliding with ones from other code. You should instead use something that is restricted to one particular “namespace” or object that is unique to your code.

What you may be missing is that in Ruby Classes and Modules are inherently singletons. Class and Module names are constants within a namespace. That is, within that namespace all references to a Module named “MyModule” refer to the same object. And once created that Module or Class object continues to exist for the duration of the run. That’s why two separate files can both add definitions and data to the same Class or Module. Ruby is dynamic - with a few exceptions, it just uses the last code seen during a run.

When Classes and Modules are nested, you use a double-colon separated namespace path to specify which one you mean, e.g. MyModule::MyInnerModule::MyClass. So, it you save the value in a class or module variable, it persists across the two events because that variable belongs to the Class or Module, both of which are constants.

An instance-level variable (@ variable) can preserve state between separate events, but then you need a way to preserve that instance itself, which requires care when dealing with multiple events because you have to relinquish control at the end of each event - meaning that you would need a way to make sure the same instance gets the next event. Because they automatically are preserved, module and class variables (@@ variables) are the answer - either to remember the instance that saves the state, or to save the state themselves. The choice between those two techniques (single remembered instance saved in an @@ vs individual parameters saved in a bunch of @@) is to my mind largely a matter of personal preference. Both will work and involve the same core idea of how to retain data between events.


#13

Thank you both for your explanations. I’m beginning to understand that and have been able to implement a class variable that preserves the preceding event. I’ll save trying the module variable for the future.