TweetFollow Us on Twitter

AppleEvents 101
Volume Number:9
Issue Number:5
Column Tag:C Workshop

Related Info: Notification Mgr Event Manager Apple Event Mgr

AppleEvents 101

From the beginning, how to do AppleEvents

By Jeffrey B. Kane, M.D., Boston, Massachusetts

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks. You should check the article: AppleEvents 101 Bug for more information about the source code too.

It’s been over two years since Apple introduced System 7.0 and I still find it suprising that there is such a dearth of programs which truly take advantage of System 7.0’s new features. The most obvious problem, is finding programs that used the System 7.0’s new inter-application communication feature, AppleEvents.

AppleEvents provide an easy way for programs to work together, allowing the user to integrate various tasks quickly and easily. The potential is amazing, word processors that can use any dictionary program, spreadsheets that can easily ask a symbolic or numeric mathe-matics program to do complex calculations the possibilities are endless.

One thing I have noted is a lack of clear, step by step instructions to teach programmers how to add AppleEvents to their own applications. This article will go through the various steps you need to implement so that your programs can send, receive, and process AppleEvents. One side benefit of this process is that it will allow you to cleanly separate your program’s interface from the core code which does the work.

Telling the System we are AE aware and System 7.0 savvy

The SIZE resource is more important than ever in system 7.0. The operating system now uses it to determine what kind of events your application is able to process. If the appropriate bits are not set properly, the operating system has to go through a lot of gyrations to fool your program into doing what is necessary. In the case of AppleEvents you won’t be able to receive or send them, unless these appropriate bits are set. For our program we set the flags as shown below in Table 1.

The easiest way to set these flags is either to create the resource using ResEdit with MPW and Think Pascal, or to use the “Set Project Type” menu item in Think C 5.0. Create a SIZE resource with a resource number of -1 for default use by the system. The system will use our SIZE resource, unless the user changes the memory size in via the Get Information dialog box, in which case it will create a SIZE id 0 resource with the new parameters.

Table 1. SIZE resouRce flags

example

flag value meaning

acceptSuspend-Resume

Events (formerly

isMultifinder-Aware) true tells us when we need to

convert the clipboard

canBackground false we don’t do anything in the

background, so we don’t

need this information

onlyBackground false we are a normal application

that runs in the foreground.

getFrontClicks false if someone clicks in one of

our windows when we are

in the background, just

bring us to the front

(resume), don’t also send

us a mousedown event too.

is32Bit-Compatible true Think C generates 32 bit

clean code

HighLevelEvent-Aware true lets us send and receive

AppleEvents.

localAndRemote-HLEvents true let other machines on the

network send us

AppleEvents

isStationary-Aware false we don’t save any files, let

alone use stationery pads in

this simple example

useTextEdit-Services false we don’t use TextEdit in

this example

Checking if the system supports AppleEvents

Checking Gestalt

If you are using any of the current development environ-ments (MPW, Think Pascal, or Think C), the prefered method of checking your system’s environment is to use the new Gestalt functions which are available in System 6.0.4 or later. These development environ-ments contain the glue necessary to check if the Gestalt trap is available under the current operating system. If Gestalt is not found the glue calls SysEnvirons for you, and return the results as if it came from Gestalt. As per Apple’s current guidelines we will check to make sure that each system attribute we need is available on the user’s CPU.

Gestalt lets the program query for specific environmental information. To use Gestalt you pass it a selector and you get a response. If the selector is an attribute, it returns the result as a bit field, with the appropriate bit set if the desired feature is available. If the selector is a size, or type, it returns a value in the response.

In our program we will test if AppleEvents, the PPC toolbox, and color are available. The calls look like:

/* 1 */

theErr = Gestalt(gestaltAppleEventsAttr, &response);
if BitTst(&response,31-gestaltAppleEventsPresent) 
 gHasAE = true;
theErr = Gestalt(gestaltPPCToolboxAttr,&response);
if BitTst(&response,31-gestaltPPCToolboxPresent)
 gHasPPC = true; 
theErr = Gestalt(gestaltQuickdrawFeatures, &response);
if BitTst(&response,31-gestaltHasColor)
 gHasColor = true;

Note that the Macintosh toolbox routines number the bits in an order opposite to the conventional assembly language and Motorola form(0 to 31 instead of 31 to 0). Many development systems (including Think Pascal and MPW Pascal) offer equivalent routines which use the normal syntax for numbering bits.

The attributes returned by the selector gestaltPPCToolboxAttr deserve some special attention. If the bit “gestaltPPCSupportsRealTime” is not set, then we need to initialize the PPC Toolbox. The current version of the PPC Toolbox only supports real time mode (it can only talk to programs that are up and running), so a properly initialized toolbox will always set this bit. If the bit gestaltPPCSupportsOutGoing is not set then the user has not turned on AppleTalk, so the PPC Toolbox cannot send messages to other machines on the network. If the bit gestaltPPCSupportsIncoming is not set, then either the user hasn’t turned on AppleTalk from the Chooser desk accessory, or they have turned off file sharing in the Sharing Setup control panel. If you need any of these features and they are missing, you can then put a a dialog box requesting that the user activates them.

Interacting with the user

The old Mac programing model we assumed that the user was always nearby, but scripting and AppleEvents bring up the possibility of unattended machines. In Wingz and Resolve, when an error is encountered from within a script, an alert appears, halting the script until the user dismisses it. Obviously this method of dealing with errors is inadequate, especially if the machine is on an unattended server.

System 7.0 controls interaction with the user using a new call,

FUNCTION AEInteractWithUser(
  timeOutInTicks: LONGINT;
  nmReqPtr: NMRecPtr;
  idleProc: IdleProcPtr): OSErr;

By calling this routine before each dialog is supposed to appear, two things are accomplished. Firstly, the System will automatically do nothing, notify the user, or bring the application to the front, whichever is appropriate. Secondly, the result returned by this function will tell the program whether or not it should display its error alert window. Both the client (sending application) and server (target application) determine what kind of interaction is allowed. When you initialize you program you can use the default response of only having the user interact with events sent from other programs on their own machine, or you can override this default by using the function

FUNCTION AESetInteractionAllowed(level: AEInteractAllowed): OSErr;

Possible values to pass to AESetInteractionAllowed are:

kAEInteractWithSelf Only allow interaction if the current

program sends AppleEvents to itself

kAEInteractWithLocal (the default) Only allow interaction if

the a program running on the user’s

CPU sends the AppleEvents

kAEInteractWithAll Allow user interaction if the

AppleEvent is sent locally, or through

the network

As we will see, when we send an AppleEvent one of the parameters also specifies what kind of user interaction is requested by the sending program.

Patch the main event loop to get AppleEvents

Now that the world knows that we can do AppleEvents, it’s time to patch our program to accept them. In our WaitNextEvent loop we need to add cases for two new types of events, operating system events, and high level events.

Check for Operating system events

Operating system events are the events that tell us when major context switching between applications has taken place. Suspend and resume events will tell us if the user is switching between applications under Multifinder. Mouse moved events are reported if the user moves the mouse out of a predefined region that we passed to WaitNextEvent at the beginning of the event loop.

Check for High Level Events

High level events such as AppleEvents or private PPC events are new to system 7.0. We add both of these to the standard main event loop as shown below:

/* 2 */

