AddVRActions.c

//////////
//
//  File:       AddVRActions.c
//
//  Contains:   Sample code for adding wired actions to a QuickTime VR movie.
//
//  Written by: Tim Monroe
//              Based on existing code by Bill Wright.
//
//  Copyright:  © 1999 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <4>      03/21/00    rtm     changes for supporting CarbonLib
//     <3>      11/18/99    rtm     changed _SetFrameLoadedWiredActions to _SetWiredActionsToNode,
//                                  to allow support for idle events as well as frame-loaded events;
//                                  added AddVRAct_CreateIdleActionContainer to illustrate use of
//                                  idle events; here we autopan to the left on idle events
//     <2>      07/15/99    rtm     added Endian macros; runs fine on both Mac and Windows;
//                                  added comments; removed reliance on AtomUtilities.c and
//                                  FileUtilities.c
//     <1>      07/14/99    rtm     first file from bw; revised to sample code coding style
//     
//
//  This file contains some sample code that adds a few wired actions to a QuickTime VR movie.
//  Currently you can add two kinds of wired actions to VR movies: (1) actions that are global
//  to a particular node and (2) actions associated with a particular hot spot in a node. An
//  example of a node-specific action might be setting the pan and tilt angles that are used
//  when the user first enters the node. An example of a hot-spot-specific action might be playing
//  a sound when the mouse is moved over the hot spot.
//
//  All currently-supported QTVR wired actions are specific to some particular node, so the atom
//  containers implementing the actions are placed in the node information atom container that is
//  contained in the media sample for that node in the QTVR track. (See the book Virtual Reality
//  Programming With QuickTime VR 2.x for complete information on the format of VR movie files.)
//  So our job here boils down to finding a media sample in the QTVR track, constructing some atom
//  containers for our desired actions, placing those action containers into the appropriate place
//  in the media sample, and then writing the modified media sample back into the QTVR track. We
//  also need to put an atom into the media property atom container to enable wired action processing.
//
//  For complete information about wired actions, see the chapter "Wired Sprites" in the book
//  Programming With QuickTime Sprites.
//
//////////
 
#include "AddVRActions.h"
 
 
Str255              gAppName;                           // the name of this application
 
 
//////////
//
// main/WinMain 
// The main function for this application.
//
//////////
 
#if TARGET_OS_MAC
void main (void)
#elif TARGET_OS_WIN32
int CALLBACK WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR theCmdLine, int nCmdShow)
#endif
{
    OSType                  myTypeList = MovieFileType;
    short                   myNumTypes = 1;
    FSSpec                  myFile;
    OSErr                   myErr = noErr;
 
#if TARGET_OS_WIN32
    InitializeQTML(0L);                                         // initialize QuickTime Media Layer
#endif  
 
#if TARGET_OS_MAC
#if !TARGET_API_MAC_CARBON  
    MaxApplZone();                                              // init everything
    InitGraf(&qd.thePort);
    InitFonts();
    FlushEvents(everyEvent, 0);
    InitWindows();
    InitMenus();
    InitDialogs(NULL);
    TEInit();
#endif
    InitCursor();
    
    // get the application's name from the resource file
    GetIndString(gAppName, kAppNameResID, kAppNameResIndex);
#endif  
    
    myErr = EnterMovies();
    if (myErr != noErr)
        goto bail;
 
    // elicit a movie file from the user
    myErr = AddVRAct_GetOneFileWithPreview(myNumTypes, (QTFrameTypeListPtr)&myTypeList, &myFile, NULL);
    if (myErr != noErr)
        goto bail;
 
    AddVRAct_AddWiredActionsToQTVRMovie(&myFile);
        
bail:
    ExitMovies();
 
#if TARGET_OS_WIN32
    // terminate the QuickTime Media Layer
    TerminateQTML();
    return(1);
#endif  
 
#if TARGET_OS_MAC
    return;
#endif  
}
 
 
//////////
//
// AddVRAct_GetFirstHotSpot
// Return, through the theHotSpotID parameter, the ID of the first hot spot in the specified atom container
// (which is assumed to be a node information atom container).
//
// The returned ID is not necessarily the numerically-least ID; it's just the ID of the first hot spot atom
// in the atom container.
//
//////////
 
