//////////
//
//	File:		QTDataRef.c
//
//	Contains:	Sample code for working with data references and data handlers.
//
//	Written by:	Tim Monroe
//
//	Copyright:	© 2000 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//	   
//	   <4>	 	02/21/01	rtm		added QTDR_CreateMovieInRAM and the two static functions it uses
//	   <3>	 	08/12/00	rtm		added QTFrame_ActivateController call to QTDR_GetURLFromUser
//	   <2>	 	07/13/00	rtm		added alternate code using AddEmptyTrackToMovie
//	   <1>	 	04/23/00	rtm		first file
//
//////////


//////////
//
// header files
//
//////////

#include "QTDataRef.h"


//////////
//
// global variables
//
//////////

extern short 				gAppResFile;						// file reference number for this application's resource file
extern ModalFilterUPP		gModalFilterUPP;					// UPP to our custom dialog event filter

Ptr							gDataBuffer = NULL;					// buffer that holds data being transferred
ComponentInstance			gDataReader = NULL;					// the data handler that reads data from the URL
ComponentInstance			gDataWriter = NULL;					// the data handler that writes data to a file
DataHCompletionUPP			gReadDataHCompletionUPP = NULL;
DataHCompletionUPP			gWriteDataHCompletionUPP = NULL;
long						gBytesToTransfer = 0L;				// the number of bytes to transfer
long						gBytesTransferred = 0L;				// the number of bytes already transferred
Boolean						gDoneTransferring = false;			// are we done transferring data?

#if TARGET_OS_WIN32
UINT						gTimerID;							// ID of the timer that tasks the data handlers
#endif


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Data reference creation utilities.
//
// Use these functions to create data references.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTDR_MakeFileDataRef
// Return a file data reference for the specified file.
//
// The caller is responsible for disposing of the handle returned by this function (by calling DisposeHandle).
//
//////////

Handle QTDR_MakeFileDataRef (FSSpecPtr theFile)
{
	Handle			myDataRef = NULL;

  	QTNewAlias(theFile, (AliasHandle *)&myDataRef, true);

	return(myDataRef);
}


//////////
//
// QTDR_MakeResourceDataRef
// Return a resource data reference for the specified file.
//
// The caller is responsible for disposing of the handle returned by this function (by calling DisposeHandle).
//
//////////

Handle QTDR_MakeResourceDataRef (FSSpecPtr theFile, OSType theResType, SInt16 theResID)
{
	Handle			myDataRef = NULL;
	OSType			myResType;
	SInt16			myResID;
	OSErr			myErr = noErr;

	myDataRef = QTDR_MakeFileDataRef(theFile);
	if (myDataRef == NULL)
    	goto bail;

	// append the resource type and ID to the data reference
	myResType = EndianU32_NtoB(theResType);
	myResID = EndianS16_NtoB(theResID);
	
	myErr = PtrAndHand(&myResType, myDataRef, sizeof(myResType));
	if (myErr == noErr)
		myErr = PtrAndHand(&myResID, myDataRef, sizeof(myResID));

bail:
	if (myErr != noErr) {
		if (myDataRef != NULL)
			DisposeHandle(myDataRef);
		myDataRef = NULL;
	}
	
	return(myDataRef);
}


//////////
//
// QTDR_MakeHandleDataRef
// Return a handle data reference for the specified handle.
//
// The caller is responsible for disposing of the handle returned by this function (by calling DisposeHandle).
//
//////////

Handle QTDR_MakeHandleDataRef (Handle theHandle)
{
	Handle			myDataRef = NULL;

	myDataRef = NewHandleClear(sizeof(Handle));
	if (myDataRef != NULL)
		BlockMove(&theHandle, *myDataRef, sizeof(Handle));

	// the following single line can replace the preceding three lines
//	PtrToHand(&theHandle, &myDataRef, sizeof(Handle));

	return(myDataRef);
}


//////////
//
// QTDR_MakeURLDataRef
// Return a URL data reference for the specified URL.
//
// The caller is responsible for disposing of the handle returned by this function (by calling DisposeHandle).
//
//////////

