TweetFollow Us on Twitter

Extend Applescript
Volume Number:10
Issue Number:1
Column Tag:Scripting

Related Info: Apple Event Mgr

Extending AppleScript™
With Scripting Additions

Not everything is handled in vanilla AppleScript - here’s how you add to it

By Donald Olson, Apple Computer, Inc.

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

I. A brief history

In the beginning, the high priests made AppleScript. It had conditionals and flow controls and pretty printing. It had script objects with properties and methods. It talked to applications and integrated their functionality. It conversed across the network. It was an OODL. And the high priests proclaimed it good.

But the people of AppleScript soon saw something amiss in the design. “Where is the ask/answer dialog and the lowly beep?” the people asked. “Where are the standard file and PPC Browser dialogs?” they demanded.

The high priests declared, “These things you ask for are not part of a pure language such as AppleScript. You must look to applications for the relief you seek!”

There was much wailing and gnashing of teeth in the land of AppleScript. “We must have the simple beep and the friendly ask/answer dialogs so that we may talk amongst ourselves without the use of special applications,” the people protested. “It is an abomination to our hard drives that we must have so many tiny applications to do our bidding!”

So the priests conferred. “The people of AppleScript want to add new functionalities to our language” they said. “We will not allow the purity of AppleScript, the language, to be sullied with such things as user interface elements and noises. We, therefore, will provide a mechanism which allows the people to add syntax and dialogs and data transformations to AppleScript without requiring that they be made a permanent part of our language. And we will give them a basic set of these extensions to appease their needs.”

And low, the high priests unveiled Scripting Additions to the people of AppleScript.

The people rejoiced. “We have our beep, our ask/answer and it is good! Scripting Additions are loaded on demand, are available to the entire system, and yet, do not fill up our system heap!”

“But, oh high priest, ” one of the AppleScript people said, “this minimal set of scripting additions does not have all that I need. How may I write such scripting additions myself so that I can achieve the total integration AppleScript promises?”

“Oh AppleScript people, I will bring down from the mountain of VirtualTables the coding instructions and the commandments of Scripting Addition programming. Use this information wisely, and your Scripting Additions will prosper.”

“Heed me well, people of AppleScript. Scripting Additions are part of our new OSA scripting language AppleScript. Scripting Additions provide a mechanism for extending the language and functionality of AppleScript. They are similar to XCMD’s for HyperCard and UCMD’s for Frontier.”

MacTech Editor: “Hellooow, Donald!”

Article Author: “Um, uh, yea Neil?”

MacTech Editor: “This mythology stuff is fun, but can we get on with the article??”

Article Author: “Article?? Oh! Sorry! Ok, here we go ”

II. The parts.

Scripting Additions are implemented as Apple Event handlers or coercions that AppleScript loads on demand. In order to write Scripting Additions, you need a basic understanding of the Apple Event Manager (AEM) .

The AEM is a collection of routines that provide a mechanism for sending and receiving messages (called AppleEvents) between applications either on the local machine or over an AppleTalk network. Each message contains a pair of identifiers that serve to inform the receiving application of the action to be taken as a result of this message. These identifiers are called the class and id of the message. Since the class and id specify the action to be taken, it is common to call the class and id the verb of the message.

Data may also be associated with the verb of the message. This data is placed in structures called descriptors and added as parameters to the Apple event. The AEM provides a set of routines for adding or extracting parameters from a message. Since there may be more than one parameter associated with a message, each parameter has a unique identifier called a keyword associated with it. Since a verb normally has a focus for the action it is to take, there is a standard parameter called the direct parameter (or direct object) which is used to specify the object the verb is to act on.

An example of an Apple Event message is the open document message that is sent by the Finder when you double click on one or more documents associated with an application. The class of the open document event is kCoreEventClass and the id is kAEOpenDocuments. It has one parameter, the keyDirectObject (a.k.a. the direct parameter), which contains an AppleEvent list containing aliases to the documents that the application is to open.

When an application receives an Apple event (high level event) in its event queue, it calls the AEM routine AEProcessAppleEvent. This causes the AEM to attempt to dispatch the AppleEvent to an Apple event handler that is installed by the application in the AEM application dispatch table. The AEM dispatches based on the class and id of the event. In our example above, the application would need to have installed a handler with the class kCoreEventClass and the id kAEOpenDocuments in order to successfully deal with the open documents Apple event.

What, you ask, does this have to do with writing a Scripting Addition?

The AEM provides not only a dispatch table for AppleEvents for each application that installs handlers, it also maintains a dispatch table for system handlers as well. System handlers, unlike application specific handlers, are available to all applications on the machine where they are installed. This is where AppleScript installs Scripting Additions. If the AEM does not find a match in the application dispatch table, or if the handler returns one of two special errors (errAEEventNotHandled or errAENoSuchObject), the AEM attempts to find a match in this system dispatch table and thus, OSAX are invoked.

Coercions work in a similar fashion. In coercions, class and id are not used. The ‘from’ and ‘to’ data types are used instead. The AEM maintains application and system dispatch tables for coercions as well.

Now that we’ve had a very brief overview of Apple events, let’s dive into the parts of a Scripting Addition.

The Scripting Additions File.

The file that contains the various resources that make up a Scripting Addition is created with the type ‘osax’ and the creator ‘ascr’. This allows the Finder to associate the Scripting Additions icon (stored in AppleScript) with each OSAX file.

The ‘ osax’ resource.

Each Scripting Addition file has at least one code resource of type ‘osax’ that contains the event or coercion handler for the Scripting Addition. It's the name of the ‘osax’ resource that tells AppleScript the type of Scripting Addition and the class and id of the event handler; or the from and to types for a coercion OSAX. The naming scheme works like this: The first four characters of the ‘osax’ resources name tells AppleScript the type of OSAX. It will be ‘AEVT’ for event handlers and ‘CSDS’ or ‘CSPT’ for coercion handlers. ‘AEVT’ stands for “Apple Event”, ‘CSDS’ stands for “coerce from descriptor”, and ‘CSPT’ stands for “coerce from pointer”.

For example, an ‘osax’ resource named “AEVTaevtodoc” would install an Open document handler. An ‘osax’ resource named “CSDSscptTEXT” would install a coercion from the type ‘scpt’ (the data type for an AppleScript compiled script) to ‘TEXT’ which is the type for text.

Other resources

Both event and coercion OSAX can have other resources included in there files. An event handler that puts up a dialog might have ‘DLOG’ and ‘DITL’ resources. A coercion handler might have a resource that contains conversion information for the coercion to use. The Scripting Addition mechanism in AppleScript makes the OSAX’s resource file the current when prior to dispatching to the OSAX code. An OSAX can therefore access any resource in the following resource chain: OSAX -> [target application] -> System. If no target is specified in the script (i.e. no ‘tell application’ block is used), the target application is the script editor in use.

Event handlers require one resource that coercion handlers do not require. The ‘aete’ or Apple event terminology extension resource. This is the resource that provides the language syntax for your event.

The ‘aete’ resource.

It is the ‘aete’ resource that AppleScript uses to determine the syntax for your OSAX command. In the aete you describe the Apple event and all its parameters along with a corresponding grammatical equivalent for each. In fact, the way I like to design my OSAX’s is by starting with the AppleScript syntax for the verb, write the aete to match the syntax, and then actually writing the code.

III. Writing a Scripting Addition

Design your syntax first

Let’s design an OSAX that plays a QuickTime movie in a modal dialog. Let’s see, how about the syntax: ‘play movie <the path to the movie> [at <point or rectangle>]’? That sounds about right. This gives the user the ability to specify the movie to play and optionally position the window on the screen. (We’re purposely not going to add support for the controller in this implementation. That we’ll leave as an exercise for the reader )

Here’s what the event for the above syntax looks like:

play movie - this is the verb so let’s assign the class and id for this.

class : ‘OLIE’ - this happens to be my nick name, you should use the signature of your application that you’ve registered with DTS or some other 4 character code that will differentiate your OSAX. Remember, lower case is reserved for the System (read Apple Computer).

id: ‘QTIM’ - Try to use something descriptive here.

<the path to the movie> - The direct parameter of an Apple event is defined as not having an associated language label. This is because the object the verb is acting on implies that the object to act on will be described next. Since our verb ‘play movie’ requires that we describe which movie we’re to play, we’ll use the direct parameter for the path to the movie to play.

keyword: ‘----’ - The direct parameter.

type: typeAlias - For the path we’ll use typeAlias. For a discussion about aliases versus other addressing forms, see the section ‘Tips, tricks and gotchas’ below.

[at <point or rectangle>] - our first ‘named’ parameter.

keyword: ‘LOCA’ - This can be a list of 2 or 4 integers or a record that contains labels for the positions of the point or rectangle; For example: ‘play movie alias “Cool Disk:Cooler Folder:Hot Movie” at {qttop: 15, qtleft:30}’. This is nice for the user since they don’t need to remember the ordering for the point or rectangle coordinates. Notice that we don’t simply use ‘top’ and ‘left’ for our record labels, instead we use the prefix ‘qt’. This is because of a conflict with terminology defined for the text suite included in AppleScript itself. ‘left’ and ‘right’ are already defined as formating options for a block of text. See Section V below for a discussion of other possible syntax clashes.

type: typeAEList/typeMyRectangle -Since the data can be either be a list or a record, we need to define this parameter with each of the possible data types. The type typeMyRectangle is a custom type that we define as a class. See Section V below for more information on using classes to define custom data types.

We could also use the wild card type for the ‘at’ parameter. We choose not to however because it can add a level of ambiguity to the syntax of the OSAX since it implies that any data type can be passed in for that parameter.

Build your aete second

Now take a look at the listing “SAPlayMovie.r” to see the above design work in an ‘aete’. Notice how we’ve filled in the other fields of the aete. In particular, pay close attention to the class definition near the bottom. Here is where we declare the record for our ‘at’ parameter. We define the rectangle record type to be a class of type ‘rect’ with properties for the top, left, bottom and right coordinates.

Also notice that we’ve declared the ‘at’ parameter twice: once with the type list, and once with the type of the class definition for our rectangle record. This way AppleScript knows both of the allowed types for the ‘at’ parameter.

The formal definition for an ‘aete’ resource is found in the file “AEUserTermTypes.r”.

/* 1 */

/*
   SAPlayMovie.r written by Donald O. Olson
 A simple QuickTime Scripting Addition written
 to illustrate writing Scripting Additions.

 Copyright ®1993 Donald O. Olson
 All rights reserved.
*/

#include ”Types.r"
#include "SysTypes.r"
#include "AEUserTermTypes.r"

#define typeMyRectangle 'RECT'

resource 'vers' (1) {
 0x1,
 0x0,
 final,
 0x0,
 verUS,
 "1.0",
 "1.0, Copyright ® 1993 Donald Olson"
 ". All rights reserved."
};

resource 'vers' (2) {
 0x1,
 0x0,
 final,
 0x0,
 verUS,
 "1.0",
 "(by Donald Olson)"
};

/* This string gets displayed if the user double clicks on us. */

resource 'STR ' (-16397) {
 "This document can not be opened or printed."
 " It extends the functionality of AppleScript™"
 "and should be placed in the Scripting Additions"
 "folder found in the Extensions folder of your"
 " System Folder."
};