static OSErr AddVRAct_GetFirstHotSpot (Handle theSample, long *theHotSpotID)
{
    QTAtom          myHotSpotParentAtom = 0;
    QTAtom          myHotSpotAtom = 0;
    OSErr           myErr = noErr;
    
    *theHotSpotID = 0;
 
    myHotSpotParentAtom = QTFindChildByIndex(theSample, kParentAtomIsContainer, kQTVRHotSpotParentAtomType, kIndexOne, NULL);
    if (myHotSpotParentAtom != 0)
        myHotSpotAtom = QTFindChildByIndex(theSample, myHotSpotParentAtom, kQTVRHotSpotAtomType, kIndexOne, theHotSpotID);
    
    return(myErr);
}
 
 
//////////
//
// AddVRAct_CreateHotSpotActionContainer
// Return, through the theActions parameter, an atom container that contains a hot spot action.
//
// Here we set the pan angle to 10.0 degrees when the hot spot is clicked.
//
//////////
 
static OSErr AddVRAct_CreateHotSpotActionContainer (QTAtomContainer *theActions)
{
    QTAtom          myEventAtom = 0;
    QTAtom          myActionAtom = 0;
    long            myAction;
    float           myPanAngle;
    OSErr           myErr = noErr;
    
    myErr = QTNewAtomContainer(theActions);
    if (myErr != noErr)
        goto bail;
    
    myErr = QTInsertChild(*theActions, kParentAtomIsContainer, kQTEventType, kQTEventMouseClick, kIndexOne, kZeroDataLength, NULL, &myEventAtom);
    if (myErr != noErr)
        goto bail;
    
    myErr = QTInsertChild(*theActions, myEventAtom, kAction, kIndexOne, kIndexOne, kZeroDataLength, NULL, &myActionAtom);
    if (myErr != noErr)
        goto bail;
    
    myAction = EndianS32_NtoB(kActionQTVRSetPanAngle);
    myErr = QTInsertChild(*theActions, myActionAtom, kWhichAction, kIndexOne, kIndexOne, sizeof(long), &myAction, NULL);
    if (myErr != noErr)
        goto bail;
    
    myPanAngle = 10.0;
    AddVRAct_ConvertFloatToBigEndian(&myPanAngle);
    myErr = QTInsertChild(*theActions, myActionAtom, kActionParameter, kIndexOne, kIndexOne, sizeof(float), &myPanAngle, NULL);
    if (myErr != noErr)
        goto bail;
        
bail:
    return(myErr);
}
 
 
//////////
//
// AddVRAct_CreateFrameLoadedActionContainer
// Return, through the theActions parameter, an atom container that contains a frame-loaded event action.
//
// Here we set the pan angle to 180.0 degrees.
//
//////////
 
static OSErr AddVRAct_CreateFrameLoadedActionContainer (QTAtomContainer *theActions)
{
    QTAtom          myEventAtom = 0;
    QTAtom          myActionAtom = 0;
    long            myAction;
    float           myPanAngle;
    OSErr           myErr = noErr;
    
    myErr = QTNewAtomContainer(theActions);
    if (myErr != noErr)
        goto bail;
    
    myErr = QTInsertChild(*theActions, kParentAtomIsContainer, kQTEventFrameLoaded, kIndexOne, kIndexOne, kZeroDataLength, NULL, &myEventAtom);
    if (myErr != noErr)
        goto bail;
    
    myErr = QTInsertChild(*theActions, myEventAtom, kAction, kIndexOne, kIndexOne, kZeroDataLength, NULL, &myActionAtom);
    if (myErr != noErr)
        goto bail;
    
    myAction = EndianS32_NtoB(kActionQTVRSetPanAngle);
    myErr = QTInsertChild(*theActions, myActionAtom, kWhichAction, kIndexOne, kIndexOne, sizeof(long), &myAction, NULL);
    if (myErr != noErr)
        goto bail;
    
    myPanAngle = 180.0;
    AddVRAct_ConvertFloatToBigEndian(&myPanAngle);
    myErr = QTInsertChild(*theActions, myActionAtom, kActionParameter, kIndexOne, kIndexOne, sizeof(float), &myPanAngle, NULL);
    if (myErr != noErr)
        goto bail;  
        
bail:
    return(myErr);
}   
 
 
//////////
//
// AddVRAct_CreateIdleActionContainer
// Return, through the theActions parameter, an atom container that contains an idle event action.
//
// Here we set the pan angle to a relative +1.0 degree, with wrapping at the min and max values.
//
//////////
 
