TweetFollow Us on Twitter

July 01 QT Toolkit

Volume Number: 17 (2001)
Issue Number: 07
Column Tag: QuickTime Toolkit

Back In Action

by Tim Monroe

Working with Wired Actions

Introduction

The previous four QuickTime Toolkit articles have focused largely on two topics: how to create and manipulate sprites, and how to attach dynamic, interactive behaviors to sprites. Interactivity is one of QuickTime's greatest assets and perhaps its most significant advantage over competing media architectures. And, no doubt, wired sprites are the cornerstone of QuickTime interactivity. As we've seen, we can do some pretty nifty things with just a few event and action atoms placed in the right locations in a sprite track.

In this article, we're going to take one more look at wired actions. I want to consider a few of the event types and actions recently introduced in QuickTime 5, and I especially want to consider how to use wired actions with other kinds of tracks. So far, all the actions we've encoountered have been triggered by some event involving a sprite (for example, clicking on the sprite) or a sprite track (for example, loading a sprite track key frame). Beginning in QuickTime 4, however, it's also possible to attach wired actions to QuickTime VR nodes and hot spots, to text in text tracks, and to Flash content. Let's take a moment to see what we can do here.

QuickTime VR supports two general kinds of wired actions: (1) actions that apply to a particular node, and (2) actions that apply to a particular hot spot in a node. The node-specific actions can be attached to idle events (so that an action can be triggered periodically) or to frame-loaded events (so that an action can be triggered when the node is first entered). We could, for instance, attach an action to a frame-loaded event to set the initial pan and tilt angles that are displayed when the user enters the node. Similarly, we could attach an action to a frame-loaded event to start a sound track playing when the user enters the node. In addition, we could use idle event actions to dynamically adjust the balance and volume of that sound as the user changes the pan angle of the movie; this provides an easy way to make a sound appear to emanate from a specific location in a node (which is sometimes called directional sound). Or, we could adjust the pan and tilt angles of some other QuickTime VR movie, so that panning and tilting one panorama caused similar panning and tilting in a second.

We can also attach wired actions to hot spots inside of QuickTime VR nodes. We can configure these actions to be triggered by a variety of mouse events, including moving the mouse over a hot spot, moving the mouse out of a hot spot, and clicking the mouse within a hot spot. Once again, the actions that are triggered by these events can be any actions supported by the QuickTime wired actions architecture, such as starting and stopping sounds, enabling and disabling tracks, changing pan and tilt angles, and so forth.

QuickTime allows us to attach wired actions to a text track, giving us wired text. Consider, for instance, the movie shown in Figure 1. This movie contains a text track that's wired to displays the amount of memory currently available in the application's heap. (The text track is updated every two seconds by an idle event action.) We can use this movie to track the memory usage of any QuickTime-savvy application.


Figure 1: A text track displaying the application's free memory

Or consider the movie shown in Figure 2, which also contains a single text track. This time the text track is wired so that clicking on the word "Apple" launches the user's default web browser and loads the URL ; similarly, clicking on the word "CNN" loads the URL .


Figure 2: A text track with hypertext links

Finally, we can attach wired actions to items in a Flash track. Flash is a file format developed by Macromedia, Inc. for displaying vector-based graphics and animations. It's especially popular for web-based content delivery, since it combines compact data size (and hence speedier downloads) with quick rendering on the client, advanced graphics manipulations, and support for mouse-based interactivity with the graphics objects. In QuickTime 4, Apple added a Flash media handler that provides support for Flash graphics, animations, and interactivity inside of QuickTime movies. In addition, QuickTime 4 added the ability to attach wired actions to elements in a Flash track, thereby supplementing the native Flash interactivity. So, for instance, we could use Flash graphic elements (instead of sprites) to provide a user-interface for a QuickTime movie.

Unfortunately, we don't yet know enough about the structure of QuickTime VR movies or Flash tracks to know where to insert event atoms. As a result, we'll have to postpone our hands-on work with wiring VR or Flash content. But we do know how text tracks are put together (see "Word Is Out" in MacTech, November 2000), so here we'll learn how to add some interactivity to our text tracks.

Our sample application this month is called QTWiredActions. The Test menu of QTWiredActions is shown in Figure 3. The first menu item creates the wired text movie shown in Figure 2. The second menu item creates the memory display movie shown in Figure 1.


Figure 3: The Test menu of QTWiredActions

The next two menu items create two more sprite movies, which use wired actions and operands to make sprites react "physically" with the movie rectangle and with one another.

Text Actions

Let's begin by investigating a few of the ways in which we can use wired actions in text tracks. In general, we attach an event atom to a text media sample by concatenating the atom container that holds the event atom onto the media sample data. (We'll see exactly how this works in a moment.) The event atom can specify any of the kinds of events that we've considered so far, including mouse-click events, mouse-over events, frame-loaded events, and idle events. These wired text samples work exactly as you'd expect; for instance, if a text sample contains a mouse-click event atom, then the associated actions are triggered when the user clicks the mouse button while the cursor is within the bounds of the text track.

Sometimes, however, we'd like to handle user events that involve only part of the text displayed in a text sample. Consider once again the text movie shown in Figure 2. In this case, we want to trigger actions only when the user clicks on specific portions of the text data (namely, the strings "Apple" and "CNN"). To handle this kind of wiring, QuickTime supports a class of atoms called hypertext atoms. The goal here is to provide the sort of hyperlinks that you typically find in web browsers or other HTML-based applications. By clicking on a hypertext link in a text track, the user can launch the default web browser and navigate to a specific URL, by triggering the kActionGoToURL action. But in fact hypertext atoms can contain any kinds of wired action, not just kActionGoToURL actions. So the user's actions can just as easily (for instance) trigger a jump forward or backward in the movie, or cause some changes in an external movie.

As you can see in Figure 2, QuickTime automatically sets the color of hypertext links to the familiar browser-default blue and underlines the hypertext. Both of these provide visual cues that the user can interact with that segment of text. QuickTime also provides wired actions that allow us to change the color of a segment of hypertext, for example when the user rolls the cursor over that segment or after the user has clicked on the link. Figure 4 shows the same hypertext movie (this time on Windows) with the cursor resting over the first hyperlink.


Figure 4: A selected hypertext link

Adding Actions to a Text Sample

A text media sample consists of a 16-bit length word followed by the text of that sample. Optionally, one or more atoms of additional data (called text atom extensions) may follow the text in the sample. The length word specifies the total number of bytes in the text (not including the 2 bytes occupied by the length field itself or the length of any of the optional text atom extensions). The text atom extensions are organized as "classic" atom structures: a 32-bit length field, followed by a 32-bit type field, followed by the data in the atom. Here, the length field specifies the total length of the atom (that is, 8 plus the number of bytes in the data). All the data in a text extension atom must be in big-endian format.

Whether we use a hypertext atom or any of the standard types of event atoms, we add the event atom to a text media sample in the same way, by attaching the atom container that contains the event and action atoms to the text media sample as a text atom extension. Figure 5 shows the general structure of a text media sample that contains a text action.


Figure 5: The structure of a wired text media sample

