TweetFollow Us on Twitter

AppleScript to Cocoa

Volume Number: 16 (2000)
Issue Number: 7
Column Tag: Mac OS X

Adding AppleScript Support to Cocoa Apps

by Andrew Stone

The divide between user and programmer is effectively bridged with AppleScript - Apple's scripting language with a natural language syntax. Providing a developer's API for your application is nice, but far more people will be able to use AppleScript to interact programmatically with your application. ScriptEditor, the Apple program which executes AppleScript, ships with OS X in /System/Applications and is available in BlueBox for OS X Server in the AppleExtras folder.

The good news is that it's very easy to add AppleScript support to Cocoa apps, especially if you followed my advice last month and designed your application in three separate pieces: the data model, the view, and the controller. Because AppleScript talks directly to the data model, most of your work is already done. Cocoa's AppleScript implementation makes use of the key-value coding that you already have in place, and it "knows" what methods to call to get and set instance variables of your objects.

This article will explore the basics and then propose a mechanism to add automatic applescript generation to your application so users can see the script necessary to create any given document. We'll extend the Sketch application which is provided as source code in /System/Developer/Examples/AppKit/Sketch. Copy that folder to your home directory so you can modify it as we go along.

The Basics

There is a complete introduction to AppleScripting in Cocoa in /System/Documentation/Developer/YellowBox/TasksAndConcepts/ProgrammingTopics/Scripting/ScriptableApps on OS X Server and /System/Documentation/Developer/Cocoa/TasksAndConcepts/ProgrammingTopics/Scripting/ScriptableApps on Developer Preview 3 of Mac OS X. This is a valuable introduction, but don't get bogged down in the details! What you need to know depends on how far you want to take it, so let's look at it from a top-down perspective:

There are three major components to your AppleScript implementation: the Script Suite definition, the Script Terminology, and any additional code required to implement special commands or special accessor methods. The Script Suite is a property list which defines the mapping between apple event codes, classes in your app, and the exposed instance variables of those classes, as well as handled commands. The Script Terminology is a localized property list which defines the mapping between your class names and attributes and the actual English (or French, German, etc) terms used in AppleScript to communicate with the objects in your data model. Terminology lets AppleScript know what are valid syntactical constructs.

One of your major resources in defining your suite and terminology is the NSCoreSuite and NSTextSuite which Apple has provided. They will serve as an invaluable resource on how to write your own suites and terminologies. Your app gets free access to all of this functionality, especially if you have utilized the Document Based Application architecture available as a project type in ProjectBuilder. You might want to drag these four files out onto you desktop for easy access and reference:

NSCoreSuite:

  • /System/Library/Frameworks/Scripting.framework/Resources/NSCoreSuite.scriptSuite
  • /System/Library/Frameworks/Scripting.framework/Resources/English.lproj/NSCoreSuite.scriptTerminology

NSTextSuite:

  • /System/Library/Frameworks/AppKitScripting.framework/Resources/NSTextSuite.scriptSuite
  • /System/Library/Frameworks/AppKitScripting.framework/Resources/English.lproj/NSTextSuite.scriptTerminology

To use scripting in your application, you will need to add the two frameworks, AppKitScripting.framework and Scripting.framework to your project in ProjectBuilder.

Home, Suite, Home

The Suite definition has 5 top level components: the name of the suite, the Apple Event Code for the application, its classes, its commands, and any synonyms. Each class defines its superclass, attributes, to one and to many relationships, and the ubiquitous 4 letter Apple Event Code. These Apple Event Codes need to be unique, and Apple has reserved all lowercase combinations (26 * 26 * 26 * 26 or almost a half million possibilities) for themselves, so it behooves developers to throw in at least one uppercase letter in your codes. The code is used to map attributes and relationships behind the scenes of the AppleScript implementation, and is used as a hash key to quickly retrieve information from the AppleScript dictionaries.

A skeleton, do-nothing, suite would have the following entries:

{
    "Name" = "GroovyAppSuite";
    "AppleEventCode" = "GASt";
    "Classes" = {
    };
    "Commands" = {
    };
    "Synonyms" = {
    };
}

You expose your model by defining the classes which are part of your data model. In the case of Sketch, the DrawDocument, Application, Graphic and its subclasses are exposed. The Sketch data model is quite simple: a document is a list of graphics. A real life drawing application, such as Stone Design's Create, would have a grander, recursive definition: A Create document is a list of pages, each with a list of graphics, which contain either simple graphics or graphics which are groups which contain a list of graphics (which contain either simple graphics or graphics which are groups...).

Describing a class involves up to 6 components: its superclass, the Apple Event Code, the attributes, the one to many relationships, the one to one relationships, and the supported commands. Only the Superclass and AppleEventCode fields are mandatory, as for example, the Rectangle Graphic subclass:

        "Rectangle" = {
            "Superclass" = "Graphic";
            "AppleEventCode" = "d2rc";
	    };

Attributes

