Completed Lab/QTWiredSprites.c

//////////
//
//  File:       QTWiredSprites.c
//
//  Contains:   QuickTime wired sprites support for QuickTime movies.
//
//  Written by: Sean Allen
//  Revised by: Chris Flick and Tim Monroe
//              Based (heavily!) on the existing MakeActionSpriteMovie.c code written by Sean Allen.
//
//  Copyright:  © 1997-1998 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <5>      03/20/00    rtm     made changes to get things running under CarbonLib
//     <4>      07/30/99    rtm     added QTWired_AddCursorChangeOnMouseOver to illustrate new
//                                  sprite-as-button behaviors added in QT4; added this action to penguin one
//     <3>      09/30/98    rtm     tweaked call to AddMovieResource to create single-fork movies
//     <2>      03/26/98    rtm     made fixes for Windows compiles
//     <1>      03/25/98    rtm     first file; integrated existing code with shell framework
//     
//
//  This sample code creates a wired sprite movie containing one sprite track. The sprite track contains
//  six sprites: two penguins and four buttons.
//  
//  The four buttons are initially invisible. When the mouse enters (or "rolls over") a button, it appears.
//  When the mouse is clicked inside a button, its image changes to its "pressed" image. When the mouse
//  is released, its image changes back to its "unpressed" image. If the mouse is released inside the button, 
//  an action is triggered. The buttons perform the actions of go to beginning of movie, step backward,
//  step forward, and go to end of movie.
//  
//  The first penguin shows all of the buttons when the mouse enters it, and hides them when the mouse exits.
//  The first penguin is the only sprite that has properties that are overriden by the override sprite samples.
//  These samples override its matrix (in order to move it) and its image index (in order to make it "waddle").
//  
//  When the mouse is clicked on the second penguin, it changes its image index to it's "eyes closed" image.
//  When the mouse is released, it changes back to its normal image. This makes it appear to blink when clicked on.
//  When the mouse is released over the penguin, several actions are triggered. Both penguins' graphics states are 
//  toggled between copyMode and blendMode, and the movie's rate is toggled between zero and one.
//  
//  The second penguin moves once per second. This occurs whether the movie's rate is currently zero or one,
//  because it is being triggered by a gated idle event. When the penguin receives the idle event, it changes
//  its matrix using an action which uses min, max, delta, and wraparound options.
//
//  The movie's looping mode is set to palindrome by a frame-loaded action.
//
//  So, our general strategy is as follows (though perhaps not in the order listed):
//
//      (1) Create a new movie file with a single sprite track.
//      (2) Assign the "no controller" movie controller to the movie.
//      (3) Set the sprite track's background color, idle event frequency, and hasActions properties.
//      (4) Convert our PICT resources to animation codec images with transparency.
//      (5) Create a key frame sample containing six sprites and all of their shared images.
//      (6) Assign the sprites their initial property values.
//      (7) Create a frameLoaded event for the key frame.
//      (8) Create some override samples that override the matrix and image index properties of
//          the first penguin sprite.
//
//  NOTES:
//      
//  *** (1) ***
//  There are event types other that mouse related events (for instance, Idle and FrameLoaded).
//  Idle events are independent of the movie's rate, and they can be gated so they are send at most
//  every n ticks. In our sample movie, the second penguin moves when the movie's rate is zero,
//  and moves only once per second because of the value of the sprite track's idleEventFrequencey property.
//      
//  *** (2) ***
//  Multiple actions may be executed in response to a single event. In our sample movie, rolling over
//  the first penguin shows and hides four different buttons.
//      
//  *** (3) ***
//  Actions may target any sprite or track in the movie. In our sample movie, clicking on one penguin
//  changes the graphics mode of the other.
//      
//  *** (4) ***
//  Conditional and looping control structures are supported. In our sample movie, the second penguin
//  uses the "case statement" action.
//      
//  *** (5) ***
//  Sprite track variables that have not been set have a default value of zero. (The second penguin's
//  conditional code relies on this.)
//      
//  *** (6) ***
//  Wired sprites were previously known as "action sprites". Don't let the names of some of the utility
//  functions confuse you. We'll try to update the source code as time permits.
//      
//  *** (7) ***
//  Penguins don't fly, but I hear they totally shred halfpipes on snowboards.
//
//////////
 
 
// header files
#include "QTWiredSprites.h"
 
 
//////////
//
// QTWired_CreateWiredSpritesMovie
// Create a QuickTime movie containing a wired sprites track.
//
//////////
 
