TweetFollow Us on Twitter

Apple Events in PP
Volume Number:11
Issue Number:6
Column Tag:Applying Apple Technology

Powering Up AppleEvents in PowerPlant

Add an advanced feature to this popular framework

By Jeremy Roschelle

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

One of System 7’s most advanced features is its support for system-level scripting via AppleEvents. The AppleScript programming language can give users the ability to automate tasks, customize the interface, and connect independent applications to perform a task. Although Apple has presented these capabilities as advantages to business users, they are an powerful addition to educational software - what I create. In my present project, “SimCalc,” we decided to add scripting to give students, teachers and curriculum authors the ability to set up mathematical simulations.

Unfortunately, AppleEvents sports System 7’s least comprehensible documentation. Although AppleEvents sounded attractive, dense writing in Inside Mac VI sent me running for cover. With the arrival of better documentation and direct support for the AppleEvents Object Model (AEOM) in the PowerPlant class library, I decided to come out from hiding. After learning many lessons the hard way, SimCalc is now fully scriptable.

This article brings together many bits of necessary information that I discovered along the way. It will guide you through the process of using PowerPlant to make your application “scriptable” - able to respond to AppleEvent commands written in AppleScript or other scripting systems. However, no attempt is made to fully explain the AppleEvent Object Model or the concept of a “recordable” application. For that information, see Inside Mac: InterApplication Communication. AppleEvents and AppleScript are powerful capabilities, and by inheriting the basics from PowerPlant classes, you can easily give your users the power to write scripts that manipulate your application’s objects.

Pieces of the Puzzle

One of the difficulties in getting started with AppleEvents is making several components work smoothly with each other. Here is an overview:

The AppleEvent Object Model (AEOM) defines a generic view of a scriptable application. Every application presents itself to AppleScript as a containment hierarchy. For example, in our SimCalc application, the application contains documents, documents contain pages, and pages contain graphs. Each object in the hierarchy has properties that the user can get and set. Events operate over the objects in the hierarchy, creating and deleting them, and changing their properties.

An application’s “terminology resource” (resource type ‘aete’) presents the application’s AEOM interface to other applications and scripts. It describes your application’s containment hierarchy, along with the actions possible on objects in the hierarchy. The ‘aete’ resource also translates between the English-like AppleScript vocabulary and four-character descriptors that your application can easily respond to. There are descriptors for classes, properties, and event names and parameters.

PowerPlant provides several support classes to handle AppleEvent interaction. The central class is LModelObject, which handles properties, events, and containment relations for one class in the containment hierarchy. By deriving your classes from LModelObject, handling AppleEvents becomes a relatively simple matter of overriding appropriate methods.

ScriptEditor, which is part of the AppleScript kit and included with System 7.5, is helpful for testing your work. It provides tools for reading an applications ‘aete’ terminology dictionary, writing and checking the syntax of scripts against the terminology resource, and compiling and executing the script.

AEOM Conventions and Terms

Awareness of a few conventions and terms will make reading this article easier. The AppleEvent Object Model uses the standard separation of an application into verbs and nouns. Each verb is an “Event” and each noun is a “Class.” For example, the statement “Close Window 1” consists of the “close” event applied to the first object in the “window” class. Events have a “direct object,” which, by convention, is the noun to which the verb is applied (e.g., the window). Events take a list of keyword-specified parameters and can return a value.

A class corresponds to one level in the containment hierarchy. In our application, we have a class for the application, document, page, and graph level, among many others. An AEOM class need not correspond to a C++ implementation class; in particular, you don’t need an AEOM class for every C++ class, just those that will be direct objects of the Events you implement. A class has “Properties” to represent its state. For example, for example a page includes a name and index number as properties. The containment hierarchy is organized by the “Elements” relationship: in our application, the elements of the application class are documents, the elements of the document class are pages, and the elements of the page class are graphs.

PowerPlant uses the “L” prefix to distinguish library classes and we use “SC” to distinguish SimCalc classes.

Organization of the Article

This article divides the task of making your application scriptable into five parts:

1. adding a new class to the model hierarchy

2. adding a property to the class

3. adding a generic undo facility that supports every property.

4. adding an event handler for a standard event

5. adding a custom event

Each of these processes will require changes to the C++ classes derived from the PowerPlant class library, as well as changes to the terminology resource. Each change will be tested using ScriptEditor. Organizing the effort by processes makes it easier to see how AEOM, the terminology resource, PowerPlant, and Script Editor interrelate.

Adding a New Class

In PowerPlant, LApplication, LDocument, and LWindow inherit from LModelObject, which provides the root of the containment hierarchy. The terminology resource that comes with PowerPlant already specifies the “Elements” relationship among these classes, as well as the properties of each class.

In our SimCalc project, our documents are notebooks consisting of a list of pages. We want to support our notion of a page in the AEOM containment hierarchy. This requires changes in both the terminology resource and the SimCalc C++ classes. Specifically, we add the Page class to the terminology resource in two places, define appropriate methods for our SCPage class, and override some LModelObject methods in the containing class, SCDoc.

