Replacing the menu in an applet dynamically

I was think some people might want to see how I replaced the menu items in an applet by just adding code to the applet.

I created an Applet in Script debugger and then added code to the Applet. I have included the Applet in this post so you can see what was done. To change the menu items I just run the Applet.

Bill Kopp

ASObj-C application #6.app.zip (77.8 KB)

I manage to link successfully the menu items from the application, the file menu but not the demo menu. The items from this last menu are all disabled.

For the first two menus, I moved the handlers out of the script delegate and use the setTarget: method to set the applet as the target of the menu items. I tried to do the very same thing with the items from the Demo menu, carefully re-writing the handlers and making sure they were spelled right and using the same setTarget: method but it did not work at all.

Here’s how it looks:

ModifyMenu() -- Original call in the applet
ModifyTargetsOfItems() -- my own procedure to set the menu items's target to me

on ModifyTargetsOfItems()
	set theMenuItem to ((current application's NSApp's mainMenu()'s itemAtIndex:0)'s submenu())'s itemAtIndex:0
	theMenuItem's setTarget:me

	set theMenuItem to ((current application's NSApp's mainMenu()'s itemAtIndex:1)'s submenu())'s itemAtIndex:0
	theMenuItem's setTarget:me

	set theMenuItem to ((current application's NSApp's mainMenu()'s itemAtIndex:1)'s submenu())'s itemAtIndex:1
	theMenuItem's setTarget:me

	set theMenuItem to ((current application's NSApp's mainMenu()'s itemAtIndex:1)'s submenu())'s itemAtIndex:2
	theMenuItem's setTarget:me

	set theMenuItem to ((current application's NSApp's mainMenu()'s itemAtIndex:1)'s submenu())'s itemAtIndex:3
	theMenuItem's setTarget:me

	set theMenuItem to ((current application's NSApp's mainMenu()'s itemAtIndex:1)'s submenu())'s itemAtIndex:4
	theMenuItem's setTarget:me
	
	set theDemoMenu to current application's NSApp's mainMenu()'s itemWithTitle:"Demo"
	set theDemoMenuItems to theDemoMenu's submenu()
	repeat with i from 0 to 2
		set theDemoMenuItem to (theDemoMenuItems's itemAtIndex:i)
		(theDemoMenuItem's setTarget:me)
	end repeat
end ModifyTargetsOfItems

Would you know why I does not work for the Demo menu

Probably because there’s no action handler that matches the menu items’ actions to be found.

1 Like

Well, I did a cut and paste of every action title in the script so that a handler is in there, just as I did for the menu items in the ‘File’ menu (which work just fine) and along the lines seen in Code to set preferences (as in a Cocoa app)?. It seems like the first three menus for an applet (application, File and Edit) let themselves being connected right into the script but if you create a new menu, there’s something else that must be done.

Really, all that’s required is a valid target and action. Try also changing the action in your code, to be sure it’s correct.

1 Like

OK, I’ll try that, with an applet that just create a new menu and doesn’t change the other three, and I’ll get back to you.

While I’m here and that you’re not far away, I was wondering if my way of creating menu, below,

    set extraMenu to (current application's NSMenu's alloc()'s initWithTitle:(title of customMenu))
	set customExtraMenu to (current application's NSMenuItem's alloc()'s initWithTitle:(title of customMenu) action:"" keyEquivalent:"") -- customMenu is a record containing the title (a string) and list containing info for the menu items
	(customExtraMenu's setSubmenu:extraMenu)
	(currentApplicationMenus's addItem:customExtraMenu) -- and then, a loop is use to fill this menu

is correct…

I suspect you need something more like this:

set newMenuItem to currentApplicationMenus's additemWithTitle:(title of customMenu) action:"" keyEquivalent:""
set customExtraMenu to (current application's NSMenu's alloc()'s initWithTitle:(title of customMenu))
(newMenuItem's setSubmenu:customExtraMenu)
-- add menu items to customExtraMenu...
1 Like

Here is the script (in a applet) made out of pieces of @BillKopp’s:

use AppleScript version "2.4" -- Yosemite (10.10) or later
--use framework "Foundation"
use framework "AppKit"
use scripting additions

set RootMenu to current application's NSApp's mainMenu
-- both of the following the commented out and the other work to display the menu "Demo" filled with grayed items
--set DemoMenuItem to current application's NSMenuItem's alloc()'s initWithTitle:"Demo" action:"" keyEquivalent:""
set DemoMenuItem to RootMenu's addItemWithTitle:"Demo" action:"" keyEquivalent:""
set DemoMenu to current application's NSMenu's alloc()'s initWithTitle:"Demo"
set DemoMenuItem's submenu to DemoMenu

-- Add submenus: "Demo 1," "Demo 2" and "Demo 3" to the newly created Demo menu
set ReturnValue to DemoMenu's addItemWithTitle:"Demo 1" action:"DoDemo1:" keyEquivalent:""
ReturnValue's setTarget:me
set ReturnValue to DemoMenu's addItemWithTitle:"Demo 2" action:"DoDemo2" keyEquivalent:""
ReturnValue's setTarget:me
set ReturnValue to DemoMenu's addItemWithTitle:"Demo 3" action:"DoDemo3:"
ReturnValue's setTarget:me

-- RootMenu’s addItem:DemoMenuItem
-- if DemoMenuItem is made out of current application NSMenuItem’s alloc()’s…

on doDemo1:sender
    display dialog "message" buttons {"Cancel", "OK"} default button "OK" with title "Demo 1"
end doDemo1:

on doDemo2()
    display dialog "message" buttons {"Cancel", "OK"} default button "OK" with title "Demo 2"
end doDemo2

on doDemo3:sender
    display dialog "message" buttons {"Cancel", "OK"} default button "OK" with title "Demo 3"
end doDemo3:

The menu items from the ‘Demo’ menu are still disabled.

Your code has some problems. First, action handlers must always take a single argument — you can’t have doDemo2(). Second, case is very important — the selectors in your code start with D but your handlers start with d.

Nonetheless, you’re right: they don’t work. I tried turning off autoenabling and forcing them to be enabled, but still no luck. I can’t think of any explanation, unfortunately.

1 Like

Well, I guess that’s too bad.

Thanks for the time spent.

Let me follow up on this…

Automatic enabling/disabling of menu items is usually controlled by the target implementing the method validateMenuItem:. If this returns true, the menu item will be enabled. It normally calls it’s superclass version (continue in AppleScript) if the item doesn’t match one the object doesn’t have a target for.

You can actually see this in the following code. The validation handler is simply returning true for testing:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

set DemoMenu to current application's NSMenu's alloc()'s initWithTitle:"Demo"

set ReturnValue to DemoMenu's addItemWithTitle:"Demo 1" action:"doDemo1:" keyEquivalent:""
ReturnValue's setTarget:me
set ReturnValue to DemoMenu's addItemWithTitle:"Demo 2" action:"doDemo2:" keyEquivalent:""
ReturnValue's setTarget:me
set ReturnValue to DemoMenu's addItemWithTitle:"Demo 3" action:"doDemo3:" keyEquivalent:""
ReturnValue's setTarget:me
set DemoMenuItem to current application's NSMenuItem's alloc()'s initWithTitle:"Demo" action:"" keyEquivalent:""
DemoMenuItem's setSubmenu:DemoMenu
set RootMenu to current application's NSApp's mainMenu()
RootMenu's addItem:DemoMenuItem

on doDemo1:sender
	display dialog "message" buttons {"Cancel", "OK"} default button "OK" with title "Demo 1"
end doDemo1:

on doDemo2:sender
	display dialog "message" buttons {"Cancel", "OK"} default button "OK" with title "Demo 2"
end doDemo2:

on doDemo3:sender
	display dialog "message" buttons {"Cancel", "OK"} default button "OK" with title "Demo 3"
end doDemo3:

on validateMenuItem:anItem
	set theSel to anItem's action() as text
	display dialog theSel
	return true
end validateMenuItem:

You can see that the handler is being called, but for some reason its result is being ignored. I’m guessing this is a hole in the bridging mechanism, and that the value we return is not being converted to the expected BOOL type, and is thus not being interpreted correctly.

That leaves the nuclear option, which is probably fine here. This means turning off autoenabling completely — items will be enabled as long as they have an action and a target can be found, and as long as you haven’t set them to be disabled.

It’s a nuclear option because, for some reason, we have to use it for the main menubar, so if you’re relying on other items being enabled according to circumstances, you need to handle them manually too. So:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

set DemoMenu to current application's NSMenu's alloc()'s initWithTitle:"Demo"

set ReturnValue to DemoMenu's addItemWithTitle:"Demo 1" action:"doDemo1:" keyEquivalent:""
ReturnValue's setTarget:me
set ReturnValue to DemoMenu's addItemWithTitle:"Demo 2" action:"doDemo2:" keyEquivalent:""
ReturnValue's setTarget:me
set ReturnValue to DemoMenu's addItemWithTitle:"Demo 3" action:"doDemo3:" keyEquivalent:""
ReturnValue's setTarget:me
set DemoMenuItem to current application's NSMenuItem's alloc()'s initWithTitle:"Demo" action:"" keyEquivalent:""
DemoMenuItem's setSubmenu:DemoMenu
set RootMenu to current application's NSApp's mainMenu()
RootMenu's addItem:DemoMenuItem

RootMenu's setAutoenablesItems:false -- enabling/disabling must be done manually

on doDemo1:sender
	display dialog "message" buttons {"Cancel", "OK"} default button "OK" with title "Demo 1"
end doDemo1:

on doDemo2:sender
	display dialog "message" buttons {"Cancel", "OK"} default button "OK" with title "Demo 2"
end doDemo2:

on doDemo3:sender
	display dialog "message" buttons {"Cancel", "OK"} default button "OK" with title "Demo 3"
end doDemo3:

Use at your own risk.

1 Like

It is.

Thanks for the working solution.