static OSErr AddVRAct_CreateIdleActionContainer (QTAtomContainer *theActions)
{
    QTAtom          myEventAtom = 0;
    QTAtom          myActionAtom = 0;
    long            myAction;
    float           myPanAngle;
    UInt32          myFlags;
    OSErr           myErr = noErr;
    
    myErr = QTNewAtomContainer(theActions);
    if (myErr != noErr)
        goto bail;
    
    myErr = QTInsertChild(*theActions, kParentAtomIsContainer, kQTEventIdle, kIndexOne, kIndexOne, kZeroDataLength, NULL, &myEventAtom);
    if (myErr != noErr)
        goto bail;
    
    myErr = QTInsertChild(*theActions, myEventAtom, kAction, kIndexOne, kIndexOne, kZeroDataLength, NULL, &myActionAtom);
    if (myErr != noErr)
        goto bail;
    
    myAction = EndianS32_NtoB(kActionQTVRSetPanAngle);
    myErr = QTInsertChild(*theActions, myActionAtom, kWhichAction, kIndexOne, kIndexOne, sizeof(long), &myAction, NULL);
    if (myErr != noErr)
        goto bail;
 
    myPanAngle = 1.0;
    AddVRAct_ConvertFloatToBigEndian(&myPanAngle);
    myErr = QTInsertChild(*theActions, myActionAtom, kActionParameter, kIndexOne, kIndexOne, sizeof(float), &myPanAngle, NULL);
    if (myErr != noErr)
        goto bail;  
        
    myFlags = EndianU32_NtoB(kActionFlagActionIsDelta | kActionFlagParameterWrapsAround);
    myErr = QTInsertChild(*theActions, myActionAtom, kActionFlags, kIndexOne, kIndexOne, sizeof(UInt32), &myFlags, NULL);
                
bail:
    return(myErr);
}   
 
 
//////////
//
// AddVRAct_SetWiredActionsToNode
// Set the specified actions to be a node action of the specified type. If theActions is NULL, remove any
// existing action of that type from theSample.
//
// The theSample parameter is assumed to be a node information atom container; any actions that are global
// to the node should be inserted at the root level of this atom container; in addition, the container type
// should be the same as the event type and should have an atom ID of 1.
//
//////////
 
static OSErr AddVRAct_SetWiredActionsToNode (Handle theSample, QTAtomContainer theActions, UInt32 theActionType)
{
    QTAtom          myEventAtom = 0;
    QTAtom          myTargetAtom = 0;
    OSErr           myErr = noErr;
    
    // look for an event atom in the specified actions atom container
    if (theActions != NULL)
        myEventAtom = QTFindChildByID(theActions, kParentAtomIsContainer, theActionType, kIndexOne, NULL);
            
    // look for an event atom in the node information atom container
    myTargetAtom = QTFindChildByID(theSample, kParentAtomIsContainer, theActionType, kIndexOne, NULL);
    if (myTargetAtom != 0) {
        // if there is already an event atom in the node information atom container,
        // then either replace it with the one we were passed or remove it
        if (theActions != NULL)
            myErr = QTReplaceAtom(theSample, myTargetAtom, theActions, myEventAtom);    
        else
            myErr = QTRemoveAtom(theSample, myTargetAtom);  
    } else {
        // there is no event atom in the node information atom container,
        // so add in the one we were passed
        if (theActions != NULL)
            myErr = QTInsertChildren(theSample, kParentAtomIsContainer, theActions);
    }
        
    return(myErr);
}
 
 
//////////
//
// AddVRAct_SetWiredActionsToHotSpot
// Set the specified actions to be a hot-spot action. If theActions is NULL, remove any existing
// hot-spot actions for the specified hot spot from theSample.
//
//////////
 
