TweetFollow Us on Twitter

May 92 - APPLE EVENT OBJECTS AND YOU

APPLE EVENT OBJECTS AND YOU

RICHARD CLARK

[IMAGE Clark_final_rev1.GIF]

With Apple events, Apple has opened the door for applications to control each other and work collaboratively. However, before applications can communicate, they have to agree on the commands and data they'll support. Apple event objects form the basis of such a protocol--the Apple event object model. The object model is powerful, but still a source of confusion for many developers. This article provides an overview of the object model and answers several commonly asked questions, including "What is the Apple event object model?" and "How do I support it?"


One of the greatest strengths of the Macintosh--its graphical user interface--is also the basis of one of its greatest weaknesses--the difficulty of automating routine or repetitive tasks. "Give us batch files!" many users cried. The developers responded with macro programs such as QuicKeys and Tempo, which handle many of the routine tasks but can't always make a program doexactly what the user wants.

The problem is that macro programs are generally limited to manipulating an application's human interface and have limited information about the state of the application. This means that if some setting has been changed or something has been moved, running a particular macro might not have the desired effect. In other words, one Macintosh application cannot control another application reliably through the target application's human interface.

For one application to control another application reliably, all of the following must happen:

  • The two applications must agree on a protocol for sending commands and data and agree on the specific information to be sent across this connection.
  • The controlled application needs to provide a rich enough set of commands and sufficient access to its data so that meaningful work can be done.
  • The protocols and command sets should be standardized so that many different applications can work together.

On the Macintosh, Apple events and theApple Event Registry provide the standards that allow applications to control each other reliably. The Apple event, a standard protocol for sending commands and data between applications, was introduced as part of System 7. TheApple EventRegistry  defines standard Apple event commands and two standard data types--Apple event object andprimitive.  Apple event objects describe an application's internal data, and primitive types describe the data that can be sent between applications. In essence, the Registry forms the basis for a standard language that applications can use when sending or receiving Apple events.

One of the challenges in creating theApple Event Registry was to keep the set of commands small while providing an adequate level of control between applications. The Registry does this by allowing the same command to apply to different Apple event objects within an application. The application of Apple events to Apple event objects is commonly referred to as theApple event object model.

This article provides an overview of the object model and then discusses how you can add object model support to your application. The fundamentals of Apple events are given inInside Macintosh Volume VI.

OBJECT MODEL BASICS

The Apple Event Registry defines an application's programmatic interface as a series of Apple event objects, where each object belongs to a particular object class. Each Apple event object is comprised of some data and a set of Apple event commands that operate on that data. In a traditional object- oriented fashion, new classes are defined by taking an existing class and adding new data and/or commands. Related classes are grouped together intosuites.

The most commonly used objects (and their associated commands) are grouped together into the Apple eventcore suite.  The commands in the core suite, which include Create Element, Delete, Get Data, and Set Data, cover the basic operations for any given object. The Apple event objects defined within the core suite include documents, windows, and the application itself. The core suite also includes some primitive classes such as long and short integers, Boolean values, and text. Every object model-aware application should support the core suite, and all Apple event objects defined within your application should support the core suite events.

The data portion of an Apple event object is broken into two parts: the object'sproperties  and itselements: 

  • Theproperties  of an object contain the attributes of the object--for example, its name and a 4-byte code designating its class.
  • The elements of an object are the other objects (in other words, data) that it contains. For example, a drawing application contains one or more documents, and each document may contain several rectangles and a picture or two. When the Registry describes an object, it lists all the element classes of an object, but a particular object may contain only some (or no) elements of each class at run time. (The number of elements can change during run time. For example, the number of words in a window could increase due to user typing or an incoming Apple event.)

For more detail on the difference between a property and an element, see "Properties and Elements."

Figure 1 shows three object classes that we'll use throughout the article; they've been derived from theApple Event Registry and simplified for the purpose of illustration.

[IMAGE Clark_final_rev2.GIF]

Figure 1 Some Hypothetical Apple Event Object Classes

PROPERTIES AND ELEMENTS

Each Apple event object contains exactly one of each of its properties (each of which has a name), so you might ask for the "Bounds of the frontmost Window" and receive back the pBounds property of the specified window. An object can contain zero or more of each of its element classes (each of which has a name), so you could ask for "every Paragraph in Document 1," where Paragraph is a valid element class for the document.

Many developers want to know when you should declare something as a property and when you should declare it as an element. You should make something (call it x ) a property of an object when x  describes something about that object. You should make something else (call it y ) an element of an object if y  is contained within the object.

Some developers use the rule "If there's only going to be one y  in the object, make it a property." Alas, this rule isn't always correct. Let's assume that an application could display only one document window at a time. Should that document be an element or a property? According to the Registry's definition of an element, since the document is contained within the application, you should make it an element. If you make something an element based simply on the Registry's definition, your new classes will be consistent with the existing classes.

Another useful test is to ask "Can I delete this item?" If you can, it's not a property. (You can delete a window from within an application, so a window is an element of that application, not a property. But since you cannot delete the bounds of the window, the bounds is a property.)

OBJECT SPECIFIERS

Most of the Apple events defined in theApple Event Registry contain one or moreobject specifiers as parameters. An object specifier is similar to the instructions you might give someone who's looking for a particular house: turn left at the first signal, then look for Jones Street and turn right, then travel down to the third house on the right. Object specifiers can also be used to specify a group of objects--for example, every green house on Jones Street.

