A NSStatusItem is one of those user menus in the right side of the menu bar. It is handy for a menu you want visible/available all the time like a timer/clock or utility. The menu bar is a limited resource, but sometimes it can work better than keeping an app window around.
An action handler is one that gets called by various UI objects (menu items, buttons, etc) when they are used.
Most macOS applications are event driven - that is, they don’t have a set program flow or use repeats or poll or whatever, the user decides the flow by interacting with the UI, and the system sends the app the corresponding events or calls its action handlers. A status item is like that - it just sits there until a menu item is selected. For an example (be aware that compiling a script will remove connections to any of its remaining UI objects):
use framework "Foundation"
use scripting additions
property |+| : current application -- just a shortcut (never mind that it resembles a first aid kit)
# outlets
property statusItem : missing value -- button title, etc
property statusMenu : missing value -- item enable, state, etc
on run -- example
if |+|'s NSThread's isMainThread() as boolean then -- applet
initialize()
else -- running from a script editor
my performSelectorOnMainThread:"initialize" withObject:(missing value) waitUntilDone:true
end if
end run
to initialize() -- set stuff up
buildStatusItem()
# other setup stuff as desired
end initialize
on admin() -- single action handler with no argument
display dialog "admin..." giving up after 2
# whatever
end admin
on otherStuff:sender -- common action handler with argument (the object sending the event)
set theItem to sender's title as text -- or other distinguishing property
display dialog theItem & "..." giving up after 2
# whatever
end otherStuff:
to terminate() -- used as a selector for the scripting term "quit"
quit
end terminate
to quit
display dialog "Terminating Status Item..." giving up after 2
# clean up, save preferences, etc
|+|'s NSStatusBar's systemStatusBar's removeStatusItem:statusItem -- just the current item
# continue quit -- for an app
end quit
to buildStatusItem()
tell (|+|'s NSStatusBar's systemStatusBar's statusItemWithLength:(|+|'s NSVariableStatusItemLength))
set my statusItem to it
its (button's setTitle:"Example")
its setMenu:(my buildStatusMenu())
end tell
end buildStatusItem
to buildStatusMenu()
tell (|+|'s NSMenu's alloc()'s initWithTitle:"")
set my statusMenu to it
its setAutoenablesItems:false -- manual enable/disable
my (addMenuItem to it given title:"Administration", action:"admin")
my (addMenuItem to it given title:"Settings", action:"otherStuff:")
my (addMenuItem to it given title:"Other Stuff", action:"otherStuff:")
my (addMenuItem to it) ----
my (addMenuItem to it given title:"Quit", action:"terminate")
return it
end tell
end buildStatusMenu
# Add a menuItem to a menu - sectionHeaderWithTitle: convenience method is for macOS 14+, so just use an attributedString.
to addMenuItem to theMenu given title:title as text : "", header:header as boolean : false, action:action as text : "", theKey:theKey as text : "", tag:tag as integer : 0, enabled:enabled : (missing value), state:state : (missing value), target:target : (missing value) -- given parameters are optional
if title is in {"", "missing value"} then return theMenu's addItem:(current application's NSMenuItem's separatorItem)
if action is in {"", "missing value"} then set action to missing value
if header then tell (theMenu's addItemWithTitle:"" action:(missing value) keyEquivalent:"")
set attrTitle to |+|'s NSMutableAttributedString's alloc()'s initWithString:title
attrTitle's addAttribute:(|+|'s NSFontAttributeName) value:(|+|'s NSFont's fontWithName:"System Font Bold" |size|:11) range:{0, attrTitle's |length|()}
its setAttributedTitle:attrTitle
its setEnabled:false
return it
end tell
tell (theMenu's addItemWithTitle:title action:action keyEquivalent:theKey)
if action is not missing value then its setTarget:(item (((target is missing value) as integer) + 1) of {target, me})
if tag > 0 then its setTag:tag
if enabled is not missing value then its setEnabled:(item (((enabled is false) as integer) + 1) of {true, false})
if state is not missing value then its setState:(my clamp(-1, state, 1)) -- 1=on (✓), 0=off, -1=mixed (-)
return it
end tell
end addMenuItem
to clamp(min as integer, value as integer, max as integer)
if value < min then return min
if value > max then return max
return value
end clamp
I mentioned localization because I noticed that there are a lot places where you use a bunch of individual statements to set variables to localized strings, which then just get used to set another variable or are passed to a handler. The localizations mostly use the same table/bundle, so a couple of utility handlers with optional/default parameters could be used. I don’t have any localized scripts to test, but for a piece of the Formats.applescript
file it would be something like:
# Localize a single string.
on localized_string for (term as text) given table:(table as text) : "MacYTDL", bundlePath:(bundlePath as text) : "" -- given parameters are optional
if bundlePath is in {"", "missing value"} then
return localized string term from table table
else
return localized string term from table table in bundle (file bundlePath)
end if
end localized_string
# Localize a list of strings.
on localized_list for (terms as list) given table:(table as text) : "MacYTDL", bundlePath:(bundlePath as text) : "" -- given parameters are optional
set output to {}
repeat with anItem in terms
set end of output to (localized_string for anItem given table:table, bundlePath:bundlePath)
end repeat
return output
end localized_list
# the heading localization statements could then be reduced to:
set format_chooser_headings to {"", "ID"} & ¬
(localized_list for {"Extension", ¬
"Resolution", ¬
"File size", ¬
"Bitrate", ¬
"Video Codec", ¬
"Audio Codec"} given bundlePath:path_to_MacYTDL)
Fewer variables, slightly shorter script (but could add up), a little easier to read.