The “choose from list” command (as found in Standard System Additions) has no “giving up” option such as “display dialog” has.
Now what I want to do is to simulate a time-out a selection dialog.
What I try to accomplish is to simulate a click on its Cancel button, that apparently fails:
with timeout of 8 seconds
try
beep
tell application "System Events" to ¬
set fApp to name of some application process whose frontmost is true
tell application fApp
activate
set theItems to choose from list {"2", "3", "4"} with prompt " Recent item(s) to open:" with multiple selections allowed
end tell
on error
-- tell application "QuicKeys" to play shortcut named "Cmd." --> works well
-- tell application "System Events" to tell process fApp to tell (first window whose description is "dialog") to keystroke "." using {command down}
--> works NOT
set theItems to {}
end try
end timeout
The problem seems to be that System Events does not seem to find the object to send the keystroke.
(as you see, I have successfully tried the problem with a QuicKeys keystroke ShortCut that emulates “Cmd.”, but from an Applescript point of view this is not a solution)
You’re going to have to accomplish the same thing some other way then, Eelco, because while the choose from list is frontmost nothing else will happen - it’s a modal dialog and since AppleScript is not multi-threaded, your script just waits.
This is rather crude, but it works (so far, with limited testing) on my Tiger machine. The idea is to use an external stay-open script app that does the giving up on behalf of the main script. This is the stay-open script:
global wait
global armed
global fApp
set wait to missing value
set armed to false
set fApp to missing value
on idle
if (wait is missing value) then
return 0.1 -- May need adjustment.
else if (armed) then
-- Uses GUI Scripting.
tell application "System Events"
tell application process fApp
set frontmost to true
keystroke "." using {command down}
end tell
end tell
quit
return 1
else
set armed to true
return wait
end if
end idle
When activated by the main script, it idles until its ‘wait’ variable receives the giving-up time, then arms itself and and waits out the delay, Then “ if it hasn’t been told to quit in the meantime “ it brings the dialog-displaying application to the front, does the ‘Cancel’ keystroke, and quits. For my tests, I’ve saved it as a stay-open application called “Giving up after.app”.
The handler in the calling script should look something like this:
on chooseFromList(theList, thePrompt, givingUpAfter, orNot)
tell application "System Events" to set fApp to name of some application process whose frontmost is true
if (fApp is "System Events") then set fApp to "Finder" as Unicode text
tell application "Giving up after"
activate
set its fApp to fApp
set its wait to givingUpAfter
end tell
tell application fApp
activate
set theItems to choose from list theList with prompt thePrompt multiple selections allowed orNot
end tell
tell application "Giving up after" to quit
return theItems
end chooseFromList
chooseFromList({"2", "3", "4"}, " Recent item(s) to open:", 8, true))
Works beautifully, Nigel (clever). Hadn’t thought to start a “watcher”.
(caveat: the parameter ‘givingUpAfter’ is in seconds)
Couldn’t resist a small change:
on chooseFromList(theList, thePrompt, givingUpAfter, orNot)
set theItems to false -- added
tell application "System Events" to set fApp to name of some application process whose frontmost is true
if (fApp is "System Events") then set fApp to "Finder" as Unicode text
tell application "givingUpAfter"
activate
set its fApp to fApp
set its wait to givingUpAfter
end tell
tell application fApp
activate
set theItems to choose from list theList with prompt thePrompt multiple selections allowed orNot
end tell
tell application "givingUpAfter" to quit
return theItems
end chooseFromList
set theItems to chooseFromList({"2", "3", "4"}, "Recent item(s) to open:", 20, true) -- changed
if theItems is not false then display dialog " You chose " & theItems -- changed, and you could go on.
It’s also clear that with minor changes you could deal with a default item in the list and just go ahead with that if no choice was made.
Or what about wrapping the giving up script in a do shell script like this?
tell application "Finder" to set thisApp to (name of (first application process whose frontmost is true))
set givingUpTime to 5
do shell script "osascript -e 'delay " & givingUpTime & " ' -e 'tell application \"System Events\" to tell application process \"" & thisApp & "\" to keystroke \".\" using {command down}' > /dev/null 2>&1 & "
set theItems to choose from list {"2", "3", "4"} with prompt " Recent item(s) to open:" with multiple selections allowed
-- if (theItems is false) then set theItems to {} <- *
if (theItems is false) then
set theItems to {}
end if
Great stuff, thanks you all.
(In Dominiks script I was going to suggest to replace “delay” with “do shell script sleep” to save processor power, but it doesn’t seem to have much effect though)
Thanks for the encouragement. I’m afraid I didn’t understand your caveat “ unless you meant that 8 seconds is rather a short time to make multiple selections from a list. That’s true, of course! I was simply trying to reproduce what I thought might be the intention of the timeout in Eelco’s script above.
Nor do I understand why you’ve preset theItems to ‘false’. It makes no difference.
On the face of it, Dominik’s script saves all the bother, but it does precisely nothing on my machine. I’m sure it does work under the right conditions, though.
Just to do my idea to death, here’s the deluxe version. The handler’s now labelled and takes a list and a record, the record containing the other parameters. The other parameters are optional and are labelled title, prompt, |default items|, |OK button name|, |cancel button name|, |multiple selections allowed|, |empty selection allowed|, and |giving up after|. The barred labels are, unfortunately, case sensitive. The handler returns a record with two properties: choice (whatever’s returned by choose from list) and |gave up| (a boolean). It can be arranged for the return not to include the |gave up| property if the |giving up after| parameter isn’t specified “ similarly to what happens with display dialog “ but I don’t see any point in that.
The handler (with a sample call) is:
on chooseFromList from theList given options:paramRecord
-- Add any unspecified options, with default values.
set paramRecord to paramRecord & {title:"", prompt:"Please make your selection:", |default items|:{}, |OK button name|:"OK", |cancel button name|:"Cancel", |multiple selections allowed|:false, |empty selection allowed|:false, |giving up after|:weeks}
set {t, p, di, okbn, cbn, msa, esa, givingUpAfter} to paramRecord's {title, prompt, |default items|, |OK button name|, |cancel button name|, |multiple selections allowed|, |empty selection allowed|, |giving up after|}
-- Check parameters for validity.
set textTypes to {string, Unicode text}
if not ((count paramRecord) is 8) then error "chooseFromList handler: Bad option name in call."
if not ((t's class is in textTypes) and (p's class is in textTypes) and (di's class is list) and (okbn's class is in textTypes) and (cbn's class is in textTypes) and (msa's class is boolean) and (esa's class is boolean) and (givingUpAfter's class is in {integer, real})) then
error "chooseFromList handler: Bad option value in call."
end if
tell application "System Events" to set fApp to name of first application process whose frontmost is true
if (fApp is "System Events") then set fApp to "Finder" as Unicode text
tell application "Giving up after"
activate
set its fApp to fApp
set its wait to givingUpAfter
end tell
tell application fApp
activate
set theItems to choose from list theList with title t with prompt p default items di OK button name okbn cancel button name cbn multiple selections allowed msa empty selection allowed esa
end tell
tell application "Giving up after"
set gaveUp to its gaveUp
quit
end tell
return {choice:theItems, |gave up|:gaveUp}
end chooseFromList
chooseFromList from {"2", "3", "4"} given options:{prompt:" Recent item(s) to open:", |multiple selections allowed|:true, |giving up after|:20}
The stay-open script application (“Giving up after.app”) that goes with it is:
global wait
global armed
global fApp
global gaveUp
set wait to missing value
set armed to false
set fApp to missing value
set gaveUp to false
on idle
if (wait is missing value) then
-- Just started up. Idle until the wait time's set by the calling handler.
return 0.1 -- May need adjustment.
else if (armed) then
-- Returning from the timed-wait idle. Flag the timeout and disarm.
tell application "System Events"
tell application process fApp
set frontmost to true
keystroke "." using {command down} -- GUI Scripting.
end tell
end tell
set gaveUp to true
set armed to false
return 1
else if not (gaveUp) then
-- The wait time's been set but the wait hasn't happened yet. Initiate it.
set armed to true
return wait
else
-- After giving up, waiting for a 'quit' command from the calling handler.
return 1
end if
end idle
Encouragement always deserved. The caveat arose because (mistakenly) I thought Eelco’s original request had 8 minutes (thinking 8 seconds too short to read the list if it was very long, as you point out). Preset theItems to ‘false’ is a residuum of previous fiddling - not removed when the fiddling changed direction as it made no difference - and I forgot it was there. I shouldn’t post just before I go to bed.
I’ve been looking into this further, as I felt sure Dominik wouldn’t have posted his suggestion without trying it. It turns out that his script does work “ and very effectively “ but it doesn’t run immediately after it’s compiled in (my copy of) Script Editor 2.1.1. I have to click the ‘Run’ button at least twice to get it to do anything at all.
The cause of the problem appears to be a peculiar bug triggered by the very last line of the script:
if (theItems is false) then set theItems to {}
My tests this morning are showing that with any script where:
The last line is a one-line conditional statement;
The line does not end with a quote or a line ending;
The line is preceded by a blank line or is the only line in the script;
. the script doesn’t run at all in Script Editor Script Editor 2.1.1 (81) (AS 1.10.7) immediately after it’s compiled. It does however run on subsequent run attempts, provided it’s not recompiled in the meantime. Similarly, it takes at least two attempts to save the script immediately after it’s compiled.
Is anyone else seeing this? I’m not getting it with Script Editor 2.0 (v36) (AS 1.9.1).
I seached my private ‘AppleScript clip collection’ and found the test script I used for this thread. It has an additional line at the end: ‘get theItems’ which I unfortunately had ommited when copying it to here (not expecting to produce a bug this way …). I made the test and removed the line and can confirm: You are absolutely right. It’s the same bug here with Script Editor 2.1.1 (81). (Have you/will you report/ed the bug to Apple?)
set theItems to false
if (theItems is false) then set theItems to {}
The script above does not seem to be an adequate test, Nigel. When I paste it (as is) into Script Editor 2.1.1 (81), AS 1.10.7 and type command-R to compile and run it, the immediate result is {} for me. (OS X 10.4.7)
If, however, I put anything else in between:
set theItems to false
tell application "System Events" to beep 2
if (theItems is false) then set theItems to {}
Then, it behaves as you describe. Second run brings both the beeps and the {} result.
Dominik’s script behaves exactly as you describe, however, producing a choose from list dialog only on the second click of the ‘Run’ button, and then when nothing is chosen, {}. In Script Debugger 4, it works the first time.
-- Implementing Choose From List dialog with Giving Up After N seconds functionality
-- by KniazidisR
set aList to {" ", "Giving Up: Choose From List Dialog", " ", "by: KniazidisR", " ", "for: common good", " ", "written: 7 August 2022 - 10:18:33 AM", " "}
with timeout of 2 seconds -- equivalent for Giving Up sconds
try
set TimeInt to choose from list aList with title "Intervals" with prompt "What's the Time interval?" default items {item 2 of aList, item 4 of aList} with multiple selections allowed
TimeInt
on error number -2753
tell application "System Events" to tell (1st process whose frontmost is true)
if exists window "Intervals" then click button "OK" of window "Intervals"
end tell
set TimeInt to {item 2 of aList, item 4 of aList}
end try
end timeout