if (WaitNextEvent(everyEvent, &theEvent, 15, nil) {
 switch (theEvent.what) {
 case mouseDown:
 DoMouseDown(&theEvent);
 break;
 .
 .
 .
 // process the rest of your events
 // just like you always do
 .
 .
 .

 case osEvt:
 DoOSEvt(&theEvent);
 break
 case kHighLevelEvent:
 if (gHasAppleEvents)
 {
 // check if the events 
 // message and where fields
 // are some private PPC event 
 // that you have defined,
 // otherwise assume it is 
 // an AppleEvent
 
 AEProcessAppleEvent(&theEvent);
 } /* gHasAppleEvent is true */
 } /* end of switch */
} /* end of WaitNextEvent */

The call AEProcessAppleEvent is a new toolbox routine that lets the system dispatch the AppleEvent to the proper application defined routine that handles it. We will write the various routines to handle each AppleEvent, then register them with the system so they can be called. This level of abstraction allows us to add handlers from any locked code resource. One obvious use is to add routines that can process new AppleEvents via XCMDs or other external code resources without changing the original code of existing programs .

Process OSEvt to be Multifinder cool

Operating system events store their information in the Event record in a special way. By examining the message field of the Event Record we determine what kind of event it is - either a suspend/resume or mouse moved event.

Check the most significant byte of the event message to see what kind of event it is

First, look at the most significant byte of the event’s message field. If this byte is set equal to the constant suspendResumeMessage then we are being either switched in or out under Multifinder. If the byte is equal to the constant mouseMovedMessage then the user has moved the mouse out of the predefined region we passed to WaitNextEvent.

/* 3 */

/* get rid of the last 24 bits */
topByte = theEvent.message>> 24;    
/* needed because C does sign extended shifting */
topByte = topByte & 0x00FF; 

Suspend Resume message

If the first byte of the message indicates that we have a suspend or resume event, then look at bit zero of the message to tell us which it is. Apple defines the mask resumeFlag to be 1 so if:

(theEvent.message) & resumeFlag

is zero, then our application is about to be suspended. We should respond to this event by converting any private clipboards we have and put it on the scrap. We should also execute our Deactivate event handler, and set any global variables we have defined to tell us that we are now a background task.

If the above bit is set, then we are resuming. To test if the clipboard has been changed, we test bit one. Apple defines the mask convertClipboardFlag as two so if:

(theEvent.message) & convertClipboardFlag

is not zero then we know to store the clipboard back into our private scrap.

Mouse Moved message

The mouse moved message saves us from constantly having to check the mouse position to adjust the appearance of our cursor. By setting a mouseMoved region, the system will let us know when we should check to see if the cursor needs to change shape. In our example we define two regions, the content region of our window (less the scroll bar and grow regions) over which we wish the cursor to appear as a plus. We define the rest of the screen (including any additional monitors) as a second region, over which we set the cursor to the standard arrow. Every time we receive a mouse moved event, we first note which region the cursor is now in and appropriately adjust it’s shape. We then pass this current region back to WaitNextEvent so the system will tell us if the user moves the mouse out of the current region. It is also important to remember to readjust the region if the user manipulates our window, such as dragging, zooming, closing, etc.

Grab High Level Events

If the what field of the event indicates that we have received a high level event, then the message and where fields have special meaning. You can check these fields to see if they are equal to any special PPC event that you have defined, and so, jump to your own special code to handle those events. If you don’t recognize the event as one of your own PPC events then pass it to AEProcessEvent. The system uses AEProcessEvent to dispatch the event to the appropriate AppleEvent handler which has been previously been registered with the system. If AEProcessEvent does not have a proper handler registered with it, it notifies the sending program that the event could not be processed.

Type of event (message)

High level events use the message and where event fields in a special way. The message contains the AppleEvent class, usually grouping AppleEvents by functional groups. The core event class are those basic events that can be implemented by most Macintosh programs. The Apple Event registry contains other classes, such as the text edit class, that contains those events that relate to editing any kinds of text based information. If a program defines a group of application specific events it usually uses it’s four letter creator code as the event class.

What to do (the where field)

The where field contains the specific event id. These four letter parameters tell the server (target) what specific function to perform. I often think of the two fields together as a noun, verb sentence. Both fields are needed in order to define the event. An example might be one event telling you to move some text, while another asks you to move a window. Apple is now defining many of these event suites using an object oriented model know as the Apple Event’s registry. The registry will contain the common language that all programs can use to talk to each other.

Process the AppleEvent

After you note that the what field of the event is a kHighLevelEvent, as mentioned previously, call the function

FUNCTION AEProcessAppleEvent(theEventRecord: EventRecord): OSErr;

so the system can dispatch the event to your program’s appropriate handler.

Register your AppleEvent Handlers with the system

Write your AppleEvent Handler

Any handler that you write has the same function header form:

FUNCTION myAEHandler (
 theAppleEvent, reply: 
 AppleEvent; 
 refcon: long): OSErr;

theAppleEvent is the AppleEvent that you will be processing. If a reply is needed you will add the appropriate data to the reply AppleEvent that the system passes you. The refcon parameter stores the handlerRefCon constant from the dispatcher table (ignore this value if you don’t want to use it). When you are finished with your handler, make sure you return zero if no errors occur or the appropriate constant to describe any errors you encountered.

One important gottcha. Make sure that your handlers are either in the main code segment, or is in a code segment that is not unloaded from the system. You are passing a pointer to your handler to the system, and it’s kind of nice for that routine to actually be there when the system jumps to it. Make sure you don’t call UnLoadSeg on a segment that contains your handlers. In our example all the handlers are in the main segment, which is never unloaded from RAM. You should also note that you don’t have direct access to your program’s globals, as A5 points to the calling application’s A5 world, not yours. Remember to setup your own A5 world if you need to (and don’t forget to restore it before you leave your handler).

Register your AppleEvent Handlers

After the handler is written it must be registered, so the system knows both what events your program can handle and where those routines are. The function:

FUNCTION AEInstallEventHandler(
 theAEEventClass: AEEventClass;
 theAEEventID: AEEventID;
 handler: EventHandlerProcPtr;
 handlerRefcon: LONGINT;
 isSysHandler: BOOLEAN): OSErr;

registers the handler. These routines are usually called in your program’s initialization routines, as we have done in our example. As mentioned previously, you can also store your handler as an external code resource, then register them later.

A little vocabulary

Attributes vs Parameters

AppleEvent data can be divided into two main categories, Attributes and Parameters. Attributes are general information used to describe any AppleEvent. These descriptors are usually of concern to the operating system, although you can freely access them as needed. Parameters contains the information needed to process the specific event. An attribute might be the target address you are sending the event to, the time limit for replying to the event, or any errors associated with the event. A parameter might be the list of files for an open-document-event.

The building block for data: Descriptors

The basic building block for AppleEvents are the AEDesc record. Instead of passing simple predefined types data Apple chose to create a flexible record structure which not only contains a handle to actual data, but a short description of what kind of data it is. These descriptions can distinguish between many different types of data such as integers, characters, booleans, alias records, or even a list of other descriptor records (as well as custom types of structures). Using these descriptors you can pass and retrieve data, quickly deciding if you know how to handle it properly. The form of the descriptor record is:

AEDesc = RECORD
 descriptorType: DescType;
 dataHandle: Handle;
 END;

Apple has renamed a few specific descriptor records, such as those that refer to the target program’s address (AEAddressDesc), but they are still just descriptors (a rose by any other name ). The name implies their use, more than their structure (see Table 2).

Table 2. Types of AEDesc records

Data Structure Contents Example

AEDesc Basic data type Contains a descriptor type and

a data handle which points to

some other structure. The

descriptor type tells you what

kind of structure it is.

AEKeyDesc An AEDesc used to See AERecord below describe keywords. It

holds a keyword (i.e.

keyDirectObject) in it’s

descriptor field and the

data handle points to

another AEDesc which

holds the actual data and

its type. Usually

AEKeyDesc are grouped

together within an

AERecord (see below)

AEAddressDesc An AEDesc used to Used to hold a processes

describe an address address (either a creator id

(process) code, process serial number,

session reference number,

target ID, or any other valid

type of address)

AEDescList An AEDesc who’s This might be used to group

data handle points to several file names or aliases

a group of AEDescs to pass within

an open document Apple Event

AERecord An AEDesc who’s data See below

handle points to a

group of AEDescs.

Each AEDesc in this

group has a keyword

for it’sdescriptor type field

(keyDirectObject,

keyErrorNumber, etc.)

and a data handles that

point to either a single

AEDesc or a AEDescList

that contains a group of

more AEDescs.

AppleEvent A type of AEDesc, the An apple event

same as an AERecord attributes and parameters.

but used to define One element of this group

and send AppleEvents. might be an AEKeyDesc

contains a group of

which contains the

keyDirectObject and who’s

data handle points to a group

of these parameters, each

another AEDesc

Coercing your data to a type you can handle

If you only know how to handle a limited number of data types, you can request your data be converted to convenient form for you. When you ask an AppleEvent for its data, you can request that the system give it to you in a form you can understand. This way you don’t have to handle every conceivable method of passing data, and it makes the job of send and receiving data much less rigid.

Remember not to manipulate the fields of these structures directly, but instead to use the proper traps to get and manipulate the fields of an AEDesc. If you access these fields directly there is a good chance that your code will crash in future versions of the operating system if the above structures change.

Extracting the needed information from the incoming AppleEvents

Required data - The essentials

Required data is just that. In order for you to process the AppleEvent directly there is usually a certain minimum amount of information necessary (such as a list of files when you receive an openDocument event). To get the required parameters, you first ask the system how many there are, then get them one at a time by indexing through the list.

Direct parameters

The direct parameters are the most common kind of required parameter. After receiving an AppleEvent you get all the direct parameters with the call

theErr = AEGetParamDesc(&theAppleEvent, keyDirectObject, typeAEList, 
&docList);

In the above statement we used the keyword keyDirectObject so the system returns any direct objects that it has, and we also request that these objects be returned to us in an AEDescList (typeAEList) so that the returned descriptor (docList) is a list containing all the direct objects in the event. To determine how many items are in the list call:

theErr = AECountItems(docList,n);

where the function returns n, the number of items in the list. You can then retrieve them one at time using either:

FUNCTION AEGetNthPtr(
 theAEDescList: AEDescList;
 index: LONGINT;
 desiredType: DescType;
 VAR theAEKeyword: AEKeyword;
 VAR typeCode: DescType;
 dataPtr: Ptr;
 maximumSize: Size;
 VAR actualSize: Size): OSErr;

or

FUNCTION AEGetNthDesc(
 theAEDescList: AEDescList;
 index: LONGINT;
 desiredType: DescType;
 VAR theAEKeyword: AEKeyword;
 VAR result: AEDesc): OSErr;

Another nice flexibility about the new system is that you can retrieve your data one of two different ways. If you don’t know how the data will be stored in the descriptor block, use the calls with the “Desc” suffix. These descriptor records will be returned as handles to the data, which you dispose of when you are through. If you know the particular form of the data, and have a variable set aside for it, you can have the operating system store it right into your data structure by using those calls with the “Ptr” suffix. When calling for the data using either of these forms you specify the type of data you would like returned. The operating system will then convert whatever data the sending program passed into the form you requested (if at all possible). If it cannot do the conversion you will be informed via an error returned by the call.

Additional parameters:

Optional - you supply the defaults

Optional parameters consists of information for which you have default values. An example might be the number of copies of a document that the sender wants to print, with your default being 1, just in case this value isn’t supplied.

Did you miss anything? - unknown required parameters

After you have received all the required and optional parameters that you know about, you should check if there are any more direct parameters left. If the system tells you that other descriptors are still waiting to be read then something is drastically wrong. Either you are not processing this event correctly or someone sent you some bum information. In either case we will not process this event and let the system return an error to the sending application.

Replying to an AppleEvent

Many AppleEvents request that you return some information to them in the supplied replyAppleEvent. This AppleEvent is a basic shell, to which you add the requested data. To add descriptors to the reply use the functions AEPutParamDesc() or AEPutParamPtr().

Create the descriptors by passing your data to the function:

FUNCTION AECreateDesc(
 typeCode: DescType;
 dataPtr: Ptr;
 dataSize: Size;
 VAR result: AEDesc): OSErr;

then add the descriptors to the reply with:

FUNCTION AEPutParamDesc(
 theAppleEvent: AppleEvent;
 theAEKeyword: AEKeyword;
 theAEDesc: AEDesc): OSErr;

If the data is stored in a program variable you can add it directly to your program with the call:

FUNCTION AEPutParamPtr(
 theAppleEvent: AppleEvent;
 theAEKeyword: AEKeyword;
 typeCode: DescType; 
 dataPtr: Ptr; 
 dataSize: Size): OSErr;

This process will be illucidated further in the next section. When you exit from your handler, the system will send give the modified reply AppleEvent back to the sending program.

Talking to yourself (sending AppleEvents)

Before we can create and send an AppleEvent, we first have to know who we are planning to send it to. We can send AppleEvents to ourselves (such as when the user issues a menu command), send them to other programs on our machine (local events), or send them out to a program running somewhere else on the network. For now we will start with the simplest case and send AppleEvents to ourself. By going through this extra step of abstraction we have effectively separated the user interface from our program’s engine. This may not sound like much, but imagine running a powerful engine like Mathematica, HiQ, or 4th Dimension remotely. Instead of running the program on your machine, imagine the actual engine running on the latest and greatest 100 MHz 68050 based machine down the hall. Another example near and dear to a programmer’s heart would be to using a nifty new editor like ACIUS’s Object Master, and sending AppleEvents to Think C or Think Pascal to compile, link, and run the program. Apple has recently announced that by using this mechanism your program can automatically be recorded by the forthcoming AppleScript®. Separating the user interface from the program’s engine suddenly has a lot of advantages!

Figure out who your going to send it to:

The fastest method used to send an event to ourselves is use our program’s own process serial number. A process is the Macintosh equivalent of identifying a program that is currently running. Under Multifinder, each application that appears in the “About this Macintosh ” dialog box, is a separate process. The system provides the constant kCurrentProcess, which we can use to create a special kind of descriptor describing the target for the event (ourself). This procedure will create the AEAddress descriptor we will later use when we create our AppleEvent.

ProcessSerialNumber = RECORD
 highLongOfPSN: LONGINT;
 lowLongOfPSN: LONGINT;
 END;

ProcessSerialNum myProcessSerialNum;
AEAddress myTarget;

myProcessSerialNum.highLongOfPSN = 0;
myProcessSerialNum.lowLongOfPSN = kCurrentProcess;
 theErr = AECreateDesc(
 typeProcessSerialNum,
 &myProcessSerialNum,
 sizeof(myProcessSerialNum),
 &myTarget);

Create the AppleEvent

Now that we know who we are sending the event to, we can create the basic shell of our AppleEvent. The function AECreateAppleEvent is used to create a new Apple event of the given class and ID.

FUNCTION AECreateAppleEvent(
 theAEEventClass: AEEventClass;
 theAEEventID: AEEventID;
 target: AEAddressDesc;
 returnID: INTEGER;
 transactionID: LONGINT;
 VAR result: AppleEvent): OSErr;

The event class is a constant such as kCoreEventClass (‘aevt’) that describes a group of events, usually organized by functionality. The class ID is the specific activity we want the event to do, such as kQuitApplication (‘quit’) or kOpenDocument (‘odoc’). The constants kAutoGenerateReturnID tells the system to automatically generate a unique return ID, so if we receive a return reply via the event loop we can identify it. If we wanted to supply our own return ID we would substitute it here. The final constant kAnyTransactionID, tells the system to generate a new transaction id number. If we want to send a series of AppleEvents that are all part of the same master plan, they would use a common transaction ID so the receiving application would know they are associated with each other (and could block out AppleEvents that do not have the same transaction ID if it wanted to). In this case, we might want to save the automatically generated transaction ID we had the system create for us, then use it in subsequent AppleEvents that are part of the same group of transactions.

We now have created the basic shell for our AppleEvents. In the case of simple AppleEvents like ‘aevt’ or ‘quit’, which don’t need any other data associated with them, we are done. If there is more data that needs to be included, we can add it to the newly create AppleEvent with the commands

AEPutParamPtr

or

AEPutParamDesc

We create the data or descriptor just like we created our target address descriptor (which is just another descriptor). If we have the data in a variable or buffer, we can send it using the AEPutParmPtr call. If we have a descriptor we want to create, we use the same AECreateDesc call we used before. We can also create a copy of another descriptor that we have (just like a xerox copy) then add that in, using the AEDuplicateDesc procedure.

Send the Event

To send the event we need to specify two additional pieces of information: how long we want to wait for a rely and if the receiving program is allowed to interact with the user. We can elect to specify a fixed timeout, use the default value (about a minute), or wait forever (dangerous!); unless you know for sure that the program will respond, its very dangerous to totally give up control of your program. If the receiving program needs more time, it can always request it from the system, so this option is rarely needed.

The interaction constant tells the system, what kind of interaction is allowed on the receiving end. If you tell receiving program to put up a dialog box, but you are sending to an unattended machine, then the target will sit forever until someone comes along and enters the needed information. You control how the target responds by specifying if the target can signal to the user via the notification manager, use it’s own defaults, or force itself to the front of your desktop (very rude).

The form of the call is:

FUNCTION AESend(theAppleEvent: AppleEvent;
 VAR reply: AppleEvent;
 sendMode: AESendMode;
 sendPriority: AESendPriority;
 timeOutInTicks: LONGINT;
 idleProc: IdleProcPtr;
 filterProc: EventFilterProcPtr): OSErr;

with:

theAppleEvent AppleEvent we are sending

sendMode Requested user interaction for the target

sendPriority Normal priority or shove your way to the front

of the line

timeOutInTicks How long is the client (sender) willing to wait for a

reply

idleProc Procedure to handle update, null, OSEvts, and

activate events while we wait for a reply

filterProc procedure to filter high level events to handle

those of interest to you while you are waiting

for this AESend command to finish

Clean up anything you created

Sending your AppleEvent is just like sending a fax. After you send it you still have all your original data, and the recipient has their own copy. When you are done, you have to throw out your copy, and it’s up the recipient to throw out theirs. Any descriptors created or copied by your program needs to be disposed of with the function

FUNCTION AEDisposeDesc(VAR theAEDesc: AEDesc): OSErr;

Any descriptors created with or returned by the following calls needs to be disposed of by your program.

AECoerseDesc AECoerseList

AECreateAppleEvent AECreateDesc

AECreateList AEDuplicate Desc

AEGetAttributeDesc AEGetKeyDesc

AEGetNthDesc AEGetParamDesc

Meeting your friends (sending AppleEvents to other programs)

Who’s available

We have used the constant kCurrentProcess to create a target ID so we can send the AppleEvents to ourselves. If the target is on our own machine (local) we can use a process serial number, or the signature (creator) of the application. To create a targetID for the Finder we use an AECreateDesc call of the form:

OSType  theSignature = ‘MACS’;
theErr = AECreateDesc(typeAppleSignature, &theSignature,       
 sizeof(theSignature), &theTargetID);

To create a targetID for Hypercard you would similarly set theSignature to ‘WILD’.

The PPC Browser - let the user choose

On your own machine

We could use a process serial number or signature on our own machine, but across the network we need to either a sessionID or targetID. If we know where the program located, we can use stored this information to create a targetID, but if we want to let the user choose the target we can use the new PPC browser; a standard dialog box that lets the user select any currently running program on any machine in any zone. The PPC Browser works like the old SFGetFile dialog box. You pass it a couple of data structures: a LocationRec and the PortInfoRec. The location record is used to store network information, such as the zone, machine name, etc. The PortInfoRec actually contains two important pieces of data: authorization, a boolean value that determines if the target should OK connecting with this application, and another complete record structure the PPCPortRec (don’t confuse this with the PortInfoRec!) The PPCPortRec holds information such as the name of the application, what language script that string is using (so you know that the meaningless ASCII dribble is really means something if you use a Hebrew font). The last field in the PPCPortRec contains either a process name or the creator and type codes of the application.

TYPE PortInfoRec =
 RECORD
 filler1: SignedByte;
 {space holder}
 authRequired: Boolean;
 {authentication required}
 name:  PPCPortRec {port name}
 END;

TYPE PPCPortRec = 
 RECORD
 nameScript: ScriptCode;{script identifier}
 name: Str32;  {port name in program } 
 { linking dialog box}
 portKindSelector: PPCPortKinds;
 {general category ofapplication}
 CASE PPCPortKinds OF  
 ppcByString: 
 (portTypeStr: Str32);
 ppcByCreatorAndType: 
 (portCreator: OSType;
 portType: OSType)   
 END;

Don’t worry about filling in these fields, the PPCBrowser will do that for you. You call the browser with the procedure:

FUNCTION PPCBrowser(
 prompt: Str255;
 applListLabel: Str255;
 defaultSpecified: BOOLEAN;
 VAR theLocation: LocationNameRec;
 VAR thePortInfo: PortInfoRec;
 portFilter: PPCFilterProcPtr;
 theLocNBPType: Str32): OSErr;

For our example we need to only supply the prompt string and collect the results after the user selects their choice.

theErr = PPCBroswer(
 &prompt,nil,false,
 &theLocationRec,
 &thePortInfoRec, nil, nil);

To build your own targetID you just copy the PPCPortRec into the name field of your targetID, and the returned LocationRec into the location field:

Type  TargetID = RECORD
 sessionID: LONGINT;
 name: PPCPortRec;
 location: LocationNameRec;
 recvrName: PPCPortRec;
 END;

BlockMove(&thePortInfoRec.name, &myTargetID.name, 
 sizeof(PPCPortRec));
myTarget.location = theLocationRec;

The program shell that follows actually creates an AEAddressDesc three different ways, from a kCurrentProcess, from a signature, and from a targetID, so you can see all this code in action.

Out over the network

After you create your AEAddressDesc and send an event to the target, the AESend command fills in the sessionID in the AEAddressDesc’s data structure. If you retrieve that id, and use it in subsequent calls, you will gain a substantial speed increase when sending your events. This is especially true over a network.

Twiddling your thumbs (Idle Functions)

If you tell AESend that you will wait for a reply, you can optionally supply a pointer to an idle procedure. This procedure will be called by the operating system, and lets your operate while the event is still pending. It can be used to spin a cursor, process other Apple Events, detect if the user want’s your attention, or anything else your imagination allows. Usually these routines handle update events, operating system events, null events, and activate events.

Conclusions

The sample program and this short tutorial should get you started sending and receiving AppleEvents. The AppleEvents menu send commands to AE101 via AppleEvents, illustrating the imperceptible difference to the user, while effectively isolating your interface and programing engine. These events will unleash a new kind of power for the user, while also making your job as a programers a lot easier. Instead of implementing your own spell checker or macro program, you can let the user purchase the program they want. Userland’s Frontier® scripting language (and Apple’s - eventually), will let programs send information and request services from each other. Hopefully this example can provide the framework you need to start implementing AppleEvents for all your menu commands so that other programs can quickly begin sending them instructions. Programs that interpret text macro’s, such as Hypercard and spreadsheets, are a natural for easy scripting. When products such as Userland’s Frontier ship users will be clamoring for more and unique ways to control your programs.

Step by Step Instructions

Setup

A) Turn on Sharing so you can test your program