If we're given a text media sample and an event atom container, it's quite easy to wire that atom container to the text. We simply need to determine the lengths of the media sample and the atom container, enlarge the text media sample to hold all of its existing data and the atom container, plus the 8-byte atom header shown in Figure 5. We set up the atom header as appropriate and then copy it and the atom container into the enlarged media sample.

The size of a text atom extension for wired actions, of course, is the size of the atom container plus the size of the atom header (8 bytes). The type of the text atom extension can be one of three values. If the atom container holds a frame-loaded event atom, then the type of the text atom extension should be set to kQTEventFrameLoaded. If the atom container holds any other standard event atom (including an idle event atom), then the type of the text atom extension should be set to kQTEventType. Finally, if the atom container holds a hypertext event atom, then the type of the text atom extension should be set to ‘htxt'. Currently there is no symbolic constant for this value defined in any of the public QuickTime header files, so I've included this definition in the file QTWiredActions.h:

>
#define kHyperTextTextAtomType      FOUR_CHAR_CODE(‘htxt')

Listing 1 shows our definition of the function QTWired_AddActionsToSample, which adds an event atom container to an existing text media sample. Notice that the function parameters include the text media sample, the event atom container, and the type of atom extension. This type should be one of the three recognized types for text atom extensions that specify wired actions.

Listing 1: Adding wired actions to a text media sample

static OSErr QTWired_AddActionsToSample (Handle theSample, 
               QTAtomContainer theActions, SInt32 theAtomExtType)
{
   Ptr          myPtr = NULL;
   long         myHandleLength;
   long         myContainerLength;
   long         myNewLength;
   OSErr        myErr = noErr;

   if ((theSample == NULL) || (theActions == NULL))
      return(paramErr);

   myHandleLength    = GetHandleSize(theSample);
   myContainerLength = GetHandleSize((Handle)theActions);

   myNewLength = (long)(sizeof(long) + sizeof(OSType) + 
                           myContainerLength);

   SetHandleSize(theSample, (myHandleLength + myNewLength));
   myErr = MemError();
   if (myErr != noErr)
      goto bail;

   HLock(theSample);

   // get a pointer to the beginning of the new block of space added to the sample
   // by the previous call to SetHandleSize; we need to format that space as a text
   // atom extension
   myPtr = *theSample + myHandleLength;

   // set the length of the text atom extension
   *(long *)myPtr = EndianS32_NtoB((long)(sizeof(long) + 
                              sizeof(OSType) + myContainerLength));
   myPtr += (sizeof(long));

   // set the type of the text atom extension
   *(OSType *)myPtr = EndianS32_NtoB(theAtomExtType);
   myPtr += (sizeof(OSType));
   
   // set the data of the text atom extension;
   // we assume that this data is already in big-endian format
   HLock((Handle)theActions);
   BlockMove(*theActions, myPtr, myContainerLength);

   HUnlock((Handle)theActions);
   HUnlock(theSample);

bail:
   return(myErr);
}

For more information on working with atoms, see "The Atomic Café" in MacTech, September 2000.

Creating Text Actions

As we've just seen, we add a wired action event handler to a text sample by adding a text atom extension of type kQTEventFrameLoaded or kQTEventType to the end of the sample; the data in the text atom extension is the atom container that holds the information about the wired actions triggered by some event. So, our task boils down to this: find the data in a text sample, create an atom container holding information about the desired actions, and then append a text extension atom whose data is that atom container to the end of the text sample data. Then we replace the previous text sample with the new one in the text track.

Listing 2 shows the code that handles the "Make Memory Display Movie..." menu item.

Listing 2: Handling the "Make Memory Display Movie..." menu item

case IDM_MAKE_MEM_DISPLAY_MOVIE:
   myPrompt = QTUtils_ConvertCToPascalString(kMDSavePrompt);
   myName = QTUtils_ConvertCToPascalString(kMDSaveFileName);

   // elicit a file from the user to save the new movie into
   QTFrame_PutFile(myPrompt, myName, &myFile, &myIsSelected, 
                              &myIsReplacing);

   // create a text movie that displays the amount of memory free in the application heap
   if (myIsSelected) {
      myErr = QTWired_CreateMemoryDisplayMovie(&myFile);
      if (myErr == noErr)
         QTWired_AddActionsToTextMovie(&myFile, theMenuItem);
   }

   myIsHandled = true;
   break;

We don't need to consider the function QTWired_CreateMemoryDisplayMovie in detail, since it's really just a variant of the QTText_AddTextTrack function we considered in an earlier article. In the present case, we set the initial text in the text track to the single character "0", and we set the font to 48-point Times-Roman (using the constant kFontIDTimes).

The interesting work in Listing 2 is done by the QTWired_AddActionsToTextMovie function, which creates the appropriate wired action atom container and then calls QTWired_AddActionsToSample (defined in Listing 1) to append that container to the first (and only) text media sample in the file created by QTWired_CreateMemoryDisplayMovie. Listing 3 shows our definition of QTWired_AddActionsToTextMovie.

Listing 3: Adding wired actions to a text movie

