Apple Script: Go back-function in menus - applescript

I'm not too versed in AppleScript, so maybe someone can help here:
I have several menus. Let's say, Menu A has three options, these three options lead to Menu B1, B2, B3, depending what options was chosen in Menu A. The B-menus lead to C-menus and so on. I am a bit bothered, that every time I "Cancel", I need to start from scratch. A "Go back"-button would be highly useful in this scenario.
I know that the choose from list-function does only allow Yes and no, so to say, but is it possible to somehow implement a back button?
My code looked like this (we're in Menu B):
if MenuB is false then error number -128 -- user canceled
Then I tried implementing the "Go back" function via:
if MenuB is false then set MenuA to (choose from list {"MenuB1", "MenuB2", "MenuB3"} with prompt "Menu A" default items "None" OK button name {"Go"} cancel button name {"Quit"})
However that creates a new menu and doesn't refer to the menu created before the Menu B script.
Tl;dr: So is it somehow possible to reference to existing menus based on choose from list without creating a new menu. Or is there a way to implement a "back"-button in menus via AppleScript?
Looking forward to your thoughts!
Code excerpt:
# Main Menu
set MainMenu to (choose from list {"Item1", "Item2", "Item3"} with prompt "Main Menu" default items "None" OK button name {"Go"} cancel button name {"Quit"})
if MainMenu is false then error number -128 -- user canceled
# Button 1: Item 1
if MainMenu contains "Item1" then
# Sub-Button 1: Item 1
set SubButton to (choose from list {"Create", "Rename"} with prompt "Settings" default items "None" OK button name {"Choose"} cancel button name {"Quit"})
if SubButton is false then error number -128 -- user canceled
# Sub-Sub-Button 1: Item 1
if SubButton contains "Create" then
(...)

Without knowing the exact number of menus and how they interact, the easiest way would probably be to implement a stack, where items can be pushed onto or popped from the stack depending on the dialog results. Then it is just a matter of organizing and connecting the menus, as it is doubtful you will run out of stack space before running out of patience. Trying to navigate back and forth across nested or interconnected menus by just using a long sequence of if statements leads to the dark side.
To make the menu lists a little more manageable, they can be put into a collection object such as a record, rather than hard coding the dialog statements, with the record keys being used to keep track of the current and previous lists. From each choose from list dialog, the current choice can be pushed onto the top of the stack, and the previous item popped from it if going back.
A little bit of AppleScriptObjC is used in the following example to dynamically look up the record keys, since regular AppleScript doesn’t have a way to do that. The lists contain the various menu items, which are also used to look up the keys for other menu lists - surrounding the record keys with pipes allows them to have spaces and punctuation, so pretty much any text can be used as a key. Using menu item text as record keys also allows circling and jumping around (the example script also demonstrates this), so just be aware when laying out the menu connections. The ultimate end values are kept in a different record, and are single items (string or list). The final result is also used in a choice dialog for verification and to allow going back.
Note that the funky comma placements are used to try to make the record formatting a little easier to read.
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
property stack : {"base"} -- LIFO, starting with key of the base menu
property menus : ¬
{base:{"menu A", "menu B", "menu C"} ¬
, |menu A|:{"subMenu A1", "subMenu A2", "subMenu A3"} ¬
, |menu B|:{"subMenu B1", "subMenu B2", "subMenu B3"} ¬
, |menu C|:{"A key", "A little longer key", "The quick brown fox jumped over the lazy dog's key"} ¬
, |subMenu A1|:{"Action1", "Action2", "Action3"} ¬
, |subMenu A2|:{"Action4", "Action5", "Action6"} ¬
, |subMenu A3|:{"Action7", "Action8", "Action9"} ¬
, |subMenu B1|:{"menu A", "menu B", "menu C"} ¬
, |subMenu B2|:{"subMenu A1", "subMenu A2", "subMenu A3"} ¬
, |subMenu B3|:{"A key", "A little longer key", "The quick brown fox jumped over the lazy dog's key"} ¬
, |A key|:{"Action1", "Action2", "Action3"} ¬
, |A little longer key|:{"Action4", "Action5", "Action6"} ¬
, |The quick brown fox jumped over the lazy dog's key|:{"Action7", "Action8", "Action9"}}
property actions : ¬
{Action1:"Action one", Action2:"Action two", Action3:"Action three", Action4:"Action four", Action5:"Action five", Action6:"Action six", Action7:"Action seven", Action8:"Action eight", Action9:"Action 9"}
on run -- example
set menuDict to current application's NSDictionary's dictionaryWithDictionary:menus
set actionDict to current application's NSDictionary's dictionaryWithDictionary:actions
set cancelName to "Quit"
repeat
set tos to first item of stack
set choices to (menuDict's objectForKey:tos) -- get menu
if choices is missing value then -- handle missing key
log "menu key " & quoted form of tos & " not found, trying actions"
set choices to (actionDict's objectForKey:tos) -- try actions
if choices is missing value then log "actions key " & quoted form of tos & " not found"
end if
set theChoice to (choose from list (choices as list) default items {"msng"} cancel button name cancelName) -- select missing value
if theChoice is false then
if (count stack) is 1 then error number -128 -- cancel
set stack to rest of stack -- pop
if (count stack) is 1 then set cancelName to "Quit" -- last one
else
if (count (choices as list)) is 1 then exit repeat -- final action result selected
set beginning of stack to theChoice as text -- push
set cancelName to "Go Back"
end if
end repeat
#showResult(stack, actionDict) -- uncomment to show the result
runWorkflow(((actionDict's objectForKey:(first item of stack)) as text))
end run
to showResult(stack, dictionary) -- show the final result action
set {tempTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, " > "}
set keys to (reverse of stack) as text
set AppleScript's text item delimiters to tempTID
set value to quoted form of ((dictionary's objectForKey:(first item of stack)) as text)
if value is quoted form of "missing value" then set value to value & return & return & "A menu or actions key was not found - see log."
display dialog "Key sequence: " & keys & return & "Final value: " & value with title "Final Result" buttons "OK" default button 1
end showResult
to runWorkflow(workflowName) -- run an Automator workflow
set basepath to POSIX path of (path to desktop) -- change as desired
set workflowpath to basepath & workflowName -- POSIX path/name to add to the basePath
set command to "/usr/bin/automator " & quoted form of workflowpath
set output to (do shell script command)
-- do whatever with the output
end runAction

Related

How can I pass / paste multiple variables in Automator as text in Google Chrome

Creating an cal event application that runs every day and asks for my self reported happiness and stress levels. Having issues with the final applescript, which I can't make work no matter what I try. The closest I got was with
here's a basic layout of what I'm trying to do
I know this might be basic, so I very much appreciate everyone's help!
on run {input, parameters}
set hello to item 1 of input as text
tell application "System Events" to keystroke hello
end tell
return input
end run
You don't mention exactly what you are going to be doing with the values in these variables, but you can get the value of your workflow variables by name in the AppleScript action, for example:
value of variable "Happy" of front workflow as text -- or integer, or whatever
Note that the Set Value of Variable action will output the variable value, which in this case will be used by the following Ask for Text action, so you can use the Ignore Input option to keep the previous results from being used. Your example workflow would then be something like:
Ask for Text { Question: Happy (1-10) }
Set Value of Variable { Variable: Happy }
Ask for Text { Question: Stressed (1-10) } (Ignore Input)
Set Value of Variable { Variable: Stressed }
Run AppleScript (note that the variables are coerced to text when joining to other text):
on run {input, parameters}
set happyString to "Level of happiness: " & (value of variable "Happy" of front workflow)
set stressedString to "Level of stress: " & (value of variable "Stressed" of front workflow)
display dialog happyString & return & stressedString
end run
Maybe this AppleScript code will work for you, without having to add variables to your Automator workflow
property numberList : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
set happyNumber to (choose from list numberList ¬
with title "Happiness And Stress Levels" with prompt ¬
"Choose Your Happiness Level" default items 5 ¬
OK button name "Continue" cancel button name ¬
"Cancel" multiple selections allowed false ¬
without empty selection allowed) as integer
if happyNumber is 0 then return
set happyLevel to "Your Happiness Level Is " & happyNumber
set stressNumber to (choose from list numberList ¬
with title "Happiness And Stress Levels" with prompt ¬
"Choose Your Stress Level" default items 5 ¬
OK button name "Continue" cancel button name ¬
"Cancel" multiple selections allowed false ¬
without empty selection allowed) as integer
if stressNumber is 0 then return
set stressLevel to "Your Stress Level Is " & stressNumber
(* Just Un-Comment The Next 2 Lines When You Are Ready To Use Them *)
--tell application "System Events" to keystroke happyLevel
--tell application "System Events" to keystroke stressLevel

Applescript using if then statements and choose from list

I'm working on a course selection script and each person should only be able to select three courses. I am having issues figuring out how to repeat choose from list if the user selects less than or more than three courses, and only proceed if three choices are selected. Converting the choices from the list to string should work however nothing happens after choosing from list
set theClassList to {"Math ", "English ", "Science ", "I&S ", "Design "}
repeat
set theClass to choose from list theClassList with prompt "Select three courses" with multiple selections allowed
set theClassString to theClass as string
if words in theClassString ≠ 3 then
display dialog "Please select three courses"
exit repeat
else if words in theClassString = 3 then
display dialog "Ok"
end if
end repeat
It's a clever idea to count the words in the theClassString (which would be done using number of words in theClassString, instead of simply words in theClassString). This would work the majority of the time, until a user includes "I&S" as one of their options, which sadly counts as two words, "I" and "S", since the ampersand is not a word character.
You also had your exit repeat in the wrong half of the if...then...else block, since you want to break the loop when the user selects 3 courses, not when they fail to select 3 courses.
Rather than attempting to coerce the result of the list selection into a string, you ought to just count the number of items in the result, which can be done in one of three ways:
count theClass
length of theClass
number in theClass
Here's a reworked version of your script:
property theClassList : {"Math ", "English ", "Science ", "I&S ", "Design "}
property text item delimiters : linefeed
set choices to missing value
repeat
if choices ≠ missing value then display dialog ¬
"You must select precisely three courses"
set choices to choose from list theClassList with prompt ¬
"Select three courses" with multiple selections allowed
if choices = false or the number of choices = 3 then exit repeat
end repeat
if choices = false then return
display dialog choices as text
...And here's a version that uses a recursive handler instead of a repeat loop:
property theClassList : {"Math", "English", "Science", "I&S", "Design"}
property text item delimiters : linefeed
to choose()
tell (choose from list theClassList ¬
with prompt ("Select three courses") ¬
with multiple selections allowed) to ¬
if it = false or its length = 3 then ¬
return it
display dialog "You must select precisely three courses"
choose()
end choose
display alert (choose() as text)
to make it easier by respecting the fluidity of your script. It would be better to declare your "theClass" as a rather string list and declare n as count of list. Below is your modified script or the following that takes your own but declaring n to count of words in "theClassString".`
set theClassList to {"Math", "English", "Science", "I & S", "Design"}
repeat
set theClass to choose from list theClassList with prompt "Select three courses" with multiple selections allowed
set theClassString to theClass as list
set n to count of theClassString
set n to do shell script "echo" & n
if n ≠ "3" then
display dialog "Please select three courses"
exit repeat
else if n = "3" then
display dialog "Ok"
end if
end repeat
Below
By declaring String
set theClassList to {"Math ", "English ", "Science ", "I&S ", "Design "}
repeat
set theClass to choose from list theClassList with prompt "Select three courses" with multiple selections allowed
set theClassString to theClass as string
set n to count of words in theClassString
set n to do shell script "echo " & n
if n ≠ "3" then
display dialog "Please select three courses"
exit repeat
else if n = "3" then
display dialog "Ok"
end if
end repeat
um maybe like this
set theClassList to {"Math ", "English ", "Science ", "I&S ", "Design "}
repeat
set theClass to choose from list theClassList with prompt "Select three courses" with multiple selections allowed
set theClassString to theClass as string
set n to count of words in theClassString
set n to do shell script "echo " & n
if n ≠ "3" then
display dialog "Please select three courses" buttons {"ok"}
else if n = "3" then
display dialog "Ok"
exit repeat
end if
end repeat

How to update AppleScript List "Forever"

Look at the following code:
set TheStringsQ1Happy to {"Fabulous", "Great", "Alright", "Excited", "Not Bad", "", "Decent", "Fine", "Awesome", "Bored", "Cool", "Sad", "Fantastic", "Alright", "Good", "Ok"}
set theResponse to the text returned of (display dialog "" default answer "" giving up after 5)
if TheStringsQ1Happy contains theResponse then
display dialog "That's Great!"
else
say "That term is not in my vocabulary. Would you like me to add it?" using "Tom" speaking rate 220
set theResponseNotInVocabulary to text returned of (display dialog "" default answer "" giving up after 5)
if theResponseNotInVocabulary is "Yes" then
set end of TheStringsQ1Happy to theResponse
return TheStringsQ1Happy
end if
Although I can update TheStringsQ1Happy, this update only lasts the span of the script. How can I change the code so that every time I run the script, it also contains the updated vocabulary?
For example, if I said "All Good", the computer would recognize that the vocabulary is not on the list, and would later update this list only for that instance. How can I make it so that "All Good" stays for every instance from now on?
The following is strictly an example to help you with what you asked, not fix the broken code you posted.
If you run the following in Script Editor:
property theList : {1, 2, 3}
copy (count theList) + 1 to end of theList
log theList
You'll see theList as a property grow by 1 each time you run it, that is until the script is recompiled.
If you need absolute long term storage where nothing will be lost of anything added to theList, then you need to save to and retrieve from a disk file.
Variables in AppleScript don't span outside the duration of execution of the script in which they are defined, as you've quite rightly noticed.
However, a property can, and will continue into subsequent executions of a script with the information left over from the previous execution.
Bear in mind, though, that a property will get reset (restored to its original value) each time the script is re-compiled (which happens whenever you make edits to it, or trigger it to compile manually).
With this in mind, change this line:
set TheStringsQ1Happy to {"Fabulous", "Great", "Alright", "Excited", "Not Bad", "", "Decent", "Fine", "Awesome", "Bored", "Cool", "Sad", "Fantastic", "Alright", "Good", "Ok"}
to this:
property TheStringsQ1Happy : {"Fabulous", "Great", "Alright", "Excited", "Not Bad", "", "Decent", "Fine", "Awesome", "Bored", "Cool", "Sad", "Fantastic", "Alright", "Good", "Ok"}
and you'll be good to go.
If you want a more permanent way to ensure you don't accidentally lose the new additions to this property, such as when you need to make any changes to the script in the future that will reset its value, then you'll need to store the information in an external file, which will serve as a sort of "dictionary" of vocabulary terms.
The simplest way to do this is to create a text file and put each item of the list on its own line, like this:
Fabulous
Great
Alright
Excited
...etc.
Save it as something like HappyTerms.txt, somewhere like your Documents folder, then change the variable declaration for TheStringsQ1Happy to this:
set TheStringsQ1Happy to the paragraphs of (read "/Users/%you%/Documents/HappyTerms.txt")
replacing %you% with the name of your Home folder in which your Documents folder lives. In fact, it's a useful idea to put the path to this text file in its own variable definition just beforehand:
set VocabDictionary to "/Users/%you%/Documents/HappyTerms.txt"
set TheStringsQ1Happy to the paragraphs of (read VocabDictionary)
Finally, to make changes to this file and add new terms to it, immediately after this line:
if theResponseNotInVocabulary is "Yes" then set end of TheStringsQ1Happy to theResponse
simply add either these lines:
set AppleScript's text item delimiters to return
write (TheStringsQ1Happy as text) to VocabDictionary starting at 1
OR this line
write "\n" & theResponse to VocabDictionary starting at (get eof VocabDictionary) + 1
The first version overwrites the entire file with all the terms in the new list. The second version simply appends the new addition to the end of the file. You might want to experiment with both and get the file turning out the way you want it to, as you'll sneakily discover that one might give you a stray blank line in the file that results harmlessly in an empty string "" appearing in your list; whilst the other does not; but I'll leave you to figure out if and why this happens, and—should you really want it not to happen—how to prevent it. 🙃 Either way, it shouldn't cause you any problems.
If you have any other queries, post a comment and I'll back to you. If this is helpful, don't forget to +1 my answer, and mark it as "correct" if solves your problem for you.
This works for me using the latest version of Sierra
If this script is saved as an application,and you launch this new app... every time a new Item is added to the list, that new item will remain in the list every time you reopen the application. However if you open the application again in script editor,to edit the code and re-save... You will lose all of the values of the added list items And the whole cycle starts over again with the default list.
property TheStringsQ1Happy : {"Fabulous", "Great", "Alright", "Excited", "Not Bad", "", "Decent", "Fine", "Awesome", "Bored", "Cool", "Sad", "Fantastic", "Alright", "Good", "Ok"}
set theResponse to (display dialog "How Do You Feel Today?" default answer "" giving up after 25)
if text returned of theResponse is in TheStringsQ1Happy then
display dialog "That's Great!"
else
say "That term is not in my vocabulary. Would you like me to add it?" using "Tom" speaking rate 220
set theResponseNotInVocabulary to display dialog "Add This Term" & " " & quote & text returned of theResponse & quote & " " & "To My Vocabulary?" buttons {"No", "Yes"} default button 2
if the button returned of the result is "Yes" then
if TheStringsQ1Happy does not contain text returned of theResponse then
set end of TheStringsQ1Happy to text returned of theResponse
end if
return TheStringsQ1Happy
end if
end if

Applescript Returned Text not Splitting

Ive been playing with applescript for about 2 weeks now, but I have hit a problem
Im trying to create an applescript that reads all the names of the folders on our server.
Then displays them in a drop down menu so that I can select the client.
The problem I have is that it is displaying the result as one selection option as a big sentence and is not separating each client, so they can be selected individually.
so far I have:
set theFolder to alias "server:"
tell application "Finder"
set theText to name of theFolder & return
set k to 0
repeat with thisSubfolder in (get folders of theFolder)
set k to k + 1
set theText to theText & name of thisSubfolder & return
end repeat
end tell
set l to {theText}
set l2 to ""
repeat with i in l
set l2 to l2 & quoted form of i & " "
end repeat
do shell script "/Applications/CocoaDialog.app/Contents/MacOS/CocoaDialog \\
standard-dropdown --title Title --text Text --items " & l2
set {button, answer} to paragraphs of result
if button is 1 then
return item {answer + 1} of l
end if
Many thanks
D
When you do this:
set l to {theText}
You're just creating a list of one item (your string), which means you end up with this:
{"theFolder
folder1
folder2
folder3
"}
You're then repeating in that "list," trying to add spaces between the items. But, you don't have a list really. You have a one item list, with return-delimited strings.
The best way to get a list of folder names would be to get them from System Events. Notice that in this case, you have to create a list with the name of the first folder as the only item. Otherwise, the & operation will join everything together as a string, instead of creating a list.
tell application "System Events"
set l to (name of theFolder as list) & name of folders of theFolder
end tell
There are also some syntactical issues that will hurt you later:
1 != "1"
CocoaDialog returns a string, with the button number: "1". You are using if button is 1. For equality, it should be if button is "1".
Parentheses are used for grouping, not brackets
If button is "1", you are returning item {answer + 1} of l. I blame Applescript for letting this work when it shouldn't. You're actually creating a list with a number, which then gets coerced by Applescript for the list index. Here are all the steps, assuming answer is 0:
item {answer + 1} of l gets turned into
item {1} of {folder1, folder2, folder3}
Applescript coerces to item 1 of {folder1, folder2, folder3}
Value returned: folder1
Here is a fully updated version of your script:
set theFolder to alias "server:"
tell application "System Events"
set l to {name of theFolder} & name of folders of theFolder
end tell
set args to ""
repeat with i from 1 to (count l)
set args to args & quoted form of item i of l
if i < (count l) then
set args to args & " "
end if
end repeat
do shell script "/Applications/CocoaDialog.app/Contents/MacOS/CocoaDialog \\
standard-dropdown --title Title --text Text --items " & args
set {button, answer} to paragraphs of result
if button is "1" then
return item (answer + 1) of l
end if
Change the line:
set l to {theText}
to:
set l to paragraphs of theText
and you should be good to go.

Multiple Criteria for If statement in AppleScript

I'm attempting to modify an applescript that fires a growl notification when there is a new message in Outlook. The original script is here.
In my if statement, I am trying to say that if the folder is either Deleted Items, Junk E-mail or Sent Items, don't fire the notification.
Here is the statement:
if folder of theMsg is "Junk E-mail" or "Deleted Items" or "Sent Items" then
set notify to false
else
set notify to true
end if
It appear that applescript does not like the multiple is/or items I've added. Is there a way to include the multiple criteria, or do I need to write a nested if/then?
The correct way to chain if conditions in AppleScript is to repeat the full conditional:
if folder of theMsg is "A" or folder of theMsg is "B" or folder of theMsg is "C" then
– there is no implicit repetition of the left hand argument. A more elegant way to do this is to compare your left hand argument to a list of items:
if folder of theMsg is in {"A", "B", "C"} then
which has the same effect (note this relies on the implicit coercion of text to list, which, depending on your tell context, may fail. In that case, explicitly coerce your left, i.e. (folder of theMsg as list)).
When including multiple criteria in your conditional statements, you must rewrite the entire conditional. This can be extremely tedious at times, but that's just the way AppleScript works. Your expression would become the following:
if folder of theMsg is "Junk E-mail" or folder of theMsg is "Deleted Items" or folder of theMsg is "Sent Items" then
set notify to false
else
set notify to true
end if
There is a workaround, though. You can initialize all of your criteria into a list and see if your list contains a match:
set the criteria to {"A","B","C"}
if something is in the criteria then do_something()
Having come accross this post by Googling "applescript if multiple conditions" and didn't came accross the code snippet I was expecting here is what's I've done (just for informative purposes) :
You could also recursively scan multiple conditions. The following example is :
— Looking if the sender e-mail address contains (Arg 1.1) something (Arg 2.1.1 and 2.1.2) to immediately stop the script and "notify" => true (Arg 3.1).
— Looking if the folder/mailbox (Arg 1.2) starts with "2012" (Arg 2.2.1) but is not folder 2012-A B or C (Arg 2.2.2) if it doesn't start with 2012 or is contained in one of the 3 folders stop and do nothing => false (Arg 3.2).
if _mc({"\"" & theSender & " \" contains", "\"" & (name of theFolder) & "\""}, {{"\"#me.com\"", "\"Tim\""}, {"starts with \"2012\"", "is not in {\"2012-A\", \"2012-B\", \"2012-C\"}"}}, {true, false}) then
return "NOTIFY "
else
return "DO NOTHING "
end if
-- multiple conditions comparaison via a shell script
on _mc(_args, _crits, _r)
set i to 0
repeat with _arg in _args
set i to i + 1
repeat with _crit in (item i of _crits)
if (item i of _r) as text is equal to (do shell script "osascript -e '" & (_arg & " " & _crit) & "'") then
return (item i of _r)
end if
end repeat
end repeat
return not (item i of _r)
end _mc
https://developer.apple.com/library/mac/#documentation/AppleScript/Conceptual/AppleScriptLangGuide/conceptual/ASLR_about_handlers.html#//apple_ref/doc/uid/TP40000983-CH206-SW3
Try:
repeat with theMsg in theMessages
set theFolder to name of theMsg's folder
if theFolder is "Junk E-mail" or theFolder is "Deleted Items" or theFolder is "Sent Items" then
set notify to false
else
set notify to true
end if
end repeat
Although the other two answers solve the multiple criteria correctly, they won't work unless you specify name of theMsg's folder or you will get
mail folder id 203 of application "Microsoft Outlook"

Resources