Aa a contrived example of what I’m trying to do, this is something that works:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions
set toolsMenu to current application's NSMenu's alloc()'s initWithTitle:"Tools"
set ReturnValue to toolsMenu's addItemWithTitle:"Open Contacts" action:"openApp:" keyEquivalent:""
ReturnValue's setTarget:me
set ReturnValue to toolsMenu's addItemWithTitle:"Open Clock" action:"openApp:" keyEquivalent:""
ReturnValue's setTarget:me
set toolsMenuItem to current application's NSMenuItem's alloc()'s initWithTitle:"Tools" action:"" keyEquivalent:""
toolsMenuItem's setSubmenu:toolsMenu
set RootMenu to current application's NSApp's mainMenu()
RootMenu's addItem:toolsMenuItem
on openApp:sender
set menuItemName to title of sender as string
set appName to characters 6 thru (length of menuItemName) of menuItemName as string
tell application appName to activate
end openApp:
This way I can have any number of Open App menus commands calling a generic openApp: handler.
But I’d rather do this by sending an appName parameter to the handler. In vanilla AppleScript, this does the trick:
its openApp:"Open Contacts" appName:"Contacts"
on openApp:sender appName:appName
tell application appName to activate
end openApp:appName:
But I can’t figure out how to shoehorn appName into addItemWithTitle, if it’s even possible.
An action handler is only sent the object that is calling it (the sender). Each NSMenuItem has title, tag, and representedObject properties to use for identifying or comparing it, so one option would be to set representedObject to the name, id, or whatever, and use that instead of manipulating the title, for example:
use framework "Foundation"
use scripting additions
on run
my performSelectorOnMainThread:"doStuff" withObject:(missing value) waitUntilDone:true
end run
on doStuff() -- UI stuff needs to be done on the main thread
set toolsMenu to current application's NSMenu's alloc()'s initWithTitle:""
repeat with appName in {"Contacts", "Clock"}
tell (toolsMenu's addItemWithTitle:("Open " & appName) action:"openApp:" keyEquivalent:"")
(its setTarget:me)
(its setRepresentedObject:appName)
end tell
end repeat
set toolsMenuItem to current application's NSMenuItem's alloc()'s initWithTitle:"Tools" action:"" keyEquivalent:""
toolsMenuItem's setSubmenu:toolsMenu
tell current application's NSApp's mainMenu() to addItem:toolsMenuItem
end doStuff
on openApp:sender
set appName to sender's representedObject as text
tell application appName to activate
end openApp:
Ah, so there’s a formal way to pass info to the handler, and multiple menu items can be created in a loop. Even better.
Is that (its setX:y) construction broadly applicable to other NSMenuItem properties, or do we only have access to a subset? I’m still a babe in the woods with ASObjC.
Yeah, Script Debugger is a bit touchy and can get weird if you don’t do UI stuff like menus on the main thread. Don’t ask how I know.
In addition to the various class instance methods, properties can be set (if they are writable) using the setX:ysetter construction. Note that the
set whatever of someObject to someValue
form may not work for all properties (depending on how they are bridged by AppleScriptObjC), and when it works the object may not appear to be updated to the new setting, while
someObject's setWhatever:someValue
works and is also important for Key-Value Observing (a part of Cocoa bindings), which is a mechanism that enables objects to be notified when a property changes.
Care should be taken if you are wanting to remove menu items that are not yours. In that case a better approach might be to use the hidden property, that way the menu item will still be there, it just won’t be visible.
Good point! But I was thinking more about cleaning up after myself when I’m done debugging a script in SD. Leaving the extra menu stuff hanging around (until I restart SD, anyway) would bug me.
Didn’t occur to me that I could do unnatural things to the built-in menus…
Not to mention that every time I rerun the script in SD, it adds another Tools menu. Probably should have some code to see if the menu already exists, delete it if it does, and then re-add it.