B) Create a SIZE resource (ID = -1)

C) Check Gestalt

1) Are AppleEvents available

2) Is the PPCToolbox available, initialize the toolbox if needed

D) Set the AESetInteractionAllowed(), or use the default values

E) Register your AppleEvent handlers

F) Always call AEInteractWithUser before displaying alerts or dialog boxes

Receiving AppleEvents

A) Patch your main event loop

1) Check for OSEvt

2) Check for kHighLevelEvent

a) Call AEProcessEvent to pass the AppleEvent to the dispatcher

B) Write your AppleEvent handlers

1) Get any direct parameters, usually by requesting a list (keyDirectObject)

2) If you have a list, count the number of descriptors that it contains

3) Get the data as either descriptors or pointer, by indexing through the list

4) Check if there are any missing parameters (keyMissedKeywordAttr) and

return an error if there all

5) Get any optional data you need either as a descriptor or pointer, use your own defaults if you

don’t receive any specific instructions from the sender

6) Call your own code to execute the event

7) Create a reply if appropriate using the supplied reply AppleEvent. Let the system and sender

decide if they use the information.

8) Return an error as a result of your handler if you cannot handle the event, or you find

missing parameters

Sending AppleEvents

A) Create an address descriptor (AEAddressDesc)

1) Select the target by one of the following means:

a) Use the PPCBrowser dialog box to have the user pick

b) Use the IPCListPorts to have your program select a running program

c) Use kCurrentProcess if it is your own application

d) If you know an application is running locally, or you saved information previously,

you can use that

2) Create the address descriptor (AECreateDesc) from:

a) targetID

i) roll your own from information supplied by the PPCBrowser or IPCListPorts

ii) after you send the first event get the session number and use that to send future

events, since it speeds up connections considerably

b) sessionID - if you know it, use it, especially if sending over a network

c) processSerialNumber - not valid over a network. Use kCurrentProcess to get a 10 to 15% speedup in sending events directly to yourself.

d) signatureID- not valid over a network. A simple way to send events to a program you know is running on the local machine.

B) Create the AppleEvent

C) Add any data descriptors to the AppleEvent

D) Send the event

E) Note if it was received correctly. If you need to save information (such as

a sessionID or transaction number) get it now.

F) Extract any information you need from the reply AppleEvent (if you requested one)

Process OSEvt

A) Check the most significant byte of the message to see what kind of event it is

B) suspend/resume event

1) Check bit 0 of the message to see what kind of event it is

2) suspend event

a) convert your private scrap to the clipboard

b) call your Deactivate routine

c) set any globals to reflect that your program is now in the background

3) resume event

a) if bit 1 of the message is set, convert the clipboard to your private scrap

b) call your Activate routine

c) set any globals to reflect that your program is now in the background

C) mouseMovedEvent

1) Check where which region your mouse is in

2) Adjust your cursor

3) Redefine the mouse moved region if appropriate and pass it to WaitNextEvent

Reference:

Inside Macintosh, Vol. VI,, Apple Computer Inc., Addison Wesley Publishers 1991

AppleScript is a trademark of Apple Computer Inc.

          /* 4  */

// AE101
// Copyright 1993 
// by Jeffrey B. Kane, MD
/* put compile time flags here */

/* includes */
#include <Script.h>
#include <GestaltEqu.h>
#include <AppleEvents.h>
#include <PPCToolBox.h>

/* define */
#define true1
#define false    0
#define boolean  int
#define SetRect(aRect,l,t,b,r) \
{(aRect)->top = (t); \
(aRect)->left = (l); \
(aRect)->bottom = (b); \
(aRect)->right = (r);}

/* constants */
#define kAppleMenuID 128  
/* Apple Menu Resource ID */
#define kFileMenuID  129  
/* File Menu Resource ID */
#define kEditMenuID  130  
/* Edit Menu Resource ID */
#define kSpecialMenuID  131 
/* Special Menu Resource ID */
#define kAboutBoxID128  
/* About box alert Resource ID */
#define kErrorAlertID129 
/* Error Alert Box */
#define  kWindTemplateID  128 
/* template for new windows */
#define cAboutItem 1
#define cNewItem 1
#define cOpenItem  2
#define cCloseItem 3
#define cQuitItem  5
#define cSendQuitItem1
#define cSendNewItem 2
#define cSendCloseItem    3
#define cSendAboutItem    4
#define cSendAboutToFinder6
#define cSendEmptyTrashToFinder  7
#define kAENewWindow ‘NEW ‘
#define kAECloseWindow    ‘clos’
#define kAEAbout ‘abou’
#define kAEEmptyTrash‘empt’
#define kFndrEventClass   ‘FNDR’

/* prototypes */
void  InitMac(int numMasters);
void  Loop(void);
void  DoMouseDown(EventRecord* theEvent);
void  DoCloseWindow(void);
void  DoNewWindow(void);
void  SetUpMenus(void);
void  DoMenu(EventRecord* theEvent, 
 WindowPtr whichWindow, long selection);
void  doAboutBox(void);
void  DoKey(EventRecord* theEvent);
void  DoUpdate(EventRecord* theEvent);
void  DoOSEvent(EventRecord* theEvent);
void  FixCursor(void);
extern  pascal OSErr doAEQuit(AppleEvent* theAppleEvent, 
 AppleEvent* theReply, long handlerRefcon);
extern  pascal OSErr doAENew(AppleEvent* theAppleEvent, 
 AppleEvent* theReply, long handlerRefcon);
extern  pascal OSErr doAEClose(AppleEvent* theAppleEvent, 
 AppleEvent* theReply, long handlerRefcon);
extern  pascal OSErr doAEAbout(AppleEvent* theAppleEvent, 
 AppleEvent* theReply, long handlerRefcon);
OSErr MissedRequiredParameters(AppleEvent* theAppleEvent);
void ConnectToFinder(void);
void DisconnectFromFinder(void);
boolean BitTest(long* aValue,int Bit);
void SetUpperCorner(Rect* theRect, Point aPt);
void GetUpperCorner(Rect* theRect, Point *aPt);
void SetLowerCorner(Rect* theRect, Point aPt);
void GetLowerCorner(Rect* theRect, Point *aPt);
void SendQuit(void); 
/* sending an AE Quit */
void SendNew(void); 
/* sending an AE New */
void SendClose(void); 
/* sending an AE Close */
void SendAbout(void);
void SendAboutToFinder(void);
void SendEmptyTrashToFinder(void);
void SendEvent(AEEventClass theAEEventClass, 
 AEEventID theAEEventID, 
 AEAddressDesc* theTargetAddressPtr);
void SendFndrEvent(AEEventID theAEEventID);
void SendCoreEvent(AEEventID theAEEventID);
void  ErrorAlert(ConstStr255Param theString, OSErr theErr);
void CopyPStr(char* fromStr, char* toStr);