Or imagine you send an Apple event-aware word processor the object specifier "every Paragraph in the current Document that contains the Word 'Apple'." The application would search in stages, first finding the current document and then searching through the paragraphs one at a time to see if they contained the word "Apple." Object specifiers provide a powerful general mechanism for locating a particular object in an application.

The Apple event's direct parameter typically contains the object specifier, yielding such commands as "Close Document 3" and "Delete Word 3 of Document 'fred'." Passing an object specifier as part of a command allows the same command to be reused for different objects (Newwindow , Newdocument , or Newrectangle ) instead of inventing a unique command for each action-object pair (NewWindow, NewDocument, or NewRectangle).

Internally, an object specifier consists of a series of recursive "get a particular element of classx  from objecty " commands. For example, in the command "Close Document 1," the object specifier (Document 1) is represented as "the first object of class Document contained within the Application." Another way of looking at this is "(the first object of class Document in (the Application))" where the parentheses represent one object specifier embedded within another. In addition to specifying a single element, an object specifier can refer to a property of some object or to a set of objects. For example, your application may receive the object specifier for "the Bounds of Window 1" or "every Icon contained within Rectangle 1 of Window 5."

Figure 2 shows a simplified representation of two object specifiers. Object specifiers are stored as Apple event records, with one field each for the object class and the object's container (stored as a handle) and two fields for theelement identifier . The two fields of the element identifier together represent the specific element to be selected. In part A of Figure 2, the desired object class is cDocument, the container is 'null' (in other words, a descriptor that has type typeNull and a nil handle), and the element identifier is 1. The null container typically represents the application. In part B, the desired object class is cWord, the container is a handle to the object specifier from part A, and the element identifier is 5.

[IMAGE Clark_final_rev3.GIF]

Figure 2 Simplified Representation of Object Specifiers

An actual object specifier is slightly more complicated than the ones shown in Figure 2. In the examples given above, we've consistently referred to elements by number. However, you might want to refer to some object, such as a document, by name. In that case you would need to know that the two fields of the element identifier contain akey form and some key data.

Each different way you can refer to an element uses a different key form. When we refer to an element by number, we're using the "absolute position" key form. We could also specify a "name" key form, a "property" key form (to get a property of an object instead of one of its elements), and so on. A complete object specifier is shown in Figure 3. A list of all standard key forms is given in the Apple Event Registry  and in the Apple Event Manager chapter of the new, improvedInside Macintosh  (preliminary draft) on theDeveloper CD Series disc.

[IMAGE clarkFig3.GIF]

Figure 3 The Four Fields of an Object Specifier

HOW DO I DISPATCH AN APPLE EVENT CONTAINING OBJECT SPECIFIERS?

One of the side effects of the object model is that the same command will be executed differently depending on the type of object involved. Therefore the object class, event class, and event ID are required before you can dispatch an Apple event. Since the Apple Event Manager uses only two of these values when dispatching an Apple event (the event class and event ID), you'll need to write some additional dispatching logic.

We'll discuss three major ways of dispatching object-model Apple events: an event-first approach, an object-first approach, and a method that uses a lookup table to dispatch the events. These approaches all serve the same function--extracting an object specifier and using the combined object class, event class, and event ID to select one of the application's routines. They differ only in the way you structure your code.

AN EVENT-FIRST APPROACH
The event-first approach allows the Apple Event Manager to do most of the work. The Apple Event Manager calls a different handler for each event--for example, Get Data and Set Data--and that handler calls different routines depending on the object class given by the object specifier. Figure 4 and the following sample code illustrate this approach.

pascal OSErr AESetDataHandler (AppleEvent *message,
    AppleEvent *reply, long refCon)
{
    OSErr       err;
    AEDesc      theObject, theToken;

    err = AEGetKeyDesc(message, keyDirectObject, typeObjectSpecifier,
        &theObject);
    if (err != noErr) return err;
    
    err = AEResolve(&theObject, kAEIDoMinimum, &theToken);
    AEDisposeDesc(&theObject);
    if (err != noErr) return err;
    
    /* The token is an Apple event descriptor. For now, we can */
    /* assume that the token's descriptor type is the class of the */
    /* object that should handle this event. */
    switch (theToken.descriptorType) {
        
        case cWindow: case cDocument:
            err = Win_SetData(&theToken, message, reply);
        break;
        
        case cRectangle:
            err = Rect_SetData(&theToken, message, reply);
        break;

        case cWord:
            err = Word_SetData(&theToken, message, reply);
        break;

        default:
            err = errAEEventNotHandled;
    }
    AEDisposeDesc(&theToken);
    return err;
}

An application that processes events using the event-first approach goes through the following steps after it receives an Apple event and calls AEProcessAppleEvent (the numbers correspond to the numbers in Figure 4):

  1. The Apple Event Manager locates the event in its dispatch table.
  2. The appropriate handler routine is called by the Apple Event Manager--in this example, it's Set Data. This handler routine needs to determine the object class before it can perform the appropriate action, so it calls AEResolve to convert the object specifier into a reference to a particular object.
  3. AEResolve takes an object specifier as input, and calls one or more accessor routines to convert this object specifier into a token that refers to some object. (See the section "How Do I Resolve an Object Specifier?" for more information.)
  4. The token is returned to the handler.
  5. Once the handler knows the object class, it can call the appropriate object-specific routine. This routine typically accepts the token as one of its parameters.

