Custom Dialogs with swiftDialog App

I recently learned of the open-source swiftDialog app in a post in the Late Night Software forum (thanks laughingtiger). This app takes a place somewhere between the basic dialogs included with AppleScript and the full-featured dialogs in Shane’s Dialog Toolkit Plus script library. The swiftDialog app is a command-line utility and is generally easy to configure.

A few links:

There are two basic approaches that can be used to display a swiftDialog in an AppleScript. The following are simple examples of stop/proceed dialogs:

--Approach One
try
	do shell script "/usr/local/bin/dialog --title 'Test Title' --titlefont 'size=15' --message 'Test Message' --messagefont size=14 --button2 --width 350 --height 130 --hideicon"
on error
	error number -128
end try

--Approach Two
set userInput to do shell script "/usr/local/bin/dialog --title 'Test Title' --titlefont 'size=15' --message 'Test Message' --messagefont 'size=14' --button2 --width 350 --height 130 --hideicon; echo $?"
if userInput is "2" then error number -128

The following are some additional swiftDialog examples:

--Dialog with three buttons
set userInput to do shell script "/usr/local/bin/dialog --title 'Test Title' --titlefont size=15 --message 'Test Message' --messagefont size=14 --button1text 'Button One' --button2text 'Cancel' --infobuttontext 'Button Two' --width 350 --height 130 --hideicon --moveable; echo $?" --button one returns "0" and button two returns "3"
if userInput is "2" then error number -128

--Timer with one button
set userInput to do shell script "/usr/local/bin/dialog --title 'Test Title' --titlefont size=15 --message 'Test Message' --messagefont size=14 --button1text 'Cancel'  --width 350 --height 150 --hideicon --moveable --timer 8; echo $?"
if userInput is "0" then error number -128

--Text fields
try
	set userInput to do shell script "/usr/local/bin/dialog --title 'Test Title' --titlefont size=15 --message 'Test Message' --messagefont size=14 --button2 --width 320 --height 210 --hideicon --moveable --textfield 'Text One',value='Value One' --textfield 'Text Two',value='Value Two'"
on error
	error number -128
end try
set {TID, text item delimiters} to {text item delimiters, {return, " : "}} --use whatever approach desired
set theTextItems to text items of userInput
set variableOne to item 2 of theTextItems --> "Value One"
set variabletwo to item 4 of theTextItems --> "Value two"
set text item delimiters to TID

The following is an example of a swiftDialog that contains both text fields and check boxes and that uses ASObjC to parse the dialog output:

use framework "Foundation"
use scripting additions

set {searchString, replaceString, searchOption, wordOption, caseOption} to displayDialog()

