Creating custom dropdown menus with apple script or JXA, but not with choose from list function

I also gave Keyboard Maestro a quick look, and these scripts seem to work OK on my system (Sonoma 14.7). If you are just pasting the script into an Execute an AppleScript action, another idea would be to try the Execute a Shell Script action and use osascript to run the script file from there. You could also try running from a new user account to see if there is a conflict with something else that has been installed.

Hello Jonas (@ionah) :wave:

Maybe you did not read carefully enough what I mentioned above…

Dialogs created via AppleScript and the Objective-C API‘s are not supported in Keyboard Maestro‘s Execute an AppleScript Action. It’s something that you will always have to write some code to call from a Macro when the Dialog related code is in its own file until Peter Lewis has integrated the support for such dialogs.

Another thing I would try is create a script object with the dialog related code that is compiled to the temp folder and called from the same AppleScript. This is less code to write because it’s only one Script instead of two … but it’s still the same way such dialogs have to be called from Keyboard Maestro.

If this doesn’t work on newer OS‘ses (speaking of the dialog is not rendered at all), then there might be a big issue related to osascript. On the particular OS.

But since I am still running Monterey this is only a guess from my side.

Greetings from Germany :de:

Tobias

Then why does it work here on Monterey 12.7.5?
Triggered by a “hot key” as they call it in KM…

It’s not the first time I encounter this kind of issue. Most of time it’s the application that’s not following macOS novelties. In these cases, a fix is possible. In the other case…

Maybe the dialog is rendered and showing … that’s not what I mean by isn’t supported… it’s the fact that Keyboard Maestro doesn’t execute the script the way like the System’s Script Menu or FastScripts or if you just Safe the script as a File elsewhere and call it from another Script where the Dialog is running on the main thread like it is supposed to be.

Compared to the older CocoaDialog or Pashua or the newer swiftDialog where this job is done by the CLI Tool or the Application which renders the dialog and puts it on the main thread automatically - with Keyboard Maestro you always have to ensure that this will happen thru the code you write and how it’s executed on your own.

Greetings from Germany :de:

Tobias

Hi @red_menace. I don’t understand how I missed your post.
The fact that it’s running on your Sonoma config is encouraging.
Your suggestions are very clever (as always) and @Lucky_Magician.should follow them.

Thank you for taking the time to install KM.

Hi, I’m new here but saw your earlier post that said:

In fact, if I can make a dialog with only one popup button, which will open immediately after running the script, without firstly clicking on it, then it could actually work for my case perfectly well.

There is a way to make a button automatically select after a certain amount of time with the property “giving up after”:
set userResponse to display dialog “Hello” buttons {“ok”, “cancel”} default button 1 giving up after 2

This pops up a dialog box with the text “hello” and puts up two buttons. Button 1 is automatically “clicked” after 2 seconds.

I hope that helps.

Hello Greg (@gxwalsh) :wave:

What you’re describing here is the optional feature of the standard AppleScript display dialog command.

The code above you’ll referred to is based on AppleScript with Apple‘s Objective-C API and therefor something like timeout of the Dialog would have to be written quite differently.

My Skills to write that aren’t quite on that level so I won’t post any code. Maybe any other person see‘s this and can show you the difference to your suggestion.

Greetings from Germany :de:

Tobias

Greetings from Baltimore!

Yes, I was just trying to answer OP’s request to “open immediately without first clicking on it”

I may have missed the OP’s actual intention.

Hello guys

I was on a trip, just arrived back home. I’m going to read all posts, and retry all examples. Maybe I’m missing something along the lines, don’t know.

I’ll report back how it goes, whether it works or not.
Thank you all for all suggestions and will to help, I greatly appreciate it.

I also came up with a variation that uses NSMenu (for submenus, etc), but I’m not that great at using VoiceOver to test it. Let me know if you want to take a look at it.

I want to try it absolutely.
If it would work then I would save lots of time, I planned to go through all examples you guys shared, got one more macbook with sonoma on it so planned to try it there as well.