resource 'aete' (0, "play movie") {
 /*   
 The major and minor version fields refer to the
 'aete' definition. 
 */
 0x1,   /* major version in BCD  */
 0x0,   /* minor version in BCD  */
 english, /* language code */
 roman, /* script code */
 { /* array Suites: 1 elements */
 /* [1] */
 "The Olson OSAX Suite.", /* suite name */
 /* Suite Description */
 "A collection of fine Scripting Additions"
 " for work and play.", /* suite description */
 'OLIE',/* suite ID */
 1,/* suite level */
 1,/* suite version */
 { /* array Events: 1 elements */
 /* [1] */
 "play movie",   /* event name */
 /* event description */
 "Play a QuickTime movie in a modal dialog.",
 'OLIE',/* Our Class */
 'QTIM',/* Our ID */
 noReply, /* No Reply */
 /* Reply comment. */
 "No reply is returned by this event.", 
 replyOptional,  /* Reply not required. */
 singleItem,/* Reply is a single item. */
 notEnumerated,  /* Reply is not enumerated */
 reserved, reserved, reserved, reserved, reserved,
 reserved, reserved, reserved, reserved, reserved,
 reserved, reserved, reserved,
 'alis',/* Direct param is alias. */
 /* Comment for direct parameter. */
 "Pass in path to the QuickTime movie to play.",
 directParamRequired,
 singleItem,
 notEnumerated,
 doesntChangeState,
 reserved, reserved, reserved, reserved,
 reserved, reserved, reserved, reserved,
 reserved, reserved, reserved, reserved,
 { /* array OtherParams: 2 elements */
 /* [1] */
 "at",  /* Optional parameter */
 'LOCA',/* Its keyword. */
 'list',/* Its type. */
 /* Comment for optional parameter. */
 "Point to use to position movie or rectangle"
 " to play move in. Must be in order 'left,"
 " top, right, bottom.",
 optional,/* Optional parameter */
 listOfItems,    /* Must be a list. */
 notEnumerated,  /* is not enumerated */
 reserved, reserved, reserved, reserved,
 reserved, reserved, reserved, reserved,
 reserved, reserved, reserved, reserved,
 reserved,
 /* [2] */
 /*
 We define our optional parameter
 twice so that we can allow two
 different data types.
 */
 "at",  /* Optional parameter */
 'LOCA',/* Its keyword. */
 typeMyRectangle,  /* Its type. */
 /* Comment for optional parameter. */
 "Point to use to position movie or rectangle"
 " to play move in. Must be in order 'left,"
 " top, right, bottom.",
 optional,/* Optional parameter */
 listOfItems,    /* Must be a list. */
 notEnumerated,  /* is not enumerated */
 reserved, reserved, reserved, reserved,
 reserved, reserved, reserved, reserved,
 reserved, reserved, reserved, reserved,
 reserved
 }
 },
 { /* array Classes: 1 elements */
 /* [1] */
 /*
 This is how we define our custom
 record. We define a class 'rectangle'
 with the custom keyword typeMyRectangle.
 We then define properties of this class
 for each of our records labels.
 */
 "rectangle",    /* Name of our class. */
 typeMyRectangle,/* Type of our class. */
 /* Comment for optional parameter. */
 "This is a custom class definition used to "
 "define the record we use to position our "
 "movie's window.",
 {/* array Properties: 4 elements */
 /* [1] */
 "qttop", /* Name of property. */
 'TOP ',/* Keyword */
 typeMyRectangle,/* Its type. */
 "Top of rectangle.",/* Comment field. */
 reserved,
 singleItem,
 notEnumerated,
 readOnly, 
 reserved, reserved, reserved, reserved, 
 reserved, reserved, reserved, reserved, 
 reserved, reserved, reserved, reserved,
 /* [2] */
 "qtright", /* Name of property. */
 'RGHT',/* Keyword */
 typeMyRectangle,/* Its type. */
 /* Comment field. */
 "Right side of rectangle.",
 reserved,
 singleItem,
 notEnumerated,
 readOnly, 
 reserved, reserved, reserved, reserved, 
 reserved, reserved, reserved, reserved, 
 reserved, reserved, reserved, reserved,
 /* [3] */
 "qtleft",/* Name of property. */
 'LEFT',/* Keyword */
 typeMyRectangle,/* Its type. */
 "Left side of rectangle.",/* Comment field. */
 reserved,
 singleItem,
 notEnumerated,
 readOnly, 
 reserved, reserved, reserved, reserved, 
 reserved, reserved, reserved, reserved, 
 reserved, reserved, reserved, reserved,
 /* [4] */
 "qtbottom",/* Name of property. */
 'BOTM',/* Keyword */
 typeMyRectangle,/* Its type. */
 "Bottom of rectangle.", /* Comment field. */
 reserved,
 singleItem,
 notEnumerated,
 readOnly, 
 reserved, reserved, reserved, reserved, 
 reserved, reserved, reserved, reserved, 
 reserved, reserved, reserved, reserved
 },
 { /* array Elements: 0 elements */
 }
 },
 { /* array ComparisonOps: 0 elements */
 },
 { /* array Enumerations: 0 elements */
 }
 }
};

Write your code third

Writing OSAX is very straight forward: follow all the rules for writing standalone code resources and all will be swell.

Our samples will be written in ‘C’, but Scripting Additions can be written in any language that can compile your work into a code resource.


/* 2 */

/////////////////////////////////////////////////////////////
//
//    SAPlayMovie.c written by Donald O. Olson
//    A simple QuickTime Scripting Addition written
// to illustrate writing Scripting Additions.
//
// Copyright ®1993 Donald O. Olson
// All rights reserved.
//
/////////////////////////////////////////////////////////////

// Our includes
#include <Movies.h>
#include <Memory.h>
#include <Fonts.h>
#include <OSEvents.h>
#include <limits.h> 
#include <Menus.h>
#include <Processes.h>
#include <String.h>
#include <Resources.h>
#include <Packages.h>
#include <AppleEvents.h>
#include <Errors.h>
#include <GestaltEqu.h>
#include <Files.h>   

// Our optional parameters keyword
#define keyLocation'LOCA'

// Our rectangle records keywords
#define keyRight 'RGHT' 
#define keyLeft  'LEFT' 
#define keyTop   'TOP ' 
#define keyBottom'BOTM' 

#define kLeft    1 // Our list of points are in the
#define kTop2  // order shown
#define kRight   3
#define kBottom  4

// Used to position movie on screen
#define kDefaultOffset  100
#define kBogusNumber UINT_MAX

/*
 Our prototypes, could be in a .h file but are included here
 for ease of use.
*/

OSErr PlayTheMovie(FSSpec myFSSpec, AEDesc theLocationDesc);
OSErr SetMovieRect(AEDesc theLocationDesc, Movie theMovie,
 Rect *ourRect);

/////////////////////////////////////////////////////////////
//
// main()
// The entry to our Scripting Addition.
// Remember to declare it pascal!!
//
/////////////////////////////////////////////////////////////