A good place to start making your application scriptable is with the terminology resource. Resorcerer provides a very good editor for the ‘aete’ resource. We start with the ‘aete’ supplied with PowerPlant, which describes the application, windows, and documents. To this we add information about the existence of the page class, and use the Elements relationship to indicate that documents contain pages.

Information in the terminology resource is organized into suites, with standard suites defined in the AppleEvents Registry, and custom suites defined elsewhere. Since pages are specific to our application, we create a new suite called the SimCalc suite. The name of the suite and its description are arbitrary, but your users will see them when they read your ‘aete’ resource with ScriptEditor’s “Open Dictionary” command. The suite id should be unique (registering it with Apple will maintain uniqueness).

Figure 1: Adding the Page Class to the SimCalc Suite

Adding class to the containment hierarchy involves two operations - creating the class and specifying which container has elements of this type. We’ll add our class to the SimCalc suite. To do this in Resorcerer, position the cursor within the classes box and press the New button. The class name you provide will be used by AppleScript to name the class in English, so choose a short noun. I find it helpful to capitalize the first letter of all English names for debugging: ScriptEditor’s syntax editor will replace statements with the capitalization you provide. Therefore if you type statements into ScriptEditor in lowercase, you can tell if ScriptEditor is recognizing your names by the capitalization.

The class id you choose will be used to identify objects of this class inside your application, and therefore must be unique within your ‘aete’. The description of the class is not functionally important, but will be read by users as they try to understand what the class represents.

In our application documents contain pages. In the terminology resource, the “Elements” relationship is indicated in the containing class. To specify that pages are elements of documents, we edit the definition of the document class, adding the class id of the page class to its elements field. In addition to adding the element, you must specify how the element can be accessed from the container. The most common ways to access a document are by name and index, and our pages also have unique ids, so we added that to the list.

Incidentally, Resorcerer doesn’t know about the ‘ID ’ form code; you have to type it in by hand. Also “absolute position” is a bit misleading, since PowerPlant can use this information to compute a variety of relative references as well. References to the first, last, middle element, as well as reference to an object before or after another object are computed from an absolute position reference.

Figure 2: Specifying that a page is an element in the document

Now we have completed editing the containment hierarchy. To check our work, we can open the terminology resource from ScriptEditor’s Open Dictionary command.

Figure 3: ScriptEditor shows that pages have been added to documents

ScriptEditor parses the information provided, and presents it in readable form. Because we created a class with the name “Page” and made it an element of documents, ScriptEditor can check the syntax of scripts that refer to pages with a variety of reference forms:

tell application "PoweringUp"
 get Page 1 -- by absolute position
 get last Page -- uses absolute position
 get Page "first" -- by name
 get Page after Page "first" -- by relative position
 get Page id 10 -- by id
end tell

Now its time to make the application recognize these references. Fortunately, PowerPlant makes this fairly easy. Each object only needs to know about its “SuperModel” (i.e., the container in which it resides) and any submodels it contains. The SuperModel-SubModel relationship is the same as the “Elements” containment relationship. The containment relationship is different from the Class-SubClass inheritance relationship; AEOM has no good way to express inheritance relationships .

In the SimCalc example, a page needs to know its containing document (its SuperModel), and the document needs to be able to locate pages as SubModels. In addition, the class must override GetModelKind, which is used by PowerPlant to generate an external description of an object. Returning ‘page’ will let PowerPlant know that this particular object is a page.

The external description should be the same as the class id defined in the terminology resource. In our case, the class definition looks like this:

SCPage.h
class SCPage : public LModelObject { 
 public:
 enum   { modelKind = 'page'} ;

 SCPage(SCDoc *inDoc);
 virtual ~SCPage();
 virtual DescType GetModelKind() const;
  
};

The constructor sets up the object’s SuperModel. The SuperModel is the object that contains this class in the containment hierarchy.

SCPage::SCPage
SCPage:SCPage(SCDoc *inDoc) : LModelObject()
{ 
 mSuperModel = inDoc;
 inDoc->AddSubModel(this); // stores the page in the Doc's list
} 

SCPage::~SCPage
SCPage:~SCPage(SCDoc *inDoc) : LModelObject()
{ 
 mSuperModel->RemoveSubModel(this)
 mSuperModel = nil; // prevents removal by LModelObject
} 

The GetModelKind member function overrides the default, and returns the class id.


SCPage::GetModelKind
DescType
SCPage::GetModelKind() const
{ 
 return SCPage::modelKind;
} 

In the SimCalc document class we store each page in a mPageList member variable, which is an LList. To maintain this list, we override the LModelObject::AddSubModel method, which gets called by the SCPage constructor. Now we have ensured that pages refer to the document as its SuperModel, and the document refers to pages as its elements.


SCDoc::AddSubModel
void
SCDoc::AddSubModel(LModelObject *inSubModel)
{ 
 if (SCPage::modelKind == inSubModel->GetModelKind()) { 
 mPageList.InsertItemsAt(1,arrayIndex_Last,&inSubModel);       } 
} 

SCDoc::RemoveSubModel
void
SCDoc::RemoveSubModel(LModelObject *inSubModel)
{ 
 if (SCPage::modelKind == inSubModel->GetModelKind()) { 
 mPageList.Remove(inSubModel);
 } 
} 