static OSErr AddVRAct_SetWiredActionsToHotSpot (Handle theSample, long theHotSpotID, QTAtomContainer theActions)
{
    QTAtom          myHotSpotParentAtom = 0;
    QTAtom          myHotSpotAtom = 0;
    short           myCount,
                    myIndex;
    OSErr           myErr = paramErr;
    
    myHotSpotParentAtom = QTFindChildByIndex(theSample, kParentAtomIsContainer, kQTVRHotSpotParentAtomType, kIndexOne, NULL);
    if (myHotSpotParentAtom == NULL)
        goto bail;
    
    myHotSpotAtom = QTFindChildByID(theSample, myHotSpotParentAtom, kQTVRHotSpotAtomType, theHotSpotID, NULL);
    if (myHotSpotAtom == NULL)
        goto bail;
    
    // see how many events are already associated with the specified hot spot
    myCount = QTCountChildrenOfType(theSample, myHotSpotAtom, kQTEventType);
    
    for (myIndex = myCount; myIndex > 0; myIndex--) {
        QTAtom      myTargetAtom = 0;
        
        // remove all the existing events
        myTargetAtom = QTFindChildByIndex(theSample, myHotSpotAtom, kQTEventType, myIndex, NULL);
        if (myTargetAtom != 0) {
            myErr = QTRemoveAtom(theSample, myTargetAtom);
            if (myErr != noErr)
                goto bail;
        }
    }
    
    if (theActions) {
        myErr = QTInsertChildren(theSample, myHotSpotAtom, theActions);
        if (myErr != noErr)
            goto bail;
    }
    
bail:
    return(myErr);
}
 
 
//////////
//
// AddVRAct_WriteMediaPropertyAtom
// Add a media property action to the specified media.
//
// We assume that the data passed to us through the theProperty parameter is big-endian.
//
//////////
 
static OSErr AddVRAct_WriteMediaPropertyAtom (Media theMedia, long thePropertyID, long thePropertySize, void *theProperty)
{
    QTAtomContainer     myPropertyAtom = NULL;
    QTAtom              myAtom = 0;
    OSErr               myErr = noErr;
 
    // get the current media property atom
    myErr = GetMediaPropertyAtom(theMedia, &myPropertyAtom);
    if (myErr != noErr)
        goto bail;
    
    // if there isn't one yet, then create one
    if (myPropertyAtom == NULL) {
        myErr = QTNewAtomContainer(&myPropertyAtom);
        if (myErr != noErr)
            goto bail;
    }
 
    // see if there is an existing atom of the specified type; if not, then create one
    myAtom = QTFindChildByID(myPropertyAtom, kParentAtomIsContainer, thePropertyID, kIndexOne, NULL);
    if (myAtom == NULL) {
        myErr = QTInsertChild(myPropertyAtom, kParentAtomIsContainer, thePropertyID, kIndexOne, kIndexZero, kZeroDataLength, NULL, &myAtom);
        if ((myErr != noErr) || (myAtom == NULL))
            goto bail;
    }
    
    // set the data of the specified atom to the data passed in
    myErr = QTSetAtomData(myPropertyAtom, myAtom, thePropertySize, (Ptr)theProperty);
    if (myErr != noErr)
        goto bail;
 
    // write the new atom data out to the media property atom
    myErr = SetMediaPropertyAtom(theMedia, myPropertyAtom);
 
bail:
    if (myPropertyAtom != NULL)
        QTDisposeAtomContainer(myPropertyAtom);
    
    return(myErr);
}
 
 
//////////
//
// AddVRAct_AddWiredActionsToQTVRMovie
// Add some wired actions to the specified QTVR movie.
//
// Wired actions are added to a QTVR movie by adding atom containers in the appropriate locations.
//
//////////
 
static void AddVRAct_AddWiredActionsToQTVRMovie (FSSpec *theFSSpec)
{   
    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;
    QTVRSampleDescriptionHandle     myQTVRDesc = NULL;
    Handle                          mySample = NULL;
    short                           mySampleFlags;
    Fixed                           myTrackEditRate;
    QTAtomContainer                 myActions = NULL;   
    Boolean                         myHasActions;
    long                            myHotSpotID = 0L;                       
    UInt32                          myFrequency;
    OSErr                           myErr = noErr;
    
    //////////
    //
    // open the movie file and get the QTVR track from the movie
    //
    //////////
    
    // 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 the first QTVR track in the movie;
    // this assumes that the movie is a QuickTime VR movie formatted according to version 2.0
    // or later (version 1.0 VR movies don't have a QTVR track)
    myTrack = GetMovieIndTrackType(myMovie, kIndexOne, kQTVRQTVRType, movieTrackMediaType);
    if (myTrack == NULL)
        goto bail;
    
    //////////
    //
    // get the first media sample in the QTVR track
    //
    // the QTVR track contains one media sample for each node in the movie;
    // that sample contains a node information atom container, which contains general information
    // about the node (such as its type, its ID, its name, and a list of its hot spots)
    //
    //////////
    
    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 QTVR track
    myQTVRDesc = (QTVRSampleDescriptionHandle)NewHandle(4);
    if (myQTVRDesc == NULL)
        goto bail;
 
    mySample = NewHandle(0);
    if (mySample == NULL)
        goto bail;
 
    myErr = GetMediaSample(myMedia, mySample, 0, NULL, myMediaTime, NULL, &mySampleDuration, (SampleDescriptionHandle)myQTVRDesc, NULL, 1, NULL, &mySampleFlags);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // add idle actions
    //
    //////////
    
    // create an action container for idle actions
    myErr = AddVRAct_CreateIdleActionContainer(&myActions);
    if (myErr != noErr)
        goto bail;
    
    // add idle actions to sample
    myErr = AddVRAct_SetWiredActionsToNode(mySample, myActions, kQTEventIdle);
    if (myErr != noErr)
        goto bail;
    
    myErr = QTDisposeAtomContainer(myActions);
    if (myErr != noErr)
        goto bail;
    
    //////////
    //
    // add frame-loaded actions
    //
    //////////
    
    // create an action container for frame-loaded actions
    myErr = AddVRAct_CreateFrameLoadedActionContainer(&myActions);
    if (myErr != noErr)
        goto bail;
    
    // add frame-loaded actions to sample
    myErr = AddVRAct_SetWiredActionsToNode(mySample, myActions, kQTEventFrameLoaded);
    if (myErr != noErr)
        goto bail;
    
    myErr = QTDisposeAtomContainer(myActions);
    if (myErr != noErr)
        goto bail;
    
    //////////
    //
    // add hot-spot actions
    //
    //////////
    
    // find the first hot spot in the selected node; don't bail if there are no hot spots
    myErr = AddVRAct_GetFirstHotSpot(mySample, &myHotSpotID);
    if ((myErr == noErr) && (myHotSpotID != 0)) {
        // create an action container for hot-spot actions
        myErr = AddVRAct_CreateHotSpotActionContainer(&myActions);
        if (myErr != noErr)
            goto bail;
        
        // add hot-spot actions to sample   
        myErr = AddVRAct_SetWiredActionsToHotSpot(mySample, myHotSpotID, myActions);
        if (myErr != noErr)
            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)myQTVRDesc, 
                            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;
 
    //////////
    //
    // set the media property atom to enable wired action processing and idle-time processing
    //
    //////////
            
    myHasActions = true;    // since sizeof(Boolean) == 1, there is no need to swap bytes here
    myErr = AddVRAct_WriteMediaPropertyAtom(myMedia, kSpriteTrackPropertyHasActions, sizeof(Boolean), &myHasActions);
    if (myErr != noErr)
        goto bail;
 
    myFrequency = EndianU32_NtoB(1);
    myErr = AddVRAct_WriteMediaPropertyAtom(myMedia, kSpriteTrackPropertyQTIdleEventsFrequency, sizeof(UInt32), &myFrequency);
    if (myErr != noErr)
        goto bail;
    
    //////////
    //
    // update the movie resource
    //
    //////////
    
    myErr = UpdateMovieResource(myMovie, myResRefNum, myResID, NULL);
        
bail:
    // close the movie file
    if (myResRefNum != -1)
        CloseMovieFile(myResRefNum);
    
    if (myActions != NULL)
        QTDisposeAtomContainer(myActions);
    
    if (mySample != NULL)
        DisposeHandle(mySample);        
    
    if (myQTVRDesc != NULL)
        DisposeHandle((Handle)myQTVRDesc);      
    
    if (myMovie != NULL)
        DisposeMovie(myMovie);  
}
 
 
//////////
//
// AddVRAct_ConvertFloatToBigEndian
// Convert the specified floating-point number to big-endian format.
//
//////////
 
void AddVRAct_ConvertFloatToBigEndian (float *theFloat)
{
    unsigned long       *myLongPtr;
    
    myLongPtr = (unsigned long *)theFloat;
    *myLongPtr = EndianU32_NtoB(*myLongPtr);
}
 
//////////
//
// AddVRAct_GetOneFileWithPreview
// Display the appropriate file-opening dialog box, with an optional QuickTime preview pane. If the user
// selects a file, return information about it using the theFSSpecPtr parameter.
//
//////////
 
OSErr AddVRAct_GetOneFileWithPreview (short theNumTypes, QTFrameTypeListPtr theTypeList, FSSpecPtr theFSSpecPtr, void *theFilterProc)
{
#if TARGET_OS_WIN32
    StandardFileReply   myReply;
#endif
#if TARGET_OS_MAC
    NavReplyRecord      myReply;
    NavDialogOptions    myDialogOptions;
    NavTypeListHandle   myOpenList = NULL;
#endif
    OSErr               myErr = noErr;
    
    if (theFSSpecPtr == NULL)
        return(paramErr);
    
#if TARGET_OS_WIN32
    // prompt the user for a file
    StandardGetFilePreview((FileFilterUPP)theFilterProc, theNumTypes, (ConstSFTypeListPtr)theTypeList, &myReply);
    if (!myReply.sfGood)
        return(userCanceledErr);
    
    // make an FSSpec record
    myErr = FSMakeFSSpec(myReply.sfFile.vRefNum, myReply.sfFile.parID, myReply.sfFile.name, theFSSpecPtr);
#endif
 
#if TARGET_OS_MAC
    // specify the options for the dialog box
    NavGetDefaultDialogOptions(&myDialogOptions);
//  myDialogOptions.dialogOptionFlags -= kNavNoTypePopup;
//  myDialogOptions.dialogOptionFlags -= kNavAllowMultipleFiles;
    BlockMoveData(gAppName, myDialogOptions.clientName, gAppName[0] + 1);
    
    // create a handle to an 'open' resource
    myOpenList = (NavTypeListHandle)AddVRAct_CreateOpenHandle(FOUR_CHAR_CODE('aVRa'), theNumTypes, theTypeList);
    if (myOpenList != NULL)
        HLock((Handle)myOpenList);
    
    // prompt the user for a file
    myErr = NavGetFile(NULL, &myReply, &myDialogOptions, NULL, NULL, (NavObjectFilterUPP)theFilterProc, myOpenList, NULL);
    if ((myErr == noErr) && myReply.validRecord) {
        AEKeyword       myKeyword;
        DescType        myActualType;
        Size            myActualSize = 0;
        
        // get the FSSpec for the selected file
        if (theFSSpecPtr != NULL)
            myErr = AEGetNthPtr(&(myReply.selection), 1, typeFSS, &myKeyword, &myActualType, theFSSpecPtr, sizeof(FSSpec), &myActualSize);
 
        NavDisposeReply(&myReply);
    }
    
    if (myOpenList != NULL) {
        HUnlock((Handle)myOpenList);
        DisposeHandle((Handle)myOpenList);
    }
    
#endif
 
    return(myErr);
}
 
#if TARGET_OS_MAC
//////////
//
// AddVRAct_CreateOpenHandle
// Return a handle to a dynamically-created 'open' resource.
//
//////////
 
Handle AddVRAct_CreateOpenHandle (OSType theApplicationSignature, short theNumTypes, QTFrameTypeListPtr theTypeList)
{
    Handle          myHandle = NULL;
    
    if (theTypeList == NULL)
        return(myHandle);
    
    if (theNumTypes > 0) {
        myHandle = NewHandle(sizeof(NavTypeList) + (theNumTypes * sizeof(OSType)));
        if (myHandle != NULL) {
            NavTypeListHandle   myOpenResHandle = (NavTypeListHandle)myHandle;
            
            (*myOpenResHandle)->componentSignature = theApplicationSignature;
            (*myOpenResHandle)->osTypeCount = theNumTypes;
            BlockMoveData(theTypeList, (*myOpenResHandle)->osType, theNumTypes * sizeof(OSType));
        }
    }
    
    return(myHandle);
}
#endif