on displayDialog()
	try
		set userInput to do shell script "/usr/local/bin/dialog --title none --message '' --messagefont size=14 --button2 --width 320 --height 230 --hideicon --moveable --textfield 'Search String',prompt='search for…' --textfield 'Replace String',prompt='replace with…' --checkbox 'Search Entire Script' --checkbox 'Match Whole Words Only' --checkbox 'Make Search Case-Sensitive' --vieworder 'textfield, checkbox'"
	on error
		error number -128
	end try
	set userInput to current application's NSMutableString's stringWithString:userInput
	(userInput's replaceOccurrencesOfString:"(?m)^.* : (.*)$" withString:"$1" options:1024 range:{0, userInput's |length|()})
	return (userInput's componentsSeparatedByString:return) as list
end displayDialog

The swiftDialog app can do a lot of other stuff. I use the following to view markdown tables:

set theFile to POSIX path of (choose file of type {"md"})
do shell script "/usr/local/bin/dialog --title 'Markdown Table Viewer' --titlefont 'size=16' --message " & quoted form of theFile & "  --messagefont 'size=14' --hideicon --position top --windowbuttons --moveable"

BTW, the swiftDialog PKG installer contains 7 MB and the installed executable contains 2 KB. Also, a Dialog folder is created with an application file containing 17.1 MB and a small log file. Other files may be created.

I thought I would add a few miscellaneous notes on issues that I have not been able to resolve. Two of these items are discussed as a known issue and a requested enhancement on the developer’s GitHub site:

  • A font name cannot be set for a dialog message, although font size and color work as expected (known issue).

  • Stdout is not returned when an infobutton is selected. This means that the result returned by textfields, checkboxes, and the like cannot be processed when an infobutton is selected.

  • An AppleScript display dialog dialog expands and contracts to accommodate the specified text message, but a swiftDialog does not.

  • When a dialog contains a textfield, the width of the box in which the text is entered is truncated at about half the width of the entire field (requested enhancement).

JSON can be used to set swiftDialog options (documentation here). For example:

set theJSON to "{ \"title\" : \"Test Title\", \"titlefont\" : \"colour=blue,size=16\", \"message\" : \"Test Message\",  \"messagefont\" : \"size=14\", \"button2\" : true, \"width\" : 350, \"height\" : 130, \"hideicon\" : true}"
set userInput to do shell script "/usr/local/bin/dialog --jsonstring " & quoted form of theJSON & "; echo $?"
if userInput is "2" then error number -128

The JSON options can be set one per line, but the line endings must be a return.

I made a version that uses an AppleScript list that get converted to json string.

set theJSONList to {"title", "Test Title", "titlefont", "colour=blue,size=16", "message", "Test Message", "messagefont", "size=14", "button2", true, "width", 350, "height", 130, "hideicon", true}
set theJSON to get_JSON(theJSONList)
set userInput to do shell script "/usr/local/bin/dialog  --jsonstring " & quoted form of theJSON & "; echo $?"
if userInput is "2" then error number -128

on get_JSON(JSONList)
	local tid, JSON_string
	set JSON_string to {}
	repeat with i from 2 to count JSONList by 2
		if class of item i of JSONList is text then
			set end of JSON_string to "\"" & item (i - 1) of JSONList & "\" : \"" & item i of JSONList & "\""
		else
			set end of JSON_string to "\"" & item (i - 1) of JSONList & "\" : " & (item i of JSONList) as text
		end if
	end repeat
	set tid to text item delimiters
	set text item delimiters to ", "
	set JSON_string to ("{ " & JSON_string as text) & " }"
	set text item delimiters to tid
	return JSON_string
end get_JSON
1 Like

I thought I would add simple examples of dialogs with dropdown lists and radio buttons:

# Dropdown Lists
try
	set userInput to do shell script "/usr/local/bin/dialog --title 'Test Title' --titlefont 'size=15' --message 'Test Message' --messagefont 'size=14' --button2 --width 350 --height 210 --hideicon --selecttitle 'Subtitle 1' --selectvalues 'Item 1,Item 2, Item 3' --selectdefault 'Item 1' --selecttitle 'Subtitle 2' --selectvalues 'Item 1,Item 2, Item 3' --selectdefault 'Item 1'"
on error
	error number -128
end try

# Radio Buttons
try
	set userInput to do shell script "/usr/local/bin/dialog --title 'Test Title' --titlefont 'size=15' --message 'Test Message' --messagefont 'size=14' --button2 --width 350 --height 250 --hideicon --selecttitle 'Test Radio buttons',radio --selectvalues 'Item 1,Item 2, Item 3' --selectdefault 'Item 1'"
on error
	error number -128
end try

A screenshot of a dialog with a dropdown list:

And a dialog with radio buttons:

I was motivated by another recent forum thread to create a working example of what the swiftDialog developer identifies as Updating Dialog with new content. He describes this as:

Some dialog content can be updated on the fly after it has been launched. This is facilitated by sending commands to a command file which Dialog will read and interpret.

Just to understand how this works, I set a goal of changing the dialog icon. So far, I’ve gotten this to work, but it requires two separate scripts, which I run by way of the Script Menu. The following is a screenshot of the first script and the dialog:

The following is the second script with the changed dialog icon:

I have not been able to combine both scripts in one, because when I do so the first do shell script command blocks the second do shell script command. BTW, the developer just released a new version of his app, and it fixes an issue when displaying JPG icons.

Just for brevity, I’ve combined the two scripts in the following:

--These are two separate scripts

--Show Dialog script
try
	do shell script "/usr/local/bin/dialog --title none --message 'Test Message' --messagefont size=14 --button2 --width 650 --height 250 --moveable --icon '/Volumes/Store/Save/One.jpg' --iconsize 350"
on error
	error number -128
end try

--Change icon script
do shell script "/bin/echo 'icon: /Volumes/Store/Save/Two.jpg' >> /var/tmp/dialog.log"

I was able to accomplish this by using NSTask:

use framework "Foundation"
use scripting additions

set imageOne to "/Volumes/Store/Save/One.jpg"
set imageTwo to "/Volumes/Store/Save/Two.jpg"

set commandPath to "/usr/local/bin/dialog"
set commandArguments to {"--title", "none", "--message", "Test Message", "--messagefont", "size=14", "--width", "650", "--height", "250", "--moveable", "--icon", imageOne, "--iconsize", "350"}
set theTask to current application's NSTask's new()
theTask's setLaunchPath:commandPath
theTask's setArguments:commandArguments
set theResult to theTask's launchAndReturnError:(missing value)

delay 3 --just for testing

do shell script "/bin/echo 'icon: " & imageTwo & "' >> /var/tmp/dialog.log"

I belatedly read the do shell script documentation, which yielded the following. A significant issue with this approach is that all output from the dialog is lost.

set imageOne to "/Volumes/Store/Save/One.jpg"
set imageTwo to "/Volumes/Store/Save/Two.jpg"

do shell script "/usr/local/bin/dialog --title none --message 'Test Message' --messagefont 'size=14' --width 650 --height 250 --icon " & imageOne & " --iconsize 350 &> /dev/null &"
delay 3 --just for testing
do shell script "/bin/echo 'icon: " & imageTwo & "' >> /var/tmp/dialog.log"

I had one last use scenario to resolve when updating a swiftDialog with new content, and the use scenario involves canceling a script while a dialog is being updated in a repeat loop. The solution was to get the PID of the dialog when activated and to check its existence in the repeat loop. This script is for proof-of-concept purposes only, because swiftDialog has dedicated timer options.

set dialogPID to do shell script "/usr/local/bin/dialog --title none --message 'The computer will sleep in 10 seconds' --messagefont 'size=14' --width 320 --height 75 --button1text 'Cancel' --hideicon &> /dev/null & echo $!"

repeat with i from 9 to 0 by -1 --update dialog content
	delay 1
	if dialogExists(dialogPID) is false then error number -128 --stop script if dialog not found
	do shell script "/bin/echo 'message: The computer will sleep in " & i & " seconds.' >> /var/tmp/dialog.log"
end repeat

do shell script "/bin/echo 'quit: true' >> /var/tmp/dialog.log" --quit dialog

on dialogExists(dialogPID)
	try
		do shell script "ps -p " & dialogPID & " > /dev/null"
		return true
	end try
	return false
end dialogExists

I wrote a Markdown Viewer script for use with simple text editors, and I thought I’d post a copy here. The script sets the height of the dialog based on the number of words in the selected text, and some testing with the dialogHeight calculation will probably be necessary. A simpler approach is to set the dialog to some acceptable width and height, in which case scroll bars will be shown when necessary.

A screenshot example:

The script:

set the clipboard to ""

tell application "System Events" to tell process "TextEdit"
	set frontmost to true
	keystroke "c" using {command down} --this is the Edit > Copy menu command
end tell

delay 0.1 --test smaller values

set theText to (the clipboard)
if theText is "" then error number -128

set wordCount to count (words of theText)
set dialogHeight to (160 + (wordCount * 2.5)) --test different values
if dialogHeight is less than 300 then set dialogHeight to 300 --set to desired value

do shell script "/usr/local/bin/dialog --title 'Markdown Viewer' --titlefont 'size=16' --message " & quoted form of theText & " --messagefont 'size=16' --width 800 --height " & dialogHeight & " --hideicon --moveable --ontop"

Upon further testing, the above approach doesn’t work very well. The following uses a fixed dialog size and is probably a better approach. A different approach is to set the size of the dialog relative to the size of the text editor window. That’s not possible with swiftDialog as currently written, but I’ve submitted an enhancement request on the developer’s GibHub site.

set the clipboard to ""

tell application "System Events" to tell process "TextEdit"
	set frontmost to true
	keystroke "c" using {command down} --this is the Edit > Copy menu command
end tell

delay 0.1 --test smaller values

set theText to (the clipboard)
if theText is "" then error number -128

do shell script "/usr/local/bin/dialog --title 'Markdown Viewer' --titlefont 'size=16' --message " & quoted form of theText & " --messagefont 'size=16' --width 750 --height 700 --hideicon --moveable --ontop"