pascal OSErr main( AppleEvent *theEvent, 
 AppleEvent *theReply, 
 long theRefCon)
{
 OSErr  theErr = noErr;
 FSSpec theFSSpec;
 DescType typeCode;
 long   theGestaltReturn = 0;
 Size   actualSize;
 AEDesc theLocationDesc;
 
 /* Is QuickTime present? */
 theErr = Gestalt(gestaltQuickTime, &theGestaltReturn);
 if(theErr) return theErr; // If not, bail.
    
   /*
   Grab the movie file's path from the direct parameter.
   We declared the direct parameter to be an alias in our
   aete.  Since the AEM will coerce an alias to a FSSpec for
   us, and that's what the OpenMovieFile call wants, we'll
   ask for it as an FSSpec.
   */
      
  theErr = AEGetParamPtr( theEvent, keyDirectObject, 
        typeFSS, &typeCode, (Ptr)&theFSSpec, 
        sizeof(FSSpec), &actualSize);
  if(theErr) return theErr;

 /*
 Now get the location parameter, if it's present.
 We don't check errors for this call since the AEM
 will return the descriptor with the descriptorType
 field set to typeNull if there is an error.  We check 
 for NULL in the PlayTheMovie function.
 */
 
  theErr = AEGetParamDesc(theEvent, keyLocation,
                          typeWildCard, &theLocationDesc);
    
  /* Start up the movie tools */
  if(EnterMovies()) return theErr; // Bail on error.
 
 theErr = PlayTheMovie(theFSSpec, theLocationDesc);
 
 ExitMovies(); // Close our connection to the movie tools
 
 return theErr;  // And return our error.
}

/////////////////////////////////////////////////////////////
//
// PlayTheMovie() Opens and plays Movie File
// This code is based on the SimplePlayer sample
// that comes with the QuickTime Developers Disk.
//
/////////////////////////////////////////////////////////////

OSErr  PlayTheMovie(FSSpec myFSSpec, AEDesc theLocationDesc)
{
 Movie  theMovie;
 Rect   dispBounds;
 WindowPtrmovieWindow = NULL;
 OSErr  theErr = noErr;
 short  resRefNum; 
 long   duration = 60, finalTick;
 
 /* Open the movie file */
 if(OpenMovieFile(&(myFSSpec), &resRefNum, 0)) {
 SysBeep(1);// Signal our error
 return theErr;  // And bail 
 }
 
 if(NewMovieFromFile( &theMovie, resRefNum, 
 NULL, NULL,0, NULL )) {
 SysBeep(1);// Signal our error
 return theErr;  // And bail
 }
 
 /* Get the bounds for the movie. */
 
 GetMovieBox( theMovie, &dispBounds);
 
 /* 
 If the user passed in a location or size for the window,
 let's grab it now.
 */
 
 if(theLocationDesc.descriptorHandle != NULL) {
 // Use the values sent to us by the user.
 theErr = SetMovieRect( theLocationDesc, theMovie, 
 &dispBounds);
 if(theErr) return theErr;
 } else { 
 OffsetRect(&dispBounds,-dispBounds.left,-dispBounds.top);
 SetMovieBox(theMovie, &dispBounds); 
 // Make sure window not under menu bar
 OffsetRect(&dispBounds,kDefaultOffset,kDefaultOffset);
 
 }
 
 /* 
 Any time you are going to put up a dialog be sure to 
 call AEInteractWithUser. The AppleEvent Manager will 
 take care of posting notification and/or layer switching 
 as needed.
 */
 
 theErr = AEInteractWithUser(kAEDefaultTimeout, NULL, NULL);
 if(theErr) return theErr;
 
 /* Set up our window */
 movieWindow = NewCWindow(0L, &dispBounds,
 (StringPtr)myFSSpec.name,
 true,0,(WindowPtr)-1L,false,0L);
 if(movieWindow == NULL) {
 // Whoops, no window so BAIL;
 SysBeep(1);
 return memFullErr;
 } 
 
 ShowWindow(movieWindow); // Make the window visible
 SetPort(movieWindow);    // Set the part
 // Set up the movie world
 SetMovieGWorld(theMovie,NULL,NULL); 
 
 /* Now we're ready to play the movie */
 GoToBeginningOfMovie(theMovie);// Rewind movie
 PrerollMovie(theMovie,0,0);// Get the movie ready to play 
 SetMovieActive(theMovie,true);  // Activate movie 
 StartMovie(theMovie);    // Start playing
 
 /* 
 Play the movie to the end unless the mouse button has 
 been pressed 
 */
 while ( !IsMovieDone(theMovie) && !Button())
 MoviesTask(theMovie,0);

 FlushEvents(everyEvent, 0);// Clean up spurious events
 Delay(duration, &finalTick); // Pause on the final frame
 /* Clean up and go home */
 DisposeMovie(theMovie);  // Get rid of the movie 
 CloseMovieFile(resRefNum); // Close movie file
 DisposeWindow(movieWindow);// And dispose our window
 return theErr;
}

/////////////////////////////////////////////////////////////
//
// SetMovieRect
// Set the position and bounding rectangle of 
// our movie window.
//
/////////////////////////////////////////////////////////////