While these methods are simple, a subtle C++ gotcha is carefully avoided. In the SCPage constructor, we could have set up the SuperModel-SubModel relationship by passing inDoc in the LModelObject initializer, i.e.,

SCPage:SCPage(SCDoc *inDoc) : LModelObject(inDoc)
{ 
} 

If we did this, the dynamic type check in SCDoc::AddSubModel would return the wrong value. It turns out that GetModelKind would return typeNull instead of scPage. Why?

AddSubModel is called within the LModelObject constructor, and when this constructor is called, the SCPage vtable is not yet constructed, and the object is just an LModelObject. When the SCEleDoc method asks for its type, LModelObject::GetModelKind is called, instead of SCPage::GetModelKind. Gotcha!

The same problem occurs upon deletion. When the LModelObject destructor is called, the vtable has already been deconstructed from an SCPage to a LModelObject, so GetModelKind returns typeNull again. (So in our destructor, we manually break the super-sub model relationship instead.)

We could also try adding items to the LList without checking their type, but this turns out to be a problem, too: PowerPlant will call AddSubModel whenever a property is constructed for an object, so any properties of the document will end up inside mPageList with the pages - not a good thing.

Once we have the super-sub model relationship set up, we use it to enable 
AppleEvents to find pages within the container hierarchy.  The document 
class must be able to count and locate its elements.  The CountSubModels 
method is used to calculate references that use the “middle” and “last” 
forms.


SCDoc::CountSubModels
Int32   
SCDoc::CountSubModels(DescType inModelID) const
{ 
 if (inModelID == SCPage::modelKind) return mPageList.GetCount();
 ThrowOSErr_(errAENotAnElement);
 return 0; // prevent compiler warning
} 

PowerPlant catches exceptions within its AppleEvent handlers and returns an error code, so whenever an operation can’t be completed its best to throw an error.

Next we must take care of an omission in LModelObject. Even though LModelObject defines the CountSubModels method, it doesn’t use it to respond to a CountElements AppleEvent. So we can’t ask for the “number of pages” in AppleScript. Fortunately, its simple to remedy this by overriding HandleAppleEvent to call CountSubModels:


SCDoc::HandleAppleEvent

void  
SCDoc::HandleAppleEvent(  
 const AppleEvent  &inAppleEvent,
 AppleEvent &outAEReply,
 AEDesc &outResult,
 Int32  inAENumber)
{ 
 switch (inAENumber) { 
 case ae_CountElements:
    // note: we should extract the desired kind
    // rather than assuming its SCPage::modelKind
 Int32 count = CountSubModels(SCPage::modelKind);
   OSErr err = AECreateDesc(typeLongInteger,
 (Ptr) &count, 
 sizeof(long), &outResult);
 FailOSErr_(err);
 break;
 default:
 inherited::HandleAppleEvent(inAppleEvent,outAEReply,
 outResult);
 }
}

Since SimCalc defined absolute position, name, and id forms in the terminology resource we need to override the parallel LModelObject member functions for our document type.

The most common way to find an element is by position. For example, in AppleScript you can say:

page 1 of application "PoweringUp"
last page of application "PoweringUp"

When PowerPlant receives an AppleEvent with a position-based object specifier, it calls the containing LModelObject’s GetSubModelByPosition member function. This member function locates the element at the desired location, and returns a “token” containing that element. PowerPlant supplies and manages an appropriate class of tokens, so we don’t need to worry about the details of token implementation.