OSErr QTWired_CreateWiredSpritesMovie (void)
{
    short                   myResRefNum = 0;
    short                   myResID = movieInDataForkResID;
    Movie                   myMovie = NULL;
    Track                   myTrack;
    Media                   myMedia;
    FSSpec                  myFile;
    Boolean                 myIsSelected = false;
    Boolean                 myIsReplacing = false;  
    StringPtr               myPrompt = QTUtils_ConvertCToPascalString(kWiredSavePrompt);
    StringPtr               myFileName = QTUtils_ConvertCToPascalString(kWiredSaveFileName);
    QTAtomContainer         mySample = NULL;
    QTAtomContainer         myActions = NULL;
    QTAtomContainer         myBeginButton, myPrevButton, myNextButton, myEndButton;
    QTAtomContainer         myPenguinOne, myPenguinTwo, myPenguinOneOverride;
    QTAtomContainer         myBeginActionButton, myPrevActionButton, myNextActionButton, myEndActionButton;
    QTAtomContainer         myPenguinOneAction, myPenguinTwoAction;
    RGBColor                myKeyColor;
    Point                   myLocation;
    short                   isVisible, myLayer, myIndex, myID, i, myDelta;
    Boolean                 hasActions;
    long                    myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
    OSType                  myType = FOUR_CHAR_CODE('none');
    UInt32                  myFrequency;
    QTAtom                  myEventAtom;
    long                    myLoopingFlags;
    ModifierTrackGraphicsModeRecord     myGraphicsMode;
    OSErr                   myErr = noErr;
 
    //////////
    //
    // create a new movie file and set its controller type
    //
    //////////
 
    // ask the user for the name of the new movie file
    QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
    if (!myIsSelected)
        goto bail;
 
 
    // create a movie file for the destination movie
    myErr = CreateMovieFile(&myFile,                /* fsspec for the movie to be created */
                            FOUR_CHAR_CODE('TVOD'), /* creator value for the new file */
                            smSystemScript,         /* script in which the movie file should be created */
                            myFlags,                /* movie creation flags */
                            &myResRefNum,           /* file reference for movie file return here */
                            &myMovie);              /* new movie identifier return here */
    if (myErr != noErr)
        goto bail;
    
    // select the "no controller" movie controller option
    myType = EndianU32_NtoB(myType);
    SetUserDataItem(GetMovieUserData(myMovie),
                    &myType,
                    sizeof(myType),
                    kUserDataMovieControllerType,
                    1);
    
    //////////
    //
    // create the sprite track and media
    //
    //////////
    
    myTrack = NewMovieTrack(myMovie,
                            ((long)kSpriteTrackWidth << 16),    /* display width in pixels */
                            ((long)kSpriteTrackHeight << 16),   /* display height in pixels */
                            kNoVolume);                         /* volume setting of the track */
    myMedia = NewTrackMedia(myTrack,
                            SpriteMediaType,        /* type of media to create */
                            kSpriteMediaTimeScale,  /* media time scale */
                            NULL,                   /* data ref, pass nil to use the file the movie was created in */
                            0);                     /* data ref type, pass nil to use the file the movie was created in */
 
 
    //////////
    //
    // create a key frame sample containing six sprites and all of their shared images
    //
    //////////
 
    // create a new, empty key frame sample
    myErr = QTNewAtomContainer(&mySample);
    if (myErr != noErr)
        goto bail;
 
    myKeyColor.red = 0xffff;                        // white
    myKeyColor.green = 0xffff;
    myKeyColor.blue = 0xffff;
 
    // add images to the key frame sample
    AddPICTImageToKeyFrameSample(mySample,                      /* the key frame sample */
                                kGoToBeginningButtonUp,         /* pict resource id */
                                &myKeyColor,                    /* RGBColor that should be used as the transparency color */
                                kGoToBeginningButtonUpIndex,    /* atom id for the kSpriteImageAtomType atom */
                                NULL,                           /* optional sprite registration point */
                                NULL);                          /* optional sprite image name */
    AddPICTImageToKeyFrameSample(mySample, kGoToBeginningButtonDown, &myKeyColor, kGoToBeginningButtonDownIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kGoToEndButtonUp, &myKeyColor, kGoToEndButtonUpIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kGoToEndButtonDown, &myKeyColor, kGoToEndButtonDownIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kGoToPrevButtonUp, &myKeyColor, kGoToPrevButtonUpIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kGoToPrevButtonDown, &myKeyColor, kGoToPrevButtonDownIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kGoToNextButtonUp, &myKeyColor, kGoToNextButtonUpIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kGoToNextButtonDown, &myKeyColor, kGoToNextButtonDownIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kPenguinForward, &myKeyColor, kPenguinForwardIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kPenguinLeft, &myKeyColor, kPenguinLeftIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kPenguinRight, &myKeyColor, kPenguinRightIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kPenguinClosed, &myKeyColor, kPenguinClosedIndex, NULL, NULL);
 
    for (myIndex = kPenguinDownRightCycleStartIndex, myID = kWalkDownRightCycleStart; myIndex <= kPenguinDownRightCycleEndIndex; myIndex++, myID++)
        AddPICTImageToKeyFrameSample(mySample, myID, &myKeyColor, myIndex, NULL, NULL);
 
 
    /* assign group IDs to the images - you must assign group IDs to your sprite sample
        if you want a sprite to display images with non-equivalent image descriptions 
        (i.e., images with different dimensions) */
    AssignImageGroupIDsToKeyFrame(mySample);
 
    
    //////////
    //
    // add samples to the sprite track's media
    //
    //////////
    
    BeginMediaEdits(myMedia);
 
    // go to beginning button with no actions
    myErr = QTNewAtomContainer(&myBeginButton);
    if (myErr != noErr)
        goto bail;
    myLocation.h    = (1 * kSpriteTrackWidth / 8) - (kStartEndButtonWidth / 2);
    myLocation.v    = (4 * kSpriteTrackHeight / 5) - (kStartEndButtonHeight / 2);
    isVisible       = false;
    myLayer         = 1;
    myIndex         = kGoToBeginningButtonUpIndex;
    myErr = SetSpriteData(myBeginButton,
                        &myLocation,    /* sprite location */
                        &isVisible,     /* sprite visible property */
                        &myLayer,       /* sprite layer (smaller #'s are further forward) */
                        &myIndex,       /* sprite index property */
                        NULL,
                        NULL,
                        myActions);
    if (myErr != noErr)
        goto bail;
 
    // go to previous button with no actions
    myErr = QTNewAtomContainer(&myPrevButton);
    if (myErr != noErr)
        goto bail;
    myLocation.h    = (3 * kSpriteTrackWidth / 8) - (kNextPrevButtonWidth / 2);
    myLocation.v    = (4 * kSpriteTrackHeight / 5) - (kStartEndButtonHeight / 2);
    isVisible       = false;
    myLayer         = 1;
    myIndex         = kGoToPrevButtonUpIndex;
    myErr = SetSpriteData(myPrevButton, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, myActions);
    if (myErr != noErr)
        goto bail;
 
    // go to next button with no actions
    myErr = QTNewAtomContainer(&myNextButton);
    if (myErr != noErr)
        goto bail;
    myLocation.h    = (5 * kSpriteTrackWidth / 8) - (kNextPrevButtonWidth / 2);
    myLocation.v    = (4 * kSpriteTrackHeight / 5) - (kStartEndButtonHeight / 2);
    isVisible       = false;
    myLayer         = 1;
    myIndex         = kGoToNextButtonUpIndex;
    myErr = SetSpriteData(myNextButton, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, myActions);
    if (myErr != noErr)
        goto bail;
 
    // go to end button with no actions
    myErr = QTNewAtomContainer(&myEndButton);
    if (myErr != noErr)
        goto bail;
    myLocation.h    = (7 * kSpriteTrackWidth / 8) - (kStartEndButtonWidth / 2);
    myLocation.v    = (4 * kSpriteTrackHeight / 5) - (kStartEndButtonHeight / 2);
    isVisible       = false;
    myLayer         = 1;
    myIndex         = kGoToEndButtonUpIndex;
    myErr = SetSpriteData(myEndButton, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, myActions);
    if (myErr != noErr)
        goto bail;
 
    // first penguin sprite with no actions
    myErr = QTNewAtomContainer(&myPenguinOne);
    if (myErr != noErr)
        goto bail;
    myLocation.h    = (3 * kSpriteTrackWidth / 8) - (kPenguinWidth / 2);
    myLocation.v    = (kSpriteTrackHeight / 4) - (kPenguinHeight / 2);
    isVisible       = true;
    myLayer         = 2;
    myIndex         = kPenguinDownRightCycleStartIndex;
    myGraphicsMode.graphicsMode = blend;
    myGraphicsMode.opColor.red = myGraphicsMode.opColor.green = myGraphicsMode.opColor.blue = 0x8FFF;   // grey
    myErr = SetSpriteData(myPenguinOne, &myLocation, &isVisible, &myLayer, &myIndex, &myGraphicsMode, NULL, myActions);
    if (myErr != noErr)
        goto bail;
        
    // second penguin sprite with no actions
    myErr = QTNewAtomContainer(&myPenguinTwo);
    if (myErr != noErr)
        goto bail;
    myLocation.h    = (5 * kSpriteTrackWidth / 8) - (kPenguinWidth / 2);
    myLocation.v    = (kSpriteTrackHeight / 4) - (kPenguinHeight / 2);
    isVisible       = true;
    myLayer         = 3;
    myIndex         = kPenguinForwardIndex;
    myErr = SetSpriteData(myPenguinTwo, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, myActions);
    if (myErr != noErr)
        goto bail;
 
 
    //////////
    //
    // add actions to the six sprites
    //
    //////////
 
    // add go to beginning button
    myErr = QTCopyAtom(myBeginButton, kParentAtomIsContainer, &myBeginActionButton);
    if (myErr != noErr)
        goto bail;
 
    AddSpriteSetImageIndexAction(myBeginActionButton, kParentAtomIsContainer, kQTEventMouseClick, 0, NULL, 0, 0, NULL, kGoToBeginningButtonDownIndex, NULL);
    AddSpriteSetImageIndexAction(myBeginActionButton, kParentAtomIsContainer, kQTEventMouseClickEnd, 0, NULL, 0, 0, NULL, kGoToBeginningButtonUpIndex, NULL);
    AddMovieGoToBeginningAction(myBeginActionButton, kParentAtomIsContainer, kQTEventMouseClickEndTriggerButton);
    AddSpriteSetVisibleAction(myBeginActionButton, kParentAtomIsContainer, kQTEventMouseEnter, 0, NULL, 0, 0, NULL, true, NULL);
    AddSpriteSetVisibleAction(myBeginActionButton, kParentAtomIsContainer, kQTEventMouseExit, 0, NULL, 0, 0, NULL, false, NULL);
    AddSpriteToSample(mySample, myBeginActionButton, kGoToBeginningSpriteID);   
    QTDisposeAtomContainer(myBeginActionButton);
 
    // add go to prev button
    myErr = QTCopyAtom(myPrevButton, kParentAtomIsContainer, &myPrevActionButton);
    if (myErr != noErr)
        goto bail;
 
    AddSpriteSetImageIndexAction(myPrevActionButton, kParentAtomIsContainer, kQTEventMouseClick, 0, NULL, 0, 0, NULL, kGoToPrevButtonDownIndex, NULL);
    AddSpriteSetImageIndexAction(myPrevActionButton, kParentAtomIsContainer, kQTEventMouseClickEnd, 0, NULL, 0, 0, NULL, kGoToPrevButtonUpIndex, NULL);
    AddMovieStepBackwardAction(myPrevActionButton, kParentAtomIsContainer, kQTEventMouseClickEndTriggerButton);
    AddSpriteSetVisibleAction(myBeginActionButton, kParentAtomIsContainer, kQTEventMouseEnter, 0, NULL, 0, 0, NULL, true, NULL);
    AddSpriteSetVisibleAction(myBeginActionButton, kParentAtomIsContainer, kQTEventMouseExit, 0, NULL, 0, 0, NULL, false, NULL);
    AddSpriteToSample(mySample, myPrevActionButton, kGoToPrevSpriteID);
    QTDisposeAtomContainer(myPrevActionButton);
 
    // add go to next button
    myErr = QTCopyAtom(myNextButton, kParentAtomIsContainer, &myNextActionButton);
    if (myErr != noErr)
        goto bail;
 
    AddSpriteSetImageIndexAction(myNextActionButton, kParentAtomIsContainer, kQTEventMouseClick, 0, NULL, 0, 0, NULL, kGoToNextButtonDownIndex, NULL);
    AddSpriteSetImageIndexAction(myNextActionButton, kParentAtomIsContainer, kQTEventMouseClickEnd, 0, NULL, 0, 0, NULL, kGoToNextButtonUpIndex, NULL);
    AddMovieStepForwardAction(myNextActionButton, kParentAtomIsContainer, kQTEventMouseClickEndTriggerButton);
    AddSpriteSetVisibleAction(myBeginActionButton, kParentAtomIsContainer, kQTEventMouseEnter, 0, NULL, 0, 0, NULL, true, NULL);
    AddSpriteSetVisibleAction(myBeginActionButton, kParentAtomIsContainer, kQTEventMouseExit, 0, NULL, 0, 0, NULL, false, NULL);
    AddSpriteToSample(mySample, myNextActionButton, kGoToNextSpriteID);
    QTDisposeAtomContainer(myNextActionButton);
 
    // add go to end button
    myErr = QTCopyAtom(myEndButton, kParentAtomIsContainer, &myEndActionButton);
    if (myErr != noErr)
        goto bail;
 
    AddSpriteSetImageIndexAction(myEndActionButton, kParentAtomIsContainer, kQTEventMouseClick, 0, NULL, 0, 0, NULL, kGoToEndButtonDownIndex, NULL);
    AddSpriteSetImageIndexAction(myEndActionButton, kParentAtomIsContainer, kQTEventMouseClickEnd, 0, NULL, 0, 0, NULL, kGoToEndButtonUpIndex, NULL);
    AddMovieGoToEndAction(myEndActionButton, kParentAtomIsContainer, kQTEventMouseClickEndTriggerButton);
    AddSpriteSetVisibleAction(myBeginActionButton, kParentAtomIsContainer, kQTEventMouseEnter, 0, NULL, 0, 0, NULL, true, NULL);
    AddSpriteSetVisibleAction(myBeginActionButton, kParentAtomIsContainer, kQTEventMouseExit, 0, NULL, 0, 0, NULL, false, NULL);
    AddSpriteToSample(mySample, myEndActionButton, kGoToEndSpriteID);
    QTDisposeAtomContainer(myEndActionButton);
 
    // add penguin one
    myErr = QTCopyAtom(myPenguinOne, kParentAtomIsContainer, &myPenguinOneAction);
    if (myErr != noErr)
        goto bail;
 
    // show the buttons on mouse enter and hide them on mouse exit
    AddSpriteSetVisibleAction(myPenguinOneAction, kParentAtomIsContainer, kQTEventMouseEnter, 0, NULL, 0, kTargetSpriteID, (void *)kGoToBeginningSpriteID, true, NULL);
    AddSpriteSetVisibleAction(myPenguinOneAction, kParentAtomIsContainer, kQTEventMouseExit, 0, NULL, 0, kTargetSpriteID, (void *)kGoToBeginningSpriteID, false, NULL);
    AddSpriteSetVisibleAction(myPenguinOneAction, kParentAtomIsContainer, kQTEventMouseEnter, 0, NULL, 0, kTargetSpriteID, (void *)kGoToPrevSpriteID, true, NULL);
    AddSpriteSetVisibleAction(myPenguinOneAction, kParentAtomIsContainer, kQTEventMouseExit, 0, NULL, 0, kTargetSpriteID, (void *)kGoToPrevSpriteID, false, NULL);
    AddSpriteSetVisibleAction(myPenguinOneAction, kParentAtomIsContainer, kQTEventMouseEnter, 0, NULL, 0, kTargetSpriteID, (void *)kGoToNextSpriteID, true, NULL);
    AddSpriteSetVisibleAction(myPenguinOneAction, kParentAtomIsContainer, kQTEventMouseExit, 0, NULL, 0, kTargetSpriteID, (void *)kGoToNextSpriteID, false, NULL);
    AddSpriteSetVisibleAction(myPenguinOneAction, kParentAtomIsContainer, kQTEventMouseEnter, 0, NULL, 0, kTargetSpriteID, (void *)kGoToEndSpriteID, true, NULL);
    AddSpriteSetVisibleAction(myPenguinOneAction, kParentAtomIsContainer, kQTEventMouseExit, 0, NULL, 0, kTargetSpriteID, (void *)kGoToEndSpriteID, false, NULL);
    AddSpriteToSample(mySample, myPenguinOneAction, kPenguinOneSpriteID);
    QTWired_AddCursorChangeOnMouseOver(mySample, kPenguinOneSpriteID);
    QTDisposeAtomContainer(myPenguinOneAction);
 
    // add penguin two
    myErr = QTCopyAtom(myPenguinTwo, kParentAtomIsContainer, &myPenguinTwoAction);
    if (myErr != noErr)
        goto bail;
 
    // blink when clicked on
    AddSpriteSetImageIndexAction(myPenguinTwoAction, kParentAtomIsContainer, kQTEventMouseClick, 0, NULL, 0, 0, NULL, kPenguinClosedIndex, NULL);
    AddSpriteSetImageIndexAction(myPenguinTwoAction, kParentAtomIsContainer, kQTEventMouseClickEnd, 0, NULL, 0, 0, NULL, kPenguinForwardIndex, NULL);
 
    AddQTEventAtom(myPenguinTwoAction, kParentAtomIsContainer, kQTEventMouseClickEndTriggerButton, &myEventAtom);
 
    // toggle the movie rate and both of the birds' graphics modes
    QTWired_AddPenguinTwoConditionalActions(myPenguinTwoAction, myEventAtom);
 
    QTWired_AddWraparoundMatrixOnIdle(myPenguinTwoAction);
 
    AddSpriteToSample(mySample, myPenguinTwoAction, kPenguinTwoSpriteID);
    QTDisposeAtomContainer(myPenguinTwoAction);
    
    // add an action for when the key frame is loaded, to set the movie's looping mode to palindrome;
    // note that this will actually be triggered every time the key frame is reloaded,
    // so if the operation was expensive we could use a conditional to test if we've already done it
    myLoopingFlags = loopTimeBase | palindromeLoopTimeBase;
    AddMovieSetLoopingFlagsAction(mySample, kParentAtomIsContainer, kQTEventFrameLoaded, myLoopingFlags);
 
 
    // add the key frame sample to the sprite track media
    //
    // to add the sample data in a compressed form, you would use a QuickTime DataCodec to perform the
    // compression; replace the call to the utility AddSpriteSampleToMedia with a call to the utility
    // AddCompressedSpriteSampleToMedia to do this
    
    AddSpriteSampleToMedia(myMedia, mySample, kSpriteMediaFrameDuration, true, NULL);   
 
 
    //////////
    //
    // add a few override samples to move penguin one and change its image index
    //
    //////////
 
    // original penguin one location
    myLocation.h    = (3 * kSpriteTrackWidth / 8) - (kPenguinWidth / 2);
    myLocation.v    = (kSpriteTrackHeight / 4) - (kPenguinHeight / 2);
 
    myDelta = (kSpriteTrackHeight / 2) / kNumOverrideSamples;
    myIndex = kPenguinDownRightCycleStartIndex;
    
    for (i = 1; i <= kNumOverrideSamples; i++) {
        QTRemoveChildren(mySample, kParentAtomIsContainer);
        QTNewAtomContainer(&myPenguinOneOverride);
 
        myLocation.h += myDelta;
        myLocation.v += myDelta;
        myIndex++;
        if (myIndex > kPenguinDownRightCycleEndIndex)
            myIndex = kPenguinDownRightCycleStartIndex;
            
        SetSpriteData(myPenguinOneOverride, &myLocation, NULL, NULL, &myIndex, NULL, NULL, NULL);
        AddSpriteToSample(mySample, myPenguinOneOverride, kPenguinOneSpriteID);
        AddSpriteSampleToMedia(myMedia, mySample, kSpriteMediaFrameDuration, false, NULL);  
        QTDisposeAtomContainer(myPenguinOneOverride);
    }
 
    EndMediaEdits(myMedia);
 
 
    // add the media to the track
    InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
 
    
    //////////
    //
    // set the sprite track properties
    //
    //////////
    {
        QTAtomContainer     myTrackProperties;
        RGBColor            myBackgroundColor;
        
        // add a background color to the sprite track
        myBackgroundColor.red = EndianU16_NtoB(0x8000);
        myBackgroundColor.green = EndianU16_NtoB(0);
        myBackgroundColor.blue = EndianU16_NtoB(0xffff);
        
        QTNewAtomContainer(&myTrackProperties);
        QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyBackgroundColor, 1, 1, sizeof(RGBColor), &myBackgroundColor, NULL);
 
        // tell the movie controller that this sprite track has actions, Jackson
        hasActions = true;
        QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyHasActions, 1, 1, sizeof(hasActions), &hasActions, NULL);
    
        // tell the sprite track to generate QTIdleEvents
        myFrequency = EndianU32_NtoB(60);
        QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyQTIdleEventsFrequency, 1, 1, sizeof(myFrequency), &myFrequency, NULL);
        myErr = SetMediaPropertyAtom(myMedia, myTrackProperties);
        if (myErr != noErr)
            goto bail;
 
        QTDisposeAtomContainer(myTrackProperties);
    }
 
 
    //////////
    //
    // finish up
    //
    //////////
    
    // add the movie resource to the movie file
    myErr = AddMovieResource(myMovie, myResRefNum, &myResID, myFile.name);
    
bail:
    free(myPrompt);
    free(myFileName);
 
    if (mySample != NULL)
        QTDisposeAtomContainer(mySample);
 
    if (myBeginButton != NULL)
        QTDisposeAtomContainer(myBeginButton);  
            
    if (myPrevButton != NULL)
        QTDisposeAtomContainer(myPrevButton);
                
    if (myNextButton != NULL)
        QTDisposeAtomContainer(myNextButton);
                
    if (myEndButton != NULL)
        QTDisposeAtomContainer(myEndButton);        
        
    if (myResRefNum != 0)
        CloseMovieFile(myResRefNum);
 
    if (myMovie != NULL)
        DisposeMovie(myMovie);
        
    return(myErr);
}
 
 
//////////
//
// QTWired_AddPenguinTwoConditionalActions
// Add actions to the second penguin that transform him (her?) into a two state button
// that plays or pauses the movie.
//
// We are relying on the fact that a "GetVariable" for a variable ID which has never been set
// will return zero. If we needed a different default value, we could initialize it using the
// frameLoaded event.
//
// A higher-level description of the logic is:
// 
//  On MouseUpInside
//     If (GetVariable(DefaultTrack, 1) = 0)
//        SetMovieRate(1)
//        SetSpriteGraphicsMode(DefaultSprite, { blend, grey } )
//        SetSpriteGraphicsMode(GetSpriteByID(DefaultTrack, 5), { ditherCopy, white } )
//        SetVariable(DefaultTrack, 1, 1)
//     ElseIf (GetVariable(DefaultTrack, 1) = 1)
//        SetMovieRate(0)
//        SetSpriteGraphicsMode(DefaultSprite, { ditherCopy, white })
//        SetSpriteGraphicsMode(GetSpriteByID(DefaultTrack, 5), { blend, grey })
//        SetVariable(DefaultTrack, 1, 0)
//     Endif
//  End
// 
//////////
 
OSErr QTWired_AddPenguinTwoConditionalActions (QTAtomContainer theContainer, QTAtom theEventAtom)
{
    QTAtom                              myNewActionAtom, myNewParamAtom, myConditionalAtom;
    QTAtom                              myExpressionAtom, myOperatorAtom, myActionListAtom;
    short                               myParamIndex, myConditionIndex, myOperandIndex;
    float                               myConstantValue;
    QTAtomID                            myVariableID;
    ModifierTrackGraphicsModeRecord     myBlendMode, myCopyMode;
    
    myBlendMode.graphicsMode = blend;
    myBlendMode.opColor.red = myBlendMode.opColor.green = myBlendMode.opColor.blue = 0x8fff;    // grey
 
    myCopyMode.graphicsMode = ditherCopy;
    myCopyMode.opColor.red = myCopyMode.opColor.green = myCopyMode.opColor.blue = 0xffff;       // white
 
    AddActionAtom(theContainer, theEventAtom, kActionCase, &myNewActionAtom);
    
    myParamIndex = 1;
    AddActionParameterAtom(theContainer, myNewActionAtom, myParamIndex, 0, NULL, &myNewParamAtom);
 
    // first condition
    myConditionIndex = 1;
    AddConditionalAtom(theContainer, myNewParamAtom, myConditionIndex, &myConditionalAtom);
    AddExpressionContainerAtomType(theContainer, myConditionalAtom, &myExpressionAtom);
    AddOperatorAtom(theContainer, myExpressionAtom, kOperatorEqualTo, &myOperatorAtom);
 
    myOperandIndex = 1;
    myConstantValue = kButtonStateOne;
    AddOperandAtom(theContainer, myOperatorAtom, kOperandConstant, myOperandIndex, NULL, myConstantValue);
 
    myOperandIndex = 2;
    myVariableID = kPenguinStateVariableID;
    AddVariableOperandAtom(theContainer, myOperatorAtom, myOperandIndex, 0, NULL, 0, myVariableID);
 
    AddActionListAtom(theContainer, myConditionalAtom, &myActionListAtom);
    AddMovieSetRateAction(theContainer, myActionListAtom, 0, Long2Fix(1));
    AddSpriteSetGraphicsModeAction(theContainer, myActionListAtom, 0, 0, NULL, 0, 0, NULL, &myBlendMode, NULL);
    AddSpriteSetGraphicsModeAction(theContainer, myActionListAtom, 0, 0, NULL, 0, kTargetSpriteID, (void *)kPenguinOneSpriteID, &myCopyMode, NULL);
    AddSpriteTrackSetVariableAction(theContainer, myActionListAtom, 0, kPenguinStateVariableID, kButtonStateTwo, 0, NULL, 0);
                                       
    // second condition
    myConditionIndex = 2;
    AddConditionalAtom(theContainer, myNewParamAtom, myConditionIndex, &myConditionalAtom);
    AddExpressionContainerAtomType(theContainer, myConditionalAtom, &myExpressionAtom);
    AddOperatorAtom(theContainer, myExpressionAtom, kOperatorEqualTo, &myOperatorAtom);
 
    myOperandIndex = 1;
    myConstantValue = kButtonStateTwo;
    AddOperandAtom(theContainer, myOperatorAtom, kOperandConstant, myOperandIndex, NULL, myConstantValue);
 
    myOperandIndex = 2;
    myVariableID = kPenguinStateVariableID;
    AddVariableOperandAtom(theContainer, myOperatorAtom, myOperandIndex, 0, NULL, 0, myVariableID);
 
    AddActionListAtom(theContainer, myConditionalAtom, &myActionListAtom);
    AddMovieSetRateAction(theContainer, myActionListAtom, 0, Long2Fix(0));
    AddSpriteSetGraphicsModeAction(theContainer, myActionListAtom, 0, 0, NULL, 0, 0, NULL, &myCopyMode, NULL);
    AddSpriteSetGraphicsModeAction(theContainer, myActionListAtom, 0, 0, NULL, 0, kTargetSpriteID, (void *)kPenguinOneSpriteID, &myBlendMode, NULL);
    AddSpriteTrackSetVariableAction(theContainer, myActionListAtom, 0, kPenguinStateVariableID, kButtonStateOne, 0, NULL, 0);
 
    return(noErr);
}
 
 
//////////
//
// QTWired_AddWraparoundMatrixOnIdle
// Add beginning, end, and change matrices to the specified atom container.
//
//////////
 
OSErr QTWired_AddWraparoundMatrixOnIdle (QTAtomContainer theContainer)
{
    MatrixRecord    myMinMatrix, myMaxMatrix, myDeltaMatrix;
    long            myFlags = kActionFlagActionIsDelta | kActionFlagParameterWrapsAround;
    QTAtom          myActionAtom;
    OSErr           myErr = noErr;
    
    myMinMatrix.matrix[0][0] = myMinMatrix.matrix[0][1] = myMinMatrix.matrix[0][2] = EndianS32_NtoB(0xffffffff);
    myMinMatrix.matrix[1][0] = myMinMatrix.matrix[1][1] = myMinMatrix.matrix[1][2] = EndianS32_NtoB(0xffffffff);
    myMinMatrix.matrix[2][0] = myMinMatrix.matrix[2][1] = myMinMatrix.matrix[2][2] = EndianS32_NtoB(0xffffffff);
 
    myMaxMatrix.matrix[0][0] = myMaxMatrix.matrix[0][1] = myMaxMatrix.matrix[0][2] = EndianS32_NtoB(0x7fffffff);
    myMaxMatrix.matrix[1][0] = myMaxMatrix.matrix[1][1] = myMaxMatrix.matrix[1][2] = EndianS32_NtoB(0x7fffffff);
    myMaxMatrix.matrix[2][0] = myMaxMatrix.matrix[2][1] = myMaxMatrix.matrix[2][2] = EndianS32_NtoB(0x7fffffff);
    
    myMinMatrix.matrix[2][1] = EndianS32_NtoB(Long2Fix((1 * kSpriteTrackHeight / 4) - (kPenguinHeight / 2)));
    myMaxMatrix.matrix[2][1] = EndianS32_NtoB(Long2Fix((3 * kSpriteTrackHeight / 4) - (kPenguinHeight / 2)));
 
    SetIdentityMatrix(&myDeltaMatrix);
    myDeltaMatrix.matrix[2][1] = Long2Fix(1);
    
    // change location
    myErr = AddSpriteSetMatrixAction(theContainer, kParentAtomIsContainer, kQTEventIdle, 0, NULL, 0, 0, NULL, &myDeltaMatrix, &myActionAtom);
    if (myErr != noErr)
        goto bail;
 
    myErr = AddActionParameterOptions(theContainer, myActionAtom, 1, myFlags, sizeof(myMinMatrix), &myMinMatrix, sizeof(myMaxMatrix), &myMaxMatrix);
 
bail:
    return(myErr);
}
 
 
//////////
//
// QTWired_AddCursorChangeOnMouseOver
// Add a cursor-change-on-mouse-over action to the sprite having the specified ID
// in the specified atom container.
//
// Here we use the new "sprite behaviors atom" introduced in QuickTime 4.0, which
// simplifies adding button-like capabilities to sprites.
//
//////////
 
OSErr QTWired_AddCursorChangeOnMouseOver (QTAtomContainer theContainer, QTAtomID theID)
{
    QTAtom                              mySpriteAtom = 0;
    QTAtom                              myBehaviorAtom = 0;
    QTSpriteButtonBehaviorStruct        myBehaviorRec;
    OSErr                               myErr = noErr;
    
    // find the sprite atom with the specified ID in the specified container
    mySpriteAtom = QTFindChildByID(theContainer, kParentAtomIsContainer, kSpriteAtomType, theID, NULL);
    if (mySpriteAtom == 0) {
        // if there is none, insert a new sprite atom into the specified container
        myErr = QTInsertChild(theContainer, kParentAtomIsContainer, kSpriteAtomType, theID, 0, 0, NULL, &mySpriteAtom);
        if (myErr != noErr)
            goto bail;
    }
    
    // insert a new sprite behaviors atom into the sprite atom
    myErr = QTInsertChild(theContainer, mySpriteAtom, kSpriteBehaviorsAtomType, 1, 1, 0, NULL, &myBehaviorAtom);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // insert three atoms into the sprite behaviors atom; these three atoms specify what to do on each
    // of the four defined state transitions for the (1) sprite image, (2) cursor, and (3) status string
    //
    //////////
    
    // set the sprite image behavior; -1 means: no change associated with this state transition
    myBehaviorRec.notOverNotPressedStateID = EndianS32_NtoB(-1);
    myBehaviorRec.overNotPressedStateID = EndianS32_NtoB(-1);
    myBehaviorRec.overPressedStateID = EndianS32_NtoB(-1);
    myBehaviorRec.notOverPressedStateID = EndianS32_NtoB(-1);
 
    myErr = QTInsertChild(theContainer, myBehaviorAtom, kSpriteImageBehaviorAtomType, 1, 1, sizeof(QTSpriteButtonBehaviorStruct), &myBehaviorRec, NULL);
    if (myErr != noErr)
        goto bail;
    
    // set the sprite cursor behavior; -1 means: no change associated with this state transition
    myBehaviorRec.notOverNotPressedStateID = EndianS32_NtoB(-1);
    myBehaviorRec.overNotPressedStateID = EndianS32_NtoB(kQTCursorOpenHand);
    myBehaviorRec.overPressedStateID = EndianS32_NtoB(-1);
    myBehaviorRec.notOverPressedStateID = EndianS32_NtoB(-1);
 
    myErr = QTInsertChild(theContainer, myBehaviorAtom, kSpriteCursorBehaviorAtomType, 1, 1, sizeof(QTSpriteButtonBehaviorStruct), &myBehaviorRec, NULL);
    if (myErr != noErr)
        goto bail;
    
    // set the status string behavior; -1 means: no change associated with this state transition
    myBehaviorRec.notOverNotPressedStateID = EndianS32_NtoB(-1);
    myBehaviorRec.overNotPressedStateID = EndianS32_NtoB(-1);
    myBehaviorRec.overPressedStateID = EndianS32_NtoB(-1);
    myBehaviorRec.notOverPressedStateID = EndianS32_NtoB(-1);
 
    myErr = QTInsertChild(theContainer, myBehaviorAtom, kSpriteStatusStringsBehaviorAtomType, 1, 1, sizeof(QTSpriteButtonBehaviorStruct), &myBehaviorRec, NULL);
    if (myErr != noErr)
        goto bail;
            
bail:
    return(myErr);
}