/* globals */
boolean gHasColor= false; 
/* does this machine support 32 bit quickdraw? */
boolean gProcessor = 0;
/* what microprocessor is this? */
boolean gHasAppleEvents = false;
/* do we support IAC (Apple Events)? */
boolean gFinished= false; 
/* did the user want to quit? */
boolean gInForeground = true;
/* are we running in the foreground */
/* or background?  */
CursHandle gPlusCursor = nil;
/* data for the plus cursor */
boolean  gHasPPCToolbox = false;
RgnHandle gMainContentRgn = nil;
RgnHandle gAllElseRgn = nil;
RgnHandle gMouseMovedRgn = nil;

ProcessSerialNumber gFinderPSN = {0L,0L};
TargetID gFinderTargetID;

MenuHandle AppleMenu = nil;
MenuHandle FileMenu = nil;
MenuHandle SpecialMenu = nil;
MenuHandle EditMenu = nil;

WindowPtr gWindow = nil;
WindowRecord gWStorage;

/* ================================= */
boolean BitTest(long* aValue, int Bit)
{
 asm {
 clr.l  D0
 clr.l  D2
 move.l aValue,A0
 move.l (A0),D1
 move.w Bit,D2
 btst.l D2,D1
 beq.s  @1
 moveq.l#1,D0
 @1
 }

}
/* actual code */

/* ================================= */
void CopyPStr(char* fromStr, char* toStr)
{
 short  i = 0;

 for (i=0; i <= (fromStr[0]); i++) 
 {toStr[i] = fromStr[i];}
} /* CopyPStr */

/* ================================= */
void ConnectToFinder(void)
{
 OSErr  theErr;
 PortInfoRecthePortInfo;
 LocationNameRec theLocationNameRec;
 
/* find out who’s available */
/* (kinda like dating)    */
 thePortInfo.authRequired = false;
 thePortInfo.name.nameScript = smRoman; /* english */
 thePortInfo.name.portKindSelector = ppcByString;
 CopyPStr((char*) ”\pOscar”, (char*) thePortInfo.name.name);

/* local machine */
 theLocationNameRec.locationKindSelector = ppcNoLocation; 
// since we just stated we are on our 
// own machine, we don’t need to fill 
// anything else out 

 theErr = PPCBrowser(
 (ConstStr255Param)”\pLocate the Finder and click on it”,
   nil, false, /* we are not Specifing a default to find */
   &theLocationNameRec, &thePortInfo, nil, nil);   
/* use the default */
                        
/* construct a targetID from the data provided */
    gFinderTargetID.sessionID = 0; 
/* we haven’t opened a session yet */
    
/* the PPCBrowser’s PPCPortRec */
/* Returned */
 BlockMove(&(thePortInfo.name), &(gFinderTargetID.name), 
 sizeof(PPCPortRec));
 gFinderTargetID.location = theLocationNameRec; 
/* the PPCBrowser’s  */
/* LocationNameRec returned */
// the system will fill in the recvrName 
// and sesionID after we make a connection
// we can use them later to speed up AESend
 
 if (theErr != noErr) 
 ErrorAlert(
 (ConstStr255Param)”\pBrowser Result Bad: “,theErr);     
 else 
 { 
 // This is only valid because we are 
 // on a local machine, you have to  
 // use TragetID or SessionIDs if you 
 // are connecting out on the network 

 theErr = GetProcessSerialNumberFromPortName(
 (PPCPortPtr)(&(thePortInfo.name)),
 (ProcessSerialNumberPtr)(&gFinderPSN));
 if (theErr != noErr) { 
 ErrorAlert((ConstStr255Param)
 ”\pError getting Process Serial Number”,theErr);
 }
 } /* else */
 
 /* start communications */

} /* ConnectToFinder */

/* ================================= */
void  DisconnectFromFinder(void)
{
 OSErr  theErr;
 PPCClosePBRec   thePPCClosePBRec;
 PPCEndPBRecthePPCEndPBRec;
 
}/* DisconnectFromFinder */


/* ================================= */
void  InitMac(int numMasters)
{
 int    i;
 long   response = 0L;
 OSErr  theErr;
 PScrapStufftheScrapStuff;
 
 // set a few enviromental globals that 
 // will be handy later
 
 theErr = Gestalt(gestaltQuickdrawFeatures, &response);
 if (BitTest(&response, gestaltHasColor)) 
 gHasColor = true;
 
 theErr = Gestalt(gestaltProcessorType, &response);
 gProcessor = response;
 
 theErr = Gestalt(gestaltAppleEventsAttr, &response);
 if (BitTest(&response, gestaltAppleEventsPresent)) 
 gHasAppleEvents = true;
 
 // see if we need to initalize the PPC toolbox 

 theErr = Gestalt(gestaltPPCToolboxAttr, &response);
 if (theErr == noErr)
 {
 gHasPPCToolbox = true; 
// if no error was returned we have a PPC toolbox
 if (BitTest(&response, gestaltPPCSupportsRealTime) != 0)
 theErr = PPCInit(); 
// if this bit is not set, we need to intialize
 
 // You can add your own alert in 
 // response to the following PPC tests 
 if (BitTest(&response, gestaltPPCSupportsOutGoing) != 0);
 // Tell the User to turn on 
 // AppleTalk in the Chooser
 if (BitTest(&response, gestaltPPCSupportsIncoming) != 0);
 // Tell the User to activate 
 // file sharing or AppleTalk (in 
 // the chooser)
 
 }
 gPlusCursor = GetCursor(plusCursor);
 if (gPlusCursor) 
 {
 MoveHHi((Handle)gPlusCursor);
 HLock((Handle)gPlusCursor);
 }
 
 /* now initialize the program itself */
 InitGraf(&thePort);
 InitFonts();
 InitMenus();
 InitWindows();
 InitDialogs(nil);
 TEInit();
 InitCursor();
 for (i=1; i > numMasters; i++)
 MoreMasters();
 
 /* if we have IAC capability, install the AE handler */
 if (gHasAppleEvents)
 {
 theErr = AEInstallEventHandler(kCoreEventClass,
 kAEQuitApplication, (ProcPtr)DoAEQuit, 0L, false);
                                   
   theErr = AEInstallEventHandler(kCoreEventClass,
 kAENewWindow, (ProcPtr)DoAENew, 0L, false);
                                   
   theErr = AEInstallEventHandler(kCoreEventClass, 
 kAECloseWindow, (ProcPtr)DoAEClose, 0L, false);
                                   
   theErr = AEInstallEventHandler(kCoreEventClass, 
 kAEAbout, (ProcPtr)DoAEAbout, 0L, false);
 } /* gHasAppleEvents */
 
 /* create the regions to check our mouse against */
 gMainContentRgn = NewRgn();
 gAllElseRgn = NewRgn();  
 CopyRgn(GetGrayRgn(), gMainContentRgn);
 /* we are initially in an empty desktop */
 gMouseMovedRgn = gAllElseRgn; 

 /* Deal with the scrap */
 theScrapStuff = InfoScrap();
 theErr = ZeroScrap();
 {
 char tempScrap[] = “More Stuff”;
 PicHandle tempPict = nil;
 /* off screen */
 Rect  tempRect = { -5000,-5000,-4800,-4800 }; 
 long scrapSize = 0;
 Ptr thePtr = nil;
 CWindowRecord theWStorage;
 WindowPtr tempWind = nil;
 
 OffsetRect(&tempRect, 5050, 5050);
 tempWind = NewCWindow(&theWStorage, &tempRect, &”\p”, 
 true, plainDBox, (Ptr)(-1), false, 0);
 SetPort(tempWind);
 ClipRect(&(tempWind->portRect));
 ForeColor(greenColor);
 tempRect.left = 0;
 tempRect.top = 0;
 tempRect.right = 100;
 tempRect.bottom = 20;
 tempPict = OpenPicture(&tempRect);
 TextSize(14);
 TextFace(bold | italic);
 TextBox(tempScrap, sizeof(tempScrap), 
 &tempRect, teJustCenter);
 TextFace(0);
 TextSize(12);
 ClosePicture();
 HLock((Handle) tempPict);
 scrapSize = GetHandleSize((Handle) tempPict);
 thePtr = (Ptr) (*tempPict); 
 theErr = PutScrap(scrapSize, ’PICT’, thePtr);
 HUnlock((Handle)tempPict);
 KillPicture(tempPict);
 CloseWindow(tempWind);
 }
} /* InitMac */

/* ================================= */
OSErr MissedRequiredParameters(AppleEvent* theAppleEvent)
{
 /* This is a general use routine to see if a handler */
 /* missed any required parameters */
 
 OSErr  theErr;
 Size   actualSize;
 DescType returnedType;
 
 /* note that the buffer is nil, and the size we */
 /* request is zero */
 theErr = AEGetAttributePtr(theAppleEvent, 
 keyMissedKeywordAttr, typeWildCard, &returnedType, 
 nil,0,&actualSize);
 
 /* we are ok “if” we don’t have any more parameters! */
 if (theErr == errAEDescNotFound) 
 return noErr;   /* desired results */
 else if (theErr == noErr) 
 return errAEEventNotHandled; /* more stuff found */
 else
 return theErr;  /* something else happened */
} /* MissedRequiredParameters */

/* ================================= */
extern pascal OSErr doAEQuit(AppleEvent* theAppleEvent, 
 AppleEvent* theReply, long handlerRefcon)
{
 /* Quit Handler for AEs */
 OSErr  result = 0;

 /* make sure we have all our required parameters */
 
 result = MissedRequiredParameters(theAppleEvent);
 if (result == noErr)
 {
 /* NOTE: if you want to call a routine to */
 /* double check with the user do it here */
 gFinished = true;
 return result;
 } /* if */
} /* doAEQuit */

/* ================================= */
extern pascal OSErr doAENew(AppleEvent* theAppleEvent, 
 AppleEvent* theReply, long handlerRefcon)
{
 /* New window handler for AEs */
 OSErr  result = 0;

 /* make sure we have all our required parameters */
 
 result = MissedRequiredParameters(theAppleEvent);
 if (result == noErr)
 {
 /* DoStuffHere */
 if (gWindow == nil) 
 {
 DoNewWindow();  
 }
 
 return result;
 } /* if */
} /* doAENew */

/* ================================= */
extern pascal OSErr doAEClose(AppleEvent* theAppleEvent, 
 AppleEvent* theReply, long handlerRefcon)
{
 /* Close window handler for AEs */
 OSErr  result = 0;

 /* make sure we have all our required parameters */
 
 result = MissedRequiredParameters(theAppleEvent);
 if (result == noErr)
 {
 /* DoStuffHere */
 if (gWindow != nil) 
 DoCloseWindow();
 
 return result;
 } /* if */
} /* doAEClose */

/* ================================= */
extern pascal OSErr doAEAbout(AppleEvent* theAppleEvent, 
 AppleEvent* theReply, long handlerRefcon)
{
 doAboutBox();
} /* doAEAbout */

/* ================================= */
void  DoCloseWindow(void)
{
 if (gWindow != nil)
 {
 CloseWindow(gWindow);
 gWindow = nil;
 DisableItem(FileMenu, cCloseItem);
 EnableItem(FileMenu, cNewItem);
 
 /* this is only because we are sending from */
 /* out own application, normally the */
 /* sending app handles this stuff */
 if (gHasAppleEvents)
 {
 DisableItem(SpecialMenu, cSendCloseItem);
 EnableItem(SpecialMenu, cSendNewItem);
 }
 
 FixCursor();
 } /* if */
} /* DoCloseWindow */

/* ================================= */
void DoMenu(EventRecord* theEvent, 
 WindowPtr whichWindow, long selection)
{
 short  theMenu;
 short  theItem;
 static Str255 theName;
 short  trash;
 
 theMenu = HiWord(selection);
 theItem = LoWord(selection);
 
 switch (theMenu) {
 case kAppleMenuID:
 { if (theItem == cAboutItem)
 doAboutBox();
 else {
 GetItem(AppleMenu,theItem,theName);
 trash = OpenDeskAcc(theName);
 }
 break;
 }
 case kFileMenuID: 
 switch (theItem) {
 case cNewItem:
 DoNewWindow();
 break;
 case cCloseItem:
 DoCloseWindow();
 break;
 case cQuitItem:
 gFinished = true;
 }
 break;
 case kEditMenuID:
 break;
 case kSpecialMenuID:
 switch (theItem) {
 case cSendQuitItem:
 SendQuit();
 break;
 case cSendNewItem:
 SendNew();
 break;
 case cSendCloseItem:
 SendClose();
 break;
 case cSendAboutItem:
 SendAbout();
 break;
 case cSendAboutToFinder:
 SendAboutToFinder();
 break;
 case cSendEmptyTrashToFinder:
 SendEmptyTrashToFinder();
 break;
 }
 break;
 
 } /* switch */
 HiliteMenu(0);
}/* DoMenu */

/* ================================= */
void DoMouseDown(EventRecord* theEvent)
{
 WindowPtrwhichWindow;
 long   selection;
 Rect   dragLimit;
 GDHandle theGDHandle   = nil;
 Rect   tempRect;
 GrafPtroldPort = nil;
 Point  thePt;

 /* calculate a limit for dragging on this mouse click */
 if (gHasColor) {
 theGDHandle = GetGDevice();
 dragLimit = (**theGDHandle).gdRect;
 } /* if */
 else {
 dragLimit = screenBits.bounds;
 } /* else */
 
 switch (FindWindow(theEvent->where,&whichWindow)) {
 case inDesk:
 break;
 case inMenuBar:
 selection = MenuSelect(theEvent->where);
 DoMenu(theEvent, whichWindow, selection);
 break;
 case inSysWindow:
 SystemEvent(theEvent);
 break;
 case inContent:
 {
 /* Flash the rectangle, just to show we are there */
 GetPort(&oldPort);
 SetPort(whichWindow);
 tempRect = whichWindow->portRect;
 tempRect.right -= 16;
 tempRect.bottom -= 16;
 thePt = theEvent->where;
 GlobalToLocal(&thePt);
 if (PtInRect(thePt,&tempRect))
 {
 InvertRect(&tempRect);
 InvertRect(&tempRect);
 }
 SetPort(oldPort);
 }
 break;
 case inDrag: 
 InsetRect(&dragLimit,4,4);
 dragLimit.top = dragLimit.top + GetMBarHeight();
 DragWindow(whichWindow, theEvent->where, &dragLimit);
 FixCursor();
 break;
 case inGrow:
 {
 tempRect.top = 40;
 tempRect.left = 40;
 tempRect.bottom = (4 + dragLimit.bottom-dragLimit.top);
 tempRect.right = (dragLimit.right-dragLimit.left + 4);
 selection = GrowWindow(whichWindow, 
 theEvent->where, &tempRect);
 SizeWindow(whichWindow, LoWord(selection), 
 HiWord(selection), true);
 
 tempRect = whichWindow->portRect;
 GetPort(&oldPort);
 SetPort(whichWindow);
 InvalRect(&tempRect);
 SetPort(oldPort);
 FixCursor();
 }
 break;
 
 case inGoAway:
 if (TrackGoAway(whichWindow, theEvent->where))
 DoCloseWindow();
 break;
 case inZoomIn:
 if (TrackBox(whichWindow, theEvent->where, inZoomIn)) 
 {
 GetPort(&oldPort);
 SetPort(whichWindow);
 EraseRect(&(whichWindow->portRect));
 ZoomWindow(whichWindow, inZoomIn, true);
 SetPort(oldPort);
 FixCursor();
 }
 break;
 case inZoomOut:
 if (TrackBox(whichWindow, theEvent->where, inZoomOut)) 
 {
 GetPort(&oldPort);
 SetPort(whichWindow);
 EraseRect(&(whichWindow->portRect));
 ZoomWindow(whichWindow, inZoomOut, true);
 SetPort(oldPort);
 FixCursor();
 }
 break;
 } /* switch FindWindow */
} /* DoMouseDown */

/* ================================= */
void DoKey(EventRecord* theEvent)
{
 char   theKey;
 long   selection;
 
 /* is the cmd key down? */
 theKey = charCodeMask & theEvent->message;
 if (cmdKey & theEvent->modifiers) {
 selection = MenuKey(theKey);
 DoMenu(theEvent, FrontWindow(), selection);
 } /* if */
 else
 ;
} /* DoKey */

/* ================================= */
void  DoNewWindow(void)
{
 Str255 theName = “\p<Untitled>”;
 
 gWindow = GetNewWindow(kWindTemplateID, 
 &gWStorage, (WindowPtr)-1L);
 SetWTitle(gWindow, theName);
 ShowWindow(gWindow);
 DisableItem(FileMenu, cNewItem);
 EnableItem(FileMenu, cCloseItem);
 /* this is only because we are sending from */
 /* out own application, normally the */
 /* sending app handles this stuff */
 if (gHasAppleEvents)
 {
 EnableItem(SpecialMenu, cSendCloseItem);
 DisableItem(SpecialMenu, cSendNewItem);
 }
 
 FixCursor();

}/* DoNewWindow */

/* ================================= */
void DoUpdate(EventRecord* theEvent)
{
 WindowPtr  whichWindow;
 GrafPtroldPort;
 Rect   contentRect;
 
 whichWindow = (WindowPtr)theEvent->message;
 GetPort(&oldPort);
 BeginUpdate(whichWindow);
 SetPort(whichWindow);
 
 /* do the real drawing here */
 
 PenSize(3,3);
 contentRect = whichWindow->portRect;
 /* make sure we redraw the scroll bars */
 EraseRect(&contentRect);
 /* now only draw in the real content area */
 contentRect.right -= 18;
 contentRect.bottom -= 18;
 DrawGrowIcon(whichWindow);
 ForeColor(redColor);
 MoveTo(contentRect.left, contentRect.top);
 /* is there a C equiv to “with” ? */
 LineTo(contentRect.right, contentRect.bottom);
 MoveTo(contentRect.right, contentRect.top);
 LineTo(contentRect.left, contentRect.bottom);
 
 /* clean up */
 ForeColor(blackColor);
 PenNormal();
 EndUpdate(whichWindow);
 SetPort(oldPort);

} /* DoUpdate */