[IMAGE Clark_final_rev4.GIF]

Figure 4 Event-First Approach to Dispatching Apple Events

Since many of the things you can do with a token fall into a few basic operations, such as reading, writing, inserting, or deleting the information represented by a token, you can choose to write a set of token-handling routines for each token type that you define. Token-handling routines are not required, but they are useful. (See the section "What Are Token-Handling Routines?" for more information.)

Due to its simplicity, the event-first approach is recommended for all applications written in a procedural programming style (as is typically done in C or Pascal). Its only real drawback is that if you add a new object class to your application, you have to modify a number of Apple event handlers to recognize the new class (one handler per event that the new object class supports).

If you have code spread across several source files, consider whether this could present a code maintenance problem. If so, the object-first approach might work better for your application.

AN OBJECT-FIRST APPROACH
You can limit the amount of work required when adding a new object class by making each object class a self-contained unit. In this approach, an individual file (or group of files) contains all the code required to implement a single object class, including the event-dispatching code, object accessors, and token handlers. (For more information on token handlers, see the section "What Are Token- Handling Routines?")

Since the object includes its own event-dispatching code, you don't usually install a separate handler for each individual Apple event. Instead, you install one or more wild-card handlers that route the event to the appropriate object using the following algorithm:

  1. Extract the parameter containing the object specifier.
  2. Call AEResolve to convert this object specifier into a token.
  3. Extract the object class from the token.
  4. Call the event dispatcher within the appropriate object.

Since most Apple events carry their object specifiers in the direct parameter, a single wild-card handler works for all of these Apple events. However, there are some events that carry their object specifiers in different places, so you need to install specific handlers for these events. (For example, the Create Element event carries its object specifier inside an insertionLoc structure.) Using a single handler that uses the first object specifier it finds is inadequate, since some events use multiple object specifiers and an object specifier can appear anywhere another parameter can.

The handler that extracted the object specifier passes the token, the message, and the reply event to the object's central event dispatcher. This dispatcher then calls the appropriate routine, which typically calls one or more token-handling routines. This approach is illustrated in Figure 5 and in the following sample code.

/* This is a typical Apple event handler that you install using */
/* a wild card (in this case, the class = 'core', and the event */
/* ID = '****'). This would go in a "common area" file, */
/* separate from the individual object implementation files. */
pascal OSErr AECoreSuiteHandler (AppleEvent *message,
    AppleEvent *reply, long refcon)
{
    OSErr       err;
    AEDesc      directParam, theToken;

    /* The following code works for all core Apple events except */
    /* Create Element. Either this routine would need to be */
    /* modified for Create Element, or a specific handler */
    /* installed. */
    err = AEGetKeyDesc(message, keyDirectObject, typeWildCard,
        &directParam);
    if (err != noErr) return err;

    if (directParam.descriptorType == 'null') {
    /* AEResolve doesn't like null descriptors, so skip it. */
        theToken = directParam;
    }
    else {
        err = AEResolve(&directParam, kAEIDoMinimum, &theToken);
        AEDisposeDesc(&directParam);
        if (err != noErr) return err;
    }
    /* We assume the token's type is the class that handles this */
    /* event. */
    switch (theToken.descriptorType) {
        /* Include one entry for each object class. */

        case 'null':
            /* This is the application object's token class. */
            err = AppEventDispatcher(&theToken, message, reply);
        break;

        case cDocument:
            /* See the example of this routine below.*/
            err = DocumentEventDispatcher(&theToken, message, reply);
        break;

        /* And so on for cRectangle, cWord, etc. */

        default:
            err = errAEEventNotHandled;
    }
    AEDisposeDesc(&theToken);
    return err;
} /* AECoreSuiteHandler */

/* ===In the Document Object file...=== */

OSErr DocumentEventDispatcher (AEDesc *theToken,
    const AppleEvent *message, AppleEvent *reply)
{
    OSErr           err = noErr;
    AEEventID   eventID;
    OSType      typeCode;
    Size            actualSize;

    /* Get the event ID. */
    err = AEGetAttributePtr(message, keyEventIDAttr, typeType,
        &typeCode, (Ptr)&eventID, sizeof(eventID), &actualSize);
    if (err != noErr) return err;

    switch (eventID) {

        case kAECreateElement:
            err = Doc_CreateElement(theToken, message, reply);
        break;

        case kAEGetData:
            err = Doc_GetData(theToken, message, reply);
        break;

        /* And so on for Set Data, Delete, Open, Close, Print, */
        /* etc. */

        default:
            err = errAEEventNotHandled;
    }
    return err;
} /* DocumentEventDispatcher */

When an event is processed using the object-first technique, the application takes the following steps after it receives an Apple event and calls AEProcessAppleEvent (the numbers correspond to the numbers in Figure 5):

  1. The Apple Event Manager locates a handler routine in its dispatch table. The handler is usually installed with a wild-card value so that it's passed all (or most) events.
  2. The appropriate handler routine is called. This routine acts as an object dispatcher--it determines the type of object involved and calls the code in the appropriate object's source file. This handler routine needs to determine the object class, so it calls AEResolve to convert the object specifier into a reference to a particular object.
  3. AEResolve takes an object specifier as input, and calls one or more accessor routines to convert this object specifier into a token that refers to some object. (See the section "How Do I Resolve an Object Specifier?" for more information.)
  4. The token is returned to the handler.
  5. Once the handler knows the object class, it can call the appropriate object's event dispatcher. The dispatcher looks at the event's class and ID and calls the appropriate routine.
  6. The called routine performs a task specific to the event class, event ID, and object class. It typically accepts the token as one of its parameters.

