macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Programming With Cocoa
Making Cocoa-Java Apps Scriptable

by Mike Butler
10/22/2004

All Mac apps should provide at least a minimum amount of AppleScript support--that's what the guidelines say. However, adding an AppleScript interface soon gained an air of mystery due partly to inadequate documentation and poor tools. For some, an AppleScript interface became a nice-to-have feature; they just didn't have the time to figure out how it all worked.

But things have changed. Frameworks are now available to do the donkey work. Adding a scripting interface is no longer black magic. And Cocoa in particular does a good job of making scripting more approachable.

Luckily, just like most of the other AppKit technologies, Cocoa means Cocoa. So it's just as easy to add a scripting interface to a Java Cocoa app as it is to an Objective-C Cocoa app. It's just not well publicised. But that too is changing.

This article will take you through a number of the most common operations undertaken when implementing an AppleScript interface in a Cocoa-Java based application. The main aim is to show you that it works just as you would expect it to. If you encounter a problem, at least you can be happy in the knowledge that it's of your own making!

First Steps.

To start off, we will use XCode to create a simple Cocoa-Java application that will be used throughout the article.

  1. Create a new "Cocoa-Java Document-Based Application" project in XCode and call it "ScriptableCJ."
  2. Double-click on the "ScriptableCJ" entry of the "Targets" group. This can be found in the "Groups & Files" column of the main window.
  3. Select the Expert View of the "Info.plist Entries" section. This will bring up a table containing all of the preset values and allow you to add new ones.

    Setting NSAppleScriptEnabled

  4. Add a new entry called "NSAppleScriptEnabled" and set its value to "YES."
  5. Build and run the application. (See the Tips section below.)

And hey presto, you have an AppleScript-enabled, Java-based application. Don't believe me? Run the following script in Script Editor and see.

tell application "ScriptableCJ"
	set name of front document to "Hiya World"
end tell
O'Reilly Mac OS X Conference

Hiya World Window Title

This little script will launch ScriptableCJ, if its not already running, and change the name of its frontmost document.

It's worth noting that just like any other scriptable application, you can peruse the commands and classes offered by ScriptableCJ by looking at its Dictionary with Script Editor. If you do, you'll notice that it contains a Standard suite and a Text suite. Both of these suites are provided by Cocoa automatically. We will be adding custom commands of our own soon, but you can find out more about the default suites by reading the developer documentation.

Becoming Useful: Adding Properties

To let AppleScript know about any new features in our application, we need to provide a pair of files. Both of the files have the same name but a different extension.

The first file uses the extension ".scriptSuite". This is the file that tells AppleScript and Cocoa about the structure of the new classes, elements, properties, and commands that are provided by the new application. It details how they relate to each other and how they map to the Obj-C or Java code.

The second file is the terminology file and it requires the extension ".scriptTerminology". AppleScript does its best to look and behave like a human language and it is this file that describes how to do so for the custom commands that you are adding. Storing the natural language information in a separate file away from the structural information allows it to be localised safely.

Both files use the plist format to structure their contents. As such, either XML or Key=Value String notation can be used. From a structural point of view, the XML variation is preferable; however, for online readability reasons, the Key=Value String notation will be used in this article.

Now that we know about the scriptSuite and scriptTerminology files, let's get ready to add them. To demonstrate this we will be turning ScriptableCJ into a mini text editor.

    Add an NSTextView
  1. Open MyDocument.nib with Interface Builder.
  2. Remove the default "Your document contents here" NSTextField from the document window.
  3. Add an NSTextView and resize it as appropriate.
  4. Save the changes.

Now let AppleScript and Cocoa know about the NSTextView.

  1. Create two new files, called MySuite.scriptSuite and MySuite.scriptTerminology. You can use any name you want instead of "MySuite" as long as both files have the same name (apart from the extension) and that this matches the value used for the Name key (see below).
  2. In MySuite.scriptSuite, add the following:

    {
      "Name" = "MySuite";
      "AppleEventCode" = "CJst";
      
      "Classes" = {
        "NSApplication" = {
          "Superclass" = "NSCoreSuite.NSApplication";
          "ToManyRelationships" = {
            "orderedDocuments" = {
              "Type" = "MyDocument";
              "AppleEventCode" = "docu";
            };
          };
          "AppleEventCode" = "capp";
        };
      
        "MyDocument" = {
          "Superclass" = "NSCoreSuite.NSDocument";
          "AppleEventCode" = "docu";
          "ToOneRelationships" = {
            "myContents" = {
              "Type" = "NSTextStorage";
              "AppleEventCode" = "tact";
            };
          };
        };
      };
    		
      "Synonyms" = {
        "tact" = "NSTextSuite.NSTextStorage";
      };
    }
  3. In MySuite.scriptTerminology, add the following:

    {
      "Name" = "MySuite";
      "Description" = "This is my hand made suite";
      
      "Classes" = {
      
        "NSApplication" = {
          "Name" = "application";
          "PluralName" = "applications";
          "Description" = "The top level scripting object.";
        };
      
        "MyDocument" = {
          "Name" = "document";
          "PluralName" = "documents";
          "Description" = "A ScriptableCJ document.";
        };
      };
      
      
      "Synonyms" = {
        "tact" = {
          "Name" = "text contents";
          "Description" = "The textual contents of the document";
        };
      };
    }

These two files used together tell the system that ScriptableCJ may have one or more documents. Each document has one repository of text called text contents. It shows that to get the text contents, the method myContents must be called, and that this method is part of the MyDocument object. That's all we need for now. As before, a full description of both of these files can be found as part of the developer documentation on your machine.

Everything else is in place; all that is left is for the TextView accessor methods to be added.

  1. Open MyDocument and add an outlet that will be used to point to the TextView.
    /** IBOutlet **/
    public NSTextView mainTextView;
  2. Add the myContents() method to return the text storage from the text view. Because of the scriptSuite file that we've just created, this method will be called automatically whenever AppleScript needs to get information from the text contents.

    public NSTextStorage myContents(){
    	return mainTextView.textStorage();
    }
  3. If AppleScript tries to alter text contents, Cocoa will automatically try to find a set version of the myContents method. So we are going to provide one. To keep things simple, however, we will only provide simple replace-text functionality.
    public void setMyContents( NSTextStorage inStorage ){
      mainTextView.textStorage().setAttributedString( inStorage );
    }
  4. Open MyDocument.nib in Interface Builder.
  5. Double-click on "File's Owner," add an outlet called mainTextView, and make it of type NSTextView.
  6. Connect the outlet to the text view as usual. If you have never done this before, then switch back to the Instances view of MyDocument.nib and Control-drag from the File's Owner to the text view that we added earlier. Click connect in the info window.
  7. Save the changes.

That's it. We've added a TextView. Told AppleScript and Cocoa about it. Told them how to access it. And finally we've provided the Java code to programmatically access the contents of the text view. Quit Script Editor if it's still running from earlier, then rebuild and test with the following script.

tell application "ScriptableCJ"
  set the text contents of the front document to "hello there again"
end tell

Not really that impressive, is it? But how about adding a word-count facility to our little app without having to provide any direct support? Now things are starting to become interesting.

tell application "ScriptableCJ"
  count the words in the text contents of the front document
end tell

Pages: 1, 2

Next Pagearrow