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
$106.98
Apple Inc.
+0.00
MSFT
$46.05
Microsoft Corpora
+0.00
GOOG
$550.31
Google Inc.
+0.00

MacTech Search:
Community Search:

Software Updates via MacUpdate

Dropbox 2.10.44 - Cloud synchronization...
Dropbox is an application that creates a special Finder folder that automatically syncs online and between your computers. It allows you to both backup files and keep them up-to-date between systems... Read more
Sandvox 2.9.2 - Easily build eye-catchin...
Sandvox is for Mac users who want to create a professional looking website quickly and easily. With Sandvox, you don't need to be a Web genius to build a stylish, feature-rich, standards-compliant... Read more
Cocktail 8.0.1 - General maintenance and...
Cocktail is a general purpose utility for OS X that lets you clean, repair and optimize your Mac. It is a powerful digital toolset that helps hundreds of thousands of Mac users around the world get... Read more
LibreOffice 4.3.3.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
VMware Fusion 7.0.1 - Run Windows apps a...
VMware Fusion allows you to create a Virtual Machine on your Mac and run Windows (including Windows 8.1) and Windows software on your Mac. Run your favorite Windows applications alongside Mac... Read more
OneNote 15.3.2 - Free digital notebook f...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more
Audio Hijack Pro 2.11.4 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Iridient Developer 3.0.0 beta 3 - Powerf...
Iridient Developer (was RAW Developer) is a powerful image conversion application designed specifically for OS X. Iridient Developer gives advanced photographers total control over every aspect of... Read more
TextWrangler 4.5.11 - Free general purpo...
TextWrangler is the powerful general purpose text editor, and Unix and server administrator's tool. Oh, and also, like the best things in life, it's free. TextWrangler is the "little brother" to... Read more
NeoFinder 6.6 - Catalog your external me...
NeoFinder (formerly CDFinder) rapidly organizes your data, either on external or internal disks, or any other volumes. It catalogs all your data, so you stay in control of your data archive or disk... Read more

Latest Forum Discussions

See All

Night Sky Pro™ (Reference)
Night Sky Pro™ 3.0.1 Device: iOS Universal Category: Reference Price: $2.99, Version: 3.0.1 (iTunes) Description: Night Sky Pro™Wonder No More™ Night Sky Pro™ is the ultimate stargazing experience. From the creators of the original... | Read more »
Audio Defence : Zombie Arena (Games)
Audio Defence : Zombie Arena 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: A zombie shooter audio game. Made from gut-wrenching 3D binaural sound, for a new kind of weird immersion. You... | Read more »
RPG Asdivine Hearts (Games)
RPG Asdivine Hearts 1.1.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.1.0 (iTunes) Description: SPECIAL PRICE50% OFF (USD 7.99 -> USD 3.99)!!! Travel alongside four companions and a cat in the adventure of a... | Read more »
Haunt the House: Terrortown (Games)
Haunt the House: Terrortown 1.0.1 Device: iOS Universal Category: Games Price: $.99, Version: 1.0.1 (iTunes) Description: 66.6% OFF! SPECIAL SPOOKY HALLOWEEN LAUNCH PRICE! 66.6% OFF! ...What was that sound? Is somebody there? | Read more »
SAS: Zombie Assault 4 Review
SAS: Zombie Assault 4 Review By Jennifer Allen on October 30th, 2014 Our Rating: :: FLAWED SHOOTERUniversal App - Designed for iPhone and iPad Shoot everything that moves in this fun, if flawed, twin-stick shooter.   | Read more »
Naailde the Witch Review
Naailde the Witch Review By Amy Solomon on October 30th, 2014 Our Rating: :: PITCH-PERFECT STORYTELLINGUniversal App - Designed for iPhone and iPad Marvelous storytelling, narration, and moving illustrations make this storybook... | Read more »
1st & Goal Review
1st & Goal Review By Andrew Fisher on October 30th, 2014 Our Rating: :: FOR THE D&D LOVING QBUniversal App - Designed for iPhone and iPad 1st & Goal is a board gamer’s football game, a football fan’s board game, and... | Read more »
French Developer Pated Unveils Seashine
French Developer Pated Unveils Seashine Posted by Ellis Spice on October 30th, 2014 [ permalink ] French one-man studio Pated has unveiled Seashine, “a poetic journey into the abyss.” Players take on the role of a jellyfish strugglin | Read more »
Agents of Storm: Tips, Tricks, and Strat...
Calling all agents: Would you like to see what we thought of this rather pretty base builder? Check out our Agents of Storm review! Have you downloaded Agents of Storm, been bowled over by the graphics, and aren’t quite sure what to do next? Never... | Read more »
Any.DO 2.0 Hopes to Help Manage Producti...
Any.DO 2.0 Hopes to Help Manage Productivity Posted by Ellis Spice on October 30th, 2014 [ permalink ] iPhone App - Designed for the iPhone, compatible with the iPad | Read more »

Price Scanner via MacPrices.net

Apple Regains Momentum As Windows Stutters An...
The latest smartphone sales data from Kantar Worldpanel ComTech, for the three months to March 2014, shows Apple performing strongly in the first quarter of the year, with sales bouncing back in... Read more
Worldwide Smartphone Shipments Increase 25.2%...
New smartphone releases and an increased emphasis on emerging markets drove global smartphone shipments above 300 million units for the second consecutive quarter, according to preliminary data from... Read more
Apple now offering refurbished 2014 15-inch M...
The Apple Store is now offering Apple Certified Refurbished 2014 15″ Retina MacBook Pros for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Apple drops prices on refurbished 2013 Retina...
The Apple Store has dropped prices on 2013 Apple Certified Refurbished 13″ and 15″ Retina MacBook Pros, with Retina models now available starting at $999. Apple’s one-year warranty is standard, and... Read more
New 2.8GHz Mac mini on sale for $949, save $5...
Abt Electronics has the new 2.8GHz Mac mini in stock and on sale for $949.05 including free shipping. Their price is $50 off MSRP, and it’s the lowest price available for this model from any reseller... Read more
Sale! 3.7GHz Quad Core Mac Pro available for...
 B&H Photo has the 3.7GHz Quad Core Mac Pro on sale for $2649 including free shipping plus NY sales tax only. Their price is $350 off MSRP, and it’s the lowest price for this model from any... Read more
Mujjo Steps Up The Game With Refined Touchscr...
Netherlands based Mujjo have just launched their Refined Touchscreen Gloves, stepping up their game. The gloves feature a updated elegant design that takes these knitted gloves to the next level. A... Read more
Sale! Preorder the new 27-inch 5K iMac for $2...
 Abt Electronics has the new 27″ 3.5GHz 5K iMac on sale and available for preorder for $2374.05 including free shipping. Their price is $125 off MSRP, and it’s the lowest price available for this... Read more
Simplex Solutions Inc. Brings Secure Web Surf...
New York based Simplex Solutions Inc. has announced the release and immediate availability of Private Browser 1.0, its revolutionary new secure web browser developed for iPhone, iPad and iPod touch... Read more
Save up to $180 off MSRP with an Apple refurb...
The Apple Store has Apple Certified Refurbished 2014 MacBook Airs available for up to $180 off the cost of new models. An Apple one-year warranty is included with each MacBook, and shipping is free.... Read more

Jobs Board

Position Opening at *Apple* - Apple (United...
**Job Summary** Every day, business customers come to the Apple Store to discover what powerful, easy-to-use Apple products can do for them. As a Business Leader, Read more
Sr. Manager, *Apple* Deployment Programs fo...
**Job Summary** Apple is seeking candidates for a new position on the Education Content and Technology team. iPad and Mac is in the hands of millions of teachers and Read more
*Apple* Solutions Consultant (ASC) - Apple I...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
*Apple* Solutions Consultant (ASC) - Apple I...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
*Apple* Solutions Consultant (ASC) - Apple I...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.