Previous Page
Next Page

2.5. Application

The reasons why an application might want to employ AppleScript are the same as the reasons why anyone else wouldthe application wishes to communicate with some other application by way of Apple events (see "Is This Application Scriptable?" in Chapter 1). It is possible to write an application that forms and sends raw Apple events directly, without using AppleScript; but AppleScript makes the task much easier for the developer of an application, just as it does for anyone else.

To write an application that uses AppleScript, you don't have to be a professional developer who spends 15 hours a day at the computer and wears a beanie with a propeller. (Of course, the beanie can't hurt, either.) In fact, writing an AppleScript application could be as simple as saving a script from a script editor application. It may be useful to distinguish three different "levels" of application into which AppleScript can be incorporated: an applet , an AppleScript Studio application, and a standard compiled application that happens to call AppleScript. I'll just briefly survey all three levels here; the first two are revisited in more detail in Chapter 27.

2.5.1. Applet

An applet is just a compiled script saved with a tiny application framework wrapped around it. This application framework is just sufficient to turn the script into a stand-alone application. You can make an applet very easily: save your script from within a script editor application, and as you do so, choose to save it as an Application. (You make this choice in the Save dialog; if the script has already been saved, you may need to choose File Save As to bring up the Save dialog.) The result is an application that, when it runs, behaves almost exactly like your script when it runs.

If an applet behaves like the script it contains, why would you bother to make one? Why not simply leave the script as a compiled script file? One reason would be that you want the script to run in some context where merely opening a compiled script file would not run it. One obvious example is the Finder. Let's say there's some operation you frequently need to perform, and the way you want to perform it is by double-clicking something in the Finder. Perhaps you find the Script Menu too much trouble; perhaps you like having an icon right on your desktop, where you can see and access it easily by double-clicking it. Or perhaps you don't want it on your desktop; perhaps you'd like to put it in the toolbar area of your Finder windows. (The toolbar is the area of a Finder window above the files but below the titlebar.) Single-clicking a toolbar item is exactly like double-clicking the same item on the desktop or in a Finder window. But double-clicking a compiled script file in the Finder doesn't run it; it opens the script for editing in a script editor application. On the other hand, double-clicking an applet (or single-clicking it in the toolbar) does run the script. (Indeed, Apple provides, at http://www.apple.com/applescript/toolbar, some example scripts for you to put into your Finder window toolbar, and guess what? They're all applets.)

Similarly, suppose you have a script that you'd like to run automatically when you log in to your computer. To run things automatically when you log in, as you doubtless already know, you put them into the list of Login Items in the Accounts preference pane. But it's no use putting a compiled script into that list; this is not an automatic location, where a compiled script, if found, will be run. The Login Items list is not, for example, like BBEdit's Startup Items folder discussed earlier in this chapter. What the Login Items list expects is an application. Well, you can turn a script into an application by making it an applet; so that's what you do.

Another advantage of applets over compiled scripts is that an applet can be a droplet an application onto which you can drag and drop Finder items (files and folders) in order to process them with your script. An example appeared in "Calculation and Repetition" in Chapter 1.

2.5.2. AppleScript Studio

A compiled script or an applet has essentially no user interface. Your script can present a few basic dialogs for the user to interact with (as explained in Chapter 21), but that's all. This is usually not a problem, but sometimes it would really help to have some slightly more sophisticated user interface.

In this situation, AppleScript Studio can be helpful. AppleScript Studio is a way of writing a standard Cocoa application, with Mac OS X-native windows and interface widgets, when the only programming language you know is AppleScriptthere is no need to know Objective-C, the default Cocoa programming language, and you don't need a very extensive understanding of the Cocoa application framework. AppleScript Studio doesn't give you direct AppleScript access to everything that Cocoa can do, not by a long chalk; but it can be an easy and rapid way to wrap an interface around some AppleScript code.

For example, Figure 2-8 shows an AppleScript Studio application containing a window that lists the user's hard disks. Here's the code:

on awake from nib theObject
    tell application "Finder"
        set L to (get name of every disk)
    end tell
    set content of table view 1 of scroll view 1 of window 1 to L
end awake from nib

Figure 2-8. A Cocoa application written with AppleScript Studio


That's all there is to it. The code is recognizable AppleScript; indeed, within the awake from nib handler, the first three lines are essentially the same as the script in Figure 2-1, asking the Finder for the names of the disks. The only addition is the fourth line, starting with set content, which populates the interface with the Finder's reply.

2.5.3. Cocoa

As an example of incorporating AppleScript into a standard application, I'll recreate the previous example as a Cocoa application written in Objective-C. The task is more involved than in AppleScript Studio, because we must twice "cross the bridge" between Objective-C and AppleScript: from Objective-C we must summon AppleScript, and we must translate the response from AppleScript to Objective-C (whereas with AppleScript Studio there's no need for any of that, because the program is already written in AppleScript).

Cocoa has an NSAppleScript class that accepts and executes AppleScript code. There are two approaches: you can start with a string and compile and execute it, or you can start with a compiled script and execute that. Here's code demonstrating the first approach:

- (void) awakeFromNib {
    [self willChangeValueForKey:@"diskList"];
    diskList = [[NSMutableArray alloc] init];
    NSAppleScript* scpt = [[[NSAppleScript alloc] initWithSource 
:
        @"tell application \"Finder\"\r"
        "get name of disk 1\r"
        "end tell"]
        autorelease];
    NSAppleEventDescriptor* result = [scpt executeAndReturnError 
: nil];
    if ([result descriptorType] == 'utxt')
        [diskList addObject: [result stringValue]];
    else if ([result descriptorType] == 'list') {
        int i, u = [result numberOfItems];
        for (i=1; i<=u; i++)
            [diskList addObject: [[result descriptorAtIndex: i] stringValue]];
    }
    [self didChangeValueForKey:@"diskList"];
}

Even if you don't know any Objective-C, you can get a sense of what's going on here. Within the awakeFromNib method, the first two lines and the last line have essentially to do with the interface, and needn't concern us except to say that there is an instance variable diskList (an NSMutableArray) and whatever we put into it is going to show up in the interface as a line of the list displayed in our window. There are three lines where we form a literal string constituting our AppleScript code (the same code used in Figure 2-1); this string is slightly complicated because we must "escape" its quote characters, just as in the FileMaker code earlier in this chapter ("Internally Scriptable Application"), and we must explicitly insert "escaped" return characters. The next line (starting with the word NSAppleEventDescriptor) is where we ask for this AppleScript code to be compiled and executed.

After that, we have to do a surprising amount of work (and in fact we should be doing even moreI've deliberately omitted error checking, to condense the presentation). The problem is that when the reply comes back, we have to parse it differently depending on its type. This is all stuff that's taken care of for us when we get a result in a script editor application, but here we have to do it ourselves. If there's only one disk, the result will be a string; we insert that into diskList and that's that. If there's more than one disk, the result will be a list of strings, so we have to cycle through that list and insert each string into diskList. (The main surprise here for an experienced Cocoa programmer is that list indexes in an Apple event, unlike Objective-C collections, are 1-based!)

The other approach, probably faster, would be to compile the AppleScript code beforehand and incorporate the compiled script file into the project. When the application is built, the compiled script file is copied into its bundle as a resource. Instead of constructing the AppleScript code as a string, we retrieve the compiled script file from the bundle. So, for example, if the compiled script file is called askFinder.scpt, the relevant line of code (where we create our NSAppleScript object) would be changed to this:

    NSAppleScript* scpt = [[[NSAppleScript alloc] initWithContentsOfURL 
:
        [NSURL fileURLWithPath:
            [[NSBundle mainBundle] pathForResource: @"askFinder" ofType: @"scpt"]]
        error: nil] autorelease];

The result when the application runs is a window that appears identicaland I do mean identicalto Figure 2-8; so I won't bother to repeat the screenshot.


Previous Page
Next Page