This approach, or some variant of it, could be implemented using object-oriented programming and is recommended for object-oriented applications.

[IMAGE Clark_final_rev5.GIF]

Figure 5 Object-First Method for Dispatching Apple Events

If you use the object-first approach in a procedural application, you can still get some of the benefits of object-oriented programming, since this technique can be used to implement a simple form of inheritance for Apple event objects. If a particular object's event dispatcher doesn't recognize an event, it can pass the event to its superclass's event dispatcher. If that dispatcher doesn't recognize the event, the request can be passed up the chain until the topmost dispatcher is reached (typically cObject). This minimizes the code required for adding a new object, since an object only needs to implement its unique events (and any standard events that it handles differently) and can pass all other events to its superclass.

One drawback to this approach is the overhead involved in dispatching the event. Each event goes through the Apple Event Manager, AEResolve, a pair of switch statements (one in the top-level Apple event handler, and another in the object's dispatch routine), and possibly a couple of superclassevent dispatchers. Still, each of our approaches requires the initial use of the Apple Event Manager and a call to AEResolve, so the added overhead lies primarily in the switch statements.

Another drawback is that each Apple event typically has several parameters, and each Apple event handler needs to extract the set of Apple event-dependent parameters for that Apple event. This can lead to redundant code.

TABLE-BASED DISPATCHING
One way to lower the overhead associated with dispatching object-model Apple events involves building a dispatch table of your own to replace the Apple Event Manager's. The Apple Event Manager constructs a two-way hash table based on the event class and event ID. Since this isn't enough information to properly dispatch an object-model Apple event (you also need to know which object class will be responsible for handling the event), the solution is to construct your own table using a three-part index (event class, event ID, and object class) that contains the addresses of the appropriate routines.

As in the object-first example, this dispatcher should be "attached" to the Apple Event Manager through a wild-card handler in the Manager's regular dispatch table. (This is necessary since there's no other robust way to "unpack" an Apple event when it arrives from the outside world.) This handler would extract the event class and event ID attributes and would get the object specifier from the direct parameter. The handler would then call AEResolve and pass the object class (along with the event class and event ID) to your table lookup routine.

The only real problem occurs when the object specifier isn't contained in the direct parameter. The solution here is to install handlers for any events that don't contain their object specifiers in their direct parameters, and have these handlers call AEResolve and then jump directly into your table lookup routine.

The implementation of such a table-based dispatcher is left to you.

HOW DO I RESOLVE AN OBJECT SPECIFIER?

When an object-model Apple event is received, such as "Close Document 1," the object specifier (Document 1) is usually contained in the direct parameter of the event. Before the event can be processed, the object specifier needs to beresolved.  Resolving an object specifier involves locating the specified information in memory so that the Apple event can act on this information.

While it's possible to parse an object specifier directly, object specifiers can be much more complicated than the simple examples shown here. The Apple event Object Support Library (OSL) helps you resolve an object specifier through a set ofobject accessor routines, which you write and then install. One type of accessor routine extracts one or more types of element from a given object, while other accessor routines extract a property from an object. When you ask the OSL to resolve an object specifier, it calls the appropriate accessor routines in the necessary order.

Figure 6 shows how the OSL resolves the object specifier "Word 5 of Window 1." First, the accessor for the innermost specifier (Window 1) is called. This accessor returns a token, which is an Apple event descriptor (AEDesc) referring to some data in your application. The returned token and the next part of the object specifier to be processed are then passed to the appropriate accessor. This process is repeated until the object specifier has been fully resolved, and the final result is returned to your application.

[IMAGE Clark_final_rev6.GIF]

Figure 6 Object-First Method for Dispatching Apple Events

HOW DO I IMPLEMENT AN OBJECT ACCESSOR?

Each accessor routine should accept one part of an object specifier and return a token. An accessor routine has the form

pascal OSErr MyAccessor (DescType desiredClass,
    const AEDesc *container, DescType containerClass,
    DescType keyForm, const AEDesc *keyData,
    AEDesc *value, long refCon);
and is passed the desiredClass, containerClass, keyForm, and keyData fields directly from the part of the object specifier being resolved. The container is either the token returned from the last accessor called or an AEDesc of type 'null' containing a null handle (if this is the first accessor in the series to be called).

All accessors have to perform essentially the same functions:

    Check that the specified key form is valid.
  1. Locate the requested information.
  2. Construct a return token.

The following code illustrates this process using a simple "extract a Window from a null container" accessor. (In most applications, this accessor extracts both windows and documents from the null container since most applications maintain a one-to-one correspondence between documents and windows.)