The attribute section is where you expose the instance variables of your custom subclass by defining its type and its unique Apple Event Code. The Cocoa AppleScript framework will use key-value coding to automatically generate the glue code to allow a user, for example, to set the color of a rectangle. Because a subclass of Graphic inherits all of the attributes of Graphic, Rectangle will also have the fill color attribute:

            "Attributes" = {	
		/* expose your ivars here - no code needed if you have methods named:
			setXXXX: and XXXX */
		...
                "fillColor" = {
                    "Type" = "NSColor";
                    "AppleEventCode" = "fclr";
                };

The scripting framework will look for a method named "setFillColor:(NSColor *)color" when attempting to execute the simple applescript code:

	set the fill color of graphic 1 to "red"

In Sketch's Terminology, the mapping between the instance method "fillColor" and "fill color" are made:

                "fillColor" = {
                    "Name" = "fill color";
                    "Description" = "The fill color.";
                };

Part of the magic of Cocoa AppleScripting is the automatic coercion of values to appropriate data types, in this case, the string "red" to a valid NSColor object. There are several ways to express colors in AppleScript:

  1. You can describe the color by its name, such as "black", "blue"
  2. You can pass a string containing three space separated floats between 0.0 and 1.0 expressing the red, green and blue components, exp: "0.5 0.3333 0.7777"
  3. (Post DP3) You can pass an array of three numbers between 0 and 2^16 -1 (65,535). Arrays are expressed with braces in AppleScript, and items are comma separated: for example: { 25000, 888, 65000 }

Attributes that take a boolean, integer or float should be assigned the type "NSNumber" - coercion will be made to the appropriate scalar type.

Let's expand Graphic by exposing the boolean values that control whether a graphic's stroke or fill is drawn, the "drawsStroke" and "drawsFill" methods. Add the bold lines to Sketch.scriptSuite:

        "Graphic" = {
            "Superclass" = "NSCoreSuite.AbstractObject";
            "AppleEventCode" = "grph";
            "Attributes" = {
				"drawsStroke" = {
				    "Type" = "NSNumber";
				    "AppleEventCode" = "sDrS";
				};
				"drawsFill" = {
				    "Type" = "NSNumber";
				    "AppleEventCode" = "sDrF";
				};
				...

Now, add these terms to Sketch.scriptTerminology so we can set and get these values:

        "Graphic" = {
            "Name" = "graphic";
            "PluralName" = "graphics";
            "Description" = "A graphic.  This abstract class represents the
individual shapes in a Sketch document.  There are subclasses for each specific
type of graphic.";
            "Attributes" = {
				"drawsStroke" = {
				    "Name" = "stroked";
				    "Description" = "Is object stroked?";
				};
				"drawsFill" = {
				    "Name" = "filled";
				    "Description" = "Is object filled?";
				};
		...

Non-scalar object values are also valid. You need to expose the class that is being passed as an argument, and assign the "Type" to this class. One important "gotcha" is that the attribute which takes this object must have a unique Apple Event Code different from the object's class. This is not the case of the "Type" field in One to one and One to many relationships, where the Apple Event Code must be the same as as the class code.

One to Many and One to One Relationships

One to Many relationships allow you to expose lists of items; which are at the core of any data model. Even if you are using dictionaries, you can expose them as One to Many relationships. Sketch's NSDocument subclass, DrawDocument, exposes two types of lists: the actual set of graphics in a document, and subset lists of each type of graphic:

        "DrawDocument" = {
            "Superclass" = "NSCoreSuite.NSDocument";
            "AppleEventCode" = "docu";
            "ToManyRelationships" = {
                "graphics" = {
                    "Type" = "Graphic";
                    "AppleEventCode" = "grph";
                };
                "rectangles" = {
                    "Type" = "Rectangle";
                    "AppleEventCode" = "d2rc";
                };

Again, it's very important to note that the Apple Event Code for lists have the exact same code as in the Class definition, for example, a Rectangle's Class code is "d2rc":

        "Rectangle" = {
            "Superclass" = "Graphic";
            "AppleEventCode" = "d2rc";
	};

Besides the usual accessor methods expected by AppleScript, such as setGraphics: and graphics, to many relationships also expect a way to insert an object at any point into the list, based on the pattern:

-(void) insertInXXXX:(<XXXX type> *)anXXXObject atIndex:(int)whereToInsert

So, for example, with graphics, you would implement an additional method beyond your normal accessors for use by script driven insertion:

- (void)insertInGraphics:(Graphic *)graphicToAdd atIndex:(int)whereToInsert;

Good object oriented design practice would probably have you implement this in terms of a the primitive "setGraphics:". In case back pointers or other setup is required, you will not have duplicate code since you "factored" this new method:

- (void)insertInGraphics:(Graphic *)graphicToAdd atIndex:(int)whereToInsert {
    NSMutableArray *a = [NSMutableArray arrayWithArray:_graphics];
    [a insertObject:graphic atIndex:whereToInsert];
    [self setGraphics:a];	// call the primitive method
}

Additionally, you can also implement these methods for use by scripting:

- (void)addInGraphics:(Graphic *)graphic;
- (void)removeFromGraphicsAtIndex:(unsigned)index;
- (void)replaceInGraphics:(Graphic *)graphic atIndex:(unsigned)index;

See Mike Ferris's implementations of these on line 305 of DrawDocument.m in Sketch.

On OS X Server 1.2 and Developer Preview 3, there is a mild limitation which will go away for OS X premier: you cannot tell a generic Graphic to set an attribute of a subclass, even if that graphic is of the subclass type. Class coercion is coming, but as of this writing, this won't work:

make new image at before graphic 1  with properties {x position:0 y,
position:0, width:100, height:100}  — add a new image

set the imagefile of graphic 1 to "/Some/Cool/Image.tiff"

Instead, the second line of apple script needs to be:

set the imagefile of image 1 to "/Some/Cool/Image.tiff"

This is a fairly contrived example because you could have passed in the "imagefile" argument to the properties array, but this explains why the non-existent subset arrays are exposed in DrawDocument's "ToManyRelationships": rectangles, images, circles, lines, and textAreas. Basically, Sketch just grabs those graphics which are of the desired class and returns an array with that subset:

// so Scripting can talk to the images in the document:
- (NSArray *)images {
    return [self graphicsWithClass:[Image class]];
}
// run through our list of graphics and build a list of the subtypes
// on the fly:
- (NSArray *)graphicsWithClass:(Class)theClass {
    NSArray *graphics = [self graphics];
    NSMutableArray *result = [NSMutableArray array];
    unsigned i, c = [graphics count];
    id curGraphic;
    for (i=0; i<c; i++) {
        curGraphic = [graphics objectAtIndex:i];
        if ([curGraphic isKindOfClass:theClass]) {
            [result addObject:curGraphic];
        }
    }
    return result;
}

The NSTextSuite is available for use by Cocoa developers for simple scripting of the Text Object: setting font size, font and color of text. It has a good example of a ToOneRelationShip: the mapping of an NSTextStorage to "text":

	"NSTextStorage" = {
	    "ToOneRelationShips" = {
                "text" = {
                    "Type" = "NSTextStorage";
                    "AppleEventCode" = "ctxt";
                };
	    };

This cyclical definition allows you to "setText" and get the "text" of an NSTextStorage object, the core data model behind every NSTextView. Since the Cocoa Text system is designed using the Model-View-Controller pattern, it lends itself to scriptability. You can also see examples of the use of synonyms in the Text Suite and Terminology.

Commander Coding

So far, we've taken advantage of key-value coding and haven't had to add much code. We can create and modify graphics, but in order to add more functionality, we need to add more commands. Since I noticed the "open" command was not working as of this writing, let's create a new command, "openfile" to open an existing Sketch document. There are 4 steps in adding a command: defining the command and its arguments in your Suite, defining which classes support the command, creating a terminology for the command, and actually writing the method which glues the NSScriptCommand and your preexisting code to accomplish commands.

In designing a command, think first of how a user might call it. For example, it would be natural to say:

tell application "Sketch" — note that  on DP3 you need to say "Sketch.app"
	openfile at "/Local/Users/andrew/SketchSample.sketch"
end tell

Step 1: To the bottom of the Sketch.scriptSuite add a Commands section:

    "Commands" = {
        "OpenFile" = {
            "CommandClass" = "NSScriptCommand";
            "Type" = "NSObjectReference";
		    "ResultAppleEventCode" = "obj ";
            "Arguments" = {
                "FileName" = {
                    "Type" = "NSString";
                    "AppleEventCode" = "oFNe";
                };
            };
            "AppleEventClassCode" = "sktc";
            "AppleEventCode" = "opFi";
        };
    };

Because this command takes one string argument, the file name, we have to define the file name in the "Arguments" section of the command. Commands can be of the generic NSScriptCommand class, or they can be NSScriptCommand subclasses that you create for special handling purposes. You need to define the return type of your command in the "ResultAppleEventCode" field. The Apple Event Code "obj " can be used to return an object, and the "Type" field would be an "NSObjectReference". If you don't want to return anything (a void method), use this:

            "Type" = "";

Note that the "AppleEventClassCode" is the same as our Suite Definition's Apple Event Code. The Apple Event Code for every command should, once again, be unique.

Step 2: Define the class or classes which support the command. In this case, just the Application knows how to open a file:

        "NSApplication" = {
            "Superclass" = "NSCoreSuite.NSApplication";
...
            "SupportedCommands" = {
               "Sketch.OpenFile" = "handleOpenFileCommand:";
            };
        };

Note that we designate the "OpenFile" command to be handled by the method named "handleOpenFileCommand:" which takes a single argument, the NSScriptCommand. We'll add that method to a new source file "Scripting_Additions.m" that will contain all of the extra code we add to Sketch.

Step 3: Map the command and its arguments to your native language. In English.lproj/Sketch.terminology, add this at the bottom:

    "Commands" = {
	    "OpenFile" = {
		    "Name" = "openfile";
		    "Description" = "open a file";
		    "Arguments" = {
			    "FileName" = {
				    "Name" = "at";
	"Description" = "The file to open should follow, quoted";
			    };
		    };
	    };
    };

Step 4: Write the method handleOpenFileCommand:

Add a new source file to "Other Sources" suitcase in ProjectBuilder, Scripting_Additions.m. We'll use the powerful "Categories" mechanism of Objective C which lets you add to and override methods of existing classes. The override part is tricky though - if more than one category of a class has an alternate implementation of a method, it is indeterminate which method will get called. So use categories with care!

Since NSApplication handles the "OpenFile" command, we implement a category of NSApplication:

@interface NSApplication(Scripting_Additions)
- (id)handleOpenFileCommand:(NSScriptCommand *)command {
    // ask the command to evaluate its arguments
    NSDictionary *args = [command evaluatedArguments];
    NSString *file = [args objectForKey:@"FileName"];
    // if we don't have a file argument, we're doomed!
    if (!file || [file isEqualToString:@""]) return [NSNumber numberWithBool:NO];
    // ask the Application delegate to do the work:
    return [NSNumber numberWithBool:[[self delegate] application:self openFile:file]];
}
@end

Note how the script command knows how to evaluate itself and its arguments, and we just have to check the dictionary for the file name via the key "FileName", same as in the Arguments section of the command definition.

To take advantage of more object oriented (OO) magic, namely the ability for an application to open files dragged on the application tile or via services, we'll implement application:openFile: in a DrawAppDelegate category. And using the Cocoa Document Based Architecture, it's as simple as asking the shared NSDocumentController to actually do the work:

@implementation DrawAppDelegate(Scripting_Additions)
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
   id doc = [[NSDocumentController sharedDocumentController]
openDocumentWithContentsOfFile:filename display:YES];
   return (doc != nil) ? YES: NO;
}
@end

Teach Your Users Well

Having your application be scriptable is one thing, teaching your users how to write scripts is another. So lets add a feature to Sketch which I originally implemented for Stone Create: the ability to save any document as an AppleScript that can then be executed in ScriptEditor to reproduce that document on the fly via scripting! This is useful for creating complicated templates which get modified slightly to produce current content.

The Object Model has many benefits, including code reduction, reusability, readability, modularity, data hiding, and data independence. So we'll take an OO approach to the design of our Script writer, with this simple algorithm to output the script:

	make a new document
	for each graphic do
	ask the graphic to make a new graphic just like itself

By creating a mutable string, each graphic will be asked in turn to supply the Apple Script needed to recreate it. Then, since an NSString knows how to save itself to disk, we're basically done.

Graphic will do most of the work, but will allow overrides and hooks for subclasses to set their special attributes. additionalProperties lets you specify any attributes to set during creation, such as the file name of an image, or whether a line goes up or down. setExtraPropertiesIntoStringBeforeTell and setExtraPropertiesIntoString lets you set attributes of the object after its creation, such as setting font and color attributes of a text object.

I've modified Image to "remember" its source file by adding an instance variable _sourceFileName, and modifying imageFile to return this, as well as GraphicView to set the image file name when you drag one in. Caveat: if you "paste" in a graphic, it won't have a filename, and thus won't be able to reproduce itself via a script.

Step 1: Add a new menu item to the main Menu:

	a. open Draw2.nib in InterfaceBuilder
	b. Drag a menu item from the Palette to the File menu
	c. title it "Save As AppleScript...", command-key: <OPTION><A>
	d. Double-click the First Responder in the "Instances" pane of the Draw2.nib window
	e. Double-click the "Actions" icon to drop down the actions
	f. Type <RETURN> to add a new action
	g. Retitle "myAction:" to "saveAsAppleScriptAction:"
	h. Control drag between the new menu item and the First Responder icon
	i. Double-click "saveAsAppleScriptAction:" in NSMenuItem target inspector
	j. Save the nib.

Step 2:

We'll continue adding code to Scripting_Additions.m, starting with the top level method saveAsAppleScriptAction:. Since each document needs to know how to do this, the method will be implemented in a DrawDocument category. The method will provide a SavePanel to allow the user to specify the script name, start the object graph writing the script, save the script, and then message the Finder to open the newly saved script for testing in Script Editor on OS X and in Edit on OS X Server (where you can copy/paste it into BlueBox's ScriptEditor). Because we want our Cocoa applications to be fully localizable, we'll use the NSLocalizedStringFromTable() macro so that localizers can modify the code to work without changing any source code.

Moreover, since we want the code to work on X Server as well as X, we'll use #ifdef SERVER where special code is needed for OS X Server. For example, X Server uses newlines '\n' to delineate lines, but Mac OS X and ScriptEditor use a carriage return, '\r'.

Since there are some bugs in Cocoa scripting in Developer Preview 3, we'll have another test for #ifdef DP3. To compile on OS X Server, add "-DSERVER" into the Compiler Flags field in the Project Inspector's Build Attributes window. Add "-DDP3" if you are compiling on Developer Preview 3.

Finally, for a Carbon application, such as ScriptEditor, to recognize and open a script file, we need to set the TYPE and CREATOR of the file. That will be accomplished with the help of some code I got from Ken Case of http://www.Omnigroup.com with a friendly NSString interface I added.

The Code

// Scripting_Additions.h

#import <AppKit/AppKit.h>
#import <Scripting/Scripting.h>
#import "DrawAppDelegate.h"
#import "DocumentModel.subproj/DrawDocument.h"
#import "DocumentModel.subproj/Graphic.h"
#import "DocumentModel.subproj/Line.h"
#import "DocumentModel.subproj/Circle.h"
#import "DocumentModel.subproj/Image.h"
#import "DocumentModel.subproj/TextArea.h"

@interface NSApplication(Scripting_Additions)

- (id)handleOpenFileCommand:(NSScriptCommand *)command;
@end

@interface DrawAppDelegate(Scripting_Additions)

// this is a good method to add
// it allows Sketch to open documents via the services interface:
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;

@end

@interface DrawDocument(Scripting_Additions)
// the method you hook up the menu item to the First Responder in Draw2.nib:
- (IBAction)saveAsAppleScriptAction:(id)sender;

// the actual method which generates the AppleScript:
- (NSString *)appleScript;

// TYPE/CREATOR:
- (int)setTypeString:(NSString *)type andCreatorString:(NSString *)creator
forPath:(NSString *)path;

@end

@interface Graphic(Scripting_Additions)

// each graphic knows it's own apple script:
- (void)appleScriptIntoString:(NSMutableString *)s;

// EACH GRAPHIC MUST OVERRIDE THIS WITH ITS ClassName from Terminology
- (NSString *)appleScriptName;

// EACH GRAPHIC MAY OVERRIDE THESE:
- (void)setExtraPropertiesIntoStringBeforeTell:(NSMutableString *)s;
- (void)setExtraPropertiesIntoString:(NSMutableString *)s;
- (NSString *)additionalProperties;

@end

// Scripting_Additions.m

#import "Scripting_Additions.h"

// for finder TYPE/CREATOR magic:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/attr.h>
#include <fcntl.h>

@implementation NSApplication(Scripting_Additions)

- (id)handleOpenFileCommand:(NSScriptCommand *)command {
    // ask the command to evaluate its arguments
    NSDictionary *args = [command evaluatedArguments];
    NSString *file = [args objectForKey:@"FileName"];
    // if we don't have a file argument, we're doomed!
    if (!file || [file isEqualToString:@""]) return [NSNumber numberWithBool:NO];
    // ask the Application delegate to do the work:
    return [NSNumber numberWithBool:[[self delegate] application:self openFile:file]];
}

@end

@implementation DrawAppDelegate(Scripting_Additions)

- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
   id doc = [[NSDocumentController sharedDocumentController]
openDocumentWithContentsOfFile:filename display:YES];
   return (doc != nil) ? YES: NO;
}

@end

@implementation DrawDocument (Scripting_Additions)

#define APPLE_SCRIPT_TITLE NSLocalizedStringFromTable(@"Save
AppleScript",@"Sketch",@"Save panel title when saving a document as a script")

#define COULDNT_SAVE_SCRIPT NSLocalizedStringFromTable(@"Could not save Apple
Script to %@. Check file permissions.",@"Sketch",@"Save panel title when saving
a document as a script")

#define OK	NSLocalizedStringFromTable(@"OK",@"Sketch",@"yes ok")

- (IBAction)saveAsAppleScriptAction:(id)sender {
   static NSSavePanel *savePanel = nil;
#ifndef SERVER
    NSWindow *window = [[[self windowControllers] objectAtIndex:0] window];
#endif

   // get and keep a save panel - setting it's name to a localized title:
   if (!savePanel) {
       savePanel = [[NSSavePanel savePanel]retain];
       [savePanel setTitle:APPLE_SCRIPT_TITLE];
   }
   
   // give the file a good default name based on the document's filename:
      if ([savePanel runModalForDirectory:@"" file:[[[self fileName]
stringByDeletingPathExtension]stringByAppendingPathExtension:@"script"]

    // of course, we want to use Sheets since they are soooo cool!
#ifndef SERVER
                             relativeToWindow:window
#endif
       ]) {
            NSString *file = [savePanel filename];
	    // do the work of collecting the appleScript in a simple method: "appleScript"
            if (![[self appleScript] writeToFile:file atomically:YES])

		// something went wrong: notify
#ifdef SERVER
                   NSRunCriticalAlertPanel(APPLE_SCRIPT_TITLE,COULDNT_SAVE_SCRIPT,OK,NULL,NULL,file);
#else
                   NSRunCriticalAlertPanelRelativeToWindow(APPLE_SCRIPT_TITLE,COULDNT_SAVE_SCRIPT,
OK,NULL,NULL,window,file);
#endif
		// So open it up - but provide a way to turn off this behaviour a default:
                   else if (![[NSUserDefaults standardUserDefaults]
boolForKey:@"DontOpenAppleScript"]) {
			// some OMNI magic:
				[self setTypeString:@"TEXT" 
								andCreatorString:@"ToyS" forPath:file];

			// try to open it up with the new spellings of ScriptEditor
			// the alternate one is with a space between Script and Editor:
          if (![[NSWorkspace sharedWorkspace] openFile:file
#ifndef SERVER
withApplication:@"ScriptEditor"
#endif
				] && ![[NSWorkspace sharedWorkspace] openFile:file
#ifndef SERVER
                          withApplication:@"Script Editor"

#endif
                               ]);

                   }

   }
}
// here we'll be able to localize our Apple Script that we produce
// we need to use newlines in Server, and carriage returns on X
#ifdef SERVER

#define TELL_APP NSLocalizedStringFromTable(@"tell application
\"%@\"\n",@"Sketch",@"AppleScript tell app statement")

#define TELL_DOC NSLocalizedStringFromTable(@"    tell the front
document\n",@"Sketch",@"AppleScript tell doc statement")

#define NEW_DOC_SCRIPT NSLocalizedStringFromTable(@"    
make new document at before front document\n",@"Sketch",@"AppleScript create
new doc phrase")

#define COMMENTED_NEW_DOC_SCRIPT NSLocalizedStringFromTable(@"— broken in
DP3 - uncomment next line to test:\n—    
make new document at before front document\n",@"Sketch",@"AppleScript create
new doc phrase")

#define END_TELL NSLocalizedStringFromTable(@"end
tell\n",@"Sketch",@"AppleScript end tell statement")

#else

#define TELL_APP NSLocalizedStringFromTable(@"tell application
\"%@\"\r",@"Sketch",@"AppleScript tell app statement")

#define TELL_DOC NSLocalizedStringFromTable(@"    tell the front
document\r",@"Sketch",@"AppleScript tell doc statement")

#define NEW_DOC_SCRIPT NSLocalizedStringFromTable(@"    
make new document at before front document\r",@"Sketch",@"AppleScript create
new doc phrase")

#define COMMENTED_NEW_DOC_SCRIPT NSLocalizedStringFromTable(@"— broken in
DP3 - uncomment next line to test:\r—    make new document at before front
document\r",@"Sketch",@"AppleScript create new doc phrase")

#define END_TELL NSLocalizedStringFromTable(@"end
tell\r",@"Sketch",@"AppleScript end tell statement")

#endif

#define IMAG_FILE NSLocalizedStringFromTable(@", imageFile:\"%@\"",@"Sketch",
@"Applescript extra stuff for image")

#define LINE_STUFF NSLocalizedStringFromTable(@", goes up:%d",@"Sketch",
@"Applescript extra stuff for line")

- (NSString *)appleScript {
   NSMutableString *s = [NSMutableString stringWithCapacity:4000];
    unsigned i = [_graphics count];
#ifndef DP3
   NSString *appName = @"Sketch";
#else
   NSString *appName = @"Sketch.app";

#endif
   [s appendString:[NSString stringWithFormat:TELL_APP,appName]];

#ifndef DP3
   [s appendString:NEW_DOC_SCRIPT];
#else
   [s appendString:COMMENTED_NEW_DOC_SCRIPT];
#endif

   [s appendString:TELL_DOC];

   while (i— > 0) {
       Graphic *g = [_graphics objectAtIndex:i];
       [g appleScriptIntoString:s];
   }
   [s appendString:@"\t"]; // make the "end tell" nested in a level
   [s appendString:END_TELL];
   [s appendString:END_TELL];
   return s;
}

typedef struct {
    long type;
    long creator;
    short flags;
    short locationV;
    short locationH;
    short fldr;
    short iconID;
    short unused[3];
    char script;
    char xFlags;
    short comment;
    long putAway;
} OFFinderInfo;

- (int)setType:(unsigned long)typeCode andCreator:(unsigned long)creatorCode
forPath:(NSString *)path;
{
#ifndef SERVER
     struct attrlist attributeList;
     struct {
         long ssize;
         OFFinderInfo finderInfo;
     } attributeBuffer;
     int errorCode;
     NSFileManager *fm = [NSFileManager defaultManager];

     attributeList.bitmapcount = ATTR_BIT_MAP_COUNT;
     attributeList.reserved = 0;
     attributeList.commonattr = ATTR_CMN_FNDRINFO;
     attributeList.volattr = attributeList.dirattr = attributeList.fileattr =
attributeList.forkattr = 0;
     memset(&attributeBuffer, 0, sizeof(attributeBuffer));

     getattrlist([fm fileSystemRepresentationWithPath:path],
     &attributeList, &attributeBuffer, sizeof(attributeBuffer), 0);

     attributeBuffer.finderInfo.type = typeCode;
     attributeBuffer.finderInfo.creator = creatorCode;

     errorCode = setattrlist([fm fileSystemRepresentationWithPath:path],
&attributeList, &attributeBuffer.finderInfo, sizeof(OFFinderInfo), 0);
     if (errorCode == 0)
         return 0;

     if (errno == EOPNOTSUPP) {
#define MAGIC_HFS_FILE_LENGTH 82
         unsigned char magicHFSFileContents[MAGIC_HFS_FILE_LENGTH] = {
             0x00, 0x05, 0x16, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x09, 0x00, 0x00,
             0x00, 0x32, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x52, 0x00, 0x00,
             0x00, 0x00, 't', 'y', 'p', 'e', 'c', 'r', 'e', 'a', 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00};
         unsigned int offsetWhereOSTypesAreStored = 50;
         NSData *data;
         NSString *magicHFSFilePath;

  *((int *)(&magicHFSFileContents[offsetWhereOSTypesAreStored])) = typeCode;
  *((int *)(&magicHFSFileContents[offsetWhereOSTypesAreStored + 4])) = creatorCode;
         data = [NSData dataWithBytes:magicHFSFileContents
length:MAGIC_HFS_FILE_LENGTH];
         magicHFSFilePath = [[path stringByDeletingLastPathComponent]
stringByAppendingPathComponent:[@"._" stringByAppendingString:[path
lastPathComponent]]];

         if ([fm createFileAtPath:magicHFSFilePath contents:data attributes:[fm
fileAttributesAtPath:path traverseLink:NO]])
             return 0;
         else
             return errorCode;
     }
     return errorCode;
#else
    return 1;
#endif
}

- (unsigned long)codeFromString:(NSString *)fourLetterWord {
     unsigned const char *c = [fourLetterWord cString];
    if ([fourLetterWord cStringLength] == 4) {
        return ((c[0]<<24) + (c[1]<<16) + (c[2]<<8) + c[3]);
    }
    return 0;
}

- (int)setTypeString:(NSString *) type andCreatorString:(NSString *)creator
forPath:(NSString *)path {
    return [self setType:[self codeFromString:type] andCreator:[self
codeFromString:creator]  forPath:path];
}

@end

// localize the Graphic strings:

#ifdef SERVER

#define NEW_OBJECT NSLocalizedStringFromTable(@"\t\tmake new %@ at before
graphic 1 with properties {x position:%d, y position:%d, width:%d, height:%d,
stroke color:%@, stroke thickness:%f, stroked:%d, fill color:%@,
filled:%d%@}\n",@"Sketch",@"AppleScript new Object")

#define TELL_GRAPHIC NSLocalizedStringFromTable(@"\t\ttell %@
1\n",@"Sketch",@"AppleScript tell graphic statement")

#define FONT_SET NSLocalizedStringFromTable(@"\t\tset the font of %@ of %@ 1 to
\"%@\"\n",@"Sketch",@"applescript to set the font of a text area")

