Chapter 28. AppleScript

AppleScript is a language designed to allow normal users to automate repetitious tasks. When an AppleScript script is run, it usually generates some Apple events. Apple events move between applications by way of the Apple event manager and mach messaging.

You can create and run AppleScript scripts using the application /Applications/ AppleScript/Script Editor. The following simple script will bring up a new document containing the words “Share the love” in TextEdit. Type it into Script Editor and try it out:

tell application "TextEdit"
      activate
      make new document at the beginning of documents
      set the text of the front document to "Share the love"
end tell

As a programmer, I find the AppleScript language to be rather strange and hard to work with, but it has done a great job of helping users to automate common tasks. Instead of writing a script from scratch, I usually search the Internet for one that does almost what I want, and then I tinker with it until it acts in accordance with my wishes.

Executing an AppleScript from a Cocoa application is quite straightforward. If the preceding script was in an NSString, you could compile and run it like this:

NSAppleScript *appleScript;
NSDictionary *errorDict;
NSAppleEventDescriptor *ae;
appleScript = [[NSAppleScript alloc] initWithSource:theString];
ae = [appleScript executeAndReturnError:&errorDict];

The NSAppleEventDescriptor produced has the result that was returned at the end of the script. NSAppleEventDescriptor has three methods for reading the result as standard types:

- (SInt32)int32Value
- (NSString *)stringValue
- (Boolean)booleanValue

(Boolean, BOOL, and char are all the same thing.)

Making an Application AppleScript-able

Making a Cocoa application accessible to AppleScripters is done via key-value coding. Before you begin, you should recognize that the AppleScript system is rather finicky. Your plists must be carefully created, or nothing works. (In fact, an error in your plists can crash Script Editor.)

In Xcode, open the SpeakLine project from Chapter 5.

The first step is to inform the system that your application is AppleScript enabled. The Info.plist for SpeakLine contains a dictionary; add the following key and value to that dictionary:

<key>NSAppleScriptEnabled</key>
<string>YES</string>

By default, your application automatically understands a set of AppleScript commands. To exercise these commands, clean, build, and launch the app. In Script Editor, run a script that uses SpeakLine:

tell application "SpeakLine"
      activate
      get name of first window
end tell

The title of the main window should appear in the lower pane of Script Editor (Figure 28.1).

Getting the Window Name

Figure 28.1. Getting the Window Name

To see what attributes, relationships, and commands are available, click the Open Dictionary... menu item and look at SpeakLine's AppleScript dictionary. It should look something like Figure 28.2.

Browsing the AppleScript Dictionary

Figure 28.2. Browsing the AppleScript Dictionary

Notice the two suites: Standard and Text. A suite is a collection of AppleScript-able classes and commands. Window, for example, is a class. Each class has attributes. A window, for example, has a Boolean attribute miniaturized. Some attributes are read-only; others can be set. Classes also have relationships, which come in two varieties: ToOneRelationship and ToManyRelationship. For example, the application class has a ToManyRelationship with its windows. A window has a ToOneRelationship with the document it is displaying. Finally, a class has supported commands. For example, a document can handle a Print command. These ideas are rendered as a diagram in Figure 28.3.

What Is an AppleScript Suite?

Figure 28.3. What Is an AppleScript Suite?

All of these parts are defined in a .scriptSuite file. The examples mentioned previously are all in /System/Library/Frameworks/Foundation.framework/Resources/NSCoreSuite.scriptSuite. Open that file in TextEdit and browse it for moment.

Each part in the .scriptSuite file has an entry in a corresponding .scriptTerminology file that contains the word used for it in a script, its definition, and any synonyms. To review these components, take a look at /Frameworks/Foundation/Resources/NSCoreSuite.scriptTerminology in TextEdit.

Thus, the most important part of making your application scriptable is creating good .scriptSuite and .scriptTerminology files.

Create the Plists

In Xcode, create two new text files in the Resources group: SpeakLine.scriptSuite and SpeakLine.scriptTerminology. Add a class with a command and an action to the SpeakLine.scriptSuite. (The system is very finicky about these files, so type carefully.)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
      <key>Name</key>
      <string>SpeakLine</string>
      <key>AppleEventCode</key>
      <string>Spkl</string>

      <key>Classes</key>
      <dict>
         <key>MyApplication</key>
         <dict>
            <key>AppleEventCode</key>
            <string>capp</string>
            <key>Attributes</key>
            <dict>
               <key>utterance</key>
               <dict>
                  <key>AppleEventCode</key>
                  <string>Utte</string>
                  <key>Type</key>
                  <string>NSString</string>
               </dict>
            </dict>
            <key>Superclass</key>
            <string>NSCoreSuite.NSApplication</string>
            <key>SupportedCommands</key>
            <dict>
               <key>Utter</key>
               <string>handleUtterScriptCommand:</string>
            </dict>
         </dict>
      </dict>

      <key>Commands</key>
      <dict>
         <key>Utter</key>
         <dict>
            <key>AppleEventClassCode</key>
            <string>Spkl</string>
            <key>AppleEventCode</key>
            <string>Uttr</string>
            <key>CommandClass</key>
            <string>NSScriptCommand</string>
         </dict>
      </dict>