OSErr QTWired_AddActionsToTextMovie 
                           (FSSpec *theFSSpec, UInt16 theMenuItem)
{
   short                              myResID = 0;
   short                              myResRefNum = -1;
   Movie                              myMovie = NULL;
   Track                              myTrack = NULL;
   Media                              myMedia = NULL;
   TimeValue                          myTrackOffset;
   TimeValue                          myMediaTime;
   TimeValue                          mySampleDuration;
   TimeValue                          mySelectionDuration;
   TimeValue                          myNewMediaTime;
   TextDescriptionHandle              myTextDesc = NULL;
   Handle                             mySample = NULL;
   short                              mySampleFlags;
   Fixed                              myTrackEditRate;
   QTAtomContainer                    myActions = NULL;
   OSErr                              myErr = noErr;

   // open the movie file for reading and writing
   myErr = OpenMovieFile(theFSSpec, &myResRefNum, fsRdWrPerm);
   if (myErr != noErr)
      goto bail;

   myErr = NewMovieFromFile(&myMovie, myResRefNum, &myResID, 
                        NULL, newMovieActive, NULL);
   if (myErr != noErr)
      goto bail;
      
   // find first text track in the movie
   myTrack = GetMovieIndTrackType(myMovie, kIndexOne, 
                        TextMediaType, movieTrackMediaType);
   if (myTrack == NULL)
      goto bail;
   
   // get first media sample in the text track
   myMedia = GetTrackMedia(myTrack);
   if (myMedia == NULL)
      goto bail;
   
   myTrackOffset = GetTrackOffset(myTrack);
   myMediaTime = TrackTimeToMediaTime(myTrackOffset, myTrack);

   // allocate some storage to hold the sample description for the text track
   myTextDesc = (TextDescriptionHandle)NewHandle(4);
   if (myTextDesc == NULL)
      goto bail;

   mySample = NewHandle(0);
   if (mySample == NULL)
      goto bail;

   myErr = GetMediaSample(myMedia, mySample, 0, NULL, 
                        myMediaTime, NULL, &mySampleDuration, 
                        (SampleDescriptionHandle)myTextDesc, NULL, 
                        1, NULL, &mySampleFlags);
   if (myErr != noErr)
      goto bail;

   // add actions to the first media sample
   switch (theMenuItem) {
      case IDM_MAKE_HYPERTEXT_MOVIE:
         // create an action container for hypertext actions
         myErr = 
            QTWired_CreateHyperTextActionContainer(&myActions);
         if (myErr != noErr)
            goto bail;

         // add hypertext actions to sample
         myErr = QTWired_AddActionsToSample(mySample, myActions, 
                        kHyperTextTextAtomType);
         if (myErr != noErr)
            goto bail;
         break;

      case IDM_MAKE_MEM_DISPLAY_MOVIE:
         // create an action container for wired actions
         myErr = 
         QTWired_CreateMemoryDisplayActionContainer(&myActions);
         if (myErr != noErr)
            goto bail;

         // add actions to sample
         myErr = QTWired_AddActionsToSample(mySample, myActions, 
                        kQTEventType);
         if (myErr != noErr)
            goto bail;
         break;

      default:
         myErr = paramErr;
         goto bail;
   }

   // replace sample in media
   myTrackEditRate = GetTrackEditRate(myTrack, myTrackOffset);
   if (GetMoviesError() != noErr)
      goto bail;

   GetTrackNextInterestingTime(myTrack, nextTimeMediaSample | 
                        nextTimeEdgeOK, myTrackOffset, fixed1, NULL, 
                        &mySelectionDuration);
   if (GetMoviesError() != noErr)
      goto bail;

   myErr = DeleteTrackSegment(myTrack, myTrackOffset, 
                        mySelectionDuration);
   if (myErr != noErr)
      goto bail;

   myErr = BeginMediaEdits(myMedia);
   if (myErr != noErr)
      goto bail;

   myErr = AddMediaSample(   myMedia,
                     mySample,
                     0,
                     GetHandleSize(mySample),
                     mySampleDuration,
                     (SampleDescriptionHandle)myTextDesc, 
                     1,
                     mySampleFlags,
                     &myNewMediaTime);
   if (myErr != noErr)
      goto bail;
   
   myErr = EndMediaEdits(myMedia);
   if (myErr != noErr)
      goto bail;
   
   // add the media to the track
   myErr = InsertMediaIntoTrack(myTrack, myTrackOffset, 
                        myNewMediaTime, mySelectionDuration, 
                        myTrackEditRate);
   if (myErr != noErr)
      goto bail;

   // update the movie resource
   myErr = UpdateMovieResource(myMovie, myResRefNum, myResID, 
                        NULL);
   if (myErr != noErr)
      goto bail;
   
   // close the movie file
   myErr = CloseMovieFile(myResRefNum);

bail:
   if (myActions != NULL)
      (void)QTDisposeAtomContainer(myActions);

   if (mySample != NULL)
      DisposeHandle(mySample);

   if (myTextDesc != NULL)
      DisposeHandle((Handle)myTextDesc);

   if (myMovie != NULL)
      DisposeMovie(myMovie);

   return(myErr);
}

Everything in Listing 3 is standard Movie Toolbox stuff that we've seen before, except of course for the application functions QTWired_CreateHyperTextActionContainer (which we'll discuss in the next section) and QTWired_CreateMemoryDisplayActionContainer. This latter function is in fact relatively simple. It builds an atom container that uses the kOperandFreeMemory operand to retrieve the amount of memory that is currently free in the application heap, as well as the kActionTextTrackPasteText wired action to paste the value returned by that operand into the text track. The kActionTextTrackPasteText action, which is new in QuickTime 5, takes three parameters: the text to paste and the beginning and ending locations in the text track to paste the text. Each time we paste a new value into the text track of the memory-display movie, we want to replace all of the existing text, so we'll set the second and third parameters to 0 and 0xffff respectively.

There's one undocumented "gotcha" here: in order for kActionTextTrackPasteText to have any effect, we need to explicitly enable text editing on the target text track. We can do this by executing the kActionTextTrackSetEditable wired action (also new to QuickTime 5). This action takes one parameter, which specifies the kind of editing we want to enable. QuickTime currently supports three settings for the editing state of a text track, which the documentation lists like this:

#define kKeyEntryDisabled                0
#define kKeyEntryDirect                  1
#define kKeyEntryScript                  2

(Once again, however, these values are not defined in any public header file, so I've added those lines to the file QTWiredActions.h.) The default value kKeyEntryDisabled indicates that no editing is allowed on the text track; key events are passed to the movie controller (which will probably ignore most of them) and any editing actions sent to the track are ignored. The value kKeyEntryDirect indicates that direct editing is to be enabled; this means that key events are passed directly to the text track (for instance, key-down events result in the characters being inserted into the text track). The value kKeyEntryScript indicates that script editing is to be enabled; this means that editing actions sent to the track are interpreted by the text media handler and executed.

For present purposes, we want to enable script editing on the text track. So we'll execute the code in Listing 4 inside of QTWired_CreateMemoryDisplayActionContainer.

Listing 4: Enabling editing on a text track

myErr = QTNewAtomContainer(theActions);
if (myErr != noErr)
   goto bail;

// add an event atom that enables text editing
myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, 
               kParentAtomIsContainer, kQTEventIdle, 
               kActionTextTrackSetEditable, &myActionAtom);
if (myErr != noErr)
   goto bail;

myEditState = EndianS16_NtoB(kKeyEntryScript);
myErr = WiredUtils_AddActionParameterAtom(*theActions, 
               myActionAtom, 1, sizeof(myEditState), 
               &myEditState, NULL);

On every idle event sent to the text track, we set the track to be editable (just before we execute the kActionTextTrackPasteText action). In theory, we could enable editing just once in a frame-loaded event. But the idle event action is simpler to construct and also ensures that we can always paste the current memory value into the text track (since some other action may have disabled editing on that track).

The remainder of QTWired_CreateMemoryDisplayActionContainer is straightforward. It adds an idle event call to the kActionTextTrackPasteText action, using the result of the kOperandFreeMemory operand as the text to be pasted. QuickTime is smart enough to convert the floating-point value returned by kOperandFreeMemory into a string, as expected by kActionTextTrackPasteText. Listing 5 shows the complete definition.

Listing 5: Pasting the amount of free memory into a text track

static OSErr QTWired_CreateMemoryDisplayActionContainer 
                        (QTAtomContainer *theActions)
{
   QTAtom         myEventAtom = 0;
   QTAtom         myActionAtom = 0;
   QTAtom         myParamAtom = 0;
   short          myEditState;
   UInt32         myPos;
   QTAtom         myParameterAtom = 0;
   QTAtom         myExpressionAtom = 0;
   QTAtom         myOperandAtom = 0;
   OSErr          myErr = noErr;

   myErr = QTNewAtomContainer(theActions);
   if (myErr != noErr)
      goto bail;

   // add an event atom that enables text editing
   myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, 
                  kParentAtomIsContainer, kQTEventIdle, 
                  kActionTextTrackSetEditable, &myActionAtom);
   if (myErr != noErr)
      goto bail;

   myEditState = EndianS16_NtoB(kKeyEntryScript);
   myErr = WiredUtils_AddActionParameterAtom(*theActions, 
                  myActionAtom, 1, sizeof(myEditState), 
                  &myEditState, NULL);
   if (myErr != noErr)
      goto bail;

   // add an event atom that displays the amount of application memory currently free
   myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, 
                  kParentAtomIsContainer, kQTEventIdle, 
                  kActionTextTrackPasteText, &myActionAtom);
   if (myErr != noErr)
      goto bail;

   // first parameter: the text to be pasted
   myErr = WiredUtils_AddActionParameterAtom(*theActions, 
                  myActionAtom, 1, 0, NULL, &myParameterAtom);
   if (myErr != noErr)
      goto bail;

   myErr = WiredUtils_AddExpressionContainerAtomType(
                  *theActions, myParameterAtom, 
                  &myExpressionAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(*theActions, myExpressionAtom, 
               kOperandAtomType, 1, 1, 0, NULL, &myOperandAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(*theActions, myOperandAtom, 
               kOperandFreeMemory, 1, 1, 0, NULL, NULL);
   if (myErr != noErr)
      goto bail;

   // second parameter: selection range begin: 0
   myPos = EndianU32_NtoB(0);
   myErr = WiredUtils_AddActionParameterAtom(*theActions, 
               myActionAtom, 2, sizeof(myPos), &myPos, NULL);
   if (myErr != noErr)
      goto bail;

   // third parameter: selection range end: 0xffff
   myPos = EndianU32_NtoB(0xffff);
   myErr = WiredUtils_AddActionParameterAtom(*theActions, 
               myActionAtom, 3, sizeof(myPos), &myPos, NULL);

bail:
   return(myErr);
}

On classic Macintosh systems (that is, Mac OS 8 and 9), the movie created by all this code will display the amount of free memory in the application heap (or essentially what you could determine by executing the Memory Manager function FreeMem). On Windows systems and on Mac OS X, where there are no application heaps in the classic-Mac sense, the movie will display less useful numbers. In fact, I'm not exactly sure what the numbers displayed on Windows and Mac OS X represent.

Creating Hypertext Actions

Let's consider now how to construct text movies that contain hypertext actions, like the one shown in Figures 2 and 4. The basic ideas are the same as with constructing wired text movies, except that now we add a text atom extension of type kHyperTextTextAtomType to the end of the text sample. Also, the atom container that's inside of the text atom extension should have the structure shown in Figure 6.


Figure 6: The structure of a hypertext text atom

As you can see, the root atom in a hypertext text atom is of type ‘wtxt'. This atom contains one child of type ‘htxt' for each hypertext link in the text sample. Let's call this child atom a hypertext item atom. A hypertext item atom specifies the information about a single hypertext link in a text sample. We need to specify the starting point and ending point in the sample text for the hypertext link, and we need to specify the event and action atoms associated with that segment of text. So a hypertext item atom needs to contain at least three children, of types ‘strt', ‘end', and kQTEventType. In our source code, we'll use these constants:

#define kHyperTextTextAtomType            FOUR_CHAR_CODE(‘htxt')
#define kTextWiredObjectsAtomType         FOUR_CHAR_CODE(‘wtxt')
#define kHyperTextItemAtomType            FOUR_CHAR_CODE(‘htxt')
#define kRangeStart                       FOUR_CHAR_CODE(‘strt')
#define kRangeEnd                         FOUR_CHAR_CODE(‘end ‘)

There's no need to consider the function QTWired_CreateHyperTextActionContainer in detail. It simply builds the atom container shown in Figure 6, using hard-coded values for the parameter data of the kRangeStart and kRangeEnd atoms. Each of our hypertext item atoms contains three event atoms. The first event atom looks for mouse-clicks on the hypertext link and executes a kActionGoToURL action in response. The second and third event atoms watch for the cursor to enter and exit the hypertext link, changing the color of the text appropriately. Listing 6 shows the code for changing the hypertext color to purple when the mouse enters the first hyperlink. The kActionTextTrackSetHyperTextColor action takes two parameters, which are the index of the hypertext link whose color is to change and the desired new color.

Listing 6: Changing the color of a hypertext link

ModifierTrackGraphicsModeRecord      myGraphicsModeRecord;

myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, 
               myHyperTextAtom, kQTEventMouseEnter, 
               kActionTextTrackSetHyperTextColor, &myActionAtom);
if (myErr != noErr)
   goto bail;

myIndex = EndianS16_NtoB(1);
myErr = WiredUtils_AddActionParameterAtom(*theActions, 
               myActionAtom, kIndexOne, sizeof(myIndex), 
               &myIndex, NULL);
if (myErr != noErr)
   goto bail;
   
myGraphicsModeRecord.graphicsMode = 
               EndianS32_NtoB(ditherCopy);
myGraphicsModeRecord.opColor.red = EndianS16_NtoB(0x9999);
myGraphicsModeRecord.opColor.green = EndianS16_NtoB(0x0000);
myGraphicsModeRecord.opColor.blue = EndianS16_NtoB(0xcccc);
myErr = WiredUtils_AddActionParameterAtom(*theActions, 
               myActionAtom, kIndexTwo, 
               sizeof(myGraphicsModeRecord), 
               &myGraphicsModeRecord, NULL);

Take a look at the source file QTWiredActions.c for the complete definition of QTWired_CreateHyperTextActionContainer.

Key Events

QuickTime 5 introduced a new type of event, the key event (of type kQTEventKey), which is issued when keys on the keyboard are pressed. Consider, for instance, the movie shown in Figure 7, which displays the ASCII character code of whatever key the user presses. In the case shown here, the user has just pressed the "T" key.


Figure 7: A movie that displays ASCII character codes of pressed keys.

A key event can be used in event atoms just like any of the other event types we've encountered so far. What's interesting about key events is that we can determine not only that the user has pressed a key, but also which particular key was pressed. We do this by inspecting the event parameters of the key event, using the new operand kOperandEventParameter. This operand itself takes one parameter, which specifies the index of the event parameter we want to inspect. In the case of key events, we can inspect any one of 5 event parameters:

  • Parameter index 1 is the horizontal position of the cursor at the time the key event occurred, in coordinates relative to the text track rectangle.
  • Parameter index 2 is the vertical position of the cursor at the time the key event occurred, in coordinates relative to the text track rectangle.
  • Parameter index 3 encodes any modifier keys that are down when the key event occurs, using these constants from Events.h:
   enum {
      cmdKey                  = 1 << cmdKeyBit,      // 0x0100
      shiftKey                = 1 << shiftKeyBit,    // 0x0200
      alphaLock               = 1 << alphaLockBit,   // 0x0400
      optionKey               = 1 << optionKeyBit,   // 0x0800
      controlKey              = 1 << controlKeyBit   // 0x1000
   };

For instance, if the Control and Shift keys are both down when the key event occurs, then the third parameter would be 0x00001200, or 4608.

  • Parameter index 4 is the ASCII character code of the key pressed.
  • Parameter index 5 is the virtual key code (or scan code) of the key pressed. A virtual key code is a value that represents a specific physical key on a specific model of keyboard. As a result, these values are generally less useful than the ASCII character codes.

We can construct the ASCII-display movie in Figure 7 in exactly the same way we previously constructed the memory-display movie. Instead of issuing the kActionTextTrackPasteText action in response to idle events, we now do so in response to key events. And we get the text to be pasted not from the kOperandFreeMemory operand, but from the kOperandEventParameter operand. Listing 7 shows some of the code we could use to construct the ASCII-display movie. (This code could be substituted for part of Listing 5, for instance.)

Listing 7: Adding a key event

QTAtom     myEventParamAtom = 0;
short      myIndex;

// add an event atom that displays the ASCII code of the pressed key
myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, 
                  kParentAtomIsContainer, kQTEventKey, 
                  kActionTextTrackPasteText, &myActionAtom);
if (myErr != noErr)
   goto bail;

// first parameter: the text to be pasted
myErr = WiredUtils_AddActionParameterAtom(*theActions, 
                  myActionAtom, 1, 0, NULL, &myParameterAtom);
if (myErr != noErr)
   goto bail;

myErr = WiredUtils_AddExpressionContainerAtomType(
                  *theActions, myParameterAtom, 
                  &myExpressionAtom);
if (myErr != noErr)
   goto bail;

myErr = QTInsertChild(*theActions, myExpressionAtom, 
                  kOperandAtomType, 1, 1, 0, NULL, 
                  &myOperandAtom);
if (myErr != noErr)
   goto bail;

myErr = QTInsertChild(*theActions, myOperandAtom, 
                  kOperandEventParameter, 1, 1, 0, NULL, 
                  &myEventParamAtom);
if (myErr != noErr)
   goto bail;

myIndex = EndianS16_NtoB(4);
myErr = QTInsertChild(*theActions, myEventParamAtom, 
                  kActionParameter, 1, 1, sizeof(myIndex), 
                  &myIndex, NULL);

Notice that we add a parameter atom (myEventParamAtom) to the operand atom, to specify the desired index for the event parameter we want to retrieve. In this case, we specify the index 4, to obtain the ASCII character code of the key pressed.

It's also possible to use the kOperandEventParameter operand to retrieve event parameters for mouse-related events, such as mouse-enter and mouse-click events. With mouse events, there are only three available parameters, which are the same as the first three parameters of key events: the horizontal and vertical mouse positions, and the modifier keys currently down. In an earlier article, you may recall, we used the kOperandMouseLocalHLoc and kOperandMouseLocalVLoc operands to get these mouse positions. In QuickTime 5 and later, we can instead use kOperandEventParameter, if we so desire.

Bouncing Sprites

In several of the previous articles, we've seen that we can move a sprite around inside a sprite track by altering its matrix, either directly (by setting the sprite's matrix property) or indirectly (by performing wired actions such as kActionSpriteTranslate). In this section and the next, we'll consider a couple of ways to build on this capability. First, we'll see how to make a sprite bounce around inside of the sprite track's enclosing rectangle; later we'll see how to figure out when two sprites collide with one another. Both of these are ways to give a sprite "physical" properties, so that it appears to react with things around it.

Figure 8 shows (about as well as any static image can, I guess) a sprite bouncing off the track's enclosing rectangle. The sprite is first moving down and to the right; when its bottom edge touches the bottom of the track rectangle, the sprite bounces up and continues to the right. When any other edge of the sprite touches a side of the track rectangle, the sprite does the appropriate thing by moving back away from the side it touched but otherwise continuing in the same direction. (In the event that two of its edges touch two sides of the track rectangle at the same time, the sprite would reverse both its horizontal and vertical directions; this would happen when the sprite moves cleanly into a corner of the track rectangle.)


Figure 8: A sprite bouncing off the movie edge

Moving the Sprite

The first thing we need to do is get the sprite moving. To do this, we can simply change the horizontal and vertical positions of the sprite during idle events. Since we're going to be changing the direction of movement when the sprite collides with the track rectangle, we'll maintain two sprite track variables whose values are the number of pixels in the horizontal and vertical direction that the sprite is to be offset during the next idle event. We'll use these variables IDs:

#define kXMoveVarID                     2000
#define kYMoveVarID                     2100

We can set the initial horizontal and vertical offsets by adding a couple of frame-loaded event actions to the sprite, using our utility WiredUtils_AddSpriteTrackSetVariableAction (defined in "Wired" in MacTech, May 2001):

#define kIdleOffset                     2

myErr = WiredUtils_AddSpriteTrackSetVariableAction(
               mySample, kParentAtomIsContainer, 
               kQTEventFrameLoaded, kXMoveVarID, kIdleOffset, 
               0, NULL, 0);

myErr = WiredUtils_AddSpriteTrackSetVariableAction(
               mySample, kParentAtomIsContainer, 
               kQTEventFrameLoaded, kYMoveVarID, kIdleOffset, 
               0, NULL, 0);

During idle events, we'll move the sprite horizontally by the current value of the kXMoveVarID variable and vertically by the current value of the kYMoveVarID variable. The speed of the moving sprite is determined both by the values of these variables and by the frequency with which we receive idle events (which, you'll recall, is determined by the sprite track's kSpriteTrackPropertyQTIdleEventsFrequency property). For the bouncing sprite movie, we'll tell the sprite media handler to send us an idle event every tick, and (as you can see) we'll offset the sprite in each direction by two pixels during every idle event. We add actions to move the sprite during an idle event like this:

myErr = WiredUtils_AddQTEventAtom(mySpriteData, 
            kParentAtomIsContainer, kQTEventIdle, &myEventAtom);

myErr = QTWired_AddTranslateByVariablesAction(mySpriteData, 
            myEventAtom, kXMoveVarID, kYMoveVarID, NULL);

The QTWired_AddTranslateByVariablesAction function adds to the specified event atom (here, myEventAtom) an action atom that translates the sprite relatively, taking the horizontal and vertical offsets from the sprite track variables whose IDs are specified by the third and fourth parameters (here, kXMoveVarID and kYMoveVarID). Listing 8 shows our definition of the QTWired_AddTranslateByVariablesAction function.

Listing 8: Translating a sprite using variable values

static OSErr QTWired_AddTranslateByVariablesAction (
               QTAtomContainer theSprite, QTAtom theParentAtom, 
               QTAtomID theXVariableID, QTAtomID theYVariableID, 
               QTAtom *theActionAtom)
{
   QTAtom            myActionAtom = 0;
   QTAtom            myExpressionAtom = 0;
   QTAtom            myParamAtom = 0;
   QTAtom            myOperatorAtom = 0;
   QTAtom            myOperandAtom = 0;
   QTAtom            myOperandTypeAtom = 0;
   QTAtomID          myVariableID;
   Boolean           myBoolean;
   OSErr             myErr = paramErr;

   if (theSprite == NULL)
      goto bail;

   // add a translate action atom to the specified parent atom
   myErr = WiredUtils_AddActionAtom(theSprite, theParentAtom, 
            kActionSpriteTranslate, &myActionAtom);
   if (myErr != noErr)
      goto bail;

   // first parameter: get value of variable theXVariableID
   myErr = WiredUtils_AddActionParameterAtom(theSprite, 
            myActionAtom, kFirstParam, 0, NULL, &myParamAtom);
   if (myErr != noErr)
      goto bail;

   myErr = WiredUtils_AddExpressionContainerAtomType
            (theSprite, myParamAtom, &myExpressionAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myExpressionAtom, 
            kOperandAtomType, 0, 1, 0, NULL, &myOperandAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myOperandAtom, 
            kOperandSpriteTrackVariable, 1, 1, 0, NULL, 
            &myOperandTypeAtom);
   if (myErr != noErr)
      goto bail;

   myVariableID = EndianU32_NtoB(theXVariableID);
   myErr = QTInsertChild(theSprite, myOperandTypeAtom, 
            kActionParameter, 1, 1, sizeof(myVariableID), 
            &myVariableID, NULL);
   if (myErr != noErr)
      goto bail;

   // second parameter: get value of variable theYVariableID
   myErr = WiredUtils_AddActionParameterAtom(theSprite, 
            myActionAtom, kSecondParam, 0, NULL, &myParamAtom);
   if (myErr != noErr)
      goto bail;

   myErr = WiredUtils_AddExpressionContainerAtomType
            (theSprite, myParamAtom, &myExpressionAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myExpressionAtom, 
            kOperandAtomType, 0, 1, 0, NULL, &myOperandAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myOperandAtom, 
            kOperandSpriteTrackVariable, 1, 1, 0, NULL, 
            &myOperandTypeAtom);
   if (myErr != noErr)
      goto bail;

   myVariableID = EndianU32_NtoB(theYVariableID);
   myErr = QTInsertChild(theSprite, myOperandTypeAtom, 
            kActionParameter, 1, 1, sizeof(myVariableID), 
            &myVariableID, NULL);
   if (myErr != noErr)
      goto bail;

   // third parameter: false (for relative translation)
   myBoolean = false;
   myErr = WiredUtils_AddActionParameterAtom(theSprite, 
            myActionAtom, kThirdParam, sizeof(myBoolean), 
            &myBoolean, NULL);

bail:
   if (theActionAtom != NULL)
      *theActionAtom = myActionAtom;

   return(myErr);
}

Here we add an action of type kActionSpriteTranslate, which requires three parameters: the desired horizontal translation, the desired vertical translation, and a Boolean that indicates whether the translation is absolute or relative. For the first and second parameters, we add expression container atoms that retrieve the value of the variable with the specified ID. (For more information about expression container atoms, see "Wired" in MacTech, May 2001.)

Detecting Track Rectangle Collisions

So far, so good. But if this were all the wiring we attached to the sprite, we wouldn't get quite the behavior we're looking for. When the movie was first opened, the sprite would begin moving down to the right and would continue moving in that direction forever. We need to add some wiring to figure out when an edge of the sprite hits an edge of the track rectangle and then adjust the direction of movement accordingly. In a nutshell, we want to add some logic that says:

  • If the left edge of the sprite is less than the left edge of the track rectangle, then move the sprite back inside the track rectangle and reverse the horizontal direction of travel.
  • If the right edge of the sprite is greater than the right edge of the track rectangle, then move the sprite back inside the track rectangle and reverse the horizontal direction of travel.
  • If the top edge of the sprite is less than the top edge of the track rectangle, then move the sprite back inside the track rectangle and reverse the vertical direction of travel.
  • If the bottom edge of the sprite is greater than the bottom edge of the track rectangle, then move the sprite back inside the track rectangle and reverse the vertical direction of travel.

It's worth pointing out that a more elegant design would use two "if-else" statements here instead of four "if" statements, since the opposite edges of our sprite cannot both be outside the track bounds at the same time. This refinement would, however, require a more complicated wiring. I'll leave that as an exercise for the persnickety reader.

We know the dimensions of the sprite track (since we created it using the constants kIconSpriteTrackHeight and kIconSpriteTrackWidth). So all we really have to learn now is how to figure out the position of the sprite's edges. The sprite media handler supports these four operands, which give us the information we need:

enum {
   kOperandSpriteBoundsLeft         = 3072,
   kOperandSpriteBoundsTop          = 3073,
   kOperandSpriteBoundsRight        = 3074,
   kOperandSpriteBoundsBottom       = 3075
};

The operand kOperandSpriteBoundsLeft, for instance, returns the left side of the sprite's bounding box (the rectangle that encloses the sprite), in the local coordinate system of the sprite track.

In our code, we'll add the side-bounce logic to the sprite by making four calls to a QTWiredActions function QTWired_AddSideBounceToSprite, like this:

QTWired_AddSideBounceToSprite(mySpriteData, 
         kOperandSpriteBoundsLeft, 1, kOperatorLessThan, 
         kXMoveVarID);

QTWired_AddSideBounceToSprite(mySpriteData, 
         kOperandSpriteBoundsRight, kIconSpriteTrackWidth, 
         kOperatorGreaterThan, kXMoveVarID);

QTWired_AddSideBounceToSprite(mySpriteData, 
         kOperandSpriteBoundsTop, 1, kOperatorLessThan, 
         kYMoveVarID);

QTWired_AddSideBounceToSprite(mySpriteData, 
         kOperandSpriteBoundsBottom, kIconSpriteTrackHeight, 
         kOperatorGreaterThan, kYMoveVarID);

The QTWired_AddSideBounceToSprite function is really quite simple, but (as we've grown to expect when building wired actions) a tad lengthy. Since we've built a few wired event handlers already, let's just survey the main points. First of all, we want to install an idle event atom by using our utility function WiredUtils_AddQTEventAndActionAtoms. The action should be a kActionCase action, since we want to ask whether a side of the sprite lies outside the track rectangle. As we saw in the previous article, a kActionCase action has a single parameter, whose data is an atom of type kConditionalAtomType. This conditional atom, in turn, has two children, an expression container atom and an action list atom.

The expression container atom needs to test whether the specified side of the sprite lies outside the sprite track rectangle. As you can see, when we call QTWired_AddSideBounceToSprite, we pass in the operand that we need to use to select the side, along with the track limit and the test to perform (for instance, kOperatorLessThan). We build the expression container atom data like this:

WiredUtils_AddOperatorAtom(theSprite, myExpressionAtom, 
                  theTest, &myOperatorAtom);

// first operand: the specified side of the sprite
QTInsertChild(theSprite, myOperatorAtom, kOperandAtomType, 1, 
                  1, 0, NULL, &myOperandAtom);

QTInsertChild(theSprite, myOperandAtom, theSide, 1, 1, 0, 
                  NULL, NULL);

// second operand: the specified limit
WiredUtils_AddOperandAtom(theSprite, myOperatorAtom, 
                  kOperandConstant, 2, NULL, theLimit);

The contents of the action list atom are pretty straightforward. Remember that we need to perform two actions: (1) translate the sprite back to the edge of the track rectangle and (2) reverse the direction of travel in the horizontal or vertical dimension. We've already seen how to construct a translate action, so we don't need to repeat that here. We can change the direction of travel simply by negating the value of the variable whose ID is passed to QTWired_AddSideBounceToSprite. We'll do that using another function, QTWired_AddNegateVariableAction, defined in Listing 9.

Listing 9: Negating a sprite track variable

static OSErr QTWired_AddNegateVariableAction (
            QTAtomContainer theSprite, QTAtom theParentAtom, 
            QTAtomID theVariableID)
{
   QTAtom            myActionAtom = 0;
   QTAtom            myExpressionAtom = 0;
   QTAtom            myParamAtom = 0;
   QTAtom            myOperatorAtom = 0;
   QTAtom            myOperandAtom = 0;
   QTAtom            myOperandTypeAtom = 0;
   QTAtomID          myVariableID;
   OSErr             myErr = paramErr;

   if ((theSprite == NULL) || (theParentAtom == 0))
      goto bail;

   myErr = WiredUtils_AddActionAtom(theSprite, theParentAtom, 
                  kActionSpriteTrackSetVariable, &myActionAtom);
   if (myErr != noErr)
      goto bail;
   
   // add parameters to the set variable action: variable ID (QTAtomID) and value (float)
   myVariableID = EndianU32_NtoB(theVariableID);
   myErr = QTInsertChild(theSprite, myActionAtom, 
                  kActionParameter, 0, (short)kFirstParam, 
                  sizeof(myVariableID), &myVariableID, NULL);
   if (myErr != noErr)
      goto bail;
   
   myErr = QTInsertChild(theSprite, myActionAtom, 
                  kActionParameter, 0, (short)kSecondParam, 0, 
                  NULL, &myParamAtom);
   if (myErr != noErr)
      goto bail;
   
   myErr = WiredUtils_AddExpressionContainerAtomType(
                  theSprite, myParamAtom, &myExpressionAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myExpressionAtom, 
                  kOperatorAtomType, kOperatorNegate, 1, 0, NULL, 
                  &myOperatorAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myOperatorAtom, 
                  kOperandAtomType, 0, 1, 0, NULL, 
                  &myOperandAtom);
   if (myErr != noErr)
      goto bail;

   myErr = QTInsertChild(theSprite, myOperandAtom, 
                  kOperandSpriteTrackVariable, 1, 1, 0, NULL, 
                  &myOperandTypeAtom);
   if (myErr != noErr)
      goto bail;

   myVariableID = EndianU32_NtoB(theVariableID);
   myErr = QTInsertChild(theSprite, myOperandTypeAtom, 
                  kActionParameter, 1, 1, sizeof(myVariableID), 
                  &myVariableID, NULL);

bail:
   return(myErr);
}

There's nothing too exciting here; this just says: set the value of the variable whose ID is theVariableID to the result of negating the value of the variable whose ID is theVariableID.

Colliding Sprites

So now we've got sprites bouncing off the walls. Let's get them to bounce off one another as well. In specific, let's create a movie with two sprites, both of which have the bouncing logic that we developed in the previous section. Then, let's add some wiring to make them recoil from one another when part of one sprite touches part of the other. Figure 9 shows two sprites about to collide: the sprite with the new QuickTime logo has just bounced off the bottom and is moving up to the right; the other sprite is moving down to the left. When the sprites collide, we'll reverse the horizontal direction of travel of each sprite unless they are traveling in the same horizontal direction when they collide. And ditto for the vertical directions of travel. This logic gives the sprites a nice feel as they bounce around.


Figure 9: Two sprites colliding

But how do we know when two sprites collide? Well, we know the coordinates of each of the corners of a sprite (using the operands we encountered in the previous section). So a reasonable strategy might be to ask, for each corner of the sprite, whether it lies on top of the other sprite. QuickTime 5 introduced a very useful operand for this, the kOperandSpriteTrackSpriteIDAtPoint operand. This operand takes two parameters, which are the horizontal and vertical coordinates of a point; it returns the ID of the topmost sprite at that point, if any.

In the case where we have just two sprites that can collide, we need to attach the collision logic to only one of the sprites. That sprite (let's call it the collider) can check, on every idle event, whether any of its four corners has come into contact with any part of the other sprite. That is, we'll call kOperandSpriteTrackSpriteIDAtPoint four times, each time passing one of the four corners of the collider. QTWiredActions uses the QTWired_AddCollisionLogicToSprite function (defined in Listing 10) to attach the collision logic to the collider.

Listing 10: Wiring a sprite for collisions

OSErr QTWired_AddCollisionLogicToSprite 
                                    (QTAtomContainer theSprite)
{
   OSErr            myErr = noErr;

   myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, 
         kOperandSpriteBoundsLeft, kOperandSpriteBoundsTop);
   if (myErr != noErr)
      goto bail;

   myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, 
         kOperandSpriteBoundsRight, kOperandSpriteBoundsTop);
   if (myErr != noErr)
      goto bail;

   myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, 
         kOperandSpriteBoundsLeft, kOperandSpriteBoundsBottom);
   if (myErr != noErr)
      goto bail;

   myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, 
         kOperandSpriteBoundsRight, kOperandSpriteBoundsBottom);

bail:
   return(myErr);
}

We won't bother to dissect QTWired_AddCornerCollisionLogicToSprite, as it mostly covers routine ground while setting a new record for length (almost 300 lines of code and comments). The key step is using kOperandSpriteTrackSpriteIDAtPoint, as described above. You should know that kOperandSpriteTrackSpriteIDAtPoint does its work by hit-testing for sprites in the sprite track (probably using SpriteMediaHitTestAllSprites, which we used in an earlier article). So a sprite ID will be returned as the operand's value only if some non-transparent part of a sprite is situated at the specified point.

Our collision logic as developed so far is pretty good, but alas not perfect. Recall that we are testing each of the four corners of the collider sprite's bounding rectangle, to see if it lies on top of some sprite. If the collider's sprite image at that corner is non-transparent, then the call to kOperandSpriteTrackSpriteIDAtPoint will always return a non-zero value (since the sprite hit test will find that corner, even if the other sprite is nowhere near the collider). We can solve this problem by ensuring that the other sprite has a lower layer property than the collider (so that we get its ID if the other sprite and the collider overlap at the tested point) and by ignoring the collider's ID if kOperandSpriteTrackSpriteIDAtPoint returns it to us.

Another problem arises if the collider's sprite image is transparent at a corner whose coordinates are passed to kOperandSpriteTrackSpriteIDAtPoint. In this case, it's possible for that corner to lie on top of a non-transparent part of the other sprite and hence trigger a hit, even though the sprites do not appear to be touching. Figure 10 illustrates this possibility. In this case, the collider is the top-right sprite, and its lower-left corner is transparent. kOperandSpriteTrackSpriteIDAtPoint will return the ID of the other sprite, because the collider's lower left corner does in fact lie on top of a non-transparent pixel of the other sprite.


Figure 10: Testing a transparent corner of the collider sprite

I don't see an easy way to solve this problem, but all in all it's a fairly minor one. For most purposes, I suspect, it's good enough to have the sprites recoil from one another using the simple wiring we've developed here. More complex collision algorithms are of course left as exercises for the reader.

Conclusion

In this article, we've seen how to add wiring to text tracks and we've investigated a few of the text-related actions and operands added in QuickTime 5. We've also touched briefly on the new key event (of type kQTEventKey) and seen for the first time the technique for retrieving event parameters. Finally, we've added some wiring to allow sprites to collide with the track rectangle and with one another.

With all of this, we've reached the end of our "mini-series" on sprites and wired actions. We'll revisit them in the future, however, especially when we learn how to work with QuickTime VR movies and Flash tracks. In the next article, though, we'll shift gears and move on to consider some new and exciting QuickTime capabilities.

Credits

Thanks are due to Bill Wright for clarifying some issues with text wiring.


Tim Monroe works in the QuickTime engineering team at Apple Computer, Inc. You can contact him at monroe@apple.com.

 
AAPL
$112.94
Apple Inc.
+1.16
MSFT
$47.98
Microsoft Corpora
+0.32
GOOG
$524.87
Google Inc.
+8.52

MacTech Search:
Community Search:

Software Updates via MacUpdate

NeoOffice 2014.6 - Mac-tailored, OpenOff...
NeoOffice is a complete office suite for OS X. With NeoOffice, users can view, edit, and save OpenOffice documents, PDF files, and most Microsoft Word, Excel, and PowerPoint documents. NeoOffice 3.x... Read more
LibreOffice 4.3.5.2 - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
CleanApp 5.0.0 Beta 5 - Application dein...
CleanApp is an application deinstaller and archiver.... Your hard drive gets fuller day by day, but do you know why? CleanApp 5 provides you with insights how to reclaim disk space. There are... Read more
Monolingual 1.6.2 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
NetShade 6.1 - Browse privately using an...
NetShade is an Internet security tool that conceals your IP address on the web. NetShade routes your Web connection through either a public anonymous proxy server, or one of NetShade's own dedicated... Read more
calibre 2.13 - Complete e-library manage...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
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

Latest Forum Discussions

See All

DICETINY is coming to iOS in 2015
DICETINY is coming to iOS in 2015 Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] Fakedice has announced that they’re bringing their digital board game DICETINY to iOS. | Read more »
Living Room 3D for IKEA Makes All In-App...
Living Room 3D for IKEA Makes All In-App Purchases Free Until 2015 Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] | Read more »
Crossbow Warrior – The Legend of William...
Crossbow Warrior – The Legend of William Tell Review By Lee Hamlet on December 22nd, 2014 Our Rating: :: MISSES THE MARKUniversal App - Designed for iPhone and iPad Crossbow Warrior details the entertaining adventures of legendary... | Read more »
Workflow: Powerful Automation Made Simpl...
Workflow: Powerful Automation Made Simple Review By Campbell Bird on December 22nd, 2014 Our Rating: :: GO WITH THE FLOWUniversal App - Designed for iPhone and iPad This powerful app lets users accomplish multiple tasks at touch of... | Read more »
My Little Monster Review
My Little Monster Review By Jordan Minor on December 22nd, 2014 Our Rating: :: IT'S ALIVE!Universal App - Designed for iPhone and iPad What’s it worth to make your own monster?   | Read more »
Galaxy Trucker Pocket (Games)
Galaxy Trucker Pocket 1.0.8 Device: iOS iPhone Category: Games Price: $2.99, Version: 1.0.8 (iTunes) Description: Galaxy Truckers Wanted!================================================================= (5/5) "Galaxy Trucker isn’t... | Read more »
Make your own Tribez Figures (and More)...
Make your own Tribez Figures (and More) with Toyze Posted by Jessica Fisher on December 19th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
So Many Holiday iOS Sales Oh My Goodness...
The holiday season is in full-swing, which means a whole lot of iOS apps and games are going on sale. A bunch already have, in fact. Naturally this means we’re putting together a hand-picked list of the best discounts and sales we can find in order... | Read more »
It’s Bird vs. Bird in the New PvP Mode f...
It’s Bird vs. Bird in the New PvP Mode for Angry Birds Epic Posted by Jessica Fisher on December 19th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Telltale Games and Mojang Announce Minec...
Telltale Games and Mojang Announce Minecraft: Story Mode – A Telltale Games Series Posted by Jessica Fisher on December 19th, 2014 [ permalink ] | Read more »

Price Scanner via MacPrices.net

13-inch 2.4GHz Retina MacBook Pro (Apple refu...
The Apple Store has previous-generation Apple Certified Refurbished 13″ 2.4GHz/128GB Retina MacBook Pros available for $999. Apple’s one-year warranty is standard, and shipping is free: - 13″ 2.4GHz/... Read more
Apple resellers offer free overnight shipping
The Apple Store is now offering free next-day shipping on all in stock items if ordered before 12/23/14 at 10:00am PT. Local store pickup is also available within an hour of ordering for any in stock... Read more
Holiday sales continue: MacBook Airs for up t...
 B&H Photo has 2014 MacBook Airs on sale for up to $120 off MSRP, for a limited time, for the Thanksgiving/Christmas Holiday shopping season. Shipping is free, and B&H charges NY sales tax... Read more
Holiday sale continues: 13-inch Retina MacBoo...
 B&H Photo has new 13″ MacBook Pros on sale for up to $150 off MSRP as part of their Holiday pricing. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.5GHz MacBook Pro: $979 save... Read more
Holiday sale continues: 15-inch Retina MacBoo...
 B&H Photo has the new 2014 15″ Retina MacBook Pros on sale for up to $300 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina MacBook Pro: $... Read more
Holiday sale: 13-inch 128GB MacBook Air for $...
 Best Buy has the 2014 13-inch 1.4GHz 128GB MacBook Air on sale for $849.99, or $150 off MSRP, on their online store. Choose free home shipping or free local store pickup (if available). Price valid... Read more
13-inch 2.6GHz Retina MacBook Pro on sale for...
Best Buy has lowered their price on the 2014 13″ 2.6GHz/128GB Retina MacBook Pro to $1149.99 on their online store for a limited time. That’s $150 off MSRP and the lowest price available for this... Read more
Kodak Returns to CES With New Consumer Produ...
Former photography colossus Kodak is returning to CES for the first time in three years where the Kodak booth (#21818 South Hall 1) will showcase a wide range of innovative, imaging-related products... Read more
Invaluable Launches New Eponymously -Named A...
Invaluable, the world’s largest online live auction marketplace, hhas announced the official launch of the Invaluable app for iPad, now available for download in the iTunes App Store. Invaluable... Read more
IDC Reveals Worldwide Mobile Enterprise Appli...
International Data Corporation (IDC) last week hosted the IDC FutureScape: Worldwide Mobile Enterprise Applications and Solutions 2015 Predictions Web conference. The session provided organizations... Read more

Jobs Board

*Apple* Store Leader Program (US) - Apple, I...
…Summary Learn and grow as you explore the art of leadership at the Apple Store. You'll master our retail business inside and out through training, hands-on experience, Read more
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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.