#define FONT_SIZE_SET NSLocalizedStringFromTable(@"\t\tset the size of %@ of %@
1 to %d\n",@"Sketch",@"applescript to set the font of a text area")

#define FONT_COLOR_SET NSLocalizedStringFromTable(@"\t\tset the color of %@ of
%@ 1 to %@\n",@"Sketch",@"applescript to set the font of a text area")

#define SET_CHAR_COLOR NSLocalizedStringFromTable(@"\t\tset the color of
characters %d through %d of %@ of %@ 1 to %@\n",@"Sketch",@"applescript to set
the color of a range of characters of a text area")


#define SET_CHAR_SIZE NSLocalizedStringFromTable(@"\t\tset the size of
characters %d through %d of %@ of %@ 1 to %3.2f\n",@"Sketch",@"applescript to
set the font size of a range of characters of a text area")

#define SET_CHAR_FONT NSLocalizedStringFromTable(@"\t\tset the font of
characters %d through %d of %@ of %@ 1 to \"%@\"\n",@"Sketch",@"applescript to
set the font of a range of characters of a text area")
#else

#define NEW_OBJECT NSLocalizedStringFromTable(@"\t\tmake new %@ at before
graphic 1 with properties {x position:%d, y position:%d, width:%d, height:%d,
stroke color:%@, stroke thickness:%f, stroked:%d, fill color:%@,
filled:%d%@}\r",@"Sketch",@"AppleScript new Object")

