Hi, I’ve been searching for days and still haven’t found how to do cascading menus. I’m new to Applescript and have found examples on how to create a status bar menu that I can update dynamically.
I’m using Script Editor and here’s an example of a menu with 2 items.
Fruit
Vegetable
What I would want to do is be able to create submenus so that when I select on of these main options I get other choices. This way I don’t have to list everything in a long menu but rather the categories of Fruit and Vegetable would be collapsed and when a user selects it displays the items within. e.g.:
Fruit
–Apple
–Banana
-Vegetable
–Lettuce
–Tomato
Anyone know how to do this and could provide an example? Thanks.
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
property theStatusItem : missing value
property BigMenu : missing value
on run
init() of me
end run
on init()
set theList to {"Fruit", "Vegetable", "", "Quit"}
set theStatusItem to current application's NSStatusBar's systemStatusBar()'s statusItemWithLength:(current application's NSVariableStatusItemLength)
theStatusItem's setTitle:"Food"
theStatusItem's setHighlightMode:true
theStatusItem's setMenu:createMenu(theList)
end init
on createMenu(theList)
set theMenu to current application's NSMenu's alloc()'s init()
set theCount to 1
repeat with i in theList
set j to contents of i
if j is not equal to "" then
set theMenuItem to (current application's NSMenuItem's alloc()'s initWithTitle:j action:"actionHandler:" keyEquivalent:"")
else
set theMenuItem to (current application's NSMenuItem's separatorItem())
end if
(theMenuItem's setTarget:me)
(theMenuItem's setTag:theCount)
(theMenu's addItem:theMenuItem)
if j is not equal to "" then
set theCount to theCount + 1
end if
end repeat
return theMenu
end createMenu
on actionHandler:sender
set theTag to tag of sender as integer
set theTitle to title of sender as string
if theTitle is not equal to "Quit" then
display dialog theTag as string
else
current application's NSStatusBar's systemStatusBar()'s removeStatusItem:theStatusItem
quit
end if
end actionHandler:
Thanks Mate! I found a subroutine that creates the submenu if the item on the list is another list. So by feeding the following list:
set theList to {“Fruit”, {“Apple”, “Banana”}, “Vegetable”, {“Lettuce”, “Tomato”}, “”, “Quit”}
It created the cascading menu. Here’s a copy of the solution for others to refer to.
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
property theStatusItem : missing value
property BigMenu : missing value
on run
init() of me
end run
on init()
set theList to {"Fruit", {"Apple", "Banana"}, "Vegetable", {"Lettuce", "Tomato"}, "", "Quit"}
set theStatusItem to current application's NSStatusBar's systemStatusBar()'s statusItemWithLength:(current application's NSVariableStatusItemLength)
theStatusItem's setTitle:"Food"
theStatusItem's setHighlightMode:true
theStatusItem's setMenu:createMenu(theList)
end init
on createMenu(aList)
set aMenu to current application's NSMenu's alloc()'s init()
set aCount to 10
set prevMenuItem to ""
repeat with i in aList
set j to contents of i
set aClass to (class of j) as string
if j is equal to "" then
set aMenuItem to (current application's NSMenuItem's separatorItem())
(aMenu's addItem:aMenuItem)
else
if (aClass = "text") or (aClass = "string") then
if j = "Quit" then
set aMenuItem to (current application's NSMenuItem's alloc()'s initWithTitle:j action:"actionHandler:" keyEquivalent:"")
else
set aMenuItem to (current application's NSMenuItem's alloc()'s initWithTitle:j action:"actionHandler:" keyEquivalent:"")
end if
(aMenuItem's setTag:aCount)
(aMenuItem's setTarget:me)
(aMenu's addItem:aMenuItem)
set aCount to aCount + 10
copy aMenuItem to prevMenuItem
else if aClass = "list" then
--Generate Submenu
set subMenu to current application's NSMenu's new()
(aMenuItem's setSubmenu:subMenu)
set subCounter to 1
repeat with ii in j
set jj to contents of ii
set subMenuItem1 to (current application's NSMenuItem's alloc()'s initWithTitle:jj action:"actionHandler:" keyEquivalent:"")
(subMenuItem1's setTarget:me)
(subMenuItem1's setTag:(aCount + subCounter))
(subMenu's addItem:subMenuItem1)
set subCounter to subCounter + 1
end repeat
end if
end if
end repeat
return aMenu
end createMenu
on actionHandler:sender
set theTag to tag of sender as integer
set theTitle to title of sender as string
if theTitle is not equal to "Quit" then
display dialog (theTag as string) & " " & theTitle as string
else
current application's NSStatusBar's systemStatusBar()'s removeStatusItem:theStatusItem
quit
end if
end actionHandler:
And what king is this if block, which performs on else the same action?
if j = "Quit" then
set aMenuItem to (current application's NSMenuItem's alloc()'s initWithTitle:j action:"actionHandler:" keyEquivalent:"")
else
set aMenuItem to (current application's NSMenuItem's alloc()'s initWithTitle:j action:"actionHandler:" keyEquivalent:"")
end if
and
else if aClass = "list"
may be simple
else
Basically, here is sufficient
if aClass ≠ "list" then
---blablabla
else
---blablabla
end if
And what need for on run if you do not use parameters?
Then, createMenu should be only create menu handler an addMenuItem only add menu item handler. All the rest stuff may be in imlicit on run handler or in its own handlers
In the repeat loop, you force the current application’s NSMenuItem and the like to calculate again and again. Instead, it’s more efficient to keep it in properties and refer to new NSMenuItem:
property NSStatusBar : a reference to NSStatusBar of current application
property NSVariableStatusItemLength : a reference to NSVariableStatusItemLength of current application
property NSMenu : a reference to NSMenu of current application
property NSMenuItem : a reference to NSMenuItem of current application
A great example, but put the code in order. Here, all the same, not the horses that oats chew …
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
property NSStatusBar : a reference to current application's NSStatusBar
property NSVariableStatusItemLength : a reference to current application's NSVariableStatusItemLength
property NSMenu : a reference to current application's NSMenu
property NSMenuItem : a reference to current application's NSMenuItem
property theStatusItem : missing value
property BigMenu : missing value
set theList to {"Fruit", {"Apple", "Banana"}, "Vegetable", {"Lettuce", "Tomato"}, "", "Quit"}
set theStatusItem to NSStatusBar's systemStatusBar()'s statusItemWithLength:NSVariableStatusItemLength
theStatusItem's setTitle:"Food"
theStatusItem's setHighlightMode:true
theStatusItem's setMenu:createMenu(theList)
on createMenu(aList)
set aMenu to NSMenu's alloc()'s init()
set {aCount, prevMenuItem} to {10, ""}
repeat with i in aList
set j to contents of i
if j is equal to "" then
set aMenuItem to (NSMenuItem's separatorItem())
(aMenu's addItem:aMenuItem)
else if ((class of j) as string) = "list" then
--Generate Submenu
set subMenu to NSMenu's new()
(aMenuItem's setSubmenu:subMenu)
set subCounter to 1
repeat with ii in j
set jj to contents of ii
set subMenuItem1 to (NSMenuItem's alloc()'s initWithTitle:jj action:"actionHandler:" keyEquivalent:"")
(subMenuItem1's setTarget:me)
(subMenuItem1's setTag:(aCount + subCounter))
(subMenu's addItem:subMenuItem1)
set subCounter to subCounter + 1
end repeat
else
set aMenuItem to (NSMenuItem's alloc()'s initWithTitle:j action:"actionHandler:" keyEquivalent:"")
(aMenuItem's setTag:aCount)
(aMenuItem's setTarget:me)
(aMenu's addItem:aMenuItem)
set aCount to aCount + 10
copy aMenuItem to prevMenuItem
end if
end repeat
return aMenu
end createMenu
on actionHandler:sender
set theTag to tag of sender as integer
set theTitle to title of sender as string
if theTitle is not equal to "Quit" then
display dialog (theTag as string) & " " & theTitle as string
else
NSStatusBar's systemStatusBar()'s removeStatusItem:theStatusItem
quit
end if
end actionHandler:
I will try to improve the script further - I will turn it into a recursive one, so that there will not be only 2 levels
Here is the recursive variant of the script (now submenu levels may be > 2):
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
property NSStatusBar : a reference to current application's NSStatusBar
property NSVariableStatusItemLength : a reference to current application's NSVariableStatusItemLength
property NSMenu : a reference to current application's NSMenu
property NSMenuItem : a reference to current application's NSMenuItem
property theStatusItem : missing value
set theList to {"Fruit", {"Apple", "Banana"}, "Vegetable", {"Lettuce", "Tomato", {"Fresh", "Salty"}}, "", "Quit"}
set theStatusItem to NSStatusBar's systemStatusBar()'s statusItemWithLength:NSVariableStatusItemLength
theStatusItem's setTitle:"Food"
theStatusItem's setHighlightMode:true
set aMenu to NSMenu's alloc()'s init()
set {aCount, prevMenuItem} to {10, ""}
makeNewSubMenu(theList, aCount, aMenu)
theStatusItem's setMenu:aMenu
on makeNewSubMenu(aList, aCount, aMenu)
repeat with i in aList
set j to contents of i
if j is equal to "" then
set aMenuItem to (NSMenuItem's separatorItem())
(aMenu's addItem:aMenuItem)
else if ((class of j) as string) = "list" then
--Generate Submenu
set subMenu to NSMenu's new()
(aMenuItem's setSubmenu:subMenu)
my makeNewSubMenu(j, aCount, subMenu)
else
set aMenuItem to (NSMenuItem's alloc()'s initWithTitle:j action:"actionHandler:" keyEquivalent:"")
(aMenuItem's setTag:aCount)
(aMenuItem's setTarget:me)
(aMenu's addItem:aMenuItem)
set aCount to aCount + 10
copy aMenuItem to prevMenuItem
end if
end repeat
end makeNewSubMenu
on actionHandler:sender
set theTag to tag of sender as integer
set theTitle to title of sender as string
if theTitle is not equal to "Quit" then
display dialog (theTag as string) & " " & theTitle as string
else
NSStatusBar's systemStatusBar()'s removeStatusItem:theStatusItem
quit
end if
end actionHandler:
A more basic question:
How to I add a menu item in the menubar (with some sub-menu items – if possible) to an Applet I am developing.
Just a simple code will help me starting.
Thanks
L.
PS: I tested the above script from KniazidisR but I keep getting:
“NSWindow drag regions should only be invalidated on the Main Thread!” if I run it from SD.
And if I compile it compiled into a stay-open Applet … nothing append
Thanks !
I was looking in the left side of the menubar.
But how the code would be for a menuitem associated “only” with the Applet, located on the left-side (as usual), and appears only when the Applet is the frontmost application?
Thanks again !
What I mean to have an icon instead of a text as title for a menu in the menu bar. Like the “speaker” symbol for the Sound menu in the menubar on the right-side of the menubar.
As it is now in your example in the forum “forum.latenightsw” there si the word “Demo” for the menu, and KniazidisR has “Food”. Can this words been replaced by an icon ?