« Beginning AppleScript Studio
// Introduction
AppleScript has always been a very powerful Apple technology. It had great advantages, but in terms of user interaction, you were pretty much limited to dialogs, prompts and file selections.
With AppleScript Studio, all this has changed. In fact, a -Studio application is created with Project Builder and Interface Builder, so there's no need to pick up another IDE if you do OS X programming.
I'll walk you through some of the steps I've gone through to develop my current project: RemoteTunes. The application, when it's done, will allow you to control iTunes on a remote computer. For now, let's just let it control a local copy.
This tutorial assumes some knowledge of AppleScript, Project Builder and Interface Builder.
// Getting Started
The first thing to do to build a project, is to, well, start Project Builder. Create a new project (File -> New Project), and in the Assistant, pick "AppleScript Application" under "Application".
You'll get a Project Builder project window, and if you expand a few items in the Groups && Files area, here's what you'll see:
Figure 1: The Groups && Files View
Scripts: This is where the AppleScripts for the program are located.
Resources: Things like your nib (interface) files, credits, icon, and plist files can go here. One notable item is the AppleScriptKit.asdictionary file. This is a big part of what allows the development of -Studio applications.
Other Sources: Typically this is your main.m file, which is a Cocoa/Objective-C file.
Frameworks: This contains the frameworks, such as Cocoa, AppleScriptKit, Foundation and AppKit that your -Studio project will link against.
Products: The finished, compiled application.
// The Interface
Come on, this is a Mac. Let's build an interface! I'm going to assume here that you know a bit about Interface Builder. If not, some playing around with it will show you most of what you need to know (Hint: Command-Shift-I brings up the Info window, which is very useful). If enough requests come in, I'll write an IB tutorial! Double-click the MainMenu.nib file in the above list. This will load Interface Builder. Size the window to 425 by 125 pixels.
In the Info window for "Window", set the Window Title to "RemoteTunes":
Figure 2: The Window Attributes
Next, from the Cocoa-Views part of the Palettes window, drag in 3 "System Font Text" NSTextField variants. Next, drag in 3 NSButtons. These are the main interface elements for now. Arrange them so they look something like this:
Figure 3: The Main Layout
Note that the "System Font Text" elements were centered in the Info window, under the Attributes section. It looks like this:
Figure 4: Centering Elements
Next, we should title things, so we don't have lots of Buttons and System Font Text items lying around. To rename an item in the interface, double-click on it and type. From left to right, name the buttons "Back", "Forward" and "Play/Pause". The three text fields should be "Artist", "Album" and "Title", from top to bottom and left to right. We've gotten pretty far, but our application still has no way of identifying the UI elements! So, in the Info window, select "AppleScript" from the popup menu (or hit Command-6). For the first ("Back") button, type "backButton" in the name field. Next, expand the "Action" triangle and check "clicked". Below, under "Scripts", check "RemoteTunes.applescript". Do the same for the other two buttons, naming them "forwardButton" and "ppButton". For the text areas, simply name them "artistText", "albumText" and "titleText", without checking any boxes. We have a few more things to do, and you'll see why later. Click anywhere in the main window that's not occupied by a UI element. Back in the Info window, give the window an AppleScript name "mainWindow" and check "awakeFromNib", which is under "Nib". Make sure you check "RemoteTunes.applescript" as well. Next, go to the window called "MainMenu.nib" and click on the item called "File's Owner". Back in the Info window, associate this with "idle" and "should quit when last window closed", which are under "Application". Make sure you check "RemoteTunes.applescript" for these also.
At this point, click the "Edit Script" button at the bottom of the Info window. This will take you back to Project Builder, but don't start coding just yet...
// A Word about Event Handlers
When you click a button, you expect it to do something. Well, in AppleScript Studio, the way to tell your application that clicking on a button should do something is done via Event Handlers. You associate UI elements (which can be anything in Interface Builder) with certain events, and then your application knows to call these event handlers to call the event handler when that action (such as clicking on a button) is performed.
Handler, by the way, is the AppleScript word for what's knows as a "function" or "method" in other languages. It lets you group code together and reuse it in multiple places.
// Let's Write Some Code!
So, you're in Project Builder, and you see things like this in the editor:
on clicked theObject (*Add your script here.*) end clicked
Sure, you'd like to add your script there, but what do you write? The purpose of the three buttons should be pretty obvious, so let's code those first. Remember how we associated the buttons with the "clicked" handler? Here"s where we tell the application what to do when a button is clicked. Replace (*Add your script here.*) right after on clicked theObject with this:
if the name of theObject is equal to "backButton" then using terms from application "iTunes" tell application "iTunes" previous track end tell end using terms from else if the name of theObject is equal to "forwardButton" then using terms from application "iTunes" tell application "iTunes" next track end tell end using terms from else if the name of theObject is equal to "ppButton" then using terms from application "iTunes" tell application "iTunes" playpause end tell end using terms from end if
What's this do? Well, let's start with the first line. theObject is a generic way to refer to the item (in this case, a button) that caused the clicked handler to be called. So, we see that if the name of span class="code">theObject is "backButton" (this is what we gave our Back button as a name in the AppleScript part of the Info window, remember?), it executes a block of code. using terms from allows us to use words from the specified application"s scripting dictionary, and a tell block is what allows inter-application communication. The words previous track signal to iTunes that it should go back one track. Like C and other languages, you must close your blocks in the opposite way you opened them, so here we close the tell, then the using terms from and the if in that order.
We do the same thing with the "forwardButton", noting that next track tells iTunes to play the next track. Again, the blocks are closed, and we get to the third and final if statement. playpause tells iTunes to either play if it is currently paused, or pause if it's currently playing. This is much easier than writing your own code to get the "player state" as it's called, and then call either play or pause. (I know, I did it this way first!)
// Time To Feel Good
At this point, RemoteTunes works. Granted, it's not very functional, but it works. To prove it to yourself, choose Build && Run from the Build menu. If all went well, you should see your application come up after a bit of compile time, and the three buttons should work. If iTunes wasn't running, it's been opened by the application. If the application doesn't even build (you'll know because your text will turn colors when it does), make sure you typed (or copied && pasted) correctly what was above. If it does build and run, but the buttons don't work, go back to Interface Builder and make sure the names are typed in correctly and that each button is associated with the "clicked" event and the "RemoteTunes.applescript" item.
// Give Some Feedback
Those text fields are there for a reason! They'll display the artist, album and track of the current song iTunes is playing. So let's make them do it. This information will probably change when a button is clicked, so enter the following code after the "end if" but before the "end clicked":
using terms from application "iTunes" tell application "iTunes" if exists (database ID of current track) then set cur_song to the name of the current track set cur_artist to the artist of the current track set cur_album to the album of the current track else set cur_song to "" set cur_artist to "No song currently playing." set cur_album to "" end if end tell end using terms from tell window "mainWindow" set the contents of text field "titleText" to cur_song set the contents of text field "artistText" to cur_artist set the contents of text field "albumText" to cur_album end tell
We've got two parts to this code: the first gets information from iTunes. It checks to see that there is a current track, by checking for the attribute of every track called the database ID. If there's no track playing or paused, then there"s no current database ID, so we can't get any information, so in the else block we set three variables to default values. If, however, there is a song playing, then we get certain information about that song from iTunes. Here, name, artist, album and current track are special words for iTunes, using normal AppleScript syntax.
The contents of the second block are new to AppleScript Studio. We're telling the window we gave an AppleScript name of "mainWindow" to to do three very similar things. Each one of the text areas in our application has an attribute called contents, which is modifiable. So, if we say set the contents of text field "albumText" to cur_artist we're setting the text that's displayed by that text area. The reason for the tell block is that AppleScript Studio refers to things in a hierarchy, so it knows which "titleText" we'd be talking about if we had more than one.
At this point, you may want to build and run your application again, because it's so cool.
// A Few More Things
Remember how we associated our window with "awake from nib"? This is an event that is sent when the nib file is loaded on application launch. If iTunes is already playing a song, let's have it display the information for the song currently playing. The easiest way to do this, for now, is to copy and paste the above code sample over the (*Add your script here.*) after on awake from nib theObject. Now, every time the application is launched, the information will be updated.
Don't put anything else on your clipboard just yet, though! Remember how we also associated our application (via the File's Owner) with "idle" and "should quit when last window closed"? These do cool things as well. So, in the spirit of the above instructions, paste the same code into the on idle handler. The last line before end idle needs to read return 5, but you can replace the 5 with any number. This is the amount of time, in seconds, that the application will wait before calling this code again. With this, the information in the window will be updated every so often, which is helpful when the track changes naturally and the information should be updated. Inside the on should quit when last window closed block, simply type return true. This way, when the last (and only, in our case) window of the application is closed, the application itself will quit. You could return false, but this is the default behavior anyway.
// Conclusion
So, AppleScript is powerful. AppleScript Studio is not only more powerful, but it's fun, too. At least, to programmers it is. At this point, you're no -Studio expert, but you're on your way. Some good online resources are:
http://www.lists.apple.com/mailman/listinfo/applescript-studio - the AppleScript Studio mailing list
http://www.lists.apple.com/mailman/listinfo/applescript-users - the AppleScript Users mailing list
When you're in Project Builder, you can also get information on what keywords an application has by going to Open Dictionary in the File menu and picking the application in the list that comes up.
In /Developer/Documentation/CoreTechnologies/AppleScriptStudio/StudioReference/ is a PDF called "studioreference.pdf" which is very useful for reference, or just browsing if you're learning new things.
For more information on AppleScript itself, see /Developer/Documentation/Carbon/pdf/AppleScriptLanguageGuide.pdf
And one shameless self-plug: On my website I'll be documenting my experiences with both AppleScript Studio and a bit of Cocoa. Enjoy!
I clicked on the item called "File's Owner". Bub back in the Info window, I could not find "idle" or "should quit when last window closed", under "Application".
What am I missing?
Thanks
Posted by: AK on March 8, 2003 06:54 PMAt the beginning You said it would be able to control iTunes remotely. Just wondering how to make it do that. Thanks.
Posted by: Jon on March 8, 2003 09:02 PMJon,
I thought I'd leave the "remote" part out for the first tutorial, as I'm planning on writing more, but it's pretty simple. At the end of each
tell application "iTunes"
block, add
of machine "epcc://username:password@xxx.xxx.xxx.xxx"
Where username is a username of a user on that machine, password is that user's password and xxx.xxx.xxx.xxx is the IP of the remote machine. One thing to make sure of is that you enable Remote Apple Events in the Sharing pane of System Preferences (under the Services tab).
Thanks, I couldn't quite figure out where to put it, either when I checked the syntax it erased all that I inserted, or depending on where I put it, was given an error.
But don't worry. I'll just wait until you write the next section.
Thanks again.
-Jon
Posted by: Jon on March 9, 2003 12:04 AMIt sounds like it's not compiling for you. To get it to compile the machine that your trying to connect to (the "of machine") has to have the Remote Apple Events that Chris mentioned enabled too.
Also, to clarify what Chris stated, the code should look like this:
Tell Application "iTunes" of machine "epcc://username:password@xxx.xxx.xxx.xxx"
(that's all on one line)
Posted by: Tim on March 12, 2003 12:23 PMI've got it typed in EXACTLY like that and when I check the syntax it just erases everything after
tell application "iTunes"
And I'm not actually trying to compile yet, just checking syntax. although when I try to compile, the same thing happens. most likely when it's checking the syntax
Thanks for the help.
Posted by: Jon on March 12, 2003 09:19 PMJon,
I got the same errors your getting, this is what I did!
1) I know you already have remote events turned on, but just so others will follow along... turn remote events on - on the remote Machine.
2) open up itunes on the remote machine.
3) add this command to all of the "Tell application "iTunes"..."
of machine "eppc://username:password@the.ip.address.here"
notice it is epPc not epCc.
4)build application
*** as it is, the remote machine must have iTunes already open for this to work. Maybe Chris will show us how to make it open iTunes when we open our AppleScript Studio app ;-)
So heres an example of the final bit of my code - for copy/paste purposes.
-----------------------------------------------
if the name of theObject is equal to "backButton" then
using terms from application "iTunes"
tell application "iTunes" of machine "eppc://userName:PassWord@xxx.xxx.xxx.xxx"
previous track
end tell
end using terms from
else if the name of theObject is equal to "forwardButton" then
using terms from application "iTunes"
tell application "iTunes" of machine "eppc://userName:PassWord@xxx.xxx.xxx.xxx"
next track
end tell
end using terms from
else if the name of theObject is equal to "ppButton" then
using terms from application "iTunes"
tell application "iTunes" of machine "eppc://userName:PassWord@xxx.xxx.xxx.xxx"
playpause
end tell
end using terms from
end if
------------------------------------------
Great tutorial Chris! I've been wanting to mess with AppleScriptStudio for a long time, and this was the perfect application / tutorial to get me into it.
Hi Chris,
I was wondering how one would modify the original script to go to the library or even change playlists. I opened Script Editor and took a look at the dictionary, but I am very new to AppleScript, so I was not sure what to do next.
Thanks,
Elton
Posted by: Elton on March 14, 2003 06:19 PMNice tutorial, but I get stuck pretty fast. Where it says:
Next, expand the "Action" triangle and check "clicked". Below, under "Scripts", check "RemoteTunes.applescript"
I have nothing checkable under "Scripts", and I believe I did everything the tutorial said so far. Anybody got any hints for what could be wrong?
Posted by: Jonas on March 16, 2003 09:36 AMThanks for a great tutorial. With a little work, I can see this as the base for many cool things! I'd really love to see this follow through the rest of the steps involved in working up a final application, including preferences, the about screen, icons, and the like, but this was a perfect start! Keep up the good work!
Posted by: Patrick Patoray on March 19, 2003 09:32 AMHi everyone,
Thanks for the comments. Sorry I haven't responded quickly, but I'm finally on Spring Break and will get to all your questions by the end of this week! I'll start with a couple now:
Patrick: Adding an about screen, icons, etc. is pretty simple, and what I'm planning on doing in the next tutorial (date TBD).
Jonas: Make sure you open things in this order: Quit PB and IB if they're open. Open the project in PB, then open the .nib in IB. Now, you should be able to see items under "Scripts." There's an issue with IB and PB that relates to this, and hasn't been fixed yet.
Elton: I'm not sure how to do this yet. I've looked at the dictionary, under the class "playlist window", but the "view" property is [r/o] meaning read only. I'll let you know if I figure it out!
And about the "remote" part, I'm working on it, but haven't been able to test since my iBook died a few weeks ago -- I'm getting it fixed, or will have access to another machine, soon. I will find a way around that!
Great tutorial, however I have a question, whenever I try to compile a script in project builder that contains a "tell application Itunes" command, it gives me the error "no user interaction allowed." This error also comes up when I try and script (using project builder, it works fine in regular apple script) any other OS X application. This is SO frusterating because I can't find any way to solve this anywhere!
Thanks for your help
Posted by: Rory Swanson on March 20, 2003 04:39 PM
Odd. If I try:
using terms from application "iTunes"
tell application "iTunes" of machine "eppc://192.168.1.25"
previous track
end tell
end using terms from
Then it uses the keychain properly, but it complains with an error of "Can't get the application's event dictionary." when I attempt to compile the script.
Doesn't matter if I specify the username/password in the URL or not.
Posted by: bbum on March 20, 2003 06:52 PMI like the tutorial! Question though... Why is it that when I try to script a mac os X application using the project builder, it always gives me the "No user ineraction allowed" error! Is there any way around this?
Posted by: undisclosed on March 21, 2003 05:52 PMbbum, try opening the iTunes app on the remote machine, then compiling the script. I was getting the same error, and this fixed it.
Posted by: Max on March 21, 2003 08:10 PMBbum - Max has the right idea, but I think you can also
using terms from application "iTunes"
tell application "iTunes" of machine "eppc://192.168.1.25"
activate
previous track
end tell
end using terms from
Let me know how it goes!
Undisclosed - Are you using the latest Dev Tools? If so, email me and I'll try to troubleshoot that some more (then I'll post the solution here.)
Posted by: Chris Garaffa on March 21, 2003 08:19 PMThanks Chris, will try the tutorial again with the new hint (:
Posted by: Jonas on March 27, 2003 02:27 AMThanks guys for the info.
I'll admit, this script is frivoulous, but it sure made us laugh when I ran it to the boss's machine for April fools.
tell application "Finder" of machine "eppc://jpw:@xx.xx.xx.xx"
display dialog "----NAV VIRUS FOUND----
Alert from NAV Network edition!
Deteced -ResourceForkKlemm032-
virus in Rocky:Current Jobs.
All resource forks have been deleted.
Attempts to repair have failed.
Please contact Network Administrator." with icon 2
display dialog "----NAV FILE SCAN----
Reports 1,342 files with
missing resource forks.
All attempts to repair have failed." with icon 2
display dialog "----NAV FILE REPAIR----
Reports 1,342 files with
missing data forks.
NT SP3 TCPIP stack 123.2
Error - ZERO K file size not allowed.
Removing offending files." with icon 2
display dialog "1,342 files successfully removed" with icon 2
end tell
***
Posted by: **** on May 20, 2003 03:08 PMytd
Posted by: **** on May 20, 2003 03:08 PMwrite
Posted by: breno@cattolica.it on May 20, 2003 03:09 PMMost frustrating... my script has a couple of problems:
1. It chooses OS9's iTunes as the default iTunes app, with the OSX version trying to launch but repeatedly failing. When I renamed that version of iTunes to "OS9 iTunes" my script code automatically took on that app name within the PB code! If I correct the code to "iTunes" then saving it puts back the "OS9 iTunes" name! I too get the "no user interaction allowed" error in PB. How can I force the use of a particular application within a script??
2. The playpause command shows up as green (comment?) instead of blue (like the other iTunes commands) in PB's code window. When I run the script (and get the OS9 iTunes) I get an error when trying the Pause/Play button but not the other buttons.
Posted by: Frank on May 26, 2003 01:47 PMHere is some code to set the Volume with a slider.
I hope someone find it useful:
on action theObject
set enabled of slider of window "mainWindow" to true
set volumevalue to integer value of theObject
using terms from application "iTunes"
tell application "iTunes"
set the sound volume to volumevalue
end tell
end using terms from
end action
it also needs this on "idle" and "awake from nib"
tell application itunes......etc
set sound_volume to sound volume
....end tell
tell window "mainWindow"
set the integer value of slider "volumeSlider" to sound_volume
Great tutorial, thanx, I hope you'll make some more soon.
I have just one question... when I compile this application I get an error that says:
##Component Manager: attempting to find symbols in a component alias of type (regR/carP/x!bt)
what does it mean?
Posted by: Denis on June 20, 2003 05:22 AMOk probably a stupid question but how do u get an application's scrip[ting dictionary
a) Programmaticaly
b) without using a program
Terrific article. Thanks!
Posted by: anon on September 5, 2003 05:09 AMBoris rules !
Posted by: Boris on October 1, 2003 02:54 PMThanks for the tutorial! It's really put me on the right track. If you ever get around to another tutorial, please let me know!
Posted by: Landis on November 5, 2003 01:06 PMHi, Cool Blog... I'm building mine but it's not done yet. Anyhow catch you later. I'll stop in again.
Posted by: Ryan on November 7, 2003 05:41 AMCool blog, anyone here have info on how I can start my own blog at us cash website of mine?
Oswald
Posted by: Oswald on November 10, 2003 12:47 AMCool Blog Check out mine at
http://karavshin.org/blogs/
thanks
Posted by: dvd on December 10, 2003 02:52 AMJust Surfing, I'll check ya out again soon.
Posted by: Cash Advance Dood on December 10, 2003 03:10 AMCool Blog Check out mine at http://karavshin.org/blogs/
Posted by: Online Casino Guy on December 11, 2003 12:56 AM