Handle QTDR_MakeURLDataRef (char *theURL)
{
	Handle			myDataRef = NULL;
	Size			mySize = 0;

	// get the size of the URL, plus the terminating null byte
	mySize = (Size)strlen(theURL) + 1;
	if (mySize == 1)
		goto bail;
	
	// allocate a new handle and copy the URL into the handle
	myDataRef = NewHandleClear(mySize);
    if (myDataRef != NULL)
		BlockMove(theURL, *myDataRef, mySize);

bail:
	return(myDataRef);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Movie-retrieval utilities.
//
// Use these functions to get movies specified using data references.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTDR_GetMovieFromFile
// Get the movie stored in the specified file.
//
//////////

Movie QTDR_GetMovieFromFile (FSSpecPtr theFile)
{
	Movie		myMovie = NULL;
	Handle		myDataRef = NULL;
					
	myDataRef = QTDR_MakeFileDataRef(theFile);
	if (myDataRef != NULL) {
		NewMovieFromDataRef(&myMovie, newMovieActive, NULL, myDataRef, rAliasType);
		DisposeHandle(myDataRef);
	}
		
	return(myMovie);
}


//////////
//
// QTDR_GetMovieFromHandle
// Get the movie stored in the specified block of memory.
//
//////////

Movie QTDR_GetMovieFromHandle (Handle theHandle)
{
	Movie		myMovie = NULL;
	Handle		myDataRef = NULL;
					
	myDataRef = QTDR_MakeHandleDataRef(theHandle);
	if (myDataRef != NULL) {
		NewMovieFromDataRef(&myMovie, newMovieActive, NULL, myDataRef, HandleDataHandlerSubType);
		DisposeHandle(myDataRef);
	}
		
	return(myMovie);
}


//////////
//
// QTDR_GetMovieFromResource
// Get the movie stored in the specified resource.
//
//////////

Movie QTDR_GetMovieFromResource (FSSpecPtr theFile, OSType theResType, SInt16 theResID)
{
	Movie		myMovie = NULL;
	Handle		myDataRef = NULL;
					
	myDataRef = QTDR_MakeResourceDataRef(theFile, theResType, theResID);
	if (myDataRef != NULL) {
		NewMovieFromDataRef(&myMovie, newMovieActive, NULL, myDataRef, ResourceDataHandlerSubType);
		DisposeHandle(myDataRef);
	}
		
	return(myMovie);
}


//////////
//
// QTDR_GetMovieFromURL
// Get the movie in the file referenced by the specified uniform resource locator (URL).
//
//////////

Movie QTDR_GetMovieFromURL (char *theURL)
{
	Movie		myMovie = NULL;
	Handle		myDataRef = NULL;
	
	myDataRef = QTDR_MakeURLDataRef(theURL);
	if (myDataRef != NULL) {
		NewMovieFromDataRef(&myMovie, newMovieActive, NULL, myDataRef, URLDataHandlerSubType);
		DisposeHandle(myDataRef);
	}
	
	return(myMovie);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// URL utilities.
//
// Use these functions to elicit URLs from the user and get the basename of a URL.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTDR_GetURLFromUser
// Display a dialog box to elicit a URL from the user; return a C string that contains the text in the
// dialog box when the user clicks the OK button; otherwise, return NULL.
//
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
// Note that the URLs handled here are limited to 255 characters; supporting longer URLs is left as an
// exercise for the reader.
//
//////////

char *QTDR_GetURLFromUser (short thePromptStringIndex)
{
	short 			myItem;
	short 			mySavedResFile;
	GrafPtr			mySavedPort;
	DialogPtr		myDialog = NULL;
	short			myItemKind;
	Handle			myItemHandle;
	Rect			myItemRect;
	Str255			myString;
	char			*myURL = NULL;
	OSErr			myErr = noErr;

	//////////
	//
	// save the current resource file and graphics port
	//
	//////////

	mySavedResFile = CurResFile();
	GetPort(&mySavedPort);

	// set the application's resource file
	UseResFile(gAppResFile);

	//////////
	//
	// create the dialog box in which the user will enter a URL
	//
	//////////

	myDialog = GetNewDialog(kGetURL_DLOGID, NULL, (WindowPtr)-1L);
	if (myDialog == NULL)
		goto bail;

	QTFrame_ActivateController(QTFrame_GetFrontMovieWindow(), false);
	
	MacSetPort(GetDialogPort(myDialog));
	
	SetDialogDefaultItem(myDialog, kGetURL_OKButton);
	SetDialogCancelItem(myDialog, kGetURL_CancelButton);
	
	// set the prompt string	
	GetIndString(myString, kTextKindsResourceID, thePromptStringIndex);

	GetDialogItem(myDialog, kGetURL_URLLabelItem, &myItemKind, &myItemHandle, &myItemRect);
	SetDialogItemText(myItemHandle, myString);
	
	MacShowWindow(GetDialogWindow(myDialog));
	
	//////////
	//
	// display and handle events in the dialog box until the user clicks OK or Cancel
	//
	//////////
	
	do {
		ModalDialog(gModalFilterUPP, &myItem);
	} while ((myItem != kGetURL_OKButton) && (myItem != kGetURL_CancelButton));
	
	//////////
	//
	// handle the selected button
	//
	//////////
	
	if (myItem != kGetURL_OKButton) {
		myErr = userCanceledErr;
		goto bail;
	}
	
	// retrieve the edited text
	GetDialogItem(myDialog, kGetURL_URLTextItem, &myItemKind, &myItemHandle, &myItemRect);
	GetDialogItemText(myItemHandle, myString);
	myURL = QTUtils_ConvertPascalToCString(myString);
	
bail:
	// restore the previous resource file and graphics port
	MacSetPort(mySavedPort);
	UseResFile(mySavedResFile);
	
	if (myDialog != NULL)
		DisposeDialog(myDialog);

	return(myURL);
}


//////////
//
// QTDR_GetURLBasename
// Return the basename of the specified URL.
//
// The basename of a URL is the portion of the URL following the rightmost URL separator. This function
// is useful for setting window titles of movies opened using the URL data handler to the basename of a
// URL (just like QuickTime Player does).
//
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
//////////

char *QTDR_GetURLBasename (char *theURL)
{
	char	*myBasename = NULL;
	short	myLength = 0;
	short	myIndex;

	// make sure we got a URL passed in
	if (theURL == NULL)
		goto bail;
		
	// get the length of the URL
	myLength = strlen(theURL);
	
	// find the position of the rightmost URL separator in theURL
	if (strchr(theURL, kURLSeparator) != NULL) {

		myIndex = myLength - 1;
		while (theURL[myIndex] != kURLSeparator)
			myIndex--;
			
		// calculate the length of the basename
		myLength = myLength - myIndex - 1;

	} else {
		// there is no rightmost URL separator in theURL;
		// set myIndex so that myIndex + 1 == 0, for the call to BlockMove below
		myIndex = -1;
	}
	
	// allocate space to hold the string that we return to the caller
	myBasename = malloc(myLength + 1);
	if (myBasename == NULL)
		goto bail;
		
	// copy into myBasename the substring of theURL from myIndex + 1 to the end
	BlockMove(&theURL[myIndex + 1], myBasename, myLength);
	myBasename[myLength] = '\0';
	
bail:	
	return(myBasename);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Data reference extension functions.
//
// Use these functions to add extensions to data references.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTDR_AddFilenamingExtension
// Add a filenaming extension to a data reference. If theStringPtr is NULL, add a 0-length filename.
//
// A filenaming extension is a Pascal string.
//
//////////

OSErr QTDR_AddFilenamingExtension (Handle theDataRef, StringPtr theFileName)
{
	unsigned char	myChar = 0;	
	OSErr			myErr = noErr;

	if (theFileName == NULL)
		myErr = PtrAndHand(&myChar, theDataRef, sizeof(myChar));
	else
		myErr = PtrAndHand(theFileName, theDataRef, theFileName[0] + 1);
		
	return(myErr);
}


//////////
//
// QTDR_AddMacOSFileTypeDataRefExtension
// Add a Macintosh file type as a data reference extension.
//
// A Macintosh file type data extension is an atom whose data is a 4-byte OSType.
//
//////////

OSErr QTDR_AddMacOSFileTypeDataRefExtension (Handle theDataRef, OSType theType)
{
	unsigned long	myAtomHeader[2];
	OSType			myType;
	OSErr			myErr = noErr;
	
	myAtomHeader[0] = EndianU32_NtoB(sizeof(myAtomHeader) + sizeof(theType));
	myAtomHeader[1] = EndianU32_NtoB(kDataRefExtensionMacOSFileType);
	
	myType = EndianU32_NtoB(theType);
	
	myErr = PtrAndHand(myAtomHeader, theDataRef, sizeof(myAtomHeader));
	if (myErr == noErr)
		myErr = PtrAndHand(&myType, theDataRef, sizeof(myType));
		
	return(myErr);
}


//////////
//
// QTDR_AddMIMETypeDataRefExtension
// Add a MIME type as a data reference extension.
//
// A MIME type data extension is an atom whose data is a Pascal string.
//
//////////

OSErr QTDR_AddMIMETypeDataRefExtension (Handle theDataRef, StringPtr theMIMEType)
{
	unsigned long	myAtomHeader[2];
	OSErr			myErr = noErr;
	
	if (theMIMEType == NULL)
		return(paramErr);
		
	myAtomHeader[0] = EndianU32_NtoB(sizeof(myAtomHeader) + theMIMEType[0] + 1);
	myAtomHeader[1] = EndianU32_NtoB(kDataRefExtensionMIMEType);
	
	myErr = PtrAndHand(myAtomHeader, theDataRef, sizeof(myAtomHeader));
	if (myErr == noErr)
		myErr = PtrAndHand(theMIMEType, theDataRef, theMIMEType[0] + 1);
	
	return(myErr);
}


//////////
//
// QTDR_AddInitDataDataRefExtension
// Add some initialization data as a data reference extension.
//
// An initialization data data extension is an atom whose data is any block of data.
//
//////////

OSErr QTDR_AddInitDataDataRefExtension (Handle theDataRef, Ptr theInitDataPtr)
{
	unsigned long	myAtomHeader[2];
	OSErr			myErr = noErr;
	
	if (theInitDataPtr == NULL)
		return(paramErr);
		
	myAtomHeader[0] = EndianU32_NtoB(sizeof(myAtomHeader) + GetPtrSize(theInitDataPtr));
	myAtomHeader[1] = EndianU32_NtoB(kDataRefExtensionInitializationData);
	
	myErr = PtrAndHand(myAtomHeader, theDataRef, sizeof(myAtomHeader));
	if (myErr == noErr)
		myErr = PtrAndHand(theInitDataPtr, theDataRef, GetPtrSize(theInitDataPtr));
		
	return(myErr);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// File-transfer functions.
//
// These functions implement our file-transfer capability.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTDR_CopyRemoteFileToLocalFile
// Copy a remote file (located at the specified URL) into a local file.
//
//////////

OSErr QTDR_CopyRemoteFileToLocalFile (char *theURL, FSSpecPtr theFile)
{
	Handle				myReaderRef = NULL;			// data reference for the remote file
	Handle				myWriterRef = NULL;			// data reference for the local file
	ComponentResult		myErr = badComponentType;

	//////////
	//
	// create the local file with the desired type and creator
	//
	//////////
	
	// delete the target local file, if it already exists;
	// if it doesn't exist yet, we'll get an error (fnfErr), which we just ignore
	FSpDelete(theFile);
	
	myErr = FSpCreate(theFile, kTransFileCreator, kTransFileType, smSystemScript);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// create data references for the remote file and the local file
	//
	//////////
	
	myReaderRef = QTDR_MakeURLDataRef(theURL);
    if (myReaderRef == NULL)
    	goto bail;

	myWriterRef = QTDR_MakeFileDataRef(theFile);
    if (myWriterRef == NULL)
    	goto bail;

	//////////
	//
	// find and open the URL and file data handlers; connect the data references to them
	//
	//////////
	
	gDataReader = OpenComponent(GetDataHandler(myReaderRef, URLDataHandlerSubType, kDataHCanRead));
	if (gDataReader == NULL)
		goto bail;

	gDataWriter = OpenComponent(GetDataHandler(myWriterRef, rAliasType, kDataHCanWrite));
	if (gDataWriter == NULL)
		goto bail;
		
	// set the data reference for the URL data handler
	myErr = DataHSetDataRef(gDataReader, myReaderRef);
	if (myErr != noErr)
		goto bail;
	
	// set the data reference for the file data handler
	myErr = DataHSetDataRef(gDataWriter, myWriterRef);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// allocate a data buffer; the URL data handler copies data into this buffer,
	// and the file data handler copies data out of it
	//
	//////////
	
	gDataBuffer = NewPtrClear(kDataBufferSize);
	myErr = MemError();
	if (myErr != noErr)
		goto bail;
		
	//////////
	//
	// connect to the remote and local files
	//
	//////////
	
	// open a read-only path to the remote data reference
	myErr = DataHOpenForRead(gDataReader);
	if (myErr != noErr)
		goto bail;

	// get the size of the remote file
	myErr = DataHGetFileSize(gDataReader, &gBytesToTransfer); 
	if (myErr != noErr)
		goto bail;
	
	// open a write-only path to the local data reference
	myErr = DataHOpenForWrite(gDataWriter);
	if (myErr != noErr)
		goto bail;
		
	//////////
	//
	// start reading and writing data
	//
	//////////
	
	gDoneTransferring = false;
	gBytesTransferred = 0L;
	
	gReadDataHCompletionUPP = NewDataHCompletionUPP(QTDR_ReadDataCompletionProc);
	gWriteDataHCompletionUPP = NewDataHCompletionUPP(QTDR_WriteDataCompletionProc);
		
	// start retrieving the data; we do this by calling our own write completion routine,
	// pretending that we've just successfully finished writing 0 bytes of data
	QTDR_WriteDataCompletionProc(gDataBuffer, 0L, noErr);

bail:
	// if we encountered any error, close the data handler components
	if (myErr != noErr)
		QTDR_CloseDownHandlers();
	
	return((OSErr)myErr);
}


//////////
//
// QTDR_ReadDataCompletionProc
// This procedure is called when the data handler has completed a read operation.
//
// The theRefCon parameter contains the number of bytes just read.
//
//////////

PASCAL_RTN void QTDR_ReadDataCompletionProc (Ptr theRequest, long theRefCon, OSErr theErr)
{
#pragma unused(theErr)

	// we just finished reading some data, so schedule a write operation			
	DataHWrite(	gDataWriter,
				theRequest,						// the data buffer
				gBytesTransferred,				// write from the current offset
				theRefCon,						// the number of bytes to write
				gWriteDataHCompletionUPP,
				theRefCon);
}


//////////
//
// QTDR_WriteDataCompletionProc
// This procedure is called when the data handler has completed a write operation.
//
// The theRefCon parameter contains the number of bytes just written.
//
//////////

PASCAL_RTN void QTDR_WriteDataCompletionProc (Ptr theRequest, long theRefCon, OSErr theErr)
{
#pragma unused(theErr)

	long		myNumBytesToRead;
	wide		myWide;

	// increment our tally of the number of bytes written so far
	gBytesTransferred += theRefCon;

	if (gBytesTransferred < gBytesToTransfer) {
		// there is still data to read and write, so schedule a read operation
	
		// determine how big a chunk to read
		if (gBytesToTransfer - gBytesTransferred > kDataBufferSize)
			myNumBytesToRead = kDataBufferSize;
		else
			myNumBytesToRead = gBytesToTransfer - gBytesTransferred;

		myWide.lo = gBytesTransferred;			// read from the current offset 
		myWide.hi = 0;
		
		// schedule a read operation
		DataHReadAsync(gDataReader,
						theRequest,				// the data buffer
						myNumBytesToRead,
						&myWide,
						gReadDataHCompletionUPP,
						myNumBytesToRead);

	} else {
		// we've transferred all the data, so set a flag to tell us to close down the data handlers
		gDoneTransferring = true;
	}
	
}


//////////
//
// QTDR_CloseDownHandlers
// Close our read/write access to our data references and then close down the read/write data handlers.
//
//////////

void QTDR_CloseDownHandlers (void)
{
	if (gDataReader != NULL) {
		DataHCloseForRead(gDataReader);
		CloseComponent(gDataReader);
		gDataReader = NULL;
	}

	if (gDataWriter != NULL) {
		DataHCloseForWrite(gDataWriter);
		CloseComponent(gDataWriter);
		gDataWriter = NULL;
	}
	
	// dispose of the data buffer
	if (gDataBuffer != NULL)
		DisposePtr(gDataBuffer);
		
	// dispose of the routine descriptors
	if (gReadDataHCompletionUPP != NULL)
		DisposeDataHCompletionUPP(gReadDataHCompletionUPP);
		
	if (gWriteDataHCompletionUPP != NULL)
		DisposeDataHCompletionUPP(gWriteDataHCompletionUPP);
	
	gDoneTransferring = false;
	
#if TARGET_OS_WIN32
	// kill the timer that tasks the data handlers
	KillTimer(NULL, gTimerID);
#endif
}


//////////
//
// QTDR_TimerProc
// Handle timer messages to task the data handlers.
//
//////////

#if TARGET_OS_WIN32
void CALLBACK QTDR_TimerProc (HWND theWnd, UINT theMessage, UINT theID, DWORD theTime)
{
#pragma unused(theWnd, theMessage, theID, theTime)
	QTApp_HandleEvent(NULL);
}
#endif


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Other functions.
//
// These functions are called by ComApplication.c.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTDR_CreateReferenceCopy
// Create a copy of the given file that is a reference movie (that is, a movie file and a separate
// media file).
//
//////////

OSErr QTDR_CreateReferenceCopy (Movie theSrcMovie, FSSpecPtr theDstMovieFile, FSSpecPtr theDstMediaFile)
{
	Track			mySrcTrack = NULL;
	Media			mySrcMedia = NULL;
	Movie			myDstMovie = NULL;
	Track			myDstTrack = NULL;
	Media			myDstMedia = NULL;
	Handle			myMediaRef = NULL;			// data reference for the media file
#if !USE_ADDEMPTYTRACKTOMOVIE
	Fixed			myWidth, myHeight;
	OSType			myType;
#endif
	long			myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
	short			myResRefNum = 0;
	short			myResID = movieInDataForkResID;
	OSErr			myErr = paramErr;
					
	// get the first video track and media in the source movie
	mySrcTrack = GetMovieIndTrackType(theSrcMovie, 1, VideoMediaType, movieTrackMediaType);
	if (mySrcTrack == NULL)
		goto bail;
	
	mySrcMedia = GetTrackMedia(mySrcTrack);
	if (mySrcMedia == NULL)
		goto bail;

	// create a file data reference for the new media file
	myMediaRef = QTDR_MakeFileDataRef(theDstMediaFile);
    if (myMediaRef == NULL)
    	goto bail;

	// create a file for the destination movie data
	myErr = FSpCreate(theDstMediaFile, sigMoviePlayer, MovieFileType, 0);
	if (myErr != noErr)
		goto bail;
	
	// create a file for the destination movie atom and create an empty movie
	myErr = CreateMovieFile(theDstMovieFile, sigMoviePlayer, smCurrentScript, myFlags, &myResRefNum, &myDstMovie);
	if (myErr != noErr)
		goto bail;
		
	// assign the default progress proc to the destination movie
	SetMovieProgressProc(myDstMovie, (MovieProgressUPP)-1, 0);

#if USE_ADDEMPTYTRACKTOMOVIE
	myErr = AddEmptyTrackToMovie(mySrcTrack, myDstMovie, myMediaRef, rAliasType, &myDstTrack);
	if (myErr != noErr)
		goto bail;
	
	myDstMedia = GetTrackMedia(myDstTrack);
	myErr = GetMoviesError();
	if (myErr != noErr)
		goto bail;
#else
	// get some information about the source track and media
	GetTrackDimensions(mySrcTrack, &myWidth, &myHeight);
	GetMediaHandlerDescription(mySrcMedia, &myType, 0, 0);

	// create the destination movie track and media
	myDstTrack = NewMovieTrack(myDstMovie, myWidth, myHeight, kNoVolume);
	myErr = GetMoviesError();
	if (myErr != noErr)
		goto bail;
		
	myDstMedia = NewTrackMedia(myDstTrack, myType, GetMediaTimeScale(mySrcMedia), myMediaRef, rAliasType);
	myErr = GetMoviesError();
	if (myErr != noErr)
		goto bail;

	CopyTrackSettings(mySrcTrack, myDstTrack);
#endif

	// copy the entire source track into the destination track; this copies the track's media
	// samples into the destination media file
	myErr = BeginMediaEdits(myDstMedia);
	if (myErr != noErr)
		goto bail;

	myErr = InsertTrackSegment(mySrcTrack, myDstTrack, 0, GetTrackDuration(mySrcTrack), 0);
	if (myErr != noErr)
		goto bail;

	myErr = EndMediaEdits(myDstMedia);
	if (myErr != noErr)
		goto bail;
	
	// add the movie atom to the data fork of the movie file
	myErr = AddMovieResource(myDstMovie, myResRefNum, &myResID, NULL);

bail:
	return(myErr);
}


//////////
//
// QTDR_PlayMovieFromRAM
// Play a movie.
//
//////////

OSErr QTDR_PlayMovieFromRAM (Movie theMovie)
{
	WindowPtr				myWindow = NULL;
	Rect					myBounds = {50, 50, 100, 100};
	Rect					myRect;
	StringPtr				myTitle = QTUtils_ConvertCToPascalString(kWindowTitle);
	OSErr					myErr = memFullErr;

	myWindow = NewCWindow(NULL, &myBounds, myTitle, false, 0, (WindowPtr)-1, false, 0);
	if (myWindow == NULL)
		goto bail;
		
	myErr = noErr;
	
	MacSetPort((GrafPtr)GetWindowPort(myWindow));

	GetMovieBox(theMovie, &myRect);
	MacOffsetRect(&myRect, -myRect.left, -myRect.top);
	SetMovieBox(theMovie, &myRect);

	if (!EmptyRect(&myRect))
		SizeWindow(myWindow, myRect.right, myRect.bottom, false);
	else
		SizeWindow(myWindow, 200, 0, false);
		
	MacShowWindow(myWindow);

	SetMovieGWorld(theMovie, GetWindowPort(myWindow), NULL);
	GoToBeginningOfMovie(theMovie);
	MoviesTask(theMovie, 0);
	StartMovie(theMovie);
	
	myErr = GetMoviesError();
	if (myErr != noErr)
		goto bail;
	
	while (!IsMovieDone(theMovie))
		MoviesTask(theMovie, 0);

bail:
	free(myTitle);
	
	if (theMovie != NULL)
		DisposeMovie(theMovie);

	if (myWindow != NULL)
		DisposeWindow(myWindow);
		
	return(myErr);
}


//////////
//
// QTDR_CreateMovieInRAM
// Create a movie in RAM.
//
//////////

OSErr QTDR_CreateMovieInRAM (void)
{
	Movie					myMovie = NULL;
	Track					myTrack = NULL;
	Media					myMedia = NULL;
	short					myResRefNum = 0;
	short					myResID = 0;
	Handle					myDataRef = NULL;
	Handle					myHandle = NULL;
	FSSpec					myFSSpec;
	OSErr					myErr = noErr;
	
	// create a new handle to hold the movie
	myHandle = NewHandleClear(0);
	if (myHandle == NULL)
		goto bail;
	
	// create a data reference to that handle
	myDataRef = QTDR_MakeHandleDataRef(myHandle);
	if (myDataRef == NULL)
		goto bail;
		
	myMovie = NewMovie(newMovieActive);
	if (myMovie == NULL)
		goto bail;
	
	myErr = SetMovieDefaultDataRef(myMovie, myDataRef, HandleDataHandlerSubType);
	if (myErr != noErr)
		goto bail;

	// create the movie track and media
	myTrack = NewMovieTrack(myMovie, FixRatio(kVideoTrackWidth, 1), FixRatio(kVideoTrackHeight, 1), kNoVolume);
	myErr = GetMoviesError();
	if (myErr != noErr)
		goto bail;
		
	myMedia = NewTrackMedia(myTrack, VideoMediaType, kVideoTimeScale, myDataRef, HandleDataHandlerSubType);
	myErr = GetMoviesError();
	if (myErr != noErr)
		goto bail;

	// create the media samples
	myErr = BeginMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;

	myErr = QTDR_AddVideoSamplesToMedia(myMedia, kVideoTrackWidth, kVideoTrackHeight);
	if (myErr != noErr)
		goto bail;

	myErr = EndMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;
	
	// add the media to the track
	myErr = InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
	if (myErr != noErr)
		goto bail;
	
	// add the movie atom to the movie file
	myErr = AddMovieResource(myMovie, myResRefNum, &myResID, NULL);

	myFSSpec.name[0] = (unsigned char)0;
	myFSSpec.parID = 0;
	myFSSpec.vRefNum = 0;
	
	QTFrame_OpenMovieInWindow(myMovie, &myFSSpec);

bail:
	if (myDataRef != NULL)
		DisposeHandle(myDataRef);
		
	return(myErr);
}


//////////
//
// QTDR_AddVideoSamplesToMedia
// Add video media samples to the specified media.
//
//////////

static OSErr QTDR_AddVideoSamplesToMedia (Media theMedia, short theTrackWidth, short theTrackHeight)
{
	GWorldPtr					myGWorld = NULL;
	PixMapHandle				myPixMap = NULL;
	CodecType					myCodecType = kJPEGCodecType;
	long						myNumSample;
	long						myMaxComprSize = 0L;
	Handle						myComprDataHdl = NULL;
	Ptr							myComprDataPtr = NULL;
	ImageDescriptionHandle		myImageDesc = NULL;
	CGrafPtr 					mySavedPort = NULL;
	GDHandle					mySavedDevice = NULL;
	Rect						myRect;
	OSErr						myErr = noErr;

	MacSetRect(&myRect, 0, 0, theTrackWidth, theTrackHeight);

	myErr = NewGWorld(&myGWorld, kPixelDepth, &myRect, NULL, NULL, (GWorldFlags)0);
	if (myErr != noErr)
		goto bail;

	myPixMap = GetGWorldPixMap(myGWorld);
	if (myPixMap == NULL)
		goto bail;

	LockPixels(myPixMap);
	myErr = GetMaxCompressionSize(	myPixMap,
									&myRect, 
									0,							// let ICM choose depth
									codecNormalQuality, 
									myCodecType, 
									(CompressorComponent)anyCodec,
									&myMaxComprSize);
	if (myErr != noErr)
		goto bail;

	myComprDataHdl = NewHandle(myMaxComprSize);
	if (myComprDataHdl == NULL)
		goto bail;

	HLockHi(myComprDataHdl);
#if TARGET_CPU_68K
	myComprDataPtr = StripAddress(*myComprDataHdl);
#else
	myComprDataPtr = *myComprDataHdl;
#endif

	myImageDesc = (ImageDescriptionHandle)NewHandle(4);
	if (myImageDesc == NULL)
		goto bail;

	GetGWorld(&mySavedPort, &mySavedDevice);
	SetGWorld(myGWorld, NULL);

	for (myNumSample = 1; myNumSample <= kNumVideoFrames; myNumSample++) {
		EraseRect(&myRect);
		
		QTDR_DrawFrame(theTrackWidth, theTrackHeight, myNumSample, myGWorld);

		myErr = CompressImage(	myPixMap, 
								&myRect, 
								codecNormalQuality,
								myCodecType,
								myImageDesc, 
								myComprDataPtr);
		if (myErr != noErr)
			goto bail;

		myErr = AddMediaSample(	theMedia, 
								myComprDataHdl,
								0,								// no offset in data
								(**myImageDesc).dataSize, 
								kVideoFrameDuration,			// frame duration
								(SampleDescriptionHandle)myImageDesc, 
								1,								// one sample
								0,								// self-contained samples
								NULL);
		if (myErr != noErr)
			goto bail;
	}

bail:
	SetGWorld(mySavedPort, mySavedDevice);

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

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

	if (myGWorld != NULL)
		DisposeGWorld(myGWorld);
		
	return(myErr);
}


//////////
//
// QTDR_DrawFrame
// Draw a frame of video.
//
//////////

static void QTDR_DrawFrame (short theTrackWidth, short theTrackHeight, long theNumSample, GWorldPtr theGWorld)
{
	Handle								myHandle = NULL;
	char								myData[kPICTFileHeaderSize];
	static PicHandle					myPicture = NULL;
	static GWorldPtr					myGWorld = NULL;
	static GraphicsImportComponent		myImporter = NULL;
	Rect								myRect;
	RGBColor							myColor;
	ComponentResult						myErr = noErr;

	MacSetRect(&myRect, 0, 0, theTrackWidth, theTrackHeight);

	if (myPicture == NULL) {
		myErr = NewGWorld(&myGWorld, kPixelDepth, &myRect, NULL, NULL, (GWorldFlags)0);
		if (myErr != noErr)
			goto bail;

		// read a picture from our resource file
		myPicture = GetPicture(kPictureID);
		if (myPicture == NULL)
			goto bail;

		// use Munger to prepend a 512-byte header onto the picture data; this converts the PICT
		// resource data into in-memory PICT file data (see Ice Floe 14 for an explanation of this)
		myHandle = (Handle)myPicture;
		Munger(myHandle, 0, NULL, 0, myData, kPICTFileHeaderSize);

		// get a graphics importer for the picture
		myErr = OpenADefaultComponent(GraphicsImporterComponentType, kQTFileTypePicture, &myImporter); 
		if (myErr != noErr)
			goto bail;
				
		// configure the graphics importer
		myErr = GraphicsImportSetGWorld(myImporter, myGWorld, NULL);
		if (myErr != noErr)
			goto bail;
		
		myErr = GraphicsImportSetDataHandle(myImporter, myHandle);
		if (myErr != noErr)
			goto bail;
			
		myErr = GraphicsImportSetBoundsRect(myImporter, &myRect);
		if (myErr != noErr)
			goto bail;
			
		// draw the picture into the source GWorld
		myErr = GraphicsImportDraw(myImporter);
		if (myErr != noErr)
			goto bail;
	}
	
	// set the blend amount (0 = fully transparent; 0xffff = fully opaque)
	myColor.red = (theNumSample - 1) * (0xffff / kNumVideoFrames - 1);
	myColor.green = (theNumSample - 1) * (0xffff / kNumVideoFrames - 1);
	myColor.blue = (theNumSample - 1) * (0xffff / kNumVideoFrames - 1);
	OpColor(&myColor);
	
	// blend the picture (in the source GWorld) into the empty rectangle (in the destination GWorld)
	CopyBits((BitMapPtr)*GetGWorldPixMap(myGWorld),
			 (BitMapPtr)*GetGWorldPixMap(theGWorld),
			 &myRect,
			 &myRect,
			 blend,
			 NULL);

	if (theNumSample == kNumVideoFrames)
		goto bail;
		
	return;
	
bail:
	if (myHandle != NULL)
		DisposeHandle(myHandle);
		
	if (myPicture != NULL)
		ReleaseResource((Handle)myPicture);
		
	if (myImporter != NULL)
		CloseComponent(myImporter);
} 