SCDoc:: GetSubModelByPosition
void  
SCDoc::GetSubModelByPosition( 
 DescType inModelID,
 Int32  inPosition,
 AEDesc &outToken) const
{ 
 if (inModelID == SCPage::modelKind){ 
 SCPage *page;
 if (mPageList->FetchAItemt(inPosition,&page)) { 
 PutInToken(page,outToken); 
 return;
 } 
 ThrowOSErr_(errAENotAnElement);
} 

The second most common way to refer to an element is by name, as in:

page “perfect” of document 1

Overriding the LModelObject::GetSubModelByName member function will handle this. In the method below, pages are found by iterating through the mPageList that contains them.


SCDoc::GetSubModelByName
void    
SCDoc::GetSubModelByName(
 DescType inModelID,
 Str255 inName,
 AEDesc &outToken) const
{ 
 if (inModelID == SCPage::modelKind){ 
 SCPage *page;
 Str255 pageName;
 LListIterator iter(mPageList,iterate_FromStart);

 while (iter.Next(&page)) { 
 page->GetDescriptor(pageName);
 if (EqualString(pageName,inName,false,false)) { 
   PutInToken(page,outToken);
 return;
 } 
 } 
 } 
 ThrowOSErr_(errAENotAnElement);
} 


When referring to an element by unique ID, AEOM allows any type of data be to used as the unique ID (unlike the index and name cases, which use a long and a text string, respectively). Therefore PowerPlant provides the actual descriptor record rather than providing a number or a string as in the prior cases. This adds the extra step of extracting the unique ID from the descriptor record. This is best done using the static methods of the UExtractFromAEDesc class, thereby performing any necessary coercions.


SCDoc::GetSubModelByUniqueID
void    
SCDoc::GetSubModelByUniqueID(
 DescType inModelID,
 const AEDesc  &inKeyData,
 AEDesc &outToken) const
{ 
 long   id,pageID;

 UExtractFromAEDesc::TheInt32(inKeyData,id);

 if (inModelID == SCPage::modelKind){ 
 SCPage *page;
 LListIterator iter(mPageList,iterate_FromStart);

 while (iter.Next(&page)) { 
 pageID = page->GetID();
 if (pageID == id) { 
   PutInToken(page,outToken);
 return;
 } 
 } 
 } 
 ThrowOSErr_(errAENotAnElement);
} 

Finally, we have to override the GetPositionOfSubModel method, which is used to compute relative references (i.e., page after page 3).


SCDoc::GetPositionOfSubModel
Int32   
SCDoc::GetPositionOfSubModel(
 DescType inModelID,
 const LModelObject *inSubModel) const
{ 
 if (inModelID == SCPage::modelKind) { 
 return mPageList.FetchIndexOf(&inSubModel);
 } 
 
 ThrowOSErr_(errAENotAnElement);
 return 0; // prevent compiler warning
}

In our actual SimCalc application, the containment hierarchy has additional levels: Pages contain graphs, for example. Because the implementation of the page-graph Elements relationship is exactly the same as the document-page, we’ll skip the details. The important thing to notice is that no matter how deeply nested the AppleScript container syntax gets, you only need to implement the Elements relationship between each class and its immediate SuperModel. PowerPlant and AEOM do the rest for you.

At this point, we have a working containment hierarchy for pages. However, because the page class has no properties, and responds to no events, the demo is pretty dull. We’ll fix that in the next two sections, first adding properties, and then event handling.

Adding Properties to the Class

In the AEOM, a property is a state variable of an object in the container hierarchy. Scripts change the state of the application by changing properties of objects. An application describes its classes’ properties in the terminology resource. Inside, the application handles properties via the PowerPlant LModelProperty class property. To add a property to a class, you have to edit the terminology resource and override two LModelObject methods.

The resource template has a slot for a list of properties within each class. For the page in SimCalc, we defined properties for the name, index, and id of each page. The name can be set by the user, but the index and id are read-only. The string you provide for the property name is used to refer to the property in AppleScript, and will be translated to the value in the code field when received by your application. The class field is the type of the variable. AppleScript will coerce user-supplied input into this type. The AppleEvent Registry defines many standard property names and types. You should use the most appropriate type.

A couple of warnings are due: be careful not to re-use an existing property code with a different property name (i.e., giving the ‘pnam’ property the name “descriptor” instead of “name”). This will mostly likely confuse AppleScript. Also, I’ve found that AppleScript sometimes caches an applications ‘aete’ resource. After you edit an ‘aete’, you may need to restart your Macintosh to get AppleScript to notice the changes.

Figure 4: ScriptEditor shows the properties of the Page Class

Implementing properties in an object derived from LModelObject is easy - you just override SetAEProperty and GetAEProperty. Explaining how it works is a bit more difficult, because PowerPlant does a lot of work behind the scenes to make properties easy to support. After presenting the two overridden functions, I’ll explain the gist of how it works.

GetAEProperty dispatches on the property id, and places the value of the desired property into a AppleEvent descriptor record. The property ids for pIndex and pName are defined in AERegistry.h, and our header file defines pUID to be ‘ID ’. Its no accident that these identities are the same as those that appear in terminology resource. AppleScript uses the terminology resource to translate English names for properties.

Figure 5: Adding properties to the terminology resource


SCPage::GetAEProperty
void    
SCPage::GetAEProperty(  
 DescType inProperty,
 const AEDesc  &inRequestedType,
 AEDesc &outPropertyDesc) const
{ 
 long   id, index;
 Str255 name;
 
 switch (inProperty) { 
 case pIndex:
 index = GetIndex(); 
 FailOSErr_(AECreateDesc(typeLongInteger, (Ptr) &index,
 sizeof(long), &outPropertyDesc));
 break;
 case pUID:
 id = GetID();
 FailOSErr_(AECreateDesc(typeLongInteger, (Ptr) &id,
 sizeof(long), &outPropertyDesc));
 break;
 case pName:
 GetDescriptor(name);
 FailOSErr_(AECreateDesc(typeChar, &(name[1]),
 name[0], &outPropertyDesc));
 break;
 } 
} 

Setting a property is very similar to getting a property; the main difference is that instead of putting a value into a a descriptor record, you take one out. This can be done directly by calls to the AppleEvent manager, or through PowerPlant. There is a big advantage to using PowerPlant’s methods: they will coerce data into the right type for you, and throw an exception if coercion fails. This exception will be trapped within PowerPlant’s basic AppleEvent handlers, so the exception will be transparent to you. But PowerPlant will give the user an appropriate error message. PowerPlant defines extraction methods in UExtractFromAEDesc via a pseudo-template mechanism that makes it easy for you to add extraction methods for any additional types you need.


SCPage::SetAEProperty
void    
SCPage::SetAEProperty(  
 DescType inProperty,
 const AEDesc  &inValue,
 AEDesc &outAEReply)
{ 
 Str255 name;
 
 switch (inProperty) {    
 case pName:
 UExtractFromAEDesc::ThePString(inValue,name);
 CopyPStr(name,mName);
 break;
 default:
 inherited::SetAEProperty(inProperty,inValue,outAEReply);
 } 
} 
 

As you can see, adding properties to an LModelObject is not hard. You can test that the property works in ScriptEditor. Some example lines are:

tell application “PoweringUp”
 copy name of page 1 to oldName
 copy oldName & “er” to newName
 set name of page 1 to newName
 get index of page newName
end tell

Here’s the story on how this works. AppleScript sets and gets properties by applying the standard set data and get data events to property tokens. Consider the execution of the line:

copy name of page 1 to oldName

PowerPlant handles properties by re-using standard AppleEvent handling. The routine for handling any AppleEvent is to first get a token representing the object (the noun), and then send it the event (the verb). Rather than defining a second token type for properties, PowerPlant treats properties as a class of LModelObject. During the execution of an AppleEvent that refers to a property, PowerPlant creates an LModelProperty on the fly, destroying it at the end of event processing. This LModelProperty has the target object as its SuperModel and has an mPropertyID member variable to represent the identity of the property. When the property receives a set or get data event, it calls its SuperModel’s SetAEProperty and GetAEProperty methods, specifying its property id.

Supporting Undo for All Properties

Supporting AppleEvents seems to be a lot of work for most simple tasks. But it actually can make my most dreaded task, supporting Undo, easier.

Think about it: Most of the application’s changes to state are changes to the value of properties. There is a standard form for naming these properties, the property id, and standard methods for getting and setting them. The only problem is that the property data may come in a variety of forms, and we need to be able to store them all - an anathema to C++ strong typing. This is where AppleEvents shines by providing a dynamically typed descriptor record that can store any sort of property value.

In SimCalc, we support property undo with a subclass of LModelProperty. When handling an incoming set property event, this subclass calls GetAEProperty just before it calls SetAEProperty. It stores the previous value and a duplicate of the new value in a task, along with the target LModelObject and the id of the property. Using these entities, the task can easily respond to undo and redo. As a finishing touch we parse the ‘aete’ resource for the English name of the property and incorporate this into the undo menu item in the edit menu. This provides totally automatic handling of undo for property values we might ever define, without any additional work.

Our special form of property overrides only one method, to post a SCSetPropertyAction whenever the user tries to set the value of a property.


SCModelProperty::HandleSetData
void  
SCModelProperty::HandleSetData(
 const AppleEvent  &inAppleEvent,
 AppleEvent &outAEReply)
{ 
 SCApp::PostAction(new SCSetPropertyAction(GetSuperModel(),
 mPropertyID, 
 inAppleEvent));
} 

The interesting work is done in the SCSetPropertyAction, which is derived from LSemanticAction. The constructor gets the old value, and saves it along with the copy of the new value, in member variables mNewData and mOldData. These member variables are StAEDescriptors; they hold AppleEvent descriptor records, and automatically dispose of those descriptors when upon destruction.

SCSetPropertyAction::SCSetPropertyAction

SCSetPropertyAction::SCSetPropertyAction(
 LModelObject    *inObject,
 DescType inPropID,
 const AppleEvent&inAE)
 :  LSemanticAction(200,3), mObject(inObject), mPropID(inPropID)
 
{ 
    // get old data from object itself
 StAEDescriptor  reply;
 mObject->GetAEProperty(mPropID, reply.mDesc, mOldData.mDesc);
 
 mNewData.GetParamDesc(inAE, keyAEData, typeWildCard);
 
 UAppleEventsMgr::CheckForMissedParams(inAE);
} 

Now the redo and undo methods are nearly trivial, and work regardless of the C type that stores the property value:


SCSetPropertyAction::RedoSelf
void 
SCSetPropertyAction::RedoSelf()
{ 
 StAEDescriptor  result;  // will dispose result on deletion
 mObject->SetAEProperty(mPropID,mNewData.mDesc,result.mDesc);
} 


SCSetPropertyAction::UndoSelf
void 
SCSetPropertyAction::UndoSelf()
{ 
 StAEDescriptor  result;// will dispose result on deletion
 mObject->SetAEProperty(mPropID,mOldData.mDesc,result.mDesc);
} 

The final step is to force our object to use SCModelProperty instead of LModelProperty by overriding GetModelProperty.


SCPage::GetModelProperty
LModelProperty*  
SCPage::GetModelProperty(DescType inProperty)
{ 
 return new SCModelProperty(inProperty,this);
} 

Adding Event Handling for a Standard Event

According to the AEOM, you should represent most of your application’s state as properties of classes. Many user commands therefore translate into setting a property. Some commands, however, are best thought of as verbs that operate on a noun, changing the state of the application in a way that cannot be reflected in a single property. In this case, you probably need to define and handle an event.

Consider the act of changing pages. When the user clicks the “next page” or “previous page” button, the application changes the entire contents of the screen. I think of this most naturally as sending the document a change page event, with the name or number of the desired page as a parameter. However, AEOM has its own way of thinking about things; and Apple discourages users from inventing events where a standard one will do. In this case, changing a page can also be represented by sending the page a “select” event. In AppleScript you type:

tell application "PoweringUP" 
 select page 43
end tell

At first, this convention of using sending “select” to a page rather than “change page” to a document seems arbitrary. But it turns out to have important implications. Apple’s preferred methods factor the verb from the designation of the noun, whereas the “change page” method incorporates the noun as a parameter of the verb. The practical difference is that the “select” method can operate over an arbitrary expression that designates a page object, such as:

select (page after page 4)
select (page before page id 104)
select last page
select (make new page at end)

The standard terminology resource supplied with PowerPlant already contains the select event. Therefore to add event handling for select, we need only modify code internal to the application. In particular, we should override the LModelObject method that handles AppleEvents, which strangely enough is called HandleAppleEvent.


SCPage::HandleAppleEvent
void    
SCPage::HandleAppleEvent( 
 const AppleEvent&inAppleEvent,
 AppleEvent &outAEReply,
 AEDesc &outResult,
 Int32  inAENumber)
{  
 switch (inAENumber) { 
 case ae_Select:
 ((SCDoc *)GetSuperModel())->SetSelection(this);
 break;
 default:
 inherited::HandleAppleEvent( inAppleEvent,
 outAEReply,
 outResult,
 inAENumber);
 break;
 } 
} 

Handling an event with no parameters is just a matter of dispatching on the AppleEvent number. SimCalc internally prefers to treat changing the page as a responsibility of the document, so we call the ChangePage method of the page’s SuperModel, (which will be its document; we set it up that way in the constructor).

There is one subtle mystery with the parameters to this function. Can you find it? In the terminology resource, an event is identified by its class id and its event id, but here an event is identified by a number which has no correspondence to either of those ids. The translation between these two forms of event identity was initially a deep mystery to me.

It turns out that the mapping is represented in an ‘aedt’ resource. There is one ‘aedt’ for each suite, and each item in the ‘aedt’ contains a suite id, an event id, and a long integer. PowerPlant uses this resource to translate incoming events ids to a long integer, which is then passed to your object’s HandleAppleEvent method. It is also important to understand that the class id for an event is not the id of a class! Rather “class id” in this context means a class of events contained in a suite. For example the class id for standard events like close is ‘core’ and appears in the ‘coRe’ suite.

Defining New Events

The previous example covered handling an event already defined within the terminology resource. Adding a completely new event is possible, and combines parts of the processes described in preceding sections. In short, to add an event you:

1. add the event to an appropriate suite of the terminology resource

2. add a mapping from suite and event id to a number in an ‘aedt’ resource

3. add a case to the switch statement in HandleAppleEvent that:

a. extracts descriptor records for each parameter in the event

b. extracts C++ values from the descriptor records

c. calls a member function that does the work

d. creates a descriptor record containing any return value

To illustrate, we will add a “sort” event to the document, allowing it to sort the pages into alphabetical order.

First, we need to edit the terminology resource, adding the new event to the PoweringUp suite. We’ll use ‘P Up’, our application’s signature, as the class id, and ‘sort’ as the event id. These two bits of information also need to be added to an ‘aedt’ resource along with a unique long integer that will signify this event within the application. The event name supplies the English verb that will create this event from within AppleScript, but the description is purely informational. The reply type for our event is a null, since we don’t return anything.

The direct object comes right after the verb in AppleScript syntax; it is the first parameter of the event. If the direct object has object specifier as its type, PowerPlant will call that object with the HandleAppleEvent message. You can think of this translating to C++ syntax as:

(direct object)->EventName(parameter1,parameter2,....);

The direct object for the sort command will be the document.

In this case we want one additional parameter which describes the sort direction. We could use a Boolean to represent the sort order, but to make things nicer, let’s use an enumeration. Then the user can type “ascending” or “descending” rather than remember which direction “true” implies.

To indicate that this will be an enumerated parameter, we turn on that option and use the id of the enumeration, which we will define as ‘Esrt’. Then in the enumeration section of the suite, we create an enumeration of this type, and specify each option as an enumerator. Each enumerator has a name that can be typed in AppleScript, and a corresponding id code that will be sent in the event.

Figure 6: The terminology for the Sort event

Figure 7: The terminology for the sort direction enumeration.

To finish off the parameter description we need to provide a parameter name and keyword. The name will become part of the AppleScript syntax of the event. Since we specified “in direction” as the name, we will be able to say:

sort in direction ascending
sort in direction descending

Figure 8: ScriptEditor shows the terminology for the Sort event.

Figure 9: The aedt resource maps class and id to a number

Inside the application, the work if fairly easy. We will retrieve the parameter with the ‘srtD’ keyword. To SCDoc:HandleAppleEvent, we add a case statement that triggers on the long integer we set in the ‘aedt’ resource, 10000.


SCDoc::HandleAppleEvent
void    
SCDoc::HandleAppleEvent(
 const AppleEvent&inAppleEvent,
 AppleEvent &outAEReply,
 AEDesc &outResult,
 Int32  inAENumber)
{ 
 switch (inAENumber) { 
 case ae_CountElements:
 HandleCountEvent(inAppleEvent, outAEReply, outResult);
 break;
 case ae_Sort:
 { 
 StAEDescriptor  desc;
 OSType direction = kSortAscending;
 desc.GetOptionalParamDesc( inAppleEvent,
 keySortDirection,
 typeEnumeration);
 if (desc.mDesc.dataHandle != nil) {  // check for parameter
 UExtractFromAEDesc::TheEnum(desc.mDesc,direction);
 } 
 DoSort(direction == kSortAscending);
 break;
 } 
 default:
 inherited::HandleAppleEvent( inAppleEvent,
 outAEReply,
 outResult,
 inAENumber);
 break;
 } 
} 

In the this routine’s sort case, we first get the descriptor for the sort parameter. PowerPlant provides a handy routine for this: GetOptionalParamDesc. Since this parameter is optional, it may not exist. If it does exist, the dataHandle of the descriptor will not be nil. Then we can use UExtractFromAEDesc::TheEnum to extract the enum from the descriptor. Then we dispatch to our member function that does the work.

Conclusion

Good AppleEvent support is the distinguishing mark of a System 7-savvy application. By building on the classes provided within PowerPlant, supporting AppleEvents becomes a simple, incremental process.

First, you characterize your application as an AEOM containment hierarchy, consisting of classes linked by the Elements relationship. This is accomplished by specifying the classes and the Elements relationship in the terminology resource, deriving your class from LModelObject, and overriding a few key methods. Second, you add support for setting and getting the value of the properties of your classes. This is achieved by editing the ‘aete’ to specify English property names and corresponding internal property ids. Then you override the GetAEProperty and SetAEProperty methods appropriately. Third, you add support for additional events that your class responds to. Again this requires editing the ‘aete’, as well as the ‘aedt’. Then you add a clause to your objects’ HandleAppleEvent method.

As an natural extension to this methodology, you could handle Create, Delete, and Move events for any object in your hierarchy. An abstraction that builds on LList can support these events uniformly for any container in your hierarchy, including general Undo handling. By supporting Create, Delete and Move, along with the events already supported within PowerPlant, you will be well on your way to handling the whole AppleEvents Core Suite.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Chromium 75.0.3770.142 - Fast and stable...
Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all Internet users to experience the web. Version 75.0.3770.142: Release notes were... Read more
Viber 11.1.0 - Send messages and make fr...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device, so... Read more
Wireshark 3.0.3 - Network protocol analy...
Wireshark is one of the world's foremost network protocol analyzers, and is the standard in many parts of the industry. It is the continuation of a project that started in 1998. Hundreds of... Read more
DEVONthink Pro 3.0beta4 - Knowledge base...
DEVONthink Pro is your essential assistant for today's world, where almost everything is digital. From shopping receipts to important research papers, your life often fills your hard drive in the... Read more
Adobe Creative Cloud 4.9.0.512 - Access...
Adobe Creative Cloud costs $20.99/month for a single app, or $52.99/month for the entire suite. Introducing Adobe Creative Cloud desktop applications, including Adobe Photoshop CC and Illustrator CC... Read more
SketchUp 19.1.174 - Create 3D design con...
SketchUp is an easy-to-learn 3D modeling program that enables you to explore the world in 3D. With just a few simple tools, you can create 3D models of houses, sheds, decks, home additions,... Read more
ClamXav 3.0.12 - Virus checker based on...
ClamXav is a popular virus checker for OS X. Time to take control ClamXAV keeps threats at bay and puts you firmly in charge of your Mac’s security. Scan a specific file or your entire hard drive.... Read more
BetterTouchTool 3.151 - Customize multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom)... Read more
FontExplorer X Pro 6.0.9 - Font manageme...
FontExplorer X Pro is optimized for professional use; it's the solution that gives you the power you need to manage all your fonts. Now you can more easily manage, activate and organize your... Read more
Dropbox 77.4.131 - Cloud backup and sync...
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 keeps them up-to-date between systems... Read more

Latest Forum Discussions

See All

Upcoming visual novel Arranged shines a...
If you’re in the market for a new type of visual novel designed to inform and make you think deeply about its subject matter, then Arranged by Kabuk Games could be exactly what you’re looking for. It’s a wholly unique take on marital traditions in... | Read more »
TEPPEN guide - The three best decks in T...
TEPPEN’s unique take on the collectible card game genre is exciting. It’s just over a week old, but that isn’t stopping lots of folks from speculating about the long-term viability of the game, as well as changes and additions that will happen over... | Read more »
Intergalactic puzzler Silly Memory serve...
Recently released matching puzzler Silly Memory is helping its fans with their intergalactic journeys this month with some very special offers on in-app purchases. In case you missed it, Silly Memory is the debut title of French based indie... | Read more »
TEPPEN guide - Tips and tricks for new p...
TEPPEN is a wild game that nobody asked for, but I’m sure glad it exists. Who would’ve thought that a CCG featuring Capcom characters could be so cool and weird? In case you’re not completely sure what TEPPEN is, make sure to check out our review... | Read more »
Dr. Mario World guide - Other games that...
We now live in a post-Dr. Mario World world, and I gotta say, things don’t feel too different. Nintendo continues to squirt out bad games on phones, causing all but the most stalwart fans of mobile games to question why they even bother... | Read more »
Strategy RPG Brown Dust introduces its b...
Epic turn-based RPG Brown Dust is set to turn 500 days old next week, and to celebrate, Neowiz has just unveiled its biggest and most exciting update yet, offering a host of new rewards, increased gacha rates, and a brand new feature that will... | Read more »
Dr. Mario World is yet another disappoin...
As soon as I booted up Dr. Mario World, I knew I wasn’t going to have fun with it. Nintendo’s record on phones thus far has been pretty spotty, with things trending downward as of late. [Read more] | Read more »
Retro Space Shooter P.3 is now available...
Shoot-em-ups tend to be a dime a dozen on the App Store, but every so often you come across one gem that aims to shake up the genre in a unique way. Developer Devjgame’s P.3 is the latest game seeking to do so this, working as a love letter to the... | Read more »
Void Tyrant guide - Guildins guide
I’ve still been putting a lot of time into Void Tyrant since it officially released last week, and it’s surprising how much stuff there is to uncover in such a simple-looking game. Just toray, I finished spending my Guildins on all available... | Read more »
Tactical RPG Brown Dust celebrates the s...
Neowiz is set to celebrate the summer by launching a 2-month long festival in its smash-hit RPG Brown Dust. The event kicks off today, and it’s divided into 4 parts, each of which will last two weeks. Brown Dust is all about collecting, upgrading,... | Read more »