OSErr SetMovieRect(AEDesc theLocationDesc, Movie theMovie, 
 Rect *ourRect) {
 long   numberOfListItems = 0;
 OSErr  theErr = noErr;
 AEKeyword  theAEKeyword;
 DescType typeCode;
  Size    actualSize;
  long  pointLeft, pointTop,
        pointRight = kBogusNumber, // We use pointRight
        pointBottom; // to see if we've gotten a point
        // or a rectangle

 /* Did we get passed a list? */
 if(theLocationDesc.descriptorType == typeAEList) {
 
 /* Get the data handle size to determine if point or rect */
 theErr = AECountItems( &theLocationDesc, 
 &numberOfListItems);
  if(theErr) return theErr; // Bail on error!
   /* Must be two or four items in list */
   if(numberOfListItems != 2 && numberOfListItems != 4) 
 return paramErr;
      
 /* If it's a point, just move the window. */
 if(numberOfListItems == 2) {
 theErr = AEGetNthPtr(  &theLocationDesc, kLeft, 
 typeLongInteger, &theAEKeyword, &typeCode,
         (Ptr)&pointLeft, sizeof(pointLeft), &actualSize);
 if(theErr) return theErr; // Just in case  
 theErr = AEGetNthPtr(  &theLocationDesc, kTop, 
 typeLongInteger, &theAEKeyword, &typeCode,
         (Ptr)&pointTop, sizeof(pointTop), &actualSize);
 if(theErr) return theErr; // Just in case     
 } else if(numberOfListItems == 4) { // It's a rectangle
 theErr = AEGetNthPtr(  &theLocationDesc, kLeft, 
 typeLongInteger, &theAEKeyword, &typeCode,
         (Ptr)&pointLeft, sizeof(pointLeft), &actualSize);
 if(theErr) return theErr; // Just in case  
 theErr = AEGetNthPtr(  &theLocationDesc, kTop, 
 typeLongInteger, &theAEKeyword, &typeCode,
         (Ptr)&pointTop,sizeof(pointTop), &actualSize);
 if(theErr) return theErr; // Just in case  
 
 theErr = AEGetNthPtr(  &theLocationDesc, kRight, 
 typeLongInteger, &theAEKeyword, &typeCode,
         (Ptr)&pointRight, sizeof(pointRight), &actualSize);
 if(theErr) return theErr; // Just in case  
 theErr = AEGetNthPtr(  &theLocationDesc, kBottom, 
 typeLongInteger,&theAEKeyword, &typeCode,
         (Ptr)&pointBottom, sizeof(pointBottom), &actualSize);
 if(theErr) return theErr; // Just in case  
    
 } 
/* Is it a record? */
} else if(theLocationDesc.descriptorType == typeAERecord) { 
 /* Get the points out by key names */
      
 theErr = AEGetKeyPtr(  &theLocationDesc, keyLeft, 
 typeLongInteger, &typeCode, (Ptr)&pointLeft, 
 sizeof(pointLeft), &actualSize);
     if(theErr) return theErr; // Must have these two

     theErr = AEGetKeyPtr(&theLocationDesc, keyTop, 
 typeLongInteger, &typeCode, (Ptr)&pointTop, 
 sizeof(pointTop), &actualSize);
 if(theErr) return theErr; // Must have these two
 
 theErr = AEGetKeyPtr(  &theLocationDesc, keyRight, 
 typeLongInteger, &typeCode, (Ptr)&pointRight, 
 sizeof(pointRight), &actualSize);
      // Ignore this error

     theErr = AEGetKeyPtr(&theLocationDesc, keyBottom, 
 typeLongInteger, &typeCode, (Ptr)&pointBottom, 
 sizeof(pointBottom), &actualSize);
 // Ignore this error too, but clear our variable
 theErr = noErr;
 }
 
 if(pointRight == kBogusNumber)  // We got a new origin...
 SetRect( ourRect, pointLeft, pointTop, 
 (ourRect->right - ourRect->left) + pointLeft, 
 (ourRect->bottom - ourRect->top) + pointTop);
 else   // We got a new rectangle...
 SetRect( ourRect, pointLeft, pointTop, 
 pointRight, pointBottom);

 /* Set topleft to 0,0 */
 OffsetRect(ourRect,-ourRect->left,-ourRect->top); 
 
 /* Set the movie box to the new rect. */
 SetMovieBox(theMovie, ourRect); 
 OffsetRect(ourRect,pointLeft, pointTop); 
 return theErr;
}

Compiling

To compile in MPW, use the following build commands:

/* 3 */

C -b "SAPlayMovie.c" -d SystemSevenOrLater 
Rez -a -o "play movie" -t osax -c ascr 'SAPlayMovie.r'
Link -p -w -t osax -c ascr -rt osax=6991 -m MAIN -sg "AEVTOLIEQTIM" -ra 
"AEVTOLIEQTIM"=resSysHeap,resPurgeable 
 "SAPlayMovie.c.o" 
 "{CLibraries}"StdCLib.o 
 "{Libraries}"Runtime.o 
 "{Libraries}"Interface.o 
 -o "play movie" 

To compile in Symantec THINK C, include SAPlayMovie.c, SAPlayMovie.r and MacTraps in your project. Set your project type as shown. Make sure that the resource attributes Purgeable and System Heap are set to true.

Write some test scripts fifth

 play movie (choose file) at {100, 100}
 play movie (choose file) at {qttop: 100, qtleft: 100, 
 qtright:250, qtbottom, 250}

IV. Writing a coercion Scripting Addition

Writing a coercion OSAX is simpler than writing an event OSAX. The design work is to simply notice that you need to coerce one data type to another. The one decision you need to make is whether the coercion should be coerce from pointer or coerce from descriptor. In general, always use a coerce from pointer coercion since it means AppleScript need not build an AEDesc to pass into your coercion.

Write your code

We’re going to write a sample coercion here that takes a script object and coerces it to text. We’ll do this by taking advantage of a few calls in the Open Scripting Architecture that will do the work of de-compiling a script object into its representative text form. And, this is an example of when you would want to use the coerce from descriptor form.


/* 4 */

/////////////////////////////////////////////////////////////
//
//    CoercescptToText.c written by Donald O. Olson
//    A simple Coercion Scripting Addition written
// to illustrate writing Scripting Additions.
//
// Copyright ®1993 Donald O. Olson
// All rights reserved.
//
/////////////////////////////////////////////////////////////

#include <Memory.h>
#include <Fonts.h>
#include <OSEvents.h>
#include <Menus.h>
#include <Processes.h>
#include <String.h>
#include <Resources.h>
#include <Packages.h>
#include <AppleEvents.h>
#include <Errors.h>
#include <GestaltEqu.h>
#include <Files.h>   
#include <OSA.h>   

#define typeStyledText  'STXT'

/////////////////////////////////////////////////////////////
//
// main()
// This is the interface for a coerce from descriptor
// coercion.  Remember to declare it 'pascal'!!!
//
/////////////////////////////////////////////////////////////

pascal OSErr main( AEDesc *scriptDesc, DescType toType, 
 long refcon, AEDesc *resultDesc)
{
 OSErr    theErr = noErr;
 OSAError theOSAErr;
 ComponentInstance gASComponent = 0;
 OSAID  resultingID = 0;
 long   modeFlags = 0;

 /* 
 Open an instantiation of the 
 Generic Scripting Component 
 */
 gASComponent = OpenDefaultComponent(kOSAComponentType, 
 kOSAGenericScriptingComponentSubtype);
 
 /* Checking errors here! */
 if(((long)gASComponent == (long)badComponentInstance) || 
 ((long)gASComponent == (long)badComponentSelector)) {
 theErr = invalidComponentID; 
 goto CLEANUP; // Yea! A valid use for a 'goto'!!
 }
 
 /* Load script in scriptDesc into a scriptID */
 theOSAErr = OSALoad(gASComponent, scriptDesc,
 modeFlags, &resultingID);
 
 if(  theOSAErr != noErr) {
 theErr = theOSAErr;
 goto CLEANUP;
 }
 
 /*   
 Now get the source.  Since AppleScript can coerce any
 of the various text forms to a text object (which is
 what we claim to be returning, let's just return the
 styled text and let AppleScript do the secondary 
 coercion for us.
 */
 
 theOSAErr = OSAGetSource(gASComponent, resultingID,
 typeStyledText, resultDesc);
  
 if(  theOSAErr != noErr) theErr = theOSAErr;
 
 CLEANUP:;
 if(resultingID != 0) OSADispose(gASComponent, resultingID);
 if(gASComponent != 0) CloseComponent(gASComponent);

 return theErr;
}

Compiling

To compile in MPW, use the following build commands:


/* 5*/

C -b "CoercescptToText.c" -d SystemSevenOrLater 
Link -p -w -t osax -c ascr -rt osax=9999 -m MAIN -sg "CSDSscptctxt" -ra 
"CSDSscptctxt"=resSysHeap,resPurgeable 
 "CoercescptToText.c.o" 
 "{CLibraries}"StdCLib.o 
 "{Libraries}"Runtime.o 
 "{Libraries}"Interface.o 
 -o "ScriptToTextCoercion"


To compile in Symantec THINK C, include CoercescptToText.c and MacTraps in your project. Set your project type as shown. Make sure that the resource attributes Purgeable and System Heap are set to true.

Write some test scripts

Here’s a sample script to use to test our coercion.

 
 script Fred
 property foo : 3
 property bar : 3
 on fumble()
 beep 2
 return "Whoops!"
 end fumble
 end script

Fred as text

Executing this script will return the following as a result of the coercion:

 
 "property foo : 3
 property bar : 3
 on fumble()
 beep 2
 return \"Whoops!\"
 end fumble"

This is correct since this is the script associated with the script object. The script declaration is not stored as part of the script object.

V. Tips, tricks and gotchas.

Wildcard classes and multiple verbs

The Apple Event Manager allows the use of wildcards for either the class or id for event handlers, and the ‘from’ and ‘to’ types for coercions. When the AEM does not find a direct match and event or coercion, it looks to see if it can find a partial match with an entry in the table that uses a wildcard entry for either or both of its keys. For example: if I have an entry in our AEM dispatch table for the class ‘OLIE’ and the id ‘****’ and we recieve an event with the class ‘OLIE’ and the id ‘TEXT’, as long as there is not a match for the id ‘TEXT’ in the dispatch table somewhere, the wildcard entry will be called.

We can use this ability in OSAX to have one ‘osax’ code resource that handles several different yet related events or coercions.

For events, the way to do this is to define either the class or id with the ‘****’ wildcard type. For example, I want to write a cd player OSAX that shares a large amount of the same code. I’ll define the OSAX’s name like this “AEVTCDPL****”. In the ‘aete’ for the OSAX I’ll define as many verbs as I need using the class ‘CDPL’ with unique id’s for each verb. In the ‘osax’ code for the handler I’ll extract the id using the call AEGetAttributePtr with the key keyEventIDAttr. This returns the id of the event. Simply case off the id to the appropriate code.

We can do the same thing with coercions. To declare a wildCard to text coercion (coerce anything to text) we would name the ‘osax’ code resource “CSPT****TEXT”. The big difference here is that the from type is passed in as a parameter to the coercion handler so we can case directly off of that.

Globals and the refCon

If you need to use global data in your OSAX, or if you need a place to keep a handle to some data around, you can use the refCon field of your event or coercion handler. When AppleScript installs your handler it initializes your refCon to NULL. You can then test for NULL the first time you are called to determine if you have initialized your globals or data.

To set the refCon field you use a two step process. First, you call AEGetEventHandler for event handlers or AEGetCoercionHandler for coercions. In both cases set the isSystem parameter to true. Second, you use AEInstallEventHandler or AEInstallCoercionHandler. Make sure that you use the procPtr returned to you from the AEGet call since the Scripting Addition mechanism uses a special loading scheme to load your handler or coercion and to make its resource available in the current resource chain.

Movable modals and other windows

While it is possible to write OSAX that have moveable windows, it is not recommended. The normal method for doing movable modal dialogs requires a hook into the host application’s event loop to assure that update events are handled appropriately. Since there is no simple way to pass update events to an application, only use modal windows in OSAX.

Conflicts with verbs, properties, enums and such

There are potentially three different terminology caches in use by AppleScript at any given time. AppleScript has an internal terminology cache that is available System wide. This terminology contains the Required Suite, the Core Suite, and the AppleScript Suite. AppleScript also keeps terminology caches for each running application that has been targeted in a ‘tell’ block. The last terminology cache is the OSAX cache.

There is a ‘terminology inheritance’ chain that is in affect that can cause OSAX to have terminology conflicts. The path is as follows: the targeted application (if any), AppleScript, and last, the OSAX. The fact that the OSAX is last is the biggest reason there are conflicts.

Terminology conflicts will normally display themselves at compile time. An example of a compile time conflict occurs when you target the Scriptable Text Editor with a script that uses the offset OSAX. Offset is also defined as a property of many of the text based classes such as paragraph, word and character. The following script demonstrates the problem:

 
 tell app "scriptable text editor"
 offset of "1" in "123"
 end tell

This returns the error “Can’t get ‘1’ in ‘123’. Access not allowed.”

Another example is the ‘play movie’ OSAX we wrote above. Since ‘left’ and ‘right’ are already defined as properties of the text class (align left, align right), compiling our OSAX will fail with the first colon after the the first declaration of ‘left’ or ‘right’ selected and the error message “Expected ‘,’ or ‘}’ but found ‘:’.” displayed for our enjoyment. We solved the problem in the ugliest fashion possible by pre-pending ‘qt’ to each of our four parameters.

This is a very difficult problem for OSAX authors to deal with. There are only so many descriptive verbs that apply to the desktop metaphor used so extensively by Macintosh programs. Without additional support from AppleScript to resolve these kinds of conflicts, the best thing an OSAX developer can do is test their work against as many scriptable applications as possible.

More than one ‘osax’ code resource in a file

You can place more than one OSAX in a Scripting Addition file. It may make sense to place ‘families’ of OSAX together. One example is the File Commands Scripting Additions file. It contains four different commands that all are related in that they manipulate the file system in some fashion.

With more than one OSAX in Scripting Addition file, replacing just one of the commands with a newer or more powerful version necessitates replacing the entire old file or doing surgery to remove the old command and replace it with the new. And since the terminology for a collection of OSAXs in the same file must contain all of there syntax, updating the syntax of one requires working with the collections ‘aete’. This is out of the skill range for most users and certainly not very friendly.

Errors, error strings and the AEM

The Apple Event Manager will add the error you return from your event handler as an attribute of the reply Apple event. This is the key keyErrorNumber. If you wish AppleScript to display a descriptive string with errors you return, add the error string to the reply as an attribute with the key keyErrorString. AppleScript has error strings for most common errors returned by the Toolbox so it may not be necessary to add an error string for most errors returned from your Scripting Addition.

Class/Property definitions for record labels

If you need to return a custom record type to AppleScript from either your command or coercion OSAX, create a custom class and define properties for the labels you need for your record. Simply use the id of the class you’ve created as the return data type for your command OSAX. In your OSAX, bundle up the record as an AERecord with each of the parameters being keyed off of the properties defined in your custom class. See the file “SAPlayMovie.r” for an example of this in a command OSAX.

AEInteractWithUser

Anytime you display a dialog or window from an OSAX, be sure to call AEInteractWithUser immediately before the window is shown. If the AEInteractWithUser call fails, do not display your window. Return the error returned from AEInteractWithUser instead.

‘osiz’ Resource - New for AppleScript 1.1

Starting with AppleScript 1.1, an OSAX may contain an additional resource to give AppleScript more information about the OSAX(s) in the OSAX file. The ‘osiz’ contains two flags for OSAX writers use.

The first flag specifies whether or not the OSAX mechanism opens the resource fork of the OSAX file about to be invoked. Set this flag to dontOpenResourceFile if the OSAX does not have owned resources. In other words, if the OSAX do not rely on using a resource that resides in the same file as the osax code resource, specify dontOpenResourceFile. If, on the other hand, the OSAX does use owned resources, such as dialogs or sounds, set this flag to openResourceFile. The default setting if no ‘osiz’ resource is included in the OSAX file is openResourceFile.

The second flag specifies whether or not the OSAX mechanism dispatches events that originate remotely. Set this flag to acceptRemoteEvents if you wish the OSAX to be accessable by both local and remote machines. If the OSAX is potentially dangerous, or might require a large number of CPU cycles for a protracted time, it might be appropriate to set the flag to dontAcceptRemoteEvents. The default setting if no ‘osiz’ resource is included in the OSAX file is acceptRemoteEvents.

The ‘osiz’ resource is defined as follows:


/*6*/

type 'osiz' {
 booleanopenResourceFile,
 dontOpenResourceFile;
 boolean  acceptRemoteEvents, 
 dontAcceptRemoteEvents;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
 booleanreserved;
};

VI. Prototypes for event handlers and the two forms of coercions:

In ‘C’


/*7*/

pascal OSErr MyEventHandler(AppleEvent *theEvent, 
 AppleEvent *theReply, long theRefCon)

pascal OSErr MyCoerceFromPtr(DescType fromType, 
 Ptr dataPtr, Size dataSize, DescType toType, 
 long refcon, AEDesc *resultDesc)

pascal OSErr MyCoerceFromDesc (AEDesc *fromDesc, 
 DescType toType, long refcon, AEDesc *resultDesc)


In Pascal:


/*8*/

FUNCTION MyEventHandler(  theEvent, theReply: AppleEvent; 
 theRefCon: LONGINT):OSErr;

FUNCTION MyCoerceFromPtr( fromType: DescType; 
 dataPtr: Ptr; dataSize: Size; 
 toType: DescType; refcon: LONGINT; 
 VAR resultDesc: AEDesc):OSErr;

FUNCTION MyCoerceFromDesc ( fromDesc: AEDesc; 
 toType: DescType; refcon: LONGINT; 
 VAR resultDesc: AEDesc):OSErr;








  
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Spotify 1.0.44.100. - Stream music, crea...
Spotify is a streaming music service that gives you on-demand access to millions of songs. Whether you like driving rock, silky R&B, or grandiose classical music, Spotify's massive catalogue puts... Read more
Microsoft OneNote 15.29 - Free digital n...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more
WALTR 2 2.0.8 - $39.95
WALTR 2 helps you wirelessly drag-and-drop any music, ringtones, videos, PDF, and ePub files onto your iPhone, iPad, or iPod without iTunes. It is the second major version of Softorino's critically-... Read more
Dropbox 16.3.27 - Cloud backup and synch...
Dropbox is an application that creates a special Finder folder that automatically syncs online and between your computers. It allows you to both backup files and keep them up-to-date between systems... Read more
EtreCheck 3.1.5 - For troubleshooting yo...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
Carbon Copy Cloner 4.1.12 - 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
VueScan 9.5.62 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
SpamSieve 2.9.27 - Robust spam filter fo...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
Fantastical 2.3.2 - Create calendar even...
Fantastical 2 is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event... Read more
PCalc 4.4.4 - Full-featured scientific c...
PCalc is a full-featured, scriptable scientific calculator with support for hexadecimal, octal, and binary calculations, as well as an RPN mode, programmable functions, and an extensive set of unit... Read more

Latest Forum Discussions

See All

Track Santa with these three festive app...
Christmas is fast approaching and that means it's time to prepare for Santa's yearly pilgrimage around the globe. Christmas Eve is an exciting time as parents help their kids get ready to welcome Santa. You've got the cookies and milk all planned... | Read more »
Galaxy on Fire 3 and four other fantasti...
Galaxy on Fire 3 - Manticore brings the series back for another round of daring space battles. It's familiar territory for folks who are familiar with the franchise. If you've beaten the game and are looking to broaden your horizons, might we... | Read more »
The best apps for your holiday gift exch...
What's that, you say? You still haven't started your holiday shopping? Don't beat yourself up over it -- a lot of people have been putting it off, too. It's become easier and easier to procrastinate gift shopping thanks to a number of apps that... | Read more »
Toca Hair Salon 3 (Education)
Toca Hair Salon 3 1.0 Device: iOS Universal Category: Education Price: $2.99, Version: 1.0 (iTunes) Description: | Read more »
Winter comes to Darkwood as Seekers Note...
MyTona, based in the chilly Siberian city of Yakutsk, has brought a little festive fun to its hidden object game Seekers Notes: Hidden Mystery. The Christmas update introduces some new inhabitants to players, and with them a chance to win plenty of... | Read more »
Bully: Anniversary Edition (Games)
Bully: Anniversary Edition 1.03.1 Device: iOS Universal Category: Games Price: $6.99, Version: 1.03.1 (iTunes) Description: *** PLEASE NOTE: This game is officially supported on the following devices: iPhone 5 and newer, iPod Touch... | Read more »
PINE GROVE (Games)
PINE GROVE 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: A pine grove where there are no footsteps of people due to continuous missing cases. The case is still unsolved and nothing has... | Read more »
Niantic teases new Pokémon announcement...
After rumors started swirling yesterday, it turns out there is an official Pokémon GO update on its way. We’ll find out what’s in store for us and our growing Pokémon collections tomorrow during the Starbucks event, but Niantic will be revealing... | Read more »
3 reasons why Nicki Minaj: The Empire is...
Nicki Minaj is as business-savvy as she is musically talented and she’s proved that by launching her own game. Designed by Glu, purveyors of other fine celebrity games like cult favorite Kim Kardashian: Hollywood, Nicki Minaj: The Empire launched... | Read more »
Clash of Clans is getting its own animat...
Riding on its unending wave of fame and success, Clash of Clans is getting an animated web series based on its Clash-A-Rama animated shorts.As opposed to the current shorts' 60 second run time, the new and improved Clash-A-Rama will be comprised of... | Read more »

Price Scanner via MacPrices.net

Never Settle for Low Performing Wifi With iOS...
AppYogi Software has announced the release of WiFi Signal Strength Status App 1.0, the company’s new utility developed exclusively for macOS. WiFi Signal Strength Status App features a unique, single... Read more
New 2016 13-inch Touch Bar MacBook Pros in st...
B&H Photo has stock of new 2016 Apple 13″ Touch Bar MacBook Pro models, each including free shipping plus NY sales tax only: - 13″ 2.9GHz/512GB Touch Bar MacBook Pro Space Gray: $1999 - 13″ 2.... Read more
New 2016 15″ Touch Bar MacBook Pros in stock...
B&H Photo has new 2016 Apple 15″ Touch Bar MacBook Pro models in stock today including free shipping plus NY sales tax only: - 15″ 2.7GHz Touch Bar MacBook Pro Space Gray: $2799 - 15″ 2.7GHz... Read more
DietSensor App Targeting Diabetes and Obesity...
DietSensor, Inc., a developer of smart food and nutrition applications designed to fight diabetes and obesity and help improve overall fitness, has announced the launch of its DietSensor app for... Read more
Holiday 2016 13-inch 2.0GHz MacBook Pro sales...
B&H has the non-Touch Bar 13″ MacBook Pros in stock today for $50-$100 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.0GHz MacBook Pro Space Gray (MLL42LL/A): $1449 $... Read more
Holiday sale: Apple TVs for $51-$40 off MSRP,...
Best Buy has dropped their price on the 64GB Apple TV to $159.99 including free shipping. That’s $40 off MSRP. 32GB Apple TVs are on sale right now for $98 on Sams Club’s online store. That’s $51 off... Read more
12-inch Retina MacBooks, Apple refurbished, n...
Apple has restocked a full line of Certified Refurbished 2016 12″ Retina MacBooks, now available for $200-$260 off MSRP. Refurbished 2015 models are available starting at $929. Apple will include a... Read more
Holiday sale: 12-inch Retina MacBook for $100...
B&H has 12″ Retina MacBooks on sale for $100 off MSRP as part of their Holiday sale. Shipping is free, and B&H charges NY sales tax only: - 12″ 1.1GHz Space Gray Retina MacBook: $1199 $100... Read more
Apple refurbished 13-inch MacBook Airs availa...
Apple has Certified Refurbished 13″ MacBook Airs available starting at $849. An Apple one-year warranty is included with each MacBook, and shipping is free: - 13″ 1.6GHz/8GB/128GB MacBook Air: $849 $... Read more
Apple refurbished iMacs available for up to $...
Apple has Certified Refurbished 2015 21″ & 27″ iMacs available for up to $350 off MSRP. Apple’s one-year warranty is standard, and shipping is free. The following models are available: - 21″ 3.... Read more

Jobs Board

*Apple* Brand Ambassador (Macy's) - The...
…(T-ROC), is proud of its unprecedented relationship with our partner and client, APPLE ,in bringing amazing" APPLE ADVOCATES"to "non" Apple store locations. Read more
*Apple* Retail - Multiple Positions- Trumbul...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the 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
US- *Apple* Store Leader Program - Apple (Un...
…Summary Learn and grow as you explore the art of leadership at the Apple Store. You'll master our retail business inside and out through training, hands-on Read more
Automotive Detailer - *Apple* Used Autos -...
We are currently conductinginterviews and will be accepting applications for a part-time detailer. Apple Used Autos is a great place to work andstart a career. We Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.