pascal OSErr WindowFromNull (DescType desiredClass,
    const AEDesc *containerToken, DescType containerClass, 
    DescType keyForm, const AEDesc *keyData, AEDesc *theToken,
    long theRefcon)
{
    WindowPtr       wp;
    long            count;

    /* 1. Make sure we can handle this request. We only handle */
    /* object specifiers of the form "Window 1", "Window 2", etc. */
    if ((keyForm != formAbsolutePosition) return errAEBadKeyForm;
    
    /* 2. Extract the window number and find the window. */
    count = **(long**)(keyData->dataHandle);
    wp = FrontWindow();
    while (count > 1) {
        if (wp == 0L) return errAENoSuchObject;
        wp = (WindowPtr)((WindowPeek)wp)->nextWindow;
        --count;    /* Count down by 1. */
    };

    /* 3. Create the token. */
    /* The token is an AE descriptor of type 'cwin' (window). */
    /* The AEDesc contains a handle to a WindowPtr. */
    return AECreateDesc(desiredClass, (Ptr)&wp, sizeof(wp),
               theToken);
} /* WindowFromNull */

While the above code contains many of the features of an object accessor, it's far from complete. For example, it doesn't handle formName, which is one of the more common key forms. It also assumes that the value for a formAbsolutePosition parameter will be a positive integer. In fact, the value could be a negative number (with -1 signifying the last element of the container, -2 signifying the next to the last element, and so on), or one of the special constants representing the first, last, middle, any, or every element of the container.

To make the formAbsolutePosition code complete, you need to add a routine that looks at the key data for one of the special values and converts the key data into a positive integer or returns a flag indicating that every element should be returned. Such a routine would look something like this:

OSErr GetWindowIndex (const AEDesc *keyData, long *index,
    Boolean *getAll)
{
    long        numWindows;
    long        rawIndex;
    
    /* There are three flavors of formAbsolutePosition key: */
    /* typeLongInteger/typeIndexDescriptor, */
    /* typeRelativeDescriptor, and typeAbsoluteOrdinal. */

    /* 1. Initialize some values. */
    *getAll = false; *index = 1;
    numWindows = CountUserWindows(); /* A private routine */

    /* 2. Get the number out of the key. If it's not an absolute */
    /* value, convert it to one. */
    rawIndex = **(long**)(keyData->dataHandle);
    switch (keyData->descriptorType) {
        
        case typeLongInteger:
            if (rawIndex < 0)
            /* A negative value means "the Nth object from the */
            /* end," */
            /* i.e., -1 = the last object. */
                rawIndex = numWindows + rawIndex + 1;
            /* A positive value is an absolute value, so do */
            /* nothing. */
        break;

        case typeAbsoluteOrdinal:
            /* kAEFirst, etc. are special 4-byte constants. */
            if (rawIndex == kAEFirst)       rawIndex = 1;
            else if (rawIndex == kAELast)   rawIndex = numWindows;
            else if (rawIndex == kAEMiddle)
                rawIndex = numWindows / 2;
            else if (rawIndex == kAEAll)        *getAll = true;
            else if (rawIndex == kAEAny) {
                /* Select a random window. */
                if (numWindows <= 1)     /* 0 or 1 */
                    rawIndex = numWindows;
                else
                /* Get a random number between 1 and numWindows. */
                    rawIndex =
                        1 + ((unsigned long)Random() % numWindows);
            } 
            else return errAEBadKeyForm;
        break;
    }
    return noErr; 
} /* GetWindowIndex */

To install an accessor, use the AEInstallObjectAccessor routine:

pascal OSErr AEInstallObjectAccessor (DescType desiredClass,
DescType containerType, accessorProcPtr theAccessor,
long accessorRefcon, Boolean isSysHandler)

In the "extract a Window from a null container" example, the call to the AEInstallObjectAccessor routine would look like this:

err = AEInstallObjectAccessor(cWindow, 'null', 
    (accessorProcPtr)WindowFromNull, 0, false); 

You can also install accessor routines to get one of the properties of an object (use the special constant 'prop' in specifying the desired type), or you can supply a wild card for either the container or the desired type. Most developers install one accessor routine for each of the element types supported by a particular object, and one accessor routine to handle all of the properties of that object.

WHAT SHOULD I PUT INTO A TOKEN?

As noted earlier, accessors communicate with each other and with the application using application- specifictokens . Most Apple events that contain an object specifier end up resolving the object specifier into a token and then manipulating the data represented by that token. Since the format of each object class is different, you'll typically write Read Token Data and Write Token Data routines for each object class that your application supports. (You might also choose to write Create Token Data (Create Element) and Delete Token Data routines if more than one Apple event in a given object needs to create or delete information.) What you put into these token-handling routines depends completely on the contents of your tokens.

Each token is stored in an Apple event descriptor--a data structure containing a 4-byte type code and a handle to some data, where the contents of the handle are completely up to you. While this raises the question of what should go into the handle, many developers decide to invent a different token data type for each object class or set of related object classes.

In this approach, a window token would contain a WindowPtr, a text token would contain a handle to some text, and so on. Since tokens are used for both elements and properties, each token might also contain a 4-byte property code.

Here's how the tokens might look for the object classes defined in Figure 1:

struct DocumentTokenBody {
    WindowPtr   theWindow;
    Boolean     useProperty;
    DescType        propertyCode;
};

struct RectTokenBody {
    Rect          *theRect;        /* Use a pointer so we can read */
                                   /* and write the rectangle. */
    long          elementNumber;   /* See token-handling examples */
                                   /* below. */
    Boolean       useProperty;
    DescType      propertyCode;
    WindowPtr     parentWindow;    /* The window that holds this */
                                   /* rectangle. */
};

struct WordTokenBody {
    Handle        theText;
    long          startingOffset;  /* How many bytes in does the */
                                   /* text start? */
    long          textLength;      /* How many bytes long? */
    Boolean       useProperty;
    DescType      propertyCode;
    TEHandle      parent;          /* The location from which we */
                                   /* took this text. */
};

These three sample tokens demonstrate several things you should keep in mind when designing your own tokens:

  • Each token contains a reference to the data--not a copy of the data itself. This allows the same token to be used for both reading and writing the data.
  • Each token contains a field for the property code. If the application received the object specifier "the Name of Document 1," the returned token would contain a pointer to the document's window and the Name property code--'pnam'. The token-handling routines have to include code to support property tokens.
  • Since each token format is different, you'll need to write the token-handling routines (Read/Write and, optionally, Insert/Delete) for each token type.
  • The Word and Rectangle tokens contain references to the objects that contain them. This is important, since changing the text or the rectangle could affect the document containing the information and there's no way to get either a partially resolved object specifier or the intermediate products of the resolution. Therefore, if you need to know the parent of a particular token, you must store a copy of that information in the child token yourself, since the OSL may dispose of the original parent token.  (You may need to supply a custom DisposeToken callback if your tokens contain handles or pointers to other data.)

The guidelines given above cover the contents of the token's handle, but they don't say anything about the descriptorType field. When you return a token from an accessor routine, you must put the proper type code into the descriptorType field of the AEDesc. This is required because the OSL uses the returned token type from one step of the resolution process to guide the next step. Having the accessor routines control the resolution process actually insulates outside Apple event sources from having to know about your specific implementation details.

Throughout the article, we've assumed that the token type in the token is the same as the external data type specified by the object specifier. However, your code can put anything in the token type field as long as you write the matching object accessors for those token types.

For example, let's say that you've written a word-processing program, and another application sends the request "Get DataWord 2 of Paragraph 2 of Window 1 " where the italicized part is an object specifier. The returned type would probably be some styled text. However, if the requester had sent "Get DataWord 1 of the Name of Window 1 ," your application would have to access a completely different form of text (a simple Str255) and might return some nonstyled text.

Internally, the data type that represents text within a document can be different from the data type representing a simple string. Instead of forcing the user to use two different terms for the same thing (documentWord and plainTextWord, perhaps), the application can make this determination at run time. Figure 7 shows how an application might resolve the two examples given above.

WHAT ARE TOKEN-HANDLING ROUTINES?

Token-handling routines are optional routines (in other words, routines not explicitly required by the object model or OSL) that perform common editing operations on the data referred to by a token. Generally, when you have a token, you want to read, write, insert, or delete the data the token refers to. Here are Read Token Data and Write Token Data handlers for the cRectangle object class:

[IMAGE ClarkRev7.GIF]

Figure 7 Controlling Object Specifier Resolution with Returned Tokens

struct RectTokenBody {
    Rect          *theRect;       /* Use a pointer so we can read */
                                  /* and write the rectangle. */
    long          elementNumber;
    Boolean       useProperty;
    DescType      propertyCode;
    WindowPtr     parentWindow;  
                          /* The window that holds this rectangle. */
};

typedef struct RectTokenBody RectTokenBody;
typedef RectTokenBody *RectTokenPtr, **RectTokenHandle;

OSErr ReadRectToken (const AEDesc *theToken, AEDesc *result)
{
/* This routine gets called by the Get Data Apple event handler */
/* (or any other handler that needs to read some data and possibly */
/* return it to the user). If the useProperty flag is true, we */
/* return the requested property, otherwise we return the default */
/* representation for this class (we'll use the cQDRect primitive */
/* type for this). */
    RectTokenPtr  tokenPtr;
    DescType      descCode;
    OSErr         err;
    
    HLock(theToken->dataHandle);
    tokenPtr = (RectTokenPtr)*theToken->dataHandle;
    if (tokenPtr->useProperty) {
        switch (tokenPtr->propertyCode) {

            case pClass:
                /* Tell the world that this is a rectangle. */
                descCode = cRectangle;
                err = AECreateDesc(typeType, (Ptr)&descCode,
                     sizeof(descCode), result);
            break;

            case pBounds:
                /* Return the bounds of this rectangle, as a */
                /* QuickDraw rectangle. */
                err = AECreateDesc(typeQDRectangle,
                    (Ptr)&tokenPtr->theRect, sizeof(Rect),
                     result);
            break;

            /* More property codes go here... */

            default:
                err = errAENoSuchObject;
        }
    }
    else {
        /* Return the default representation. In this simple */
        /* example, it's a QuickDraw rectangle. */
        err = AECreateDesc(typeQDRectangle, (Ptr)&tokenPtr->theRect,
            sizeof(Rect), result);
    }
    return err;
}

OSErr WriteRectToken (const AEDesc *theToken, const AEDesc *theData)
{
/* This routine gets called by the Set Data Apple event handler */
/* (or any other handler that needs to change a property or some */
/* value of the object). If the useProperty flag is true, we */
/* check to see if the property is writable and modify it, */
/* otherwise we change the contents of this object. */

    RectTokenPtr        tokenPtr;
    AEDesc              thisRectDesc;
    OSErr               err;

    HLock(theToken->dataHandle);
    tokenPtr = (RectTokenPtr)*theToken->dataHandle;
    if (tokenPtr->useProperty) {
        switch (tokenPtr->propertyCode) {
            case pClass: /* This is a read-only property. */
                err = errAEWriteDenied;
            break;
            case pBounds: /* Set the bounds of this rectangle. */
                /* Make sure we have a QuickDraw Rectangle. */
                err = AECoerceDesc(theToken, typeQDRectangle,
                    &thisRectDesc);
                if (err != noErr) return err;
                /* Copy the data into our rectangle. */
                BlockMove(*thisRectDesc.dataHandle,
                    &tokenPtr->theRect, sizeof(Rect));
                AEDisposeDesc(&thisRectDesc); 
            break;

            /* More property codes go here... */

            default:
                err = errAENoSuchObject;
        }
    }
    else {
        /* Change the default representation (the bounds of this */
        /* rectangle). */
        err = AECoerceDesc(theToken, typeQDRectangle, &thisRectDesc);
        if (err != noErr) return err;
        /* Copy the data into our rectangle. */
        BlockMove(*thisRectDesc.dataHandle, &tokenPtr->theRect,
            sizeof(Rect));
        AEDisposeDesc(&thisRectDesc); 
    }
    return err;
}

The contents of the Create Element and Delete Token Data routines are completely application- specific and are not illustrated here. Typically, the Create Element routine takes a token for the element's container and an index position within that container, and returns an object specifier describing the new element. (This object specifier may be returned as the result of a Create Element Apple event, or may be resolved so that you can insert some data into the newly created element.)

COMBINING OBJECTS AND EVENTS

Once you've created the object event dispatcher code, the object accessor routines, the token formats, and the token handlers, your last task is to write the actual event-handling routines. (These are different from the routines that you install into the Apple Event Manager's dispatch table; event-handling routines do the work for a specific event as handled by a specific object class.) While the exact content of these routines is application dependent, they do have some features in common:
  • Routines that need to return something to the outside world can use a Read Token Data handler to convert an internal token into an externally usable form, and can use the other token manipulation routines as needed.
  • Each routine should accept both the event and the reply record as parameters. The results from an event are typically placed into the direct parameter of the reply record. When your event has finished execution, the Apple Event Manager will send the reply back to the client application.

