I’m working on a script that displays a status bar icon with a menu, and I can’t figure out how to make a menu item change, depending on the state of a variable. Here’s some sample code with a Disable/Enable
menu item - I’m trying to figure out how to make it say either Enable
when the user has earlier chosen Disable
, or say Disable
when started up or when the user has earlier chosen Enable
.
I’ve found a lot of information about adding items to a menu, but I haven’t found any clear answer on how to change an existing menu item. Is that possible? Here’s my sample code, which has to be run as an application, not from SD (and apologies for any incompetence in it - I still haven’t figured out how to make it display an icon instead of text in the status bar, but I suppose that will be fairly straightforward):
EDIT: Alternatively, is there a way to add/remove a checkmark next to a menu item that says “Enabled”?? I see that there is a deprecated method called NSStateOn (if I remember correctly), but I can’t find a current method for use in AppleScript.
use scripting additions
use framework "Foundation"
use framework "AppKit"
property statusItem : missing value
property interval : 1.0
property runOption : "active"
on run
set my statusItem to current application's NSStatusBar's systemStatusBar's statusItemWithLength:(current application's NSVariableStatusItemLength)
statusItem's setTitle:"Testing"
set newMenu to current application's NSMenu's alloc()'s initWithTitle:""
(newMenu's addItemWithTitle:"Disable/Enable" action:"action1:" keyEquivalent:"")'s setTarget:me
newMenu's addItem:(current application's NSMenuItem's separatorItem)
(newMenu's addItemWithTitle:"Quit" action:"terminate" keyEquivalent:"")'s setTarget:me
statusItem's setMenu:newMenu
repeat while 1 = 1
if runOption is "active" then
-- do something
end if
delay interval
end repeat
end run
on action1:sender
if runOption is "active" then
display dialog "I won't be active." buttons ("OK") default button 1 giving up after 1
set runOption to "inactive"
else
display dialog "OK, I'll be active again" buttons ("OK") default button 1 giving up after 1
set runOption to "active"
end if
end action1:
to terminate()
tell me to quit
end terminate
To answer my own question: I found some code by Shane (of course - who else?) that pointed the way, though my version of it is mostly incompetent. At least it seems to work.
As you can see from the createMenu routine, the menu gets deleted and re-created every time something changes. Probably there’s a way to do this without re-creating the menu, but I can’t guess what it might be.
If anyone wants something like this, you can probably do it better. The code below seems to work when saved as an application and the two items specified in the comment at the top are added to the app’s info.plist.
-- to prevent the app icon from appearing in the dock
-- and to prevent the Esc key from exiting,
-- add "Application is background only" - "YES"
-- and "Application is Agent (UIElement)" - "YES"
-- to the info.plist of the AppleScript app
use scripting additions
use framework "Foundation"
use framework "AppKit"
property statusItem : missing value
property interval : 1
property runOption : "active"
on run
my createMenu()
repeat while 1 = 1
-- do something
delay interval
end repeat
end run
on createMenu()
try
current application's NSStatusBar's systemStatusBar()'s removeStatusItem:statusItem
end try
set my statusItem to current application's NSStatusBar's systemStatusBar's statusItemWithLength:(current application's NSVariableStatusItemLength)
statusItem's setTitle:"Menu"
set newMenu to current application's NSMenu's alloc()'s initWithTitle:""
(newMenu's addItemWithTitle:"About Menu" action:"action1:" keyEquivalent:"")'s setTarget:me
newMenu's addItem:(current application's NSMenuItem's separatorItem)
set returnValue to (newMenu's addItemWithTitle:"Interval 1" action:"action2:" keyEquivalent:"")
returnValue's setTarget:me
if interval is 1 then
returnValue's setState:1
end if
set returnValue to (newMenu's addItemWithTitle:"Interval 2" action:"action3:" keyEquivalent:"")
returnValue's setTarget:me
if interval is 2 then
returnValue's setState:1
end if
set returnValue to (newMenu's addItemWithTitle:"Interval 3" action:"action4:" keyEquivalent:"")
returnValue's setTarget:me
if interval is 3 then
returnValue's setState:1
end if
newMenu's addItem:(current application's NSMenuItem's separatorItem)
if runOption is "active" then
set returnValue to (newMenu's addItemWithTitle:"Enabled" action:"action5:" keyEquivalent:"")
returnValue's setTarget:me
returnValue's setState:1
else
set returnValue to (newMenu's addItemWithTitle:"Click to enable" action:"action5:" keyEquivalent:"")
returnValue's setTarget:me
end if
newMenu's addItem:(current application's NSMenuItem's separatorItem)
(newMenu's addItemWithTitle:"Quit" action:"terminate" keyEquivalent:"")'s setTarget:me
statusItem's setMenu:newMenu
end createMenu
on action1:sender
display dialog "This app doesn't do anything." buttons ("OK") default button 1
end action1:
on action2:sender
set interval to 1
my createMenu()
end action2:
on action3:sender
set interval to 2
my createMenu()
end action3:
on action4:sender
set interval to 3
my createMenu()
end action4:
on action5:sender
if runOption is "disabled" then
set runOption to "active"
my createMenu()
else
set runOption to "disabled"
my createMenu()
end if
end action5:
to terminate() -- quit handler is not called from normal NSApplication terminate:
current application's NSStatusBar's systemStatusBar's removeStatusItem:statusItem
if name of current application does not start with "Script" then tell me to quit
end terminate
My solution turns out to have been incompetent, as anyone here who looked at it could see. It was having problems with Bartender 4 (the terrific utility that manages icons on the status bar), and I sent a feedback comment to the programmer, Ben Surtees, who came back with essentially a total rewrite of my code.
EDIT: The new code is below, but my main reason is for posting is to put in a recommendation for Bartender 4, plus a word of praise for the most generous and expert tech support that I’ve ever had in many years of writing about software.
-- rewritten by Ben Surtees at macbartender.com
-- to prevent the app icon from appearing in the dock and to prevent the Esc key from exiting,
-- add "Application is background only" - "YES" and "Application is Agent (UIElement)" - "YES"
-- to info.plist of the AppleScript app
use scripting additions
use framework "Foundation"
use framework "AppKit"
property statusItem : missing value
property interval : 1.0
property runOption : "active"
on run
my createStatusItem()
end run
on idle
-- do something
return interval
end idle
on createStatusItem()
try
current application's NSStatusBar's systemStatusBar()'s removeStatusItem:statusItem
end try
set my statusItem to current application's NSStatusBar's systemStatusBar's statusItemWithLength:(current application's NSVariableStatusItemLength)
statusItem's setTitle:"Menu"
my createMenu()
end createStatusItem
on createMenu()
set newMenu to current application's NSMenu's alloc()'s initWithTitle:""
(newMenu's addItemWithTitle:"About Menu" action:"action1:" keyEquivalent:"h")'s setTarget:me
newMenu's addItem:(current application's NSMenuItem's separatorItem)
set returnValue to (newMenu's addItemWithTitle:"Timer: 0.25 sec" action:"action2:" keyEquivalent:"2")
returnValue's setTarget:me
if interval is 0.25 then
returnValue's setState:1
end if
set returnValue to (newMenu's addItemWithTitle:"Timer: 0.5 sec" action:"action3:" keyEquivalent:"5")
returnValue's setTarget:me
if interval is 0.5 then
returnValue's setState:1
end if
set returnValue to (newMenu's addItemWithTitle:"Timer: 1.0 sec" action:"action4:" keyEquivalent:"1")
returnValue's setTarget:me
if interval is 1.0 then
returnValue's setState:1
end if
newMenu's addItem:(current application's NSMenuItem's separatorItem)
if runOption is "active" then
set returnValue to (newMenu's addItemWithTitle:"Enabled" action:"action5:" keyEquivalent:"e")
returnValue's setTarget:me
returnValue's setState:1
else
set returnValue to (newMenu's addItemWithTitle:"Click to enable" action:"action5:" keyEquivalent:"e")
returnValue's setTarget:me
end if
newMenu's addItem:(current application's NSMenuItem's separatorItem)
(newMenu's addItemWithTitle:"Quit" action:"terminate" keyEquivalent:"q")'s setTarget:me
statusItem's setMenu:newMenu
end createMenu
on action1:sender
display dialog "This app doesn't do anything at all." buttons ("OK") default button 1
end action1:
on action2:sender
set interval to 0.25
my createMenu()
end action2:
on action3:sender
set interval to 0.5
my createMenu()
end action3:
on action4:sender
set interval to 1.0
my createMenu()
end action4:
on action5:sender
if runOption is "disabled" then
set runOption to "active"
my createMenu()
else
set runOption to "disabled"
my createMenu()
end if
end action5:
to terminate() -- quit handler is not called from normal NSApplication terminate:
if name of current application does not start with "Script" then tell me to quit
end terminate
1 Like