#define TELL_GRAPHIC NSLocalizedStringFromTable(@"\t\ttell %@
1\r",@"Sketch",@"AppleScript tell graphic statement")

#define FONT_SET NSLocalizedStringFromTable(@"\t\tset the font of %@ of %@ 1 to
\"%@\"\r",@"Sketch",@"applescript to set the font of a text area")

#define FONT_SIZE_SET NSLocalizedStringFromTable(@"\t\tset the size of %@ of %@
1 to %d\r",@"Sketch",@"applescript to set the font of a text area")

#define FONT_COLOR_SET NSLocalizedStringFromTable(@"\t\tset the color of %@ of
%@ 1 to %@\r",@"Sketch",@"applescript to set the font of a text area")

#define SET_CHAR_COLOR NSLocalizedStringFromTable(@"\t\tset the color of
characters %d through %d of %@ of %@ 1 to %@\r",@"Sketch",@"applescript to set
the color of a range of characters of a text area")

#define SET_CHAR_SIZE NSLocalizedStringFromTable(@"\t\tset the size of
characters %d through %d of %@ of %@ 1 to %3.2f\r",@"Sketch",@"applescript to
set the font size of a range of characters of a text area")

#define SET_CHAR_FONT NSLocalizedStringFromTable(@"\t\tset the font of
characters %d through %d of %@ of %@ 1 to \"%@\"\r",@"Sketch",@"applescript to
set the font of a range of characters of a text area")