</dict>
</plist>

Now edit the SpeakLine.scriptTerminology file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
      <key>Description</key>
      <string>SpeakLine Script Suite</string>
      <key>Name</key>
      <string>SpeakLine Suite</string>

      <key>Classes</key>
      <dict>
         <key>MyApplication</key>
         <dict>
            <key>Attributes</key>
            <dict>
               <key>utterance</key>
               <dict>
                  <key>Description</key>
                  <string>The utterance</string>
                  <key>Name</key>
                  <string>utterance</string>
               </dict>
            </dict>
            <key>Description</key>
            <string>Speakline App</string>
            <key>Name</key>
            <string>my application</string>
            <key>PluralName</key>
            <string>my applications</string>
         </dict>
      </dict>

      <key>Commands</key>
      <dict>
         <key>Utter</key>
         <dict>
            <key>Description</key>
            <string>Speak the utterance</string>
            <key>Name</key>
            <string>utter</string>
         </dict>
      </dict>
</dict>
</plist>

Using ParseFileasPropertyList menu item, make sure that you did not make an error in formatting these plists.

Clean and build your project again. In Script Editor, look at the dictionary for SpeakLine. It should now have a third suite containing your class (with the attribute utterance) and the utter command (Figure 28.4).

Browsing the Updated Dictionary

Figure 28.4. Browsing the Updated Dictionary

Handling the Apple Events

Your suite claims that you are going to subclass NSApplication and add the utterance attribute and a method called handleUtterScriptCommand:. In Xcode, create a new Objective-C class called MyApplication. Declare the method to handle the command in MyApplication.h:

#import <Cocoa/Cocoa.h>

@interface MyApplication : NSApplication {

}
- (void)handleUtterScriptCommand: (NSScriptCommand *)command; 
@end

Basically, MyApplication plans to pass the responsibility off to the AppController class. How will it get a message to the AppController? In Interface Builder, you will make AppController be the delegate of the MyApplication object.

Open MainMenu.nib and control-drag from the File's Owner (which represents the instance of MyApplication) to the AppController. Set the delegate outlet as shown in Figure 28.5.

Setting the Application's delegate Outlet

Figure 28.5. Setting the Application's delegate Outlet

Returning to Xcode, implement the method in MyApplication.m:

- (void)handleUtterScriptCommand:(NSScriptCommand *)command
{
    NSLog(@"handleUtterScriptCommand:%@", command);
    [[self delegate] sayIt:nil];
}

Import AppController.h at the beginning of MyApplication.m.

What about the utterance attribute? An application's attributes and relationships can be handled by its delegate. The delegate implements the following method:

- (BOOL)application:(NSApplication *)a
 delegateHandlesKey:(NSString *)key

For any keys that the delegate can handle, it returns YES. Add the following method to AppController.m:

- (BOOL)application:(NSApplication *)sender
          delegateHandlesKey:(NSString *)key
{
    NSLog(@"Key checked = %@", key);
    if ([key isEqual:@"utterance"])
        return YES;
    else
        return NO;
}

Next, create accessors for that attribute. Of course, you don't actually have an utterance instance variable, so the accessors that are called will access the string in the text field:

- (NSString *)utterance
{
    return [textField stringValue];
}
- (void)setUtterance:(NSString *)s
{
    [textField setStringValue:s];
}

The final step is to edit the Info.plist so that it uses your subclass instead of NSApplication when the application launches. In Xcode, edit the Info.plist:

       <key>NSPrincipalClass</key>
       <string>MyApplication</string>

Build and launch your app. In Script Editor, try this AppleScript:

tell application "SpeakLine"
      activate
      set utterance to "The rain in spain"
      utter
end tell

The utterance should change, and then you should hear it spoken.

The .scriptTerminology file can be localized so that scripters can script in their native language.

Creating these plists so that they are consistent with one another and with the CoreSuite is a tricky business. SuiteModeler is a shareware application created by Don Briggs that takes much of the sweat out of creating these plists. A license for it is less than $50. I use SuiteModeler, and it has saved me a lot of frustration.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset