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
$102.64
Apple Inc.
+1.58
MSFT
$46.56
Microsoft Corpora
-0.50
GOOG
$581.13
Google Inc.
-6.24

MacTech Search:
Community Search:

Software Updates via MacUpdate

GarageSale 6.8 - Create outstanding eBay...
GarageSale is a slick, full-featured client application for the eBay online auction system. Create and manage your auctions with ease. With GarageSale, you can create, edit, track, and manage... Read more
ScreenFlow 4.5.3 - Create screen recordi...
Save 5% with the MacUpdate coupon code: 68031AE15F -- Buy now! ScreenFlow is powerful, easy-to-use screencasting software for the Mac. With ScreenFlow you can record the contents of your entire... Read more
NeoOffice 2014.3 - 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
Typinator 6.2 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
PopChar X 6.7 - Floating window shows av...
PopChar X helps you get the most out of your font collection. With its crystal-clear interface, PopChar X provides a frustration-free way to access any font's special characters. Expanded... Read more
Evernote 5.6.0 - Create searchable notes...
Evernote allows you to easily capture information in any environment using whatever device or platform you find most convenient, and makes this information accessible and searchable at anytime, from... Read more
Monosnap 2.2.2 - Versatile screenshot ut...
Monosnap allows you to save screenshots easily, conveniently, and quickly, sharing them with friends and colleagues at once. It's the ideal choice for anyone who is looking for a smart and fast... Read more
Tunnelblick 3.4beta36 - GUI for OpenVPN...
Tunnelblick is a free, open source graphic user interface for OpenVPN on OS X. It provides easy control of OpenVPN client and/or server connections. It comes as a ready-to-use application with all... Read more
SoftRAID 5.0.4 - High-quality RAID manag...
SoftRAID allows you to create and manage disk arrays to increase performance and reliability. SoftRAID's intuitive interface and powerful feature set makes this utility a must have for any Mac OS X... Read more
Audio Hijack Pro 2.11.3 - 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

Latest Forum Discussions

See All

Manual – Custom exposure camera (Photog...
Manual – Custom exposure camera 1.0 Device: iOS iPhone Category: Photography Price: $1.99, Version: 1.0 (iTunes) Description: Custom exposure for your iPhone camera. A powerful camera app with full control over your image. Quickly... | Read more »
Why I Don’t Want to Upgrade to the iPhon...
I’ve been living with my iPhone 4S for the past two years or so, and if I was living in a world where I wasn’t bombarded with new phone announcements and people of the general public caring enough to upgrade constantly, I wouldn’t think my phone... | Read more »
Tictail Review
Tictail Review By Jennifer Allen on September 23rd, 2014 Our Rating: :: CLASSY SHOPPINGiPhone App - Designed for the iPhone, compatible with the iPad Tictail is an attractive and stylish way of looking for some great new clothes... | Read more »
Super Glyph Quest is Bringing More Match...
Super Glyph Quest is Bringing More Match-3 Magics to the App Store Soon Posted by Jessica Fisher on September 23rd, 2014 [ permalink ] Fans of Glyph Quest, by Alex Trowers and Leanne Bayley, | Read more »
Sword King Review
Sword King Review By Jennifer Allen on September 23rd, 2014 Our Rating: :: WEAK, SO WEAKUniversal App - Designed for iPhone and iPad Ever wanted to tap on a screen and assume you’ve killed a monster or two? Probably not, but just... | Read more »
Pangea Software Unveil Bundles and iOS 8...
Pangea Software Unveil Bundles and iOS 8 Updates Posted by Ellis Spice on September 23rd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Race Team Manager Review
Race Team Manager Review By Jennifer Allen on September 23rd, 2014 Our Rating: :: LIGHT RACINGUniversal App - Designed for iPhone and iPad Want to be in charge of a racing team but not be overwhelmed by tactics and options? Race... | Read more »
Kobojo Works with Creative Minds from Fi...
Kobojo has announced details for their newest game, Zodiac – a 2D persistent online RPG. Kobojo has put together a crack team of developers including composer Hitoshi Sakimoto and scenario writer Kazushige Nojima, whose work includes many of the... | Read more »
PlayHaus Review
PlayHaus Review By Amy Solomon on September 23rd, 2014 Our Rating: iPad Only App - Designed for the iPad PlayHaus is a interesting, stylish app for young children, full of cause-and-effect interactions.   | Read more »
Astropolo Review
Astropolo Review By Amy Solomon on September 23rd, 2014 Our Rating: Universal App - Designed for iPhone and iPad Astropolo is a space-themed children’s app with a great sense of style.   | Read more »

Price Scanner via MacPrices.net

Razer DeathAdder Chroma Gaming Mouse Upgraded...
Razer has announced the launch of their new Razer DeathAdder Chroma gaming mouse. Even if you’re not a gamer, the DeathAdder bears considering. I’m a fan of the hard-wired tracking accuracy,... Read more
Check Apple prices on your device with iTracx
MacPrices is proud to offer readers a free iOS app (iPhones, iPads, & iPod touch) and Android app (Google Play and Amazon App Store) called iTracx, which allows you to glance at today’s lowest... Read more
Refurbished 2013 MacBook Pros available for u...
The Apple Store has Apple Certified Refurbished 13″ and 15″ MacBook Pros available starting at $929. Apple’s one-year warranty is standard, and shipping is free: - 13″ 2.5GHz MacBook Pros (4GB RAM/... Read more
New iPhones Score Big in SquareTrade Breakabi...
SquareTrade has announced the iPhone 6 and its larger sibling, iPhone 6 Plus, performed impressively in Breakability testing, and each carries the top Breakability Score in their respective category... Read more
10 Million + First Weekend Sales Set New iPho...
Apple has announced it sold over 10 million new iPhone 6 and iPhone 6 Plus models, a new record, just three days after the launch on September 19. iPhone 6 and iPhone 6 Plus are now available in the... Read more
Betty Crocker Launches New Cookbook for iOS
Betty Crocker, a General Mills brand, an established food industry leader, has announced its free digital cookbook app has been refreshed to make cooking with iPhone, iPad and iPod touch even easier... Read more
Apple restocks some refurbished 2014 MacBook...
The Apple Store has restocked some Apple Certified Refurbished 2014 MacBook Airs, with prices starting at $769. An Apple one-year warranty is included with each MacBook, and shipping is free. These... Read more
13-inch 128GB MacBook Air on sale for $949, s...
B&H Photo has the new 2014 13″ 1.4GHz/128GB MacBook Air on sale for $949.99 including free shipping plus NY tax only. Their price is $50 off MSRP. B&H will also include free copies of... Read more
Apple offering free $25 iTunes Gift Card with...
The Apple Store is offering a free $25 iTunes Gift Card with the purchase of a $99 Apple TV for a limited time. Shipping is free. Read more
Apple refurbished iPod touch available for up...
The Apple Store has Apple Certified Refurbished 5th generation iPod touches available starting at $149. Apple’s one-year warranty is included with each model, and shipping is free. Most colors are... Read more

Jobs Board

*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
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work 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.