#endif

#define TEXT_CONTENTS NSLocalizedStringFromTable(@", text
contents:\"%@\"",@"Sketch",@"applescript to set the text of a text area")

NSString *appleScriptColor(NSColor *c) {
    float r,g,b;
    [[c colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&r
green:&g blue:&b alpha:NULL];
    return [NSString stringWithFormat:@"\"%f %f %f\"", r,g,b];
}

@implementation Graphic (Scripting_Additions)

- (void)appleScriptIntoString:(NSMutableString *)s {

    [s appendString:[NSString stringWithFormat:NEW_OBJECT,[self
appleScriptName],(int)(NSMinX(_bounds)),(int)(NSMinY(_bounds)),
(int)(NSWidth(_bounds)),(int)(NSHeight(_bounds)),
appleScriptColor(_strokeColor),_lineWidth,(int)_gFlags.drawsStroke,appleScriptColor(_fillColor),
(int)_gFlags.drawsFill,[self additionalProperties]]];

    [self setExtraPropertiesIntoStringBeforeTell:s];

    [s appendString:[NSString stringWithFormat:TELL_GRAPHIC,[self appleScriptName]]];

    [self setExtraPropertiesIntoString:s];

    [s appendString:@"\t\t"];
    [s appendString:END_TELL];

}


// EACH GRAPHIC MUST OVERRIDE THIS CORRECTLY
- (NSString *)appleScriptName {
  return NSLocalizedStringFromTable(@"rectangle",@"Sketch",@"Applescript name of this object");
}

// EACH GRAPHIC MIGHT OVERRIDE THIS CORRECTLY
- (void)setExtraPropertiesIntoStringBeforeTell:(NSMutableString *)s {
}

- (void)setExtraPropertiesIntoString:(NSMutableString *)s {

}

// for example: return "contents:\"hi\""
- (NSString *)additionalProperties {
  return @"";
}

@end

@implementation Circle (Scripting_Extras)
- (NSString *)appleScriptName {
   return NSLocalizedStringFromTable(@"circle",@"Sketch",@"Applescript name of this object");
}
@end

@implementation Image (Scripting_Extras)
- (NSString *)appleScriptName {
    return NSLocalizedStringFromTable(@"image",@"Sketch",@"Applescript name of this object");
}

- (NSString *)additionalProperties {
    return [NSString stringWithFormat:IMAG_FILE,_sourceFileName];
}

@end

@implementation Line (Scripting_Extras)
- (NSString *)appleScriptName {
    return NSLocalizedStringFromTable(@"line",@"Sketch",@"Applescript name of this object");
}

- (NSString *)additionalProperties {
    return [NSString stringWithFormat:LINE_STUFF,(int)_startsAtLowerLeft];
}

@end

@implementation TextArea (Scripting_Extras)

- (NSString *)appleScriptName {
   return NSLocalizedStringFromTable(@"text area",@"Sketch",@"Applescript name of this object");
}

- (NSString *)escapeString:(NSString *)inString {
    NSMutableString *s = [NSMutableString string];
    unsigned i, c = [inString length];
    for (i = 0; i < c; i++) {
        unichar c;
        if ((c = [inString characterAtIndex:i]) == '"') [s appendString:@"\\"];
        [s appendFormat:@"%c",c];
    }
    return s;
}

- (NSString *)contentsName {
    return NSLocalizedStringFromTable(@"the text
contents",@"Sketch",@"Applescript name of what is in a text");
}

// we need to escape any embedded quotes:
- (NSString *)additionalProperties {
    return [NSString stringWithFormat:TEXT_CONTENTS, [self escapeString:[[self contents] string]]];
}

- (BOOL)color:(NSColor *)color equals:(NSColor *)color2
{
    float r,g,b,r2,g2,b2;
    [[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&r
green:&g blue:&b alpha:NULL];
    [[color2 colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&r2
green:&g2 blue:&b2 alpha:NULL];
    return (r == r2 && g == g2 &&  b == b2);
}

// a methodical search through the text for attributes
// could be optimized by using effective range, but this works fine
- (NSFont *)font {
    NSTextStorage *contents = [self contents];
    if ([contents length])
        return [contents attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL];
    else return nil;
}

- (NSColor *)textColor{
    NSColor *c = nil;
    NSTextStorage *contents = [self contents];
    if ([contents length]) c = [contents
attribute:NSForegroundColorAttributeName atIndex:0 effectiveRange:NULL];
    if (!c) c = [NSColor blackColor];
    return c;
}

- (void)searchStringForAllAttributes:(NSMutableString *)s {
    NSColor *lastColor = [self textColor];
    NSFont *lastFont = [self font];
    float lastSize = [lastFont pointSize];
    NSString *lastFontName = [lastFont fontName];

    int length = [[[self contents] string] length];
    int newColorBeganAtIndex = 0;
    int newSizeBeganAtIndex = 0;
    int newFontNameBeganAtIndex = 0;
    int curIndex;

    for (curIndex = 0; curIndex < length; curIndex++) {
        BOOL isLast = (curIndex == length - 1);
        NSDictionary *atts = [[self contents] attributesAtIndex:curIndex
effectiveRange:NULL];
        NSFont *font = [atts objectForKey:NSFontAttributeName];
        NSColor *color = [atts objectForKey:NSForegroundColorAttributeName];
        BOOL colorChanged;
        BOOL fontChanged;
        BOOL sizeChanged;

        if (!color) color = [NSColor blackColor];
        colorChanged = ![self color:lastColor equals:color];

        if (colorChanged || (isLast)) {
            // the text block already has the correct color of the first several characters...
            if (colorChanged && newColorBeganAtIndex != 0) {
                [s appendString:[NSString
stringWithFormat:SET_CHAR_COLOR,newColorBeganAtIndex + 1, curIndex,[self
contentsName],[self appleScriptName],appleScriptColor(lastColor)]];
                newColorBeganAtIndex = curIndex;
            }
            if (isLast && newColorBeganAtIndex != 0) {
                [s appendString:[NSString
stringWithFormat:SET_CHAR_COLOR,newColorBeganAtIndex + 1, curIndex+1,[self
contentsName],[self appleScriptName],appleScriptColor(color)]];
            }
            newColorBeganAtIndex = curIndex;
            lastColor = color;
        }

        sizeChanged = ([font pointSize] != lastSize)?YES:NO;
        if (sizeChanged || (isLast)) {
            // the text block already has the correct align of the first
            several characters...
            if (sizeChanged && newSizeBeganAtIndex != 0) {
                [s appendString:[NSString
stringWithFormat:SET_CHAR_SIZE,newSizeBeganAtIndex + 1, curIndex,[self
contentsName],[self appleScriptName],lastSize]];
                newSizeBeganAtIndex = curIndex;
            }
            if (isLast && newSizeBeganAtIndex != 0) {
                [s appendString:[NSString
stringWithFormat:SET_CHAR_SIZE,newSizeBeganAtIndex + 1, curIndex + 1,[self
contentsName],[self appleScriptName],[font pointSize]]];
            }
            newSizeBeganAtIndex = curIndex;
            lastSize = [font pointSize];
        }

        fontChanged = ![[font fontName] isEqualToString:lastFontName];
        if (fontChanged  || (isLast)) {
            // the text block already has the correct align of the first several characters...
            if (fontChanged && newFontNameBeganAtIndex != 0) {
                [s appendString:[NSString
stringWithFormat:SET_CHAR_FONT,newFontNameBeganAtIndex + 1, curIndex,[self
contentsName],[self appleScriptName],lastFontName]];
   newFontNameBeganAtIndex = curIndex; // needed for next if statement
            }
            if (isLast && newFontNameBeganAtIndex != 0) {
                [s appendString:[NSString
stringWithFormat:SET_CHAR_FONT,newFontNameBeganAtIndex + 1, curIndex + 1,[self
contentsName],[self appleScriptName],[font fontName]]];
            }
            newFontNameBeganAtIndex = curIndex;
            lastFontName = [font fontName];
        }
    }
}

- (void)setExtraPropertiesIntoStringBeforeTell:(NSMutableString *)s {
    [s appendString:[NSString stringWithFormat:FONT_SET,[self
contentsName],[self appleScriptName],[[self font] fontName]]];
    [s appendString:[NSString stringWithFormat:FONT_SIZE_SET,[self
contentsName],[self appleScriptName],(int)[[self font] pointSize]]];
    [s appendString:[NSString stringWithFormat:FONT_COLOR_SET,[self
contentsName],[self appleScriptName],appleScriptColor([self textColor])]];

    [self searchStringForAllAttributes:s];
}

@end

Step 3: Add Missing Instance Variable to Line subclass:

Sketch.scriptSuite:

        "Line" = {
            "Superclass" = "Graphic";
            "AppleEventCode" = "d2ln";
	 /* andrew -> we need to be able to make lines go up and down! */
		    "Attributes" = {
			    "startsAtLowerLeft" = {
				"Type" = "NSNumber";
				"AppleEventCode" = "lnUp";
			    };
		    };
	    };

Sketch.scriptTerminology:

        "Line" = {
            "Name" = "line";
            "PluralName" = "lines";
            "Description" = "A line graphic.";
            /* acs: this was missing */
            "Attributes" = {
                "startsAtLowerLeft" = {
                    "Name" = "goes up";
      "Description" = "line goes uphill or line goes downhill";
                };
	     };
	};

Step 4: Minor diffs in other parts of source code:

diff -B -r /System/Developer/Examples/AppKit/Sketch/DocumentModel.subproj/Image.h
/Network/Users/andrew/CURRENT/MACTECH/AppleScript/SketchScript/DocumentModel.subproj/Image.h
15a16,17
>     // andrew addition:
>     NSString *_sourceFileName;
25a28,31
> 
> // for applescripting:
> - (void)setImageFile:(NSString *)filePath;
> - (NSString *)imageFile;

diff -B -r /System/Developer/Examples/AppKit/Sketch/DocumentModel.subproj/Image.m
/Network/Users/andrew/CURRENT/MACTECH/AppleScript/SketchScript/DocumentModel.subproj/Image.m
31a32
>    if (_sourceFileName) [newObj setImageFile:_sourceFileName];
169a170
> NSString *SourceFileKey = @"SourceFile";
175a177
>     if (_sourceFileName) [dict setObject:_sourceFileName forKey:SourceFileKey];
195a198,201
>     obj = [dict objectForKey:SourceFileKey];
>     if (obj) {
>         _sourceFileName = [obj copyWithZone:[self zone]];
>     }
204a211
>     _sourceFileName = [filePath copyWithZone:[self zone]];
211,212c218
<     // This is really a "write-only" attribute used for setting the image
for an Image shape from a script.  We don't remember the path so the accessor
just returns an empty string.
<     return @"";
—-
>     return _sourceFileName;
diff -B -r /System/Developer/Examples/AppKit/Sketch/GraphicView.m
/Network/Users/andrew/CURRENT/MACTECH/AppleScript/SketchScript/GraphicView.m
557,558c557,559
<             [newImage setImage:contents];
<             [contents release];
—-
>           // For applescript -> so it remembers where it came from...
>        [newImage setImageFile:filename];
>        [contents release];  // not used, but good for testing if image was valid

A Sample Script

Once you've made the changes, or downloaded the source from www.stone.com/dev/SketchScript, and built Sketch, launch it. You can make a document, and save the script, and then execute it in ScriptEditor. Here's a sample script:

tell application "Sketch"
    make new document at before front document
    tell the front document
		make new circle at before graphic 1 with properties {x position:69, y
position:106, width:179, height:164, stroke color:"0.000000 0.000000 0.000000",
stroke thickness:1.000000, stroked:1, fill color:"1.000000 0.244939 0.855686", filled:1}
		tell circle 1
		end tell
		make new text area at before graphic 1 with properties {x position:37, 
		 position:17, width:260, height:75, stroke color:"0.000000 0.000000
0.000000", stroke thickness:1.000000, stroked:1, fill color:"1.000000 1.000000
1.000000", filled:0, text contents:"HELLO World!"}
		set the font of the text contents of text area 1 to "Helvetica"
		set the size of the text contents of text area 1 to 64
		set the color of the text contents of text area 1 to "0.000000 0.000000 0.000000"
		set the size of characters 2 through 2 of the text contents of text
		area 1 to 24.00
		set the color of characters 3 through 4 of the text contents of text
area 1 to "1.000000 0.517401 0.441758"
		set the size of characters 3 through 4 of the text contents of text
		area 1 to 48.00
		set the size of characters 5 through 5 of the text contents of text
		area 1 to 24.00
		set the font of characters 2 through 5 of the text contents of text
		area 1 to "Futura"
		set the color of characters 5 through 7 of the text contents of text
area 1 to "0.000000 0.000000 0.000000"
		set the size of characters 6 through 7 of the text contents of text
		area 1 to 64.00
		set the color of characters 8 through 11 of the text contents of text
area 1 to "0.504702 0.287770 1.000000"
		set the color of characters 12 through 12 of the text contents of text
area 1 to "0.000000 0.000000 0.000000"
		set the size of characters 8 through 11 of the text contents of text
area 1 to 24.00
		set the size of characters 12 through 12 of the text contents of text
area 1 to 64.00
		set the font of characters 6 through 12 of the text contents of text
area 1 to "Helvetica"
		tell text area 1
		end tell
		make new circle at before graphic 1 with properties {x position:103, y
position:143, width:40, height:35, stroke color:"0.000000 0.000000 0.000000",
stroke thickness:6.280000, stroked:1, fill color:"1.000000 1.000000 1.000000", filled:0}
		tell circle 1
		end tell
		make new circle at before graphic 1 with properties {x position:176, y
position:146, width:40, height:35, stroke color:"0.000000 0.000000 0.000000",
stroke thickness:6.280000, stroked:1, fill color:"1.000000 1.000000 1.000000", filled:0}
		tell circle 1
		end tell
		make new rectangle at before graphic 1 with properties {x position:102,
y position:209, width:111, height:18, stroke color:"0.000000 0.000000
0.000000", stroke thickness:4.740000, stroked:1, fill color:"0.360790 0.192160 1.000000", filled:1}
		tell rectangle 1
		end tell
	end tell
end tell

Gotcha!

If you get errors when running your AppleScript, here are some of the possible causes and thing to look out for:

  1. Did you remember to provide terminology (such as "goes up" for Line's startsAtLowerLeft attribute)?
  2. Did you correctly spell the Apple Script term?
  3. Is there a name space collision between classes, commands or attributes? Apple Script can get confused if commands and classes have the same name!
  4. Are your Apple Event Codes unique? Use uppercase to disambiguate from Apple's.
  5. Are you using a synonym in an attribute? That won't work!
  6. While writing your suite definition or terminology, you might make a syntax error and the list becomes "unparseable". I recommend that you download and install Mike Ferris's excellent "TextExtras" framework from www.lorax.com which installs many useful text processing functions into every Cocoa app using the input manager architecture. One feature that you'll find extremely useful for Apple Script design is the "Parse as Property List" menu item: select your definition and choose this item to see if there are any typos or missing semi-colons.
  7. Quit Early, Quit Often: If you change your suite or terminology, be sure to quit ScriptEditor and relaunch it, since it might cache the old dictionary.
  8. You must quote strings and paths.
  9. Remember, in OneToManyRelationships, use the Apple Event Code of the class of object stored in the array or dictionary. In attributes, do not use the attributes class code, but create a unique one.

Known Bugs

Developer Preview 3

BUG:	NSUnknownKeyScriptError when executing statement:

"make new document at before front document"
*** -[NSDocumentController _addDocument:]: selector not recognized

WORKAROUND: none - make a new document manually and compile compile with -DDP3 for commented out code.

BUG: Where is application "Sketch"? And it brings up an OpenPanel...

WORKAROUND: compile with -DDP3 which will use "Sketch.app" which does work.

Conclusion

It's easy and rewarding to add AppleScript to your application, and it's a lot of fun to watch a script execute and makes a fearsome demo. But more importantly, it adds great value to your application because regular users can automate work flow and "program" your application to do various tasks.


Andrew Stone <andrew@stone.com> is the chief executive haquer at Stone Design Corp <http://www.stone.com/> and divides his time between raising children, llamas, & cane and writing applications for Mac OS X.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Things 3.1.3 - Elegant personal task man...
Things is a task management solution that helps to organize your tasks in an elegant and intuitive way. Things combines powerful features with simplicity through the use of tags and its intelligent... Read more
BetterTouchTool 2.292 - Customize Multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom... Read more
Things 3.1.3 - Elegant personal task man...
Things is a task management solution that helps to organize your tasks in an elegant and intuitive way. Things combines powerful features with simplicity through the use of tags and its intelligent... Read more
BetterTouchTool 2.292 - Customize Multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom... Read more
Bookends 12.8.3 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more
Mellel 3.5.5 - The word processor for sc...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
Mellel 3.5.5 - The word processor for sc...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
Bookends 12.8.3 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more
Carbon Copy Cloner 4.1.18 - Easy-to-use...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
Hopper Disassembler 4.2.14- - Binary dis...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32- and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about its... Read more

Little Red Lie (Games)
Little Red Lie 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: ARE YOU MORE AFRAID OF POVERTY THAN DEATH? Little Red Lie is a narrative-focused, interactive fiction experience that reduces... | Read more »
You can now apply to be Clash of Clans...
Earlier this month, word got out that the Builder, the trusty handiman who tirelessly built every single building inevery singleClash of Clansbase had called it quits. Sick of seeing his work destroyed endless, the Builder has set out for our world... | Read more »
Meshi Quest beginner's guide - how...
Meshi Quest is Square Enix's newest free-to-play release, and it's a real charmer. You start off as the head of a sushi restaurant, upgrading your food and equipment as you serve visitors heaping helpings of your delicious meals. As you progress,... | Read more »
BUST-A-MOVE JOURNEY (Games)
BUST-A-MOVE JOURNEY 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: BUST-A-MOVE Features:- Shoot bubbles and match 3 or more bubbles of the same color to make them pop!- Complete your... | Read more »
The best card combos in Clash Royale
Clash Royale is all about building a deck of units that synergise well. To help you get off to a flying start, we've put together a list of unit combinations that are incredibly effective. Looking for some choice 2v2 combos? Check out our guide. [... | Read more »
The best 2v2 card combos in Clash Royale
2v2 is making it's grand return toClash Royalequite soon. 2v2 has quickly become one of the game's most popular gameplay modes, though they still have yet to make it a permanent fixture in the game. 2v2 is exciting and adds some new flavor to... | Read more »
The best games we played this week - Aug...
Another busy week has come to a close. We played a lot of excellent games this week and now it's time to look back and reflect on some our favorites. Here are our picks for the week of August 18. [Read more] | Read more »
War Wings beginner's guide - how to...
War Wings is the newest project from well-established game maker Miniclip. It's a World War II aerial dogfighting game with loads of different airplane models to unlock and battle. The game offers plenty of single player and multiplayer action. We... | Read more »
How to win every 2v2 battle in Clash Roy...
2v2 is coming back to Clash Royale in a big way. Although it's only been available for temporary periods of time, 2v2 has seen a hugely positive fan response, with players clamoring for more team-based gameplay. Soon we'll get yet another taste of... | Read more »
Roll to Win with Game of Dice’s new upda...
Joycity’s hit Game of Dice gets a big new update this week, introducing new maps, mechanics, and even costumes. The update sets players loose on an exciting new map, The Cursed Tower, that allows folks to use special Runes mid-match. If you feel... | Read more »

Price Scanner via MacPrices.net

2016 15-inch 2.6GHz Touch Bar MacBook Pro ava...
B&H Photo has clearance 2016 15″ 2.6GHz MacBook Pros in stock today and on sale for $500 off original MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: – 15″ 2.6GHz Touch... Read more
21-inch 2.3GHz iMac on sale for $999, save $1...
Amazon has the new 2017 21″ 2.3GHz iMac (MMQA2LL/A) in stock and on sale for $999.99 including free shipping. Their price is $100 off MSRP, and it’s the lowest price available for this model. Read more
Free Instant Translator 2.0 App For iOS Relea...
Mobile application development company, Neoappz has announced the release and immediate availability of Instant Translator 2.0 for iOS devices. Instant Translator is a user-friendly application which... Read more
2017 15-inch MacBook Pros on sale for $200 of...
Amazon has 2017 15″ MacBook Pros on sale for $200 off MSRP. Shipping is free: – 15″ 2.8GHz MacBook Pro Space Gray: $2199.99, $200 off MSRP – 15″ 2.8GHz MacBook Pro Silver: $2296, $103 off MSRP – 15″... Read more
Apple’s 2017 Back to School Promotion: Free B...
Purchase a new Mac using Apple’s Education discount, and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free. As part... Read more
Clearance 2016 12-inch Retina MacBooks, Apple...
Apple has Certified Refurbished 2016 12″ Retina MacBooks available starting at $1019. Apple will include a standard one-year warranty with each MacBook, and shipping is free. The following... Read more
15-inch 2.2GHz Retina MacBook Pro, Apple refu...
Apple has Certified Refurbished 2015 15″ 2.2GHz Retina MacBook Pros available for $1699. That’s $300 off MSRP, and it’s the lowest price available for a 15″ MacBook Pro. An Apple one-year warranty is... Read more
Apple refurbished Mac minis available startin...
Apple has Certified Refurbished Mac minis available starting at $419. Apple’s one-year warranty is included with each mini, and shipping is free: – 1.4GHz Mac mini: $419 $80 off MSRP – 2.6GHz Mac... Read more
Apple refurbished iPad Pros available startin...
Apple has Certified Refurbished 2016 12″ WiFi iPad Pros available starting at $589. An Apple one-year warranty is included with each model, and shipping is free: – 32GB 12″ iPad Pro WiFi: $589... Read more
Weekend sale: 13-inch MacBook Pros for up to...
Amazon has new 2017 13″ MacBook Pros on sale today for up to $200 off MSRP, each including free shipping: – 13″ 3.1GHz/256GB Space Gray MacBook Pro (MPXV2LL/A): $1599.99 $200 off MSRP – 13″ 3.1GHz/... Read more

Jobs Board

Development Operations and Site Reliability E...
Development Operations and Site Reliability Engineer, Apple Payment Gateway Job Number: 57572631 Santa Clara Valley, California, United States Posted: Jul. 27, 2017 Read more
Frameworks Engineering Manager, *Apple* Wat...
Frameworks Engineering Manager, Apple Watch Job Number: 41632321 Santa Clara Valley, California, United States Posted: Jun. 15, 2017 Weekly Hours: 40.00 Job Summary Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Development Operations and Site Reliability E...
Development Operations and Site Reliability Engineer, Apple Payment Gateway Job Number: 57572631 Santa Clara Valley, California, United States Posted: Jul. 27, 2017 Read more
Frameworks Engineering Manager, *Apple* Wat...
Frameworks Engineering Manager, Apple Watch Job Number: 41632321 Santa Clara Valley, California, United States Posted: Jun. 15, 2017 Weekly Hours: 40.00 Job Summary Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.