Sunday, December 15, 2019

#1 2019-07-16 09:00:23 am

mnoriega
Member
Registered: 2019-07-15
Posts: 2

Cascading status bar menus

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.

Applescript:


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:




Filed under: Menu

Offline

 

#2 2019-07-16 05:24:43 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 6079

Re: Cascading status bar menus

Create a new menu containing Apple and Banana, and then call setSubmenu: on the Fruit menu item, passing the new menu.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#3 2019-07-17 12:59:21 pm

mnoriega
Member
Registered: 2019-07-15
Posts: 2

Re: Cascading status bar menus

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.

Applescript:


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:


Filed under: cascading menu

Offline

 

#4 2019-07-18 12:45:56 am

KniazidisR
Member
Registered: 2019-03-03
Posts: 802

Re: Cascading status bar menus

Very nice example, runs fine as application

But in the Script Editor fails with AppleScript Error:

"NSWindow drag regions should only be invalidated on the Main Thread!"

Last edited by KniazidisR (2019-07-18 12:54:20 am)


Model: MacBook Pro
macOS Mojave -- version 10.14.4
Safari -- version 12.1
Firefox -- version 70.0

Offline

 

#5 2019-07-18 02:10:36 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 6079

Re: Cascading status bar menus

KniazidisR wrote:

Very nice example, runs fine as application

But in the Script Editor fails with AppleScript Error:

"NSWindow drag regions should only be invalidated on the Main Thread!"



It needs to be run on the main thread.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#6 2019-07-18 02:29:40 am

KniazidisR
Member
Registered: 2019-03-03
Posts: 802

Re: Cascading status bar menus

And what king is this if block, which performs on else the same action?

Applescript:

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

Applescript:

else if aClass = "list"

may be simple

Applescript:

else

Basically, here is sufficient

Applescript:

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:

Applescript:

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 ...

Last edited by KniazidisR (2019-07-18 03:20:27 am)


Model: MacBook Pro
macOS Mojave -- version 10.14.4
Safari -- version 12.1
Firefox -- version 70.0

Offline

 

#7 2019-07-18 12:20:17 pm

KniazidisR
Member
Registered: 2019-03-03
Posts: 802

Re: Cascading status bar menus

Improved script (thanks for original):

Applescript:


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

Last edited by KniazidisR (2019-07-18 12:45:07 pm)


Model: MacBook Pro
macOS Mojave -- version 10.14.4
Safari -- version 12.1
Firefox -- version 70.0

Offline

 

#8 2019-07-18 11:45:16 pm

KniazidisR
Member
Registered: 2019-03-03
Posts: 802

Re: Cascading status bar menus

Here is the recursive variant of the script (now submenu levels may be > 2):

Applescript:


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:

Last edited by KniazidisR (2019-07-21 01:49:20 am)


Model: MacBook Pro
macOS Mojave -- version 10.14.4
Safari -- version 12.1
Firefox -- version 70.0

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)