Here is a variation that uses NSMenu instead of NSPopUpButton. It works more-or-less the same, but instead of the panel containing a control, it is just used to position a contextual menu (if you are using your own windows, they can be used).


use framework "Foundation"
use scripting additions

# UI item outlets
property mainWindow : missing value
property customMenu : missing value

# script properties
property outcome : missing value -- selection result
property failure : missing value -- error record with keys {handlerName, errorMessage, errorNumber}
property testing : true -- whether to provide feedback for the result being returned

on run -- example 
   set contextualMenu to {"Lorem Ipsum", "Donec Laoreet", {"Single Submenu", "", "Suspendisse Tempus", "Mauris Iaculis"}, "Quisque Convallis", {"Multiple Submenus", "", "Vivamus Consectetur", "Aenean Pulvinar", {"Aliquam Dignissim", "Try Not To Do This", {"No, Really, Don't Do This!"}}, "Nullam Sollicitudin"}, "Curabitur Mollis"}
   return (getSelection from contextualMenu)
end run

# Get a menu selection - performSelector doesn't return anything and actions are asynchronous, so properties are used for results.
to getSelection from menuList
   try
      if current application's NSThread's isMainThread() as boolean then
         my doStuff:menuList
      else -- UI stuff needs to be done on the main thread
         my performSelectorOnMainThread:"doStuff:" withObject:menuList waitUntilDone:true
      end if
      if failure is not missing value then error
      if testing then
         set output to item (((outcome is missing value) as integer) + 1) of {outcome, "missing value"}
         activate me
         -- display dialog output with title "Result" buttons {"OK"} giving up after 10
         say output
      end if
      return outcome
   on error errmess number errnum
      if failure is missing value then
         -- display alert "NSMenu Script Error " & errnum message errmess
         say "NSMenu Script Error," & errnum & "," & first paragraph of errmess
      else -- use keys from the failure record
         -- display alert "NSMenu Script Error " & failure's errorNumber message quoted form of failure's errorMessage & " from handler " & failure's handlerName
         say "NSMenu Script Error," & failure's errorNumber & "," & first paragraph of failure's errorMessage & " from handler " & failure's handlerName
      end if
      return missing value
   end try
end getSelection