/* ================================= */
void  DoOSEvent(EventRecord* theEvent)
{
 int  highByte;
 
 /* sign extended shift */
 highByte = theEvent->message >> 24;
 /* mask off the high bits */
 highByte &= 0x00FF;
 
 if (highByte == suspendResumeMessage)
 {
 if (theEvent->message & resumeFlag)
 { /* resuming */
 if (theEvent->message & convertClipboardFlag) {;}
 /* if we wanted the clipboard, we would */
 /* grab it here */
 else {;}
 /* resume without converting */
 
 /* save time and do your activate stuff from here */
 gInForeground = true;
 }
 else /* suspend event */
 {
 gInForeground = false;

 /* convert the clipboard here save time */
 /* and do your deactivate stuff from here */
 }
 } /* suspend/resume event */
 else if (highByte == mouseMovedMessage)
 {
 /* check if we are still over the content */
 /* area of our window */
 if (PtInRgn(theEvent->where, gMouseMovedRgn))
 {
 /* we are still in the content region */
 ;
 }
 else /* recalculate the region */
 {
 FixCursor();
 }
 } /* mouse moved */
} /* DoOSEvent */

/* ================================= */
void  SetUpperCorner(Rect* theRect,Point aPt)
{
 theRect->top = aPt.v;
 theRect->left = aPt.h;
 
} /* SetUpperCorner */

/* ================================= */
void  GetUpperCorner(Rect* theRect, Point* aPt)
{
 aPt->h = theRect->left;
 aPt->v = theRect->top;
 
} /* GetUpperCorner */

/* ================================= */
void  GetLowerCorner(Rect* theRect, Point* aPt)
{
 aPt->h = theRect->right;
 aPt->v = theRect->bottom;

} /* SetLowerCorner */

/* ================================= */
void  SetLowerCorner(Rect* theRect, Point aPt)
{
 theRect->bottom = aPt.v;
 theRect->right = aPt.h;
 
} /* SetLowerCorner */

/* ================================= */
void  SendCoreEvent(AEEventID theAEEventID)
{
 ProcessSerialNumber theProcSerialNum;
 AEAddressDesc   theTargetAddress;
 OSErr  theErr;
  
   /* first create the target... me */
 theProcSerialNum.highLongOfPSN = 0;
 theProcSerialNum.lowLongOfPSN = kCurrentProcess;
 theErr = AECreateDesc(typeProcessSerialNumber, 
 (Ptr)&theProcSerialNum, sizeof(theProcSerialNum),
 &theTargetAddress);
 
 SendEvent(kCoreEventClass, theAEEventID, &theTargetAddress);
} /* SendCoreEvent */

/* ================================= */
void  SendFndrEvent(AEEventID theAEEventID)
{
 OSType FndrType = ‘MACS’;
 OSErr  theErr;
 AEAddressDesc   theTargetAddress;
 Str255 errMsg = 
 “\pProblem creating the target desc”;

 /* first create the target descriptor... the Finder */
     
 theErr = AECreateDesc(typeProcessSerialNumber, 
 (Ptr)&gFinderPSN, sizeof(gFinderPSN), &theTargetAddress);
 
// #ifdef NOBROWSER
 { /* if you wanted to use the FINDER’s application */
 /* signature instead of the PPC browser you could */
 /* use this code instead of the Finder’s process */
 /* serial number */
 OSType theSignature = ‘MACS’;
 
 theErr = AECreateDesc(typeApplSignature, (Ptr)&FndrType, 
 sizeof(FndrType), &theTargetAddress);
 if (theErr) 
 ErrorAlert((ConstStr255Param)
 ”\pCould not create target addr from Sig”, theErr);
 }
 
 { /* if you need to send out info over the network */
 /* this is how you do it */
 theErr = AECreateDesc(typeTargetID, (Ptr)&gFinderTargetID, 
 sizeof(gFinderTargetID), &theTargetAddress);
 }
// #endif

 if (theErr != noErr) 
 ErrorAlert((ConstStr255Param) &errMsg, theErr);
 if (theAEEventID == ‘empt’)
 SendEvent(kFndrEventClass, theAEEventID, 
 &theTargetAddress);
 if (theAEEventID == ‘abou’)
 SendEvent(kCoreEventClass, theAEEventID, 
 &theTargetAddress);
 
}

/* ================================= */
void  SendQuit(void)
{
 SendCoreEvent(kAEQuitApplication);
} /* SendQuit */

/* ================================= */
void SendNew(void)
{
 SendCoreEvent(kAENewWindow);
} /* SendNew */

/* ================================= */
void SendClose(void)
{
 SendCoreEvent(kAECloseWindow);
} /* SendClose */

/* ================================= */
void SendAbout(void)
{
 SendCoreEvent(kAEAbout);
} /* SendAbout */

/* ================================= */
void SendAboutToFinder(void)
{
 SendFndrEvent(kAEAbout);
} /* SendAboutToFinder */

/* ================================= */
void SendEmptyTrashToFinder(void)
{
 SendFndrEvent(kAEEmptyTrash);
}

/* ================================= */
void  SendEvent(AEEventClass theAEEventClass, 
 AEEventID theAEEventID, 
 AEAddressDesc* theTargetAddressPtr)
{
 OSErr  theErr = 0;
 AppleEvent theAppleEvent, theAEReply;
 Str255 ErrMsg = “\pSend Failed, Error #”;
 
 theErr = AECreateAppleEvent(theAEEventClass,theAEEventID,
 theTargetAddressPtr,kAutoGenerateReturnID,
 kAnyTransactionID,&theAppleEvent);
 
 /* no direct parameters so this one is easy */
 
 /* send it! */
 theErr = AESend(&theAppleEvent, &theAEReply, kAENoReply, 
 kAENormalPriority, kAEDefaultTimeout, nil, nil);
 
 /* an IMPORTANT note: we are using kAEDefaultTimeout, */
 /* but if we chose kWaitReply and we are sending to */ 
 /* ourselves, we would be waiting a long long time */
 /* (like forever) since we can’t reply until we check */
 /* the event loop, and we don’t check the event loop */
 /* until we get a reply.  Notice a problem?  Use a */
 /* direct send instead */
 
 if (theErr != noErr) 
 ErrorAlert((ConstStr255Param) &ErrMsg, theErr);
} /* SendCoreEvent */

/* ================================= */
void ErrorAlert(ConstStr255Param theString, OSErr theErr)
{
 Str255 *theErrStr, *theErrNumStr;
 Str255 stringStorage1, stringStorage2;
 
 theErrStr = &stringStorage1;
 theErrNumStr = &stringStorage2;

 theErrStr = &”\p”;
 theErrNumStr = &”\p”;
 
 switch (theErr)
 {
 case (-609):
 theErrStr =  &”\pconnectionInvalid”;
 break;
 case (-910):
 theErrStr = 
 &”\pport is already open, maybe in another application”;
 break;
 case (-913):
 theErrStr = &”\pPPCPortRec  is bad (malformed)”;  
 break;
 case (-930):
 theErrStr = &”\pillegal service type, or not supported”;
 break;
 case (-1700):
 theErrStr = &”\perrAECoercionFail”;
 break;
 case (-1701):
 theErrStr = &”\perrAEDescNotFound”;
 break;
 case (-1702):
 theErrStr = &”\perrAECorruptData”;
 break;
 case (-1703):
 theErrStr = &”\perrAEWrongDataType”;
 break;
 case (-1704):
 theErrStr = &”\perrAENotAEDesc”;
 break;
 case (-1708): 
 theErrStr = &”\perrAEEventNotHandled”;
 break;
 case (-1709):
 theErrStr = &”\perrAEReplyNotValid”;
 break;
 case (-1710):
 theErrStr = &”\perrAEUnknownSendMode”;
 break;
 case (-1711):
 theErrStr = &”\perrAEWaitCanceled”;         
 break;
 case (-1712):
 theErrStr = &”\perrAETimeout”;         
 break;
 case (-1713):
 theErrStr = &”\perrAENoUserInteraction”;   
 break;
 case (-1714):
 theErrStr = &”\perrAENotASpecialFunction”;
 break;
 case (-1715):
 theErrStr = &”\perrAEParamMissed”; 
 break;
 case (-1716):
 theErrStr = &”\perrAEUnknownAddressType”;
 break;
 case (-1717):  
 theErrStr = &”\perrAEHandlerNotFound”;
 break;
 case (-1718): 
 theErrStr = &”\perrAEReplyNotArrived”;
 break;
 case (-1719):
 theErrStr = &”\perrAEIllegalIndex”;   
 break;   
 default:
 NumToString(theErr, theErrNumStr);
 } /* switch */
 
 ParamText(theString, (ConstStr255Param) theErrStr,
 (ConstStr255Param) theErrNumStr, (ConstStr255Param)”\p”);
 CautionAlert(kErrorAlertID, nil);
} /* ErrorAlert */

/* ================================= */
void  FixCursor(void)
{
 Point  where, aPt;
 Rect   theRect;
 GrafPtroldPort;
 
 /* if this is an event you can also use event.where */
 GetMouse(&where); 
 if ((gWindow != nil) && (gInForeground))
 { 
// GetPort(&oldPort);
// SetPort(gWindow);
 
 theRect = (**(((WindowPeek)gWindow)->contRgn)).rgnBBox;

 /* adjust for the scroll bars */
 theRect.right -= 16;
 theRect.bottom -= 16;
 
 /* divide the world into two parts, us and them */
 RectRgn(gMainContentRgn, &theRect);
 RectRgn(gAllElseRgn, &(screenBits.bounds));
 UnionRgn(gAllElseRgn, GetGrayRgn(), gAllElseRgn);
 DiffRgn(gAllElseRgn, gMainContentRgn, gAllElseRgn);
 
 if (PtInRect(where, &theRect))
 {
 gMouseMovedRgn = gMainContentRgn;
 SetCursor(*gPlusCursor);
 }
 else
 {
 gMouseMovedRgn = gAllElseRgn;
 InitCursor();
 }
// SetPort(oldPort);
 }
 else 
 { /* there is no winodow */
 InitCursor();
 RectRgn(gAllElseRgn, &(screenBits.bounds));
 UnionRgn(GetGrayRgn(), gAllElseRgn, gAllElseRgn);
 EmptyRgn(gMainContentRgn);
 gMouseMovedRgn = gAllElseRgn;
 }

} /* FixCursor */

/* ================================= */
void  Loop(void)
{
 EventRecordtheEvent;

 FixCursor();
 do {
 if (WaitNextEvent(everyEvent, &theEvent, 
 15, gMouseMovedRgn))
 {
 switch (theEvent.what) {
 case mouseDown:
 DoMouseDown(&theEvent);
 break;
 case mouseUp:
 break;
 case keyDown:
 case autoKey:
 DoKey(&theEvent);
 break;
 case diskEvt:
 break;
 case activateEvt:
 break;
 case updateEvt:
 DoUpdate(&theEvent);
 break;
 case osEvt:
 DoOSEvent(&theEvent);
 break;
 case kHighLevelEvent:
 if (gHasAppleEvents) 
 AEProcessAppleEvent(&theEvent);
 break;
 
 } /* switch */
 } /* end if WaitNextEvent */
 
 } while (!gFinished);
} /* Loop */

/* ================================= */
void SetUpMenus(void)
{
 /* create some menus from the menu resources */
 AppleMenu = GetMenu(kAppleMenuID);
 FileMenu = GetMenu(kFileMenuID);
 EditMenu = GetMenu(kEditMenuID);
 SpecialMenu = GetMenu(kSpecialMenuID);
 
 AddResMenu(AppleMenu, ’DRVR’);
  
 InsertMenu(AppleMenu, 0);
 InsertMenu(FileMenu, 0);
 InsertMenu(EditMenu, 0);
 if (gHasAppleEvents)
 InsertMenu(SpecialMenu, 0);
 
 DrawMenuBar();
 
} /* SetUpMenus */

/* ================================= */
void  doAboutBox(void)
{
 OSErr  theErr;

 theErr = Alert(kAboutBoxID,nil);
} /* doAboutBox */
 
/* ================================= */
void  main(void)
{
 InitMac(8);
 SetUpMenus();
 ConnectToFinder();
 Loop();
 DisconnectFromFinder();
} /* main() */

 
AAPL
$112.65
Apple Inc.
+0.00
MSFT
$47.52
Microsoft Corpora
+0.00
GOOG
$511.10
Google Inc.
+0.00

MacTech Search:
Community Search:

Software Updates via MacUpdate

Mellel 3.3.7 - Powerful word processor w...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
ScreenFlow 5.0.1 - Create screen recordi...
Save 10% with the exclusive MacUpdate coupon code: AFMacUpdate10 Buy now! ScreenFlow is powerful, easy-to-use screencasting software for the Mac. With ScreenFlow you can record the contents of your... Read more
Simon 4.0 - Monitor changes and crashes...
Simon monitors websites and alerts you of crashes and changes. Select pages to monitor, choose your alert options, and customize your settings. Simon does the rest. Keep a watchful eye on your... Read more
BBEdit 11.0.2 - Powerful text and HTML e...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more
ExpanDrive 4.2.1 - Access cloud storage...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
Adobe After Effects CC 2014 13.2 - Creat...
After Effects CC 2014 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous After Effects customer). After Effects CS6 is still available... Read more
Command-C 1.1.7 - Clipboard sharing tool...
Command-C is a revolutionary app which makes easy to share your clipboard between iOS and OS X using your local WiFi network, even if the app is not currently opened. Copy anything (text, pictures,... Read more
Tidy Up 4.0.2 - Find duplicate files and...
Tidy Up is a complete duplicate finder and disk-tidiness utility. With Tidy Up you can search for duplicate files and packages by the owner application, content, type, creator, extension, time... Read more
Typinator 6.3 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
GraphicConverter 9.5 - Graphics editor w...
GraphicConverter is an all-purpose image-editing program that can import 200 different graphic-based formats, edit the image, and export it to any of 80 available file formats. The high-end editing... Read more

Latest Forum Discussions

See All

Shift - Photo Filters Designed By You (...
Shift - Photo Filters Designed By You 1.0 Device: iOS Universal Category: Photography Price: $.99, Version: 1.0 (iTunes) Description: | Read more »
Elastic Drums (Music)
Elastic Drums 1.0 Device: iOS iPhone Category: Music Price: $3.99, Version: 1.0 (iTunes) Description: *** Introduction price 3,99$ instead of 7,99$ *** Elastic Drums is a music app with 6 channels of synthesized drum sounds, a step... | Read more »
Fireworks Simulator (Games)
Fireworks Simulator 1.0.8 Device: iOS Universal Category: Games Price: $.99, Version: 1.0.8 (iTunes) Description: *** 50% discount – For a short time only *** You can play Fireworks Simulator on these devices: - iPhone 5, 5s, 5c, 6,... | Read more »
Nicky's Gift (Games)
Nicky's Gift 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: Everybody! Merry Christmas! There's 48 levels in the game. Let's go! Nicky's Gift | Read more »
The Hit List — Simply Powerful Tasks, To...
The Hit List — Simply Powerful Tasks, To-Dos, Projects, & Reminders 2.0 Device: iOS iPhone Category: Productivity Price: $9.99, Version: 2.0 (iTunes) Description: >> LAUNCH SPECIAL: The Hit List 2 for iPhone is ONLY $9.99... | Read more »
Mahjong Journey Review
Mahjong Journey Review By Jennifer Allen on December 18th, 2014 Our Rating: :: STEADY MATCHINGiPad Only App - Designed for the iPad Aimed at the more laid back gamer, Mahjong Journey isn’t for everyone, but those looking for some... | Read more »
Emoji Type - custom keyboard with predic...
Emoji Type - custom keyboard with predictive emojis 0.4.0 Device: iOS iPhone Category: Utilities Price: $.99, Version: 0.4.0 (iTunes) Description: Emoji Type is custom keyboard for iOS 8 that auto suggests emojis as you type. ABOUT... | Read more »
Game of the Year 2014 – 148Apps Staff Pi...
The end of 2014 is almost here, which can only mean one thing. Okay it can mean a lot of things, but in this specific context it means Game of the Year lists! Which is why the 148Apps staff have all picked their favorites from the past year. And why... | Read more »
UponPixels Review
UponPixels Review By Jennifer Allen on December 18th, 2014 Our Rating: :: CREATIVE TYPOGRAPHYUniversal App - Designed for iPhone and iPad Add cool typography and objects to your photos with the easy to use UponPixels.   | Read more »
The Vikings are Coming! CastleStorm’s Ne...
The Vikings are Coming! CastleStorm’s New Update Adds a Survival Mode Posted by Jessica Fisher on December 18th, 2014 [ permalink ] | Read more »

Price Scanner via MacPrices.net

Save up to $400 on MacBooks with Apple Certif...
The Apple Store has Apple Certified Refurbished 2014 MacBook Pros and MacBook Airs available for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Save up to $300 on Macs, $30 on iPads with Ap...
Purchase a new Mac or iPad at The Apple Store for Education and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free,... Read more
iOS and Android OS Targeted by Man-in-the-Mid...
Cloud services security provider Akamai Technologies, Inc. has released, through the company’s Prolexic Security Engineering & Research Team (PLXsert), a new cybersecurity threat advisory. The... Read more
KMI MIDI K-Board Great Gift for Amateur &...
The K-Board is a MIDI Nano keyboard for music creation for iPad, Android, And computers; the easiest way to make music with iPads & Android tablets, and Mac, Windows, or Linux computers. Ultra-... Read more
Amazon offers 15-inch 2.2GHz Retina MacBook P...
 Amazon.com has the 15″ 2.2GHz Retina MacBook Pro on sale for $1699 including free shipping. Their price is $300 off MSRP. Stock is limited, so act now if you’re interested. Read more
Holiday sales continue: MacBook Pros for up t...
 B&H Photo has new MacBook Pros on sale for up to $300 off MSRP as part of their Holiday pricing. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina MacBook Pro: $1699... Read more
Holiday sale: Mac minis available for up to $...
 B&H Photo has new 2014 Mac minis on sale for up to $80 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 1.4GHz Mac mini: $459 $40 off MSRP - 2.6GHz Mac mini: $629 $70 off... Read more
Google Search App For iOS Gets A Major Makeov...
Google has given iOS users an early Christmas present with a substantial update of it’s not-very-often-upgraded Google Search app. Google Search has been my go-to tool for Web searches since it was... Read more
ShopKeep Apple Pay And Chip Card Reader Avail...
ShopKeep, a cloud-based technology provider to more than 10,000 small business owners to manage retail shops and restaurants with iPads, has released its new Apple Pay and chip card reader. This... Read more
Holiday sale! 27-inch 5K iMac for $2299, save...
 B&H Photo has the 27″ 3.5GHz 5K iMac in stock today and on sale for $2299 including free shipping plus NY sales tax only. Their price is $200 off MSRP, and it’s the lowest price available for... Read more

Jobs Board

Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.