MOVING ON

Writing an object model application isn't difficult; once you've implemented an object or two (including the accessors and tokens) and a couple of events, you should have a good understanding of the issues. I hope that this article has given you a good idea of where and how to begin adding the object model to your application. If you still need help there are several options: reading the related documentation (see the box below); looking at the sample code on theDeveloper CD Series disc (the samples Quill and AEObject-Edition Sample in the Apple Events and Scripting Development Kit and the sample code provided with this article); talking with other programmers; training through Apple's Developer University; and using the on-line support available through AppleLink, CompuServe, and other means.

Good luck! We all look forward to seeing the exciting things that can be done when applications can work both cooperatively and under the control of scripting environments.

RELATED READING

  • Inside Macintosh  Volume VI (Addison-Wesley, 1991) provides fundamental information about Apple events. Chapter 1 gives an overview of interapplication communication and explains the relationship of the Apple Event Manager to other parts of System 7. Chapter 6 provides a complete description of Apple events, explains how to send and receive Apple events, and includes reference information for all Apple Event Manager routines.
  • The Apple Event Manager chapter of the new, improvedInside Macintosh (preliminary draft) on theDeveloper CD Series  disc provides information about Apple event objects and object classes.
  • Apple Event Registry: Standard Suites,  on the Developer CD Series  disc,  describes standard Apple events, Apple event data types, and Apple event object classes. A printed version of the Apple Event Registry  is available from APDA (#R0130LL/A).

RICHARD CLARK, an instructor and course designer in Apple's Developer University, is no stranger to projects both large and small. (He claims that both of his recent projects--the new Advanced System 7 class and Daniel Guy Clark--took around nine months and developed a life of their own.) When he's not playing with his new son, you can find him dancing in local Renaissance Faires, stunt kite flying, searching for the ultimate chocolate recipe, and dreaming up horrible new puns.*

Concepts from object-oriented programming (notably inheritance--the process of defining new classes in terms of other classes) are used in defining the Apple event object model, but supporting Apple event objects does not require the use of an object-oriented language or class library. You can use any language or implementation technique you want, as long as your application can understand the Apple events sent to it. *

In addition to the core suite, the Apple Event Registry  includes other specialized suites for text processing, database manipulation, manipulating QuickDraw graphics, and the like. Application developers can define their own custom Apple event object classes and suites and submit them to the Apple Events Developer Association for standardization. *

For source-code samples that use the event-first technique, see the samples Quill and AEObject-Edition Sample in the Apple Events and Scripting Development Kit on the Developer CD Series  disc.*

The cRectangle class used in this code is simplified. Remember that if you're implementing the real cRectangle class from the Apple Event Registry , you'll need to support many more properties and a more complex default representation. *

THANKS TO OUR TECHNICAL REVIEWERSKevin Calhoun, Donn Denman, C. K. Haun, Eric House, Bennet Marks *

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Whitethorn Games combines two completely...
If you have ever gone fishing then you know that it is a lesson in patience, sitting around waiting for a bite that may never come. Well, that's because you have been doing it wrong, since as Whitehorn Games now demonstrates in new release Skate... | Read more »
Call of Duty Warzone is a Waiting Simula...
It's always fun when a splashy multiplayer game comes to mobile because they are few and far between, so I was excited to see the notification about Call of Duty: Warzone Mobile (finally) launching last week and wanted to try it out. As someone who... | Read more »
Albion Online introduces some massive ne...
Sandbox Interactive has announced an upcoming update to its flagship MMORPG Albion Online, containing massive updates to its existing guild Vs guild systems. Someone clearly rewatched the Helms Deep battle in Lord of the Rings and spent the next... | Read more »
Chucklefish announces launch date of the...
Chucklefish, the indie London-based team we probably all know from developing Terraria or their stint publishing Stardew Valley, has revealed the mobile release date for roguelike deck-builder Wildfrost. Developed by Gaziter and Deadpan Games, the... | Read more »
Netmarble opens pre-registration for act...
It has been close to three years since Netmarble announced they would be adapting the smash series Solo Leveling into a video game, and at last, they have announced the opening of pre-orders for Solo Leveling: Arise. [Read more] | Read more »
PUBG Mobile celebrates sixth anniversary...
For the past six years, PUBG Mobile has been one of the most popular shooters you can play in the palm of your hand, and Krafton is celebrating this milestone and many years of ups by teaming up with hit music man JVKE to create a special song for... | Read more »
ASTRA: Knights of Veda refuse to pump th...
In perhaps the most recent example of being incredibly eager, ASTRA: Knights of Veda has dropped its second collaboration with South Korean boyband Seventeen, named so as it consists of exactly thirteen members and a video collaboration with Lee... | Read more »
Collect all your cats and caterpillars a...
If you are growing tired of trying to build a town with your phone by using it as a tiny, ineffectual shover then fear no longer, as Independent Arts Software has announced the upcoming release of Construction Simulator 4, from the critically... | Read more »
Backbone complete its lineup of 2nd Gene...
With all the ports of big AAA games that have been coming to mobile, it is becoming more convenient than ever to own a good controller, and to help with this Backbone has announced the completion of their 2nd generation product lineup with their... | Read more »
Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »

Price Scanner via MacPrices.net

B&H has Apple’s 13-inch M2 MacBook Airs o...
B&H Photo has 13″ MacBook Airs with M2 CPUs and 256GB of storage in stock and on sale for up to $150 off Apple’s new MSRP, starting at only $849. Free 1-2 day delivery is available to most US... Read more
M2 Mac minis on sale for $100-$200 off MSRP,...
B&H Photo has Apple’s M2-powered Mac minis back in stock and on sale today for $100-$200 off MSRP. Free 1-2 day shipping is available for most US addresses: – Mac mini M2/256GB SSD: $499, save $... Read more
Mac Studios with M2 Max and M2 Ultra CPUs on...
B&H Photo has standard-configuration Mac Studios with Apple’s M2 Max & Ultra CPUs in stock today and on Easter sale for $200 off MSRP. Their prices are the lowest available for these models... Read more
Deal Alert! B&H Photo has Apple’s 14-inch...
B&H Photo has new Gray and Black 14″ M3, M3 Pro, and M3 Max MacBook Pros on sale for $200-$300 off MSRP, starting at only $1399. B&H offers free 1-2 day delivery to most US addresses: – 14″ 8... Read more
Department Of Justice Sets Sights On Apple In...
NEWS – The ball has finally dropped on the big Apple. The ball (metaphorically speaking) — an antitrust lawsuit filed in the U.S. on March 21 by the Department of Justice (DOJ) — came down following... Read more
New 13-inch M3 MacBook Air on sale for $999,...
Amazon has Apple’s new 13″ M3 MacBook Air on sale for $100 off MSRP for the first time, now just $999 shipped. Shipping is free: – 13″ MacBook Air (8GB RAM/256GB SSD/Space Gray): $999 $100 off MSRP... Read more
Amazon has Apple’s 9th-generation WiFi iPads...
Amazon has Apple’s 9th generation 10.2″ WiFi iPads on sale for $80-$100 off MSRP, starting only $249. Their prices are the lowest available for new iPads anywhere: – 10″ 64GB WiFi iPad (Space Gray or... Read more
Discounted 14-inch M3 MacBook Pros with 16GB...
Apple retailer Expercom has 14″ MacBook Pros with M3 CPUs and 16GB of standard memory discounted by up to $120 off Apple’s MSRP: – 14″ M3 MacBook Pro (16GB RAM/256GB SSD): $1691.06 $108 off MSRP – 14... Read more
Clearance 15-inch M2 MacBook Airs on sale for...
B&H Photo has Apple’s 15″ MacBook Airs with M2 CPUs (8GB RAM/256GB SSD) in stock today and on clearance sale for $999 in all four colors. Free 1-2 delivery is available to most US addresses.... Read more
Clearance 13-inch M1 MacBook Airs drop to onl...
B&H has Apple’s base 13″ M1 MacBook Air (Space Gray, Silver, & Gold) in stock and on clearance sale today for $300 off MSRP, only $699. Free 1-2 day shipping is available to most addresses in... Read more

Jobs Board

Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Business Analyst | *Apple* Pay - Banco Popu...
Business Analyst | Apple PayApply now " Apply now + Apply Now + Start applying with LinkedIn Start + Please wait Date:Mar 19, 2024 Location: San Juan-Cupey, PR Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.