Soon after this article about ICNS to PNG conversion was published by Rob Griffiths on Macworld, a good friend of mine called me to ask if I could write an AppleScript wrapper for the mentioned «sips» command, as he needed some advanced features to simplify his daily work life as a stressed Mac admin.
Yes, he knew that their already existed scripts and apps for this purpose…
Yes, he knew that I was busy with other projects…
Yes, he knew that I simply cannot deny a request from a good friend
And so here it is, the result of one week of coding in the evenings after work:
iconoodle - Convenient ICNS to PNG conversion (v0.7b, ca. 68 KB)
So now you might say: “Hey Martin, what exactly are those advanced features of your ‘yet another ICNS to PNG conversion script’?”
OK, here you are:
Cool and convenient features of iconoodle
¢ You can not only drag & drop ICNS files, but also applications onto iconoodle’s icon and it will convert all ICNS files found in the application package folder (cautious clapping)
¢ You can set a default archive folder for the created PNG files (growing enthusiasm)
¢ You can execute post action scripts to further process the created PNG files (giggle of the geeks), sample post actions are included with the download
¢ Preferences panel to adjust the settings (the mob goes wild!)
Requirements
¢ Mac OS X 10.4.10 (might also run on older systems)
Usage
After you installed iconoodle on your Mac - just drop it anywhere you like, dock, desktop, folder bar, you name it - you can use it in exactly two ways:
-
Drag and drop ICNS files and/or applications bundles onto iconoodle’s icon to start the ICNS to PNG conversion process. Or drag a single folder onto iconoodle’s icon to set this folder as the default archive folder for created PNG files.
-
Start iconoodle by double-clicking its icon to display the preferences panel. Here you can set/reset the default archive folder and add/remove post action scripts.
Post actions
To further enhance the iconoodle experience, you can also execute various (self-written) post action scripts to process the created PNG files. Two sample post action scripts are included with the download.
The first script, named «openpngs.scpt», will open all created PNG files in the default viewer application. The second script, named «mailpngs.scpt», will create a new eMail message in Apple Mail with the created PNG files attached.
Please have a look at the source code of those two AppleScripts to see how easy it is to write your own post action scripts, if you are familiar with AppleScript.
To add or remove post actions, just open the preferences panel and select the corresponding menu items.
FAQ
Q: Who came up with the lame name of the script?
A: I own this book and am a follower. Moreover I am absolutely blown away by this costume.
-- created: October 2007
-- modified:
-- version: 0.7b
-- history:
-- ¢ v0.7b:
-- + first public beta release
-- =====================
-- == CORE PROPERTIES ==
-- =====================
-- what a silly name for a script :)
-- DON'T INSULT OUR GOD, THE FLYING SPAGHETTI MONSTER!!!
property mytitle : "iconoodle"
property domain : ("com.jos." & mytitle)
-- =========================
-- == START MODE HANDLERS ==
-- =========================
on open dropitems
my main("on open", dropitems)
end open
on run
my main("on run", missing value)
end run
-- ==================
-- == MAIN ROUTINE ==
-- ==================
on main(startmode, args)
try
-- first of all: initializing the preferences system
my userdefaults's setdomain(domain)
-- user started the script by double-clicking its icon
-- -> display preferences panel
if startmode is "on run" then
my guiloop()
-- user started the script by dropping items onto its icon
-- -> process dropped items
else if startmode is "on open" then
set dropitems to args
set dropapps to {}
set dropicons to {}
set dropdirs to {}
-- screening dropped items for relevant item kinds (apps, folders, icns files)
repeat with dropitem in dropitems
set dropitem to dropitem as alias
set iteminfo to info for (dropitem as alias)
-- searching for dropped applications
if package folder of iteminfo is true and name extension of iteminfo is "app" then
set dropapps to dropapps & dropitem
-- searching for dropped icns files
else if folder of iteminfo is false and name extension of iteminfo is "icns" then
set dropicons to dropicons & dropitem
-- searching for dropped folders
else if folder of iteminfo is true and package folder of iteminfo is false then
set dropdirs to dropdirs & dropitem
end if
end repeat
-- surveying the booty...
set countdropicons to length of dropicons
set countdropapps to length of dropapps
set countdropdirs to length of dropdirs
-- no icns files, noapps, no folders...
if countdropicons is 0 and countdropapps is 0 and countdropdirs is 0 then
set infomsg to "Sorry, but we could not detect any processable items." & return & return & my getusageinfo()
my dspinfomsg(infomsg)
return
-- no icns files, no apps, no scripts, but more than 1 folder...
else if countdropicons is 0 and countdropapps is 0 and countdropdirs is greater than 1 then
set infomsg to "Sorry, you dropped " & countdropdirs & " folders onto the script." & return & return & "If you want to set the default archive folder, please just drop a single folder onto " & mytitle & "'s icon."
my dspinfomsg(infomsg)
return
-- no icns files, no apps, but 1 folder was dropped onto iconoodle -> archive folder
else if countdropicons is 0 and countdropapps is 0 and countdropdirs is 1 then
-- Mac path! ':'
set archivefolder to (item 1 of dropdirs) as Unicode text
my userdefaults's setpref("archivefolder", archivefolder)
set infomsg to "Your default archive folder is now located at:" & return & return & "'" & archivefolder & "'"
my dspinfomsg(infomsg)
return
end if
-- getting the archive folder...
-- archive folder is a Mac path! ':'
if my userdefaults's prefsfileexists() is false then
-- no preferences file yet...so we have to ask the user for the archive folder
set archivefolder to (my askforarchivefolder() as Unicode text)
else
-- preferences file exists and default archive folder is set
if my userdefaults's haspref("archivefolder") is true then
set archivefolder to my userdefaults's getpref("archivefolder")
-- preferences file exists but default archive folder is NOT set
else
set archivefolder to (my askforarchivefolder() as Unicode text)
end if
end if
-- container for the gathereed icns files
set icnsfiles to {}
-- dropped icns files are added directly to the list of icns files
if countdropicons is greater than 0 then
set icnsfiles to icnsfiles & dropicons
end if
-- searching the application bundles for icns files
-- adding found icns files to the list of icns files
repeat with dropapp in dropapps
set icnsfiles to icnsfiles & my searchicnsfiles(dropapp)
end repeat
-- getting filepaths for the new png files, then:
-- icns > png conversion process
set converrcounter to 0
set converrmsgs to ""
repeat with icnsfile in icnsfiles
set fileinfo to info for icnsfile
set filename to (characters 1 through -6 of (name of fileinfo)) as Unicode text
-- Posix path! '/'
set pngpath to POSIX path of (my getunusedfilepath(archivefolder, filename, "png"))
try
my icns2png(POSIX path of icnsfile, pngpath)
on error errmsg number errnum
set converrcounter to converrcounter + 1
set converrmsgs to converrmsgs & errmsg & " (" & errnum & ")" & return
end try
end repeat
set pacterrcounter to 0
set pacterrmsgs to ""
-- executing post actions if available
-- WALLA WALLA! We should check existence of postactions!
if my userdefaults's prefsfileexists() is true and my userdefaults's haspref("postactions") is true then
-- Mac paths! ':'
set postactions to my userdefaults's getpref("postactions")
set icnspaths to ""
repeat with icnsfile in icnsfiles
set icnspaths to icnspaths & " " & quoted form of (POSIX path of (icnsfile as Unicode text))
end repeat
repeat with postaction in postactions
try
set command to ("osascript " & quoted form of (POSIX path of postaction) & icnspaths) as «class utf8»
do shell script command
on error errmsg number errnum
set pacterrcounter to pacterrcounter + 1
set pacterrmsgs to pacterrmsgs & errmsg & " (" & errnum & ")" & return
end try
end repeat
end if
-- of course: I don't hope for many errors, but in case they occur,
-- we should inform the user accordingly...
if converrcounter > 0 or pacterrcounter > 0 then
set errmsg to "Sorry, some errors occured:" & return & return & "Conversion errors: " & converrcounter & return & "Post action errors: " & pacterrcounter
set errlog to "Conversion errors:" & return & converrmsgs & return & "Post action errors:" & return & pacterrmsgs
tell me
activate
display dialog errmsg buttons {"Show log", "OK"} default button 2 with icon caution with title mytitle
set choice to result
if button returned of choice is "Show log" then
tell application "TextEdit"
make new document with properties {text:errlog}
end tell
end if
end tell
end if
end if
on error errmsg number errnum
-- ignoring 'User canceled'-error
if errnum is not -128 then
my dsperrmsg(errmsg, errnum)
end if
end try
end main
-- ================
-- == ICNS TOOLS ==
-- ================
-- returns icns files found in the application package folder
-- application must be passed as an alias
-- returns the icns files as a list of aliases
on searchicnsfiles(appalias)
set icnsfiles to {}
set apppath to (POSIX path of appalias)
set command to ("find " & quoted form of apppath & " -name '*.icns'") as «class utf8»
set icnspaths to paragraphs of (do shell script command)
repeat with icnspath in icnspaths
set icnsfiles to icnsfiles & (POSIX file icnspath)
end repeat
return icnsfiles
end searchicnsfiles
-- converts an icns file to a png file
-- icns source and png destination must be passed as a Posix path
on icns2png(icnspath, pngpath)
set command to ("sips -s format png " & quoted form of icnspath & " --out " & quoted form of pngpath) as «class utf8»
do shell script command
end icns2png
-- ========================
-- == GUI CONTROL CENTER ==
-- ========================
-- the main GUI loop displaying the preferences panel
-- and coordinating actions triggered by chosen menu items
on guiloop()
try
set menuitem to my dspprefsmenu()
if menuitem is missing value then
return
else
if menuitem is "Show archive folder" then
my showarchivefolder()
else if menuitem is "Set archive folder" then
my setarchivefolder()
else if menuitem is "Reset archive folder" then
my resetarchivefolder()
else if menuitem is "Add post action(s)" then
my addpostactions()
else if menuitem is "Show post action(s)" then
my showpostactions()
else if menuitem is "Remove post action(s)" then
my removepostactions()
else if menuitem is "eMail feedback" then
my emailfeedback()
else if menuitem is "Visit JoS" then
my visitjoswebsite()
end if
end if
my guiloop()
on error errmsg number errnum
if errnum is -128 then
my guiloop()
else
my dsperrmsg(errmsg, errnum)
end if
end try
end guiloop
-- displays the preferences menu and returns the chosen menu item
on dspprefsmenu()
set menuitems to {"== Misc ==", "eMail feedback", "Visit JoS"}
if my userdefaults's prefsfileexists() is false then
set menuitems to {"== Archive folder ==", "Set archive folder", "", "== Post Actions ==", "Add post action(s)", ""} & menuitems
else
if my userdefaults's haspref("postactions") is false then
set menuitems to {"== Post Actions ==", "Add post action(s)", ""} & menuitems
else
set menuitems to {"== Post Actions ==", "Show post action(s)", "Add post action(s)", "Remove post action(s)", ""} & menuitems
end if
if my userdefaults's haspref("archivefolder") is false then
set menuitems to {"== Archive folder ==", "Set archive folder", ""} & menuitems
else
set menuitems to {"== Archive folder ==", "Show archive folder", "Set archive folder", "Reset archive folder", ""} & menuitems
end if
end if
choose from list menuitems with title mytitle with prompt "Please choose an option:" cancel button name "Quit" OK button name "Select" without multiple selections allowed and empty selection allowed
set choice to result
if choice is not false then
set menuitem to item 1 of choice
if menuitem begins with "==" or menuitem is "" then
my dspprefsmenu()
else
return menuitem
end if
else
return missing value
end if
end dspprefsmenu
-- ================================
-- == SUBROUTINES FOR MENU ITEMS ==
-- ================================
-- shows the set archive folder in the Finder
on showarchivefolder()
set archivefolder to POSIX path of (my userdefaults's getpref("archivefolder"))
set command to ("open " & quoted form of archivefolder) as «class utf8»
do shell script command
end showarchivefolder
-- asks for and sets the archive folder
on setarchivefolder()
set archivefolder to (my askforarchivefolder()) as Unicode text
my userdefaults's setpref("archivefolder", archivefolder)
end setarchivefolder
-- resets the current archive folder
on resetarchivefolder()
my userdefaults's rempref("archivefolder")
end resetarchivefolder
-- shows the parent folder of selected post actions in the Finder
on showpostactions()
set postactions to my dsppostactions()
if postactions is missing value then
return
else
repeat with postaction in postactions
set pardirpath to my getpardirpath((postaction as Unicode text))
set pardirpath to POSIX path of pardirpath
set command to ("open " & quoted form of pardirpath) as «class utf8»
do shell script command
end repeat
end if
end showpostactions
-- adds new post actions
on addpostactions()
set scriptfiles to choose file with prompt "Please choose scripts for post actions:" of type {"osas"} with multiple selections allowed without showing package contents and invisibles
set newpostactions to {}
repeat with scriptfile in scriptfiles
set newpostactions to newpostactions & (scriptfile as Unicode text)
end repeat
if my userdefaults's haspref("postactions") then
set existpostactions to my userdefaults's getpref("postactions")
repeat with newpostaction in newpostactions
if newpostaction is not in existpostactions then
set existpostactions to existpostactions & newpostaction
end if
end repeat
my userdefaults's setpref("postactions", existpostactions)
else
my userdefaults's setpref("postactions", newpostactions)
end if
end addpostactions
-- removes post actions
on removepostactions()
set chosenpostactions to my dsppostactions()
if chosenpostactions is missing value then
return
else
set existpostactions to my userdefaults's getpref("postactions")
set remainpostactions to {}
repeat with existpostaction in existpostactions
if existpostaction is not in chosenpostactions then
set remainpostactions to remainpostactions & existpostaction
end if
end repeat
if remainpostactions is {} then
my userdefaults's rempref("postactions")
else
my userdefaults's setpref("postactions", remainpostactions)
end if
end if
end removepostactions
-- creates a new email message with my address in the default mail app
on emailfeedback()
set command to "open 'mailto:martin@joyofscripting.com'" as «class utf8»
do shell script command
end emailfeedback
-- opens the Joy of Scripting website in the default browser
on visitjoswebsite()
set command to "open 'http://www.joyofscripting.com'" as «class utf8»
do shell script command
end visitjoswebsite
-- =============================
-- == MISCELLANEOUS UTILITIES ==
-- =============================
-- displays a list of available post actions
-- returns the file path of the chosen post action
-- returns a Mac path! ':'
on dsppostactions()
set postactionids to {}
set postactionnames to {}
set postactionpaths to {}
-- FUTURE: the «postactions» should be sorted alphabetically (python script cmdline?)
set postactions to my userdefaults's getpref("postactions")
set idcounter to 0
repeat with postaction in postactions
set idcounter to idcounter + 1
set olddelims to AppleScript's text item delimiters
set AppleScript's text item delimiters to {":"}
set txtitems to text items of postaction
set AppleScript's text item delimiters to olddelims
set postactionname to "[" & idcounter & "] " & last item of txtitems
set postactionids to postactionids & idcounter
set postactionnames to postactionnames & postactionname
set postactionpaths to postactionpaths & postaction
end repeat
choose from list postactionnames with title mytitle with prompt "Please choose post actions to process:" OK button name "Select" cancel button name "Cancel" with multiple selections allowed without empty selection allowed
set choice to result
if choice is not false then
set chosenpostactions to {}
set menuitems to choice
repeat with menuitem in menuitems
set rightbracketoffset to offset of "]" in menuitem
if rightbracketoffset is equal to 3 then
set menuitemid to (character 2 of menuitem) as integer
else
set menuitemid to ((characters 2 through (rightbracketoffset - 1) of menuitem) as Unicode text) as integer
end if
repeat with i from 1 to (length of postactionids)
set postactionid to item i of postactionids
if menuitemid is equal to postactionid then
set postactionpath to item i of postactionpaths
set chosenpostactions to chosenpostactions & postactionpath
end if
end repeat
end repeat
log chosenpostactions
return chosenpostactions
else
return missing value
end if
end dsppostactions
-- returns the parent folder of an item as a Mac path! ':'
-- expects «itempath» to be a Mac path! ':'
-- origin: http://www.fischer-bayern.de/applescript/html/parent_f.html
on getpardirpath(itempath)
set olddelims to AppleScript's text item delimiters
set AppleScript's text item delimiters to {":"}
set counttxtitems to (count text items of itempath)
set lasttxtitem to the last text item of itempath
if lasttxtitem = "" then
set counttxtitems to counttxtitems - 2 -- bei Pfad zu einem Ordner
else
set counttxtitems to counttxtitems - 1 -- bei Pfad zu einer Datei
end if
set pardirpath to text 1 thru text item counttxtitems of itempath & ":"
set AppleScript's text item delimiters to olddelims
return pardirpath
end getpardirpath
-- returns an unused file path (Mac path!)
-- expects «folderpath» to be a Mac path! ':'
-- example:
-- folderpath: 'DandyDisk:Users:bender:Desktop:icnsfiles:'
-- filename: 'example'
-- suffix: 'png'
-- -> filepath: 'DandyDisk:Users:bender:Desktop:icnsfiles:example.png'
-- if this filepath already exists:
-- -> filepath: 'DandyDisk:Users:bender:Desktop:icnsfiles:example 1.png'
-- and so on...
on getunusedfilepath(folderpath, filename, suffix)
set counter to 0
repeat
if counter is equal to 0 then
set filepath to folderpath & filename & "." & suffix
else
set filepath to folderpath & filename & " " & counter & "." & suffix
end if
try
set filealias to filepath as alias
on error
exit repeat
end try
set counter to counter + 1
end repeat
return filepath
end getunusedfilepath
-- returns the usage info for iconoodle
on getusageinfo()
set usageinfo to "To use " & mytitle & ", you can drop ICNS files & application bundles on its icon." & return & return & "If you want to quickly set your default archive folder, you can also drop the folder of your choice onto " & mytitle & "." & return & return & "Start " & mytitle & " by double-clicking its icon to access the preferences panel."
return usageinfo
end getusageinfo
-- asks the user to provide an archive folder
on askforarchivefolder()
tell me
activate
set archivefolder to choose folder with prompt "Please choose an archive folder for generated PNG files:" without multiple selections allowed, invisibles and showing package contents
end tell
return archivefolder
end askforarchivefolder
-- displays a simple info message
on dspinfomsg(infomsg)
tell me
activate
display dialog infomsg buttons {"OK"} default button 1 with title mytitle
end tell
end dspinfomsg
-- displays an error message
on dsperrmsg(errmsg, errnum)
tell me
activate
display dialog "Sorry, an error occured:" & return & return & errmsg & " (" & errnum & ")" buttons {"OK"} default button 1 with title mytitle with icon stop
end tell
end dsperrmsg
-- =================================
-- == USER DEFAULTS SCRIPT OBJECT ==
-- =================================
-- WOHOOOO!
script userdefaults
property domain : missing value
-- well...sets the domain
on setdomain(thedomain)
set domain to thedomain
end setdomain
-- indicates whether the preferences file already exists or not
on prefsfileexists()
set prefsfilepath to ((path to preferences folder from user domain) as Unicode text) & domain & ".plist"
try
set prefsfilealias to prefsfilepath as alias
return true
on error
return false
end try
end prefsfileexists
-- creates a preferences file
on createprefsfile()
-- :) yeah, lame hack, very quick and very dirty *g*
set command to ("defaults write " & domain & " foo foo") as «class utf8»
do shell script command
end createprefsfile
-- returns the file path of the preferences file
-- returns a Mac path! ':'
on getprefsfilepath()
set prefsfilepath to (((path to preferences folder from user domain) as Unicode text) & domain & ".plist")
return prefsfilepath
end getprefsfilepath
-- indicates whether the given preferences key exists or not
on haspref(prefkey)
try
tell application "System Events"
set prefvalue to value of property list item prefkey of property list file (my getprefsfilepath())
end tell
return true
on error
return false
end try
end haspref
-- returns the value for the given preferences key
on getpref(prefkey)
tell application "System Events"
set prefvalue to value of property list item prefkey of property list file (my getprefsfilepath())
end tell
return prefvalue
end getpref
-- creates a key/value-pair in the preferences file
-- creates the preferences file if it does not already exist
on setpref(prefkey, prefvalue)
-- if the preferences file does not already exist, we create it
if not prefsfileexists() then
my createprefsfile()
end if
tell application "System Events"
try
-- maybe we are lucky and the property list item already exists
set value of property list item prefkey of property list file (my getprefsfilepath()) to prefvalue
on error
-- no, we are unlucky and now have to create the property list item before manipulating its value
my createprefkey(prefkey)
set value of property list item prefkey of property list file (my getprefsfilepath()) to prefvalue
end try
end tell
end setpref
-- removes the given preferences key from the preferences file
-- yes, also necessary sometimes...
on rempref(prefkey)
set command to ("defaults delete " & domain & " " & prefkey) as «class utf8»
do shell script command
end rempref
-- creates a preferences key in a preferences file
-- lame hack, once again, but using "System Events" sometimes just sucks ;)
on createprefkey(prefkey)
set command to ("defaults write " & domain & " " & prefkey & " dummyvalue") as «class utf8»
do shell script command
end createprefkey
end script