Price Scanner via MacPrices.net

Verizon is offering a 50% discount on iPhone...
Verizon is offering 50% discounts on Apple iPhone 8 and iPhone 8 Plus models though July 24th, plus save 50% on activation fees. New line required. The fine print: “New device payment & new... Read more
Get a new 21″ iMac for under $1000 today at t...
B&H Photo has new 21″ Apple iMacs on sale for up to $100 off MSRP with models available starting at $999. These are the same iMacs offered by Apple in their retail and online stores. Shipping is... Read more
Clearance 2017 15″ 2.8GHz Touch Bar MacBook P...
Apple has Certified Refurbished 2017 15″ 2.8GHz Space Gray Touch Bar MacBook Pros available for $1809. Apple’s refurbished price is currently the lowest available for a 15″ MacBook Pro. An standard... Read more
Clearance 12″ 1.2GHz MacBook on sale for $899...
Focus Camera has clearance 12″ 1.2GHz Space Gray MacBooks available for $899.99 shipped. That’s $400 off Apple’s original MSRP. Focus charges sales tax for NY & NJ residents only. Read more
Get a new 2019 13″ 2.4GHz 4-Core MacBook Pro...
B&H Photo has new 2019 13″ 2.4GHz MacBook Pros on sale for up to $150 off Apple’s MSRP. Overnight shipping is free to many addresses in the US: – 2019 13″ 2.4GHz/256GB 6-Core MacBook Pro Silver... Read more
AirPods with Wireless Charging Case now on sa...
Amazon has extended their Prime Day savings on Apple AirPods by offering AirPods with the Wireless Charging case for $169.99. That’s $30 off Apple’s MSRP, and it’s the cheapest price available for... Read more
New 2019 15″ MacBook Pros on sale for $200 of...
B&H Photo has the new 2019 15″ 6-Core and 8-Core MacBook Pros on sale for $200 off Apple’s MSRP. Overnight shipping is free to many addresses in the US: – 2019 15″ 2.6GHz 6-Core MacBook Pro Space... Read more
Amazon drops prices, now offers clearance 13″...
Amazon has new dropped prices on clearance 13″ 2.3GHz Dual-Core non-Touch Bar MacBook Pros by $200 off Apple’s original MSRP, with prices now available starting at $1099. Shipping is free. Be sure to... Read more
2018 15″ MacBook Pros now on sale for $500 of...
Amazon has dropped prices on select clearance 2018 15″ 6-Core MacBook Pros to $500 off Apple’s original MSRP. Prices now start at $1899 shipped: – 2018 15″ 2.2GHz Touch Bar MacBook Pro Silver: $1899.... Read more
Price drop! Clearance 12″ 1.2GHz Silver MacBo...
Amazon has dropped their price on the recently-discontinued 12″ 1.2GHz Silver MacBook to $849.99 shipped. That’s $450 off Apple’s original MSRP for this model, and it’s the cheapest price available... Read more

Jobs Board

Best Buy *Apple* Computing Master - Best Bu...
**696259BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Location Number:** 001076-Temecula-Store **Job Description:** The Read more
Business Development Manager, *Apple* Globa...
Business Development Manager, Apple Global Tampa, FL, US Requisition Number:73805 As a Global Apple Business Development Manager at Insight, you proactively Read more
*Apple* Systems Architect/Engineer, Vice Pre...
…its vision to be the world's most trusted financial group. **Summary:** Apple Systems Architect/Engineer with strong knowledge of products and services related to Read more
*Apple* IOS Systems Engineer - Randstad (Uni...
Apple IOS Systems Engineer **job details:** + location:Irvine, CA + salary:$45 - $55 per hour + date posted:Tuesday, July 16, 2019 + job type:Temp to Perm + Read more
Business Development Manager, *Apple* Globa...
Business Development Manager, Apple Global Tampa, FL, US Requisition Number:73805 As a Global Apple Business Development Manager at Insight, you proactively Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.