Cascading status bar menus

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 …

Improved script (thanks for original):


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

The script above doesn’t work on my system, I get an error:

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

on this statement:

set theStatusItem to NSStatusBar’s systemStatusBar()'s statusItemWithLength:NSVariableStatusItemLength

Does anyone know how to solve this?

Thanks a lot
Dave

The script must be saved as a stay open application because, as Shane Stanley wrote:

My memory said that KniazidisR already wrote that but I didn’t retrieve such statement.

Doing that, the menu is created in the menu extras area, on the right side of the menu bar.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) jeudi 30 avril 2020 11:49:31

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 !

I guess that would be require an application built entirely with developers tools like Preview or Numbers …

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) jeudi 30 avril 2020 14:01:49

It can probably be done, but it’s complicated. See the discussion here:

https://forum.latenightsw.com/t/replacing-the-menu-in-an-applet-dynamically/434

Perfect. I just checked and it seems that your last post (which us from November 2017) is still working. Great !!!

One last question: is there any way to change the menu name (text) with an icon in KniazidisR’s code or in your post at “forum.latenightsw” ?

Ciao and thanks !!!
L.

If you have a reference to it, use setTitle:.

Do you mean a link (posix path) to a jpeg file ?

Such as:

theStatusItem’s setTitle:“/Users/ldicroce/Desktop/Screenshot2.jpeg”

No, I mean change the title of the menuitem. Perhaps I misunderstood.

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 ?

You coult try using setImage: and pass an NSImage, and set the title to an empty string.

Stripping header seems not be comfortable.
I remember the username did such an act and I’ll not make suggestion with them.

It worked using:

property NSImage : class "NSImage"
set ImagePath to (current application's NSBundle's mainBundle()'s bundlePath() as text) & "/Contents/Resources/Luxicon5.png"
# if image is from an external file use:
-- set theImage to NSImage's alloc()'s initWithContentsOfFile:"/Users/ldicroce/Desktop/Luxicon5.png"
set theImage to NSImage's alloc()'s initWithContentsOfFile:ImagePath
theStatusItem's setImage:theImage

Thanks !

If the image file is within the bundle, you can simplify that to:

set theImage to NSImage's imageNamed:"Luxicon5.png"

Thanks!
I put together the code above by copying independent lines from different scripts.
I was sure it could be improved (as you indeed just suggested), since my knowledge of AppleScriptObjC is zero.

I have implemented this methods for an applet I am using all the time, and it is very useful!
I wonder if it is also possible to associate a keyboard shortcut to a menu item … and if there si any script you have that I can then adapt to my case.

Ciao.
L.