to doStuff:(menuList as list) -- do the menu stuff
   try
      set my customMenu to makeMenu for menuList without usingTags
      
      # create a view for the menu
      set mainWindow to makeWindow at {} with panel and floats given contentSize:{0, 0}, styleMask:3
      mainWindow's contentView()'s setMenu:customMenu
      mainWindow's makeKeyAndOrderFront:me -- show the panel
      
      # create a right-click mouse event positioned at the view with the location offset
      set uptime to current application's NSProcessInfo's processInfo's systemUptime
      set theEvent to current application's NSEvent's mouseEventWithType:(current application's NSEventTypeRightMouseDown) location:{0, 0} modifierFlags:0 timestamp:uptime windowNumber:(mainWindow's windowNumber) context:(missing value) eventNumber:1 clickCount:1 pressure:0.0
      current application's NSMenu's popUpContextMenu:customMenu withEvent:theEvent forView:(mainWindow's contentView)
      
      # mainWindow's performClose:(missing value) -- uncomment to dismiss the positioning panel
   on error errmess number errnum
      set my failure to {handlerName:"doStuff", errorMessage:errmess, errorNumber:errnum}
      log result
   end try
end doStuff:

# Make and return a NSWindow or NSPanel.
# Default styleMask includes a title, close and minimize buttons, and is not resizeable.
# If no origin is given the window will be centered.
to makeWindow at (origin as list) given contentSize:contentSize as list : {400, 200}, styleMask:styleMask as integer : 15, title:title as text : "", panel:panel as boolean : false, floats:floats as boolean : false, aShadow:aShadow as boolean : true, minimumSize:minimumSize as list : {}, maximumSize:maximumSize as list : {}, backgroundColor:backgroundColor : missing value
   tell current application to set theClass to item ((panel as integer) + 1) of {its NSWindow, its NSPanel}
   tell (theClass's alloc()'s initWithContentRect:{{0, 0}, contentSize} styleMask:styleMask backing:2 defer:true)
      if origin is {} then
         tell it to |center|()
      else
         its setFrameOrigin:origin
      end if
      if title is not "" then its setTitle:title
      if panel and floats then its setFloatingPanel:true
      its setHasShadow:aShadow
      if minimumSize is not {} then its setContentMinSize:minimumSize
      if maximumSize is not {} then its setContentMaxSize:maximumSize
      if backgroundColor is not missing value then its setBackgroundColor:backgroundColor
      its setAutorecalculatesKeyViewLoop:true -- include added items in the key loop
      return it
   end tell
end makeWindow

# Make and return a menu from a (possibly nested) list of menu item names.
to makeMenu for menuItems given title:title : "Menu", usingTags:usingTags : true, baseTag:baseTag : 1
   set theMenu to current application's NSMenu's alloc()'s initWithTitle:(title as text)
   addMenuList to theMenu given itemList:menuItems, usingTags:usingTags, baseTag:baseTag
   return theMenu
end makeMenu

# Add a list of menu items to a menu (recursive).
# The itemList can contain nested lists, with any given list of items being the submenu for the previous item.
# If using tags, the menu items (excluding separators) are tagged in the order they are created.
# Menu items use a common action handler and have no key equivalent by default, but individual
#  items can be changed as needed by using NSMenu's itemWithTitle: method to get desired items.
to addMenuList to theMenu given itemList:itemList : {}, previousItem:previousItem : missing value, usingTags:usingTags : true, baseTag:baseTag : 1
   repeat with anItem in (itemList as list)
      if (contents of anItem) is in {"", {}, missing value} then
         (theMenu's addItem:(current application's NSMenuItem's separatorItem()))
      else if (class of anItem) is list then -- submenu items
         if previousItem is not missing value then -- set menu for the submenu as needed
            if not (previousItem's hasSubmenu) as boolean then -- create submenu
               set submenu to (current application's NSMenu's alloc's initWithTitle:(previousItem's title))
               (previousItem's setSubmenu:submenu) -- for any following lists
            end if
            set baseTag to (addMenuList to submenu given itemList:anItem, previousItem:previousItem, usingTags:usingTags, baseTag:baseTag)
         end if
      else -- treat as a menu item title
         set menuItem to (theMenu's addItemWithTitle:(anItem as text) action:"menuAction:" keyEquivalent:"")
         (menuItem's setTarget:me) -- for autoenable
         set previousItem to menuItem -- potential submenu
         if usingTags then
            (menuItem's setTag:baseTag)
            # log "Tag " & baseTag & " is menuItem '" & anItem & "' of menu '" & theMenu's title & "'" -- for reference
         end if
         set baseTag to baseTag + 1
      end if
   end repeat
   return baseTag
end addMenuList

# Common menu action - can use sender's title or tag for comparisons.
on menuAction:sender
   try -- do something with the selected menu item - example just sets the outcome property to info about the menu item
      set {tagText, submenuText, parentText} to {", with no tag value set.", "", ", of the main menu "}
      if ((sender's tag) as integer) is not 0 then set tagText to ", with a tag set to " & sender's tag & "."
      if sender's hasSubmenu then set submenuText to ", (which is also a menu)"
      try
         set parentText to ", of menu '" & ((sender's parentItem's title) as text) & "'" -- immediate parent menu
      end try
      
      set my outcome to "Menu item '" & (sender's title as text) & "'" & submenuText & parentText -- & tagText
      -- whatever (remember that properties are being used for results)
      
   on error errmess number errnum
      set my failure to {handlerName:"menuAction", errorMessage:errmess, errorNumber:errnum}
   end try
end menuAction: