//////////
//
//	File:		QTWiredActions.c
//
//	Contains:	QuickTime wired sprites target support for QuickTime movies.
//
//	Written by:	Tim Monroe
//
//	Copyright:	© 2001 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <2>	 	03/28/01	rtm		begun adding advanced sprite code
//	   <1>	 	03/10/01	rtm		first file; based on existing AddHTActions sample code
//	   
//
//////////

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

#include "QTWiredActions.h"


//////////
//
// QTWired_CreateTextMovie 
// Create a movie with a text track.
//
//////////

OSErr QTWired_CreateTextMovie (FSSpec *theFSSpec)
{
	short			myResRefNum = -1;
	short			myResID = movieInDataForkResID;
	Movie			myMovie = NULL;
	Track			myTrack = NULL;
	Media			myMedia = NULL;
	MediaHandler	myHandler = NULL;
	Rect			myTextBox;
	RGBColor		myTextColor =	{0x0000, 0x0000, 0x0000};
	RGBColor		myBackColor =	{0x8888, 0x8888, 0x8888};
	RGBColor		myHiliteColor =	{0x0000, 0x0000, 0x0000};
	long			myDisplayFlags = 0;
	short			myHiliteStart = 0;
	short			myHiliteEnd = 0;
	TimeValue		myNewMediaTime;
	TimeValue		myScrollDelay = 0;
#if TARGET_OS_MAC
	char			myText[] = "\rPlease take me to Apple or CNN";
#else
	char			myText[] = "\nPlease take me to Apple or CNN";
#endif
	long			myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile | newMovieActive;
	OSType			myType = FOUR_CHAR_CODE('none');
	OSErr			myErr = noErr;
		
	//////////
	//
	// create a new text movie
	//
	//////////

	myErr = CreateMovieFile(theFSSpec, FOUR_CHAR_CODE('TVOD'), 0, myFlags, &myResRefNum, &myMovie);
	if (myErr != noErr)
		goto bail;
	
	// select the "no-interface" movie controller
	myType = EndianU32_NtoB(myType);
	SetUserDataItem(GetMovieUserData(myMovie), &myType, sizeof(myType), kUserDataMovieControllerType, 1);

	myTrack = NewMovieTrack(myMovie, FixRatio(kWidth320, 1), FixRatio(kHeight240, 1), kTrackVolumeZero);	
	myMedia = NewTrackMedia(myTrack, TextMediaType, kTimeScale600, NULL, 0);
	if ((myTrack == NULL) || (myMedia == NULL))
		goto bail;
	
	myErr = BeginMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;

	myHandler = GetMediaHandler(myMedia);
	if (myHandler == NULL)
		goto bail;

	//////////
	//
	// add a text sample to the movie
	//
	//////////

	MacSetRect(&myTextBox, 0, 0, kWidth320, kHeight240);
	MacInsetRect(&myTextBox, kTextBoxInset, kTextBoxInset);
	
	myErr = (OSErr)TextMediaAddTextSample(	myHandler,
											myText,
											strlen(myText),
											kFontIDTimes,
											kSize48,
											kFacePlain,
											&myTextColor,
											&myBackColor,
											teCenter,
											&myTextBox,
											myDisplayFlags,
											myScrollDelay,
											myHiliteStart,
											myHiliteEnd,
											&myHiliteColor,
											kTimeScale600,
											&myNewMediaTime);
	if (myErr != noErr)
		goto bail;

	myErr = EndMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;
		
	// add the media to the track, at time 0
	myErr = InsertMediaIntoTrack(myTrack, kTrackStartTimeZero, myNewMediaTime, kTimeScale600, fixed1);
	if (myErr != noErr)
		goto bail;
	
	// add the movie resource
	myErr = AddMovieResource(myMovie, myResRefNum, &myResID, NULL);
	
bail:
	if (myResRefNum != -1)
		CloseMovieFile(myResRefNum);
	
	if (myMovie != NULL)
		DisposeMovie(myMovie);
	
	return(myErr);
}


//////////
//
// QTWired_CreateHyperTextActionContainer
// Return, through the theActions parameter, an atom container that contains some hypertext actions.
//
//////////

static OSErr QTWired_CreateHyperTextActionContainer (QTAtomContainer *theActions)
{
	QTAtom			myEventAtom = 0;
	QTAtom			myActionAtom = 0;
	QTAtom			myHyperTextAtom = 0;
	QTAtom			myWiredObjectsAtom = 0;
	long			mySelStart1 = 19;
	long			mySelEnd1 = 24;
	long			mySelStart2 = 28;
	long			mySelEnd2 = 31;
	long			myValue;
	short			myIndex;
	char			myAppleURL[] = "www.apple.com";
	char			myCNNURL[] = "www.cnn.com";
	ModifierTrackGraphicsModeRecord		myGraphicsModeRecord;
	OSErr			myErr = noErr;
	
	myErr = QTNewAtomContainer(theActions);
	if (myErr != noErr)
		goto bail;
	
	// create a wired objects atom
	myErr = QTInsertChild(*theActions, kParentAtomIsContainer, kTextWiredObjectsAtomType, kIndexOne, kIndexZero, kZeroDataLength, NULL, &myWiredObjectsAtom);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// add a hypertext link to the wired objects atom: ID 1
	//
	//////////
	
	myErr = QTInsertChild(*theActions, myWiredObjectsAtom, kHyperTextItemAtomType, kIDOne, kIndexZero, kZeroDataLength, NULL, &myHyperTextAtom);
	if (myErr != noErr)
		goto bail;
	
	myValue = EndianS32_NtoB(mySelStart1);
	myErr = QTInsertChild(*theActions, myHyperTextAtom, kRangeStart, kIDOne, kIndexZero, sizeof(long), &myValue, NULL);
	if (myErr != noErr)
		goto bail;
	
	myValue = EndianS32_NtoB(mySelEnd1);
	myErr = QTInsertChild(*theActions, myHyperTextAtom, kRangeEnd, kIDOne, kIndexZero, sizeof(long), &myValue, NULL);
	if (myErr != noErr)
		goto bail;
	
	// add an event atom to the hypertext atom
	myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, myHyperTextAtom, kQTEventMouseClick, kActionGoToURL, &myActionAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, kIndexOne, strlen(myAppleURL) + 1, &myAppleURL, NULL);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, myHyperTextAtom, kQTEventMouseEnter, kActionTextTrackSetHyperTextColor, &myActionAtom);
	if (myErr != noErr)
		goto bail;

	myIndex = EndianS16_NtoB(1);
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, kIndexOne, sizeof(myIndex), &myIndex, NULL);
	if (myErr != noErr)
		goto bail;
		
	myGraphicsModeRecord.graphicsMode = EndianS32_NtoB(ditherCopy);
	myGraphicsModeRecord.opColor.red = EndianS16_NtoB(0x9999);
	myGraphicsModeRecord.opColor.green = EndianS16_NtoB(0x0000);
	myGraphicsModeRecord.opColor.blue = EndianS16_NtoB(0xcccc);
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, kIndexTwo, sizeof(myGraphicsModeRecord), &myGraphicsModeRecord, NULL);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, myHyperTextAtom, kQTEventMouseExit, kActionTextTrackSetHyperTextColor, &myActionAtom);
	if (myErr != noErr)
		goto bail;
	
	myIndex = EndianS16_NtoB(1);
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, kIndexOne, sizeof(myIndex), &myIndex, NULL);
	if (myErr != noErr)
		goto bail;
		
	myGraphicsModeRecord.graphicsMode = EndianS32_NtoB(ditherCopy);
	myGraphicsModeRecord.opColor.red = EndianS16_NtoB(0x0000);
	myGraphicsModeRecord.opColor.green = EndianS16_NtoB(0x0000);
	myGraphicsModeRecord.opColor.blue = EndianS16_NtoB(0xffff);
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, kIndexTwo, sizeof(myGraphicsModeRecord), &myGraphicsModeRecord, NULL);
	if (myErr != noErr)
		goto bail;
		
	//////////
	//
	// add a hypertext link to the wired objects atom: ID 2
	//
	//////////

	myErr = QTInsertChild(*theActions, myWiredObjectsAtom, kHyperTextItemAtomType, kIDTwo, kIndexZero, kZeroDataLength, NULL, &myHyperTextAtom);
	if (myErr != noErr)
		goto bail;
	
	myValue = EndianS32_NtoB(mySelStart2);
	myErr = QTInsertChild(*theActions, myHyperTextAtom, kRangeStart, kIDOne, kIndexZero, sizeof(long), &myValue, NULL);
	if (myErr != noErr)
		goto bail;
	
	myValue = EndianS32_NtoB(mySelEnd2);
	myErr = QTInsertChild(*theActions, myHyperTextAtom, kRangeEnd, kIDOne, kIndexZero, sizeof(long), &myValue, NULL);
	if (myErr != noErr)
		goto bail;
	
	// add an event atom to the hypertext atom
	myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, myHyperTextAtom, kQTEventMouseClick, kActionGoToURL, &myActionAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, kIndexOne, strlen(myCNNURL) + 1, &myCNNURL, NULL);
	if (myErr != noErr)
		goto bail;
	
	myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, myHyperTextAtom, kQTEventMouseEnter, kActionTextTrackSetHyperTextColor, &myActionAtom);
	if (myErr != noErr)
		goto bail;
	
	myIndex = EndianS16_NtoB(2);
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, kIndexOne, sizeof(myIndex), &myIndex, NULL);
	if (myErr != noErr)
		goto bail;
		
	myGraphicsModeRecord.graphicsMode = EndianS32_NtoB(ditherCopy);
	myGraphicsModeRecord.opColor.red = EndianS16_NtoB(0x9999);
	myGraphicsModeRecord.opColor.green = EndianS16_NtoB(0x0000);
	myGraphicsModeRecord.opColor.blue = EndianS16_NtoB(0xcccc);
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, kIndexTwo, sizeof(myGraphicsModeRecord), &myGraphicsModeRecord, NULL);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, myHyperTextAtom, kQTEventMouseExit, kActionTextTrackSetHyperTextColor, &myActionAtom);
	if (myErr != noErr)
		goto bail;
	
	myIndex = EndianS16_NtoB(2);
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, kIndexOne, sizeof(myIndex), &myIndex, NULL);
	if (myErr != noErr)
		goto bail;
		
	myGraphicsModeRecord.graphicsMode = EndianS32_NtoB(ditherCopy);
	myGraphicsModeRecord.opColor.red = EndianS16_NtoB(0x0000);
	myGraphicsModeRecord.opColor.green = EndianS16_NtoB(0x0000);
	myGraphicsModeRecord.opColor.blue = EndianS16_NtoB(0xffff);
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, kIndexTwo, sizeof(myGraphicsModeRecord), &myGraphicsModeRecord, NULL);

bail:
	return(myErr);
}	


//////////
//
// QTWired_AddActionsToSample
// Add the specified atom container to the end of the specified media sample.
//
// Text actions are stored at the end of the sample as a normal text atom extension.
//
//////////

static OSErr QTWired_AddActionsToSample (Handle theSample, QTAtomContainer theActions, SInt32 theAtomExtType)
{
	Ptr			myPtr = NULL;
	long		myHandleLength;
	long		myContainerLength;
	long		myNewLength;
	OSErr		myErr = noErr;

	if ((theSample == NULL) || (theActions == NULL))
		return(paramErr);

	myHandleLength = GetHandleSize(theSample);
	myContainerLength = GetHandleSize((Handle)theActions);
	
	myNewLength = (long)(sizeof(long) + sizeof(OSType) + myContainerLength);
			
	SetHandleSize(theSample, (myHandleLength + myNewLength));
	myErr = MemError();		
	if (myErr != noErr)
		goto bail;
	
	HLock(theSample);
	
	// get a pointer to the beginning of the new block of space added to the sample
	// by the previous call to SetHandleSize; we need to format that space as a text
	// atom extension
	myPtr = *theSample + myHandleLength;
	
	// set the length of the text atom extension
	*(long *)myPtr = EndianS32_NtoB((long)(sizeof(long) + sizeof(OSType) + myContainerLength));
	myPtr += (sizeof(long));
	
	// set the type of the text atom extension
	*(OSType *)myPtr = EndianS32_NtoB(theAtomExtType);
		
	myPtr += (sizeof(OSType));
	
	// set the data of the text atom extension;
	// we assume that this data is already in big-endian format
	HLock((Handle)theActions);
	BlockMove(*theActions, myPtr, myContainerLength);
	
	HUnlock((Handle)theActions);
	HUnlock(theSample);

bail:
	return(myErr);
}


//////////
//
// QTWired_AddActionsToTextMovie
// Add some actions to the specified text movie; theMenuItem specifies which actions to add.
//
//////////

OSErr QTWired_AddActionsToTextMovie (FSSpec *theFSSpec, UInt16 theMenuItem)
{
	short							myResID = 0;
	short							myResRefNum = -1;
	Movie							myMovie = NULL;
	Track							myTrack = NULL;
	Media							myMedia = NULL;
	TimeValue						myTrackOffset;
	TimeValue						myMediaTime;
	TimeValue						mySampleDuration;
	TimeValue						mySelectionDuration;
	TimeValue						myNewMediaTime;
	TextDescriptionHandle			myTextDesc = NULL;
	Handle							mySample = NULL;
	short							mySampleFlags;
	Fixed 							myTrackEditRate;
	QTAtomContainer					myActions = NULL;	
	OSErr							myErr = noErr;

	//////////
	//
	// open the movie file and get the first text 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 first text track in the movie
	myTrack = GetMovieIndTrackType(myMovie, kIndexOne, TextMediaType, movieTrackMediaType);
	if (myTrack == NULL)
		goto bail;
	
	//////////
	//
	// get first media sample in the text track
	//
	//////////
	
	myMedia = GetTrackMedia(myTrack);
	if (myMedia == NULL)
		goto bail;
	
	myTrackOffset = GetTrackOffset(myTrack);
	myMediaTime = TrackTimeToMediaTime(myTrackOffset, myTrack);

	// allocate some storage to hold the sample description for the text track
	myTextDesc = (TextDescriptionHandle)NewHandle(4);
	if (myTextDesc == NULL)
		goto bail;

	mySample = NewHandle(0);
	if (mySample == NULL)
		goto bail;

	myErr = GetMediaSample(myMedia, mySample, 0, NULL, myMediaTime, NULL, &mySampleDuration, (SampleDescriptionHandle)myTextDesc, NULL, 1, NULL, &mySampleFlags);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// add actions to the first media sample
	//
	//////////
	
	switch (theMenuItem) {
		case IDM_MAKE_HYPERTEXT_MOVIE:
			// create an action container for hypertext actions
			myErr = QTWired_CreateHyperTextActionContainer(&myActions);
			if (myErr != noErr)
				goto bail;
			
			// add hypertext actions to sample
			myErr = QTWired_AddActionsToSample(mySample, myActions, kHyperTextTextAtomType);
			if (myErr != noErr)
				goto bail;
			break;
			
		case IDM_MAKE_MEM_DISPLAY_MOVIE:
			// create an action container for wired actions
			myErr = QTWired_CreateMemoryDisplayActionContainer(&myActions);
			if (myErr != noErr)
				goto bail;
			
			// add actions to sample
			myErr = QTWired_AddActionsToSample(mySample, myActions, kQTEventType);
			if (myErr != noErr)
				goto bail;
			break;
			
		default:
			myErr = paramErr;
			goto bail;
	}	
		
	//////////
	//
	// replace sample in media
	//
	//////////
	
	myTrackEditRate = GetTrackEditRate(myTrack, myTrackOffset);
	if (GetMoviesError() != noErr)
		goto bail;

	GetTrackNextInterestingTime(myTrack, nextTimeMediaSample | nextTimeEdgeOK, myTrackOffset, fixed1, NULL, &mySelectionDuration);
	if (GetMoviesError() != noErr)
		goto bail;

	myErr = DeleteTrackSegment(myTrack, myTrackOffset, mySelectionDuration);
	if (myErr != noErr)
		goto bail;
		
	myErr = BeginMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;
	
	myErr = AddMediaSample(	myMedia,
							mySample,
							0,
							GetHandleSize(mySample),
							mySampleDuration,
							(SampleDescriptionHandle)myTextDesc, 
							1,
							mySampleFlags,
							&myNewMediaTime);
	if (myErr != noErr)
		goto bail;
	
	myErr = EndMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;
	
	// add the media to the track
	myErr = InsertMediaIntoTrack(myTrack, myTrackOffset, myNewMediaTime, mySelectionDuration, myTrackEditRate);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// update the movie resource
	//
	//////////
	
	myErr = UpdateMovieResource(myMovie, myResRefNum, myResID, NULL);
	if (myErr != noErr)
		goto bail;
	
	// close the movie file
	myErr = CloseMovieFile(myResRefNum);
		
bail:
	if (myActions != NULL)
		(void)QTDisposeAtomContainer(myActions);
	
	if (mySample != NULL)
		DisposeHandle(mySample);		
	
	if (myTextDesc != NULL)
		DisposeHandle((Handle)myTextDesc);		
	
	if (myMovie != NULL)
		DisposeMovie(myMovie);	

	return(myErr);
}


//////////
//
// QTWired_CreateMemoryDisplayMovie 
// Create a movie that displays the available application memory in a text track.
//
//////////

OSErr QTWired_CreateMemoryDisplayMovie (FSSpec *theFSSpec)
{
	short			myResRefNum = -1;
	short			myResID = movieInDataForkResID;
	Movie			myMovie = NULL;
	Track			myTrack = NULL;
	Media			myMedia = NULL;
	MediaHandler	myHandler = NULL;
	Rect			myTextBox;
	RGBColor		myTextColor =	{0x0000, 0x0000, 0x0000};
	RGBColor		myBackColor =	{0x8888, 0x8888, 0x8888};
	RGBColor		myHiliteColor =	{0x0000, 0x0000, 0x0000};
	long			myDisplayFlags = 0;
	short			myHiliteStart = 0;
	short			myHiliteEnd = 0;
	TimeValue		myNewMediaTime;
	TimeValue		myScrollDelay = 0;
	char			myText[] = "0";
	long			myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile | newMovieActive;
	OSType			myType = FOUR_CHAR_CODE('none');
	OSErr			myErr = noErr;
		
	//////////
	//
	// create a new text movie
	//
	//////////

	myErr = CreateMovieFile(theFSSpec, FOUR_CHAR_CODE('TVOD'), 0, myFlags, &myResRefNum, &myMovie);
	if (myErr != noErr)
		goto bail;
	
	// select the "no-interface" movie controller
	myType = EndianU32_NtoB(myType);
	SetUserDataItem(GetMovieUserData(myMovie), &myType, sizeof(myType), kUserDataMovieControllerType, 1);

	myTrack = NewMovieTrack(myMovie, FixRatio(kWidth200, 1), FixRatio(kHeight50, 1), kTrackVolumeZero);	
	myMedia = NewTrackMedia(myTrack, TextMediaType, kTimeScale600, NULL, 0);
	if ((myTrack == NULL) || (myMedia == NULL))
		goto bail;
	
	myErr = BeginMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;

	myHandler = GetMediaHandler(myMedia);
	if (myHandler == NULL)
		goto bail;

	//////////
	//
	// add a text sample to the movie
	//
	//////////

	MacSetRect(&myTextBox, 0, 0, kWidth200, kHeight50);
	MacInsetRect(&myTextBox, kTextBoxInset, kTextBoxInset);
	
	myErr = (OSErr)TextMediaAddTextSample(	myHandler,
											myText,
											strlen(myText),
											kFontIDTimes,
											kSize48,
											kFacePlain,
											&myTextColor,
											&myBackColor,
											teCenter,
											&myTextBox,
											myDisplayFlags,
											myScrollDelay,
											myHiliteStart,
											myHiliteEnd,
											&myHiliteColor,
											kTimeScale600,
											&myNewMediaTime);
	if (myErr != noErr)
		goto bail;

	myErr = EndMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;
		
	// add the media to the track, at time 0
	myErr = InsertMediaIntoTrack(myTrack, kTrackStartTimeZero, myNewMediaTime, kTimeScale600, fixed1);
	if (myErr != noErr)
		goto bail;
	
	// set the text track properties
	QTWired_SetTextTrackProperties(myMedia, 120, false);
	
	// add the movie resource
	myErr = AddMovieResource(myMovie, myResRefNum, &myResID, NULL);
	
bail:
	if (myResRefNum != -1)
		CloseMovieFile(myResRefNum);
	
	if (myMovie != NULL)
		DisposeMovie(myMovie);
	
	return(myErr);
}


//////////
//
// QTWired_CreateMemoryDisplayActionContainer
// Return, through the theActions parameter, an atom container that contains some edit-text actions.
//
//////////

static OSErr QTWired_CreateMemoryDisplayActionContainer (QTAtomContainer *theActions)
{
	QTAtom			myEventAtom = 0;
	QTAtom			myActionAtom = 0;
	QTAtom			myParamAtom = 0;
	short			myEditState;
	UInt32			myPos;
	QTAtom			myParameterAtom = 0;
	QTAtom			myExpressionAtom = 0;
	QTAtom			myOperandAtom = 0;
	OSErr			myErr = noErr;
	
	myErr = QTNewAtomContainer(theActions);
	if (myErr != noErr)
		goto bail;
	
	// add an event atom that enables text editing;
	// in theory we could do this just once in a frame-loaded event, but it doesn't seem to impact performance here
	myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, kParentAtomIsContainer, kQTEventIdle, kActionTextTrackSetEditable, &myActionAtom);
	if (myErr != noErr)
		goto bail;

	myEditState = EndianS16_NtoB(kKeyEntryScript);
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, 1, sizeof(myEditState), &myEditState, NULL);
	if (myErr != noErr)
		goto bail;

	// add an event atom that displays the amount of application memory currently free
	myErr = WiredUtils_AddQTEventAndActionAtoms(*theActions, kParentAtomIsContainer, kQTEventIdle, kActionTextTrackPasteText, &myActionAtom);
	if (myErr != noErr)
		goto bail;

	// first parameter: the text to be pasted
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, 1, 0, NULL, &myParameterAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddExpressionContainerAtomType(*theActions, myParameterAtom, &myExpressionAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(*theActions, myExpressionAtom, kOperandAtomType, 1, 1, 0, NULL, &myOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(*theActions, myOperandAtom, kOperandFreeMemory, 1, 1, 0, NULL, NULL);
	if (myErr != noErr)
		goto bail;

	// second parameter: selection range begin: 0
	myPos = EndianU32_NtoB(0);	
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, 2, sizeof(myPos), &myPos, NULL);
	if (myErr != noErr)
		goto bail;
		
	// third parameter: selection range end: 0xffff		
	myPos = EndianU32_NtoB(0xffff);	
	myErr = WiredUtils_AddActionParameterAtom(*theActions, myActionAtom, 3, sizeof(myPos), &myPos, NULL);

bail:
	return(myErr);
}	


//////////
//
// QTWired_SetTextTrackProperties
// Set the track properties for the specified text media.
//
//////////

void QTWired_SetTextTrackProperties (Media theMedia, UInt32 theIdleFrequency, Boolean isEditable)
{
	QTAtomContainer		myTrackProperties;
	Boolean				myBoolean;
	UInt32				myFrequency;
	OSErr				myErr = noErr;
		
	myErr = QTNewAtomContainer(&myTrackProperties);
	if (myErr == noErr) {
		// tell the movie controller that this text track has actions
		myBoolean = true;
		QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyHasActions, 1, 1, sizeof(myBoolean), &myBoolean, NULL);
	
		// tell the text track to generate QTIdleEvents
		myFrequency = EndianU32_NtoB(theIdleFrequency);
		QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyQTIdleEventsFrequency, 1, 1, sizeof(myFrequency), &myFrequency, NULL);

		// set the text track to be editable or not, as determined by the isEditable parameter
		QTInsertChild(myTrackProperties, 0, kTrackFocusCanEditFlag, 1, 1, sizeof(isEditable), &isEditable, NULL);

		SetMediaPropertyAtom(theMedia, myTrackProperties);
		
		QTDisposeAtomContainer(myTrackProperties);
	}
}
	

//////////
//
// QTWired_CreateWiredIconMovie
// Create a wired sprite movie containing one or more icons with the specified behavior.
//
//////////

OSErr QTWired_CreateWiredIconMovie (UInt16 theMenuItem)
{
	Movie					myMovie = NULL;
	Track					myTrack = NULL;
	Media					myMedia = NULL;
	FSSpec					myFile;
	Boolean					myIsSelected = false;
	Boolean					myIsReplacing = false;	
	Fixed					myHeight = 0;
	Fixed					myWidth = 0;
	StringPtr 				myPrompt = QTUtils_ConvertCToPascalString(kSpriteSavePrompt);
	StringPtr 				myFileName = QTUtils_ConvertCToPascalString(kSpriteSaveMovieFileName);
	long					myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
	OSType					myType = FOUR_CHAR_CODE('none');
	short					myResRefNum = 0;
	short					myResID = movieInDataForkResID;
	OSErr					myErr = noErr;

	//////////
	//
	// create a new movie file
	//
	//////////

	// prompt the user for the destination file name
	QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
	myErr = myIsSelected ? noErr : userCanceledErr;
	if (!myIsSelected)
		goto bail;

	// create a movie file for the destination movie
	myErr = CreateMovieFile(&myFile, FOUR_CHAR_CODE('TVOD'), smSystemScript, myFlags, &myResRefNum, &myMovie);
	if (myErr != noErr)
		goto bail;
	
	// select the "no-interface" movie controller
	myType = EndianU32_NtoB(myType);
	SetUserDataItem(GetMovieUserData(myMovie), &myType, sizeof(myType), kUserDataMovieControllerType, 1);
	
	//////////
	//
	// create the sprite track and media
	//
	//////////
	
	myWidth = Long2Fix(kIconSpriteTrackWidth);
	myHeight = Long2Fix(kIconSpriteTrackHeight);

	myTrack = NewMovieTrack(myMovie, myWidth, myHeight, kNoVolume);
	myMedia = NewTrackMedia(myTrack, SpriteMediaType, kSpriteMediaTimeScale, NULL, 0);

	myErr = BeginMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// add the appropriate samples to the sprite media
	//
	//////////
	
	switch (theMenuItem) {
		case IDM_MAKE_BOUNCE_ICON_MOVIE:
			myErr = QTWired_AddBouncingIconSpriteToMedia(myMedia);
			break;

		case IDM_MAKE_COLLIDE_ICONS_MOVIE:
			myErr = QTWired_AddCollidingIconsSpritesToMedia(myMedia);
			break;
		
//		case IDM_MAKE_CUSTOM_EVENT_MOVIE:
//			myErr = QTWired_AddCustomEventSpritesToMedia(myMedia);
//			break;
		
		default:
			myErr = paramErr;
			break;
	}

	if (myErr != noErr)
		goto bail;
	
	myErr = EndMediaEdits(myMedia);
	if (myErr != noErr)
		goto bail;
	
	// add the media to the track
	InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
		
	//////////
	//
	// set the sprite track properties
	//
	//////////
	
	QTWired_SetTrackProperties(myMedia, 1);
	
	//////////
	//
	// add the movie resource to the movie file
	//
	//////////
	
	myErr = AddMovieResource(myMovie, myResRefNum, &myResID, myFile.name);
		
bail:
	if (myResRefNum != 0)
		CloseMovieFile(myResRefNum);

	if (myMovie != NULL)
		DisposeMovie(myMovie);
		
	free(myPrompt);
	free(myFileName);

	return(myErr);
}


//////////
//
// QTWired_AddBouncingIconSpriteToMedia
// Build the key frame for the bouncing icon sprite movie.
//
//////////

OSErr QTWired_AddBouncingIconSpriteToMedia (Media theMedia)
{
	QTAtomContainer			mySample = NULL;
	QTAtomContainer			mySpriteData = NULL;
	QTAtom					myEventAtom = 0;
	QTAtom					myActionAtom = 0;
	QTAtom					myNewParamAtom = 0;
	QTAtom					myExpressionAtom = 0;
	QTAtom					myOperandAtom = 0;
	QTAtom					myOperandTypeAtom = 0;
	RGBColor				myKeyColor;
	short					isVisible, myIndex;
	FixedPoint				myPoint;
	OSErr					myErr = noErr;
	
	//////////
	//
	// create a key frame sample containing the sprite images
	//
	//////////

	// create a new, empty key frame sample
	myErr = QTNewAtomContainer(&mySample);
	if (myErr != noErr)
		goto bail;

	myKeyColor.red = myKeyColor.green = myKeyColor.blue = 0xffff;		// white
	
	myPoint.x = 0;
	myPoint.y = 0;

	// add images to the key frame sample
	myErr = SpriteUtils_AddPICTImageToKeyFrameSample(mySample, kNewQTIconID, &myKeyColor, 1, &myPoint, NULL);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// add frame-loaded events that initialize the two sprite track variables to 1
	//
	//////////
	
	myErr = WiredUtils_AddSpriteTrackSetVariableAction(mySample, kParentAtomIsContainer, kQTEventFrameLoaded, kXMoveVarID, kIdleOffset, 0, NULL, 0);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddSpriteTrackSetVariableAction(mySample, kParentAtomIsContainer, kQTEventFrameLoaded, kYMoveVarID, kIdleOffset, 0, NULL, 0);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// add the initial sprite properties to the key frame sample
	//
	//////////

	myErr = QTNewAtomContainer(&mySpriteData);
	if (myErr != noErr)
		goto bail;

	// the QT icon sprite
	isVisible		= true;
	myIndex			= kQTIconImageIndex;
	
	myErr = SpriteUtils_SetSpriteData(mySpriteData, NULL, &isVisible, NULL, &myIndex, NULL, NULL, NULL);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// add idle event actions to the sprite:
	// (1) translate by the current x and y offsets
	// (2) if the left side of the sprite is less than 1 or the right side is greater than the track width,
	//     negate the x offset and move the sprite back to the edge
	// (3) if the top side of the sprite is less than 1 or the bottom side is greater than the track height,
	//     negate the y offset and move the sprite back to the edge
	//
	//////////
	
	//////////
	//
	// (1) translate by the current x and y offsets
	//
	//////////
	
	myErr = WiredUtils_AddQTEventAtom(mySpriteData, kParentAtomIsContainer, kQTEventIdle, &myEventAtom);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTWired_AddTranslateByVariablesAction(mySpriteData, myEventAtom, kXMoveVarID, kYMoveVarID, NULL);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// (2) and (3): if we hit a side, bounce back
	//
	//////////

	myErr = QTWired_AddSideBounceToSprite(mySpriteData, kOperandSpriteBoundsLeft, 1, kOperatorLessThan, kXMoveVarID);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTWired_AddSideBounceToSprite(mySpriteData, kOperandSpriteBoundsRight, kIconSpriteTrackWidth, kOperatorGreaterThan, kXMoveVarID);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTWired_AddSideBounceToSprite(mySpriteData, kOperandSpriteBoundsTop, 1, kOperatorLessThan, kYMoveVarID);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTWired_AddSideBounceToSprite(mySpriteData, kOperandSpriteBoundsBottom, kIconSpriteTrackHeight, kOperatorGreaterThan, kYMoveVarID);
	if (myErr != noErr)
		goto bail;
		
	// add the sprite to the key frame sample
	myErr = SpriteUtils_AddSpriteToSample(mySample, mySpriteData, kQTIconSpriteAtomID);
	if (myErr != noErr)
		goto bail;

	myErr = SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, kSpriteMediaFrameDurationIcon, true, NULL);	

bail:	
	if (mySample != NULL)
		QTDisposeAtomContainer(mySample);

	if (mySpriteData != NULL)
		QTDisposeAtomContainer(mySpriteData);
	
	return(myErr);
}


//////////
//
// QTWired_AddSideBounceToSprite
// If the specified sprite side is beyond the limit, bounce back.
//
//////////

OSErr QTWired_AddSideBounceToSprite (QTAtomContainer theSprite, QTAtomType theSide, float theLimit, QTAtomType theTest, QTAtomID theVariableID)
{
	QTAtom				myActionAtom = 0;
	QTAtom				myNewActionAtom = 0;
	QTAtom				myExpressionAtom = 0;
	QTAtom				myParamAtom = 0;
	QTAtom				myNewParamAtom = 0;
	QTAtom				myOperatorAtom = 0;
	QTAtom				myOperandAtom = 0;
	QTAtom				myOperandTypeAtom = 0;
	QTAtom				myConditionalAtom = 0;
	QTAtom				myActionListAtom = 0;
	Boolean				myBoolean;
	OSErr				myErr = noErr;
	
	// add an idle event "if" action
	myErr = WiredUtils_AddQTEventAndActionAtoms(theSprite, kParentAtomIsContainer, kQTEventIdle, kActionCase, &myActionAtom);
	if (myErr != noErr)
		goto bail;
	
	// add a parameter atom to the kActionCase action atom; this will serve as a parent to hold the expression and action atoms
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myActionAtom, kFirstParam, 0, NULL, &myNewParamAtom);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// if the side of the sprite is less/greater than the specified limit...
	//
	//////////

	myErr = WiredUtils_AddConditionalAtom(theSprite, myNewParamAtom, 1, &myConditionalAtom);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myConditionalAtom, &myExpressionAtom);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddOperatorAtom(theSprite, myExpressionAtom, theTest, &myOperatorAtom);
	if (myErr != noErr)
		goto bail;
		
	// first operand: the specified side of the sprite
	myErr = QTInsertChild(theSprite, myOperatorAtom, kOperandAtomType, 1, 1, 0, NULL, &myOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myOperandAtom, theSide, 1, 1, 0, NULL, NULL);
	if (myErr != noErr)
		goto bail;

	// second operand: the specified limit
	myErr = WiredUtils_AddOperandAtom(theSprite, myOperatorAtom, kOperandConstant, 2, NULL, theLimit);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// ...translate back to the edge...
	//
	//////////

	myErr = WiredUtils_AddActionListAtom(theSprite, myConditionalAtom, &myActionListAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddActionAtom(theSprite, myActionListAtom, kActionSpriteTranslate, &myNewActionAtom);
	if (myErr != noErr)
		goto bail;
	
	// add parameters to the translate action: Fixed x, Fixed y, Boolean isAbsolute
	// first parameter: theLimit - the side of sprite
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myNewActionAtom, kFirstParam, 0, NULL, &myParamAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myParamAtom, &myExpressionAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myExpressionAtom, kOperatorAtomType, kOperatorSubtract, 1, 0, NULL, &myOperatorAtom);
	if (myErr != noErr)
		goto bail;

	// first operand: theLimit
	myErr = WiredUtils_AddOperandAtom(theSprite, myOperatorAtom, kOperandConstant, 1, NULL, theLimit);
	if (myErr != noErr)
		goto bail;

	// second operand: the side of sprite
	myErr = QTInsertChild(theSprite, myOperatorAtom, kOperandAtomType, 2, 2, 0, NULL, &myOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myOperandAtom, theSide, 1, 1, 0, NULL, NULL);
	if (myErr != noErr)
		goto bail;

	// second parameter: 0
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myNewActionAtom, kSecondParam, 0, NULL, &myParamAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myParamAtom, &myExpressionAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddOperandAtom(theSprite, myExpressionAtom, kOperandConstant, 1, NULL, 0);
	if (myErr != noErr)
		goto bail;
	
	// third parameter: false
	myBoolean = false;
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myNewActionAtom, kThirdParam, sizeof(myBoolean), &myBoolean, NULL);

	//////////
	//
	// ...and negate the x or y offset
	//
	//////////

	myErr = QTWired_AddNegateVariableAction(theSprite, myActionListAtom, theVariableID);
		
bail:
	return(myErr);
}


//////////
//
// QTWired_AddCollidingIconsSpritesToMedia
// Build the key frame for the colliding icons sprite movie.
//
//////////

OSErr QTWired_AddCollidingIconsSpritesToMedia (Media theMedia)
{
	QTAtomContainer			mySample = NULL;
	QTAtomContainer			mySpriteData = NULL;
	QTAtom					myEventAtom = 0;
	QTAtom					myActionAtom = 0;
	QTAtom					myNewParamAtom = 0;
	QTAtom					myExpressionAtom = 0;
	QTAtom					myOperandAtom = 0;
	QTAtom					myOperandTypeAtom = 0;
	RGBColor				myKeyColor;
	short					isVisible, myIndex, myLayer;
	FixedPoint				myPoint;
	short					myCount;
	Point					myLocation;
	OSErr					myErr = noErr;
	
	//////////
	//
	// create a key frame sample containing the sprite images
	//
	//////////

	// create a new, empty key frame sample
	myErr = QTNewAtomContainer(&mySample);
	if (myErr != noErr)
		goto bail;

	myKeyColor.red = myKeyColor.green = myKeyColor.blue = 0xffff;		// white
	
	myPoint.x = 0;
	myPoint.y = 0;

	// add images to the key frame sample
	myErr = SpriteUtils_AddPICTImageToKeyFrameSample(mySample, kOldQTIconID, &myKeyColor, kOldQTIconSpriteAtomID, &myPoint, NULL);
	if (myErr != noErr)
		goto bail;

	myErr = SpriteUtils_AddPICTImageToKeyFrameSample(mySample, kNewQTIconID, &myKeyColor, kNewQTIconSpriteAtomID, &myPoint, NULL);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// add frame-loaded events that initialize the sprite track variables
	//
	//////////
	
	myErr = WiredUtils_AddSpriteTrackSetVariableAction(mySample, kParentAtomIsContainer, kQTEventFrameLoaded, kXMoveVarIDNew, kIdleOffset, 0, NULL, 0);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddSpriteTrackSetVariableAction(mySample, kParentAtomIsContainer, kQTEventFrameLoaded, kYMoveVarIDNew, kIdleOffset, 0, NULL, 0);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddSpriteTrackSetVariableAction(mySample, kParentAtomIsContainer, kQTEventFrameLoaded, kXMoveVarIDOld, -kIdleOffset, 0, NULL, 0);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddSpriteTrackSetVariableAction(mySample, kParentAtomIsContainer, kQTEventFrameLoaded, kYMoveVarIDOld, kIdleOffset, 0, NULL, 0);
	if (myErr != noErr)
		goto bail;
	
	for (myCount = 0; myCount <= 1; myCount++) {
		
		myErr = QTNewAtomContainer(&mySpriteData);
		if (myErr != noErr)
			goto bail;

		//////////
		//
		// add the initial sprite properties to the key frame sample
		//
		//////////
		
		isVisible		= true;
		myIndex			= kOldQTIconImageIndex + myCount;
		myLayer			= myCount;
		
		myLocation.h	= 100 * myCount;
		myLocation.v	= 50 * myCount;
				
		myErr = SpriteUtils_SetSpriteData(mySpriteData, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);
		if (myErr != noErr)
			goto bail;

		//////////
		//
		// add idle event actions to the sprite:
		// (1) translate by the current x and y offsets
		// (2) if the left side of the sprite is less than 1 or the right side is greater than the track width,
		//     negate the x offset and move the sprite back to the edge
		// (3) if the top side of the sprite is less than 1 or the bottom side is greater than the track height,
		//     negate the y offset and move the sprite back to the edge
		// (4) if we hit another sprite, recoil
		//
		//////////
		
		//////////
		//
		// (1) translate by the current x and y offsets
		//
		//////////
	
		myErr = WiredUtils_AddQTEventAtom(mySpriteData, kParentAtomIsContainer, kQTEventIdle, &myEventAtom);
		if (myErr != noErr)
			goto bail;
			
		myErr = QTWired_AddTranslateByVariablesAction(mySpriteData, myEventAtom, kXMoveVarID + myCount, kYMoveVarID + myCount, NULL);
		if (myErr != noErr)
			goto bail;

		//////////
		//
		// (2) and (3): if we hit a side, bounce back
		//
		//////////

		myErr = QTWired_AddSideBounceToSprite(mySpriteData, kOperandSpriteBoundsLeft, 1, kOperatorLessThan, kXMoveVarID + myCount);
		if (myErr != noErr)
			goto bail;
			
		myErr = QTWired_AddSideBounceToSprite(mySpriteData, kOperandSpriteBoundsRight, kIconSpriteTrackWidth, kOperatorGreaterThan, kXMoveVarID + myCount);
		if (myErr != noErr)
			goto bail;
			
		myErr = QTWired_AddSideBounceToSprite(mySpriteData, kOperandSpriteBoundsTop, 1, kOperatorLessThan, kYMoveVarID + myCount);
		if (myErr != noErr)
			goto bail;
			
		myErr = QTWired_AddSideBounceToSprite(mySpriteData, kOperandSpriteBoundsBottom, kIconSpriteTrackHeight, kOperatorGreaterThan, kYMoveVarID + myCount);
		if (myErr != noErr)
			goto bail;
			
		//////////
		//
		// (4) if we hit another sprite, recoil
		//
		//////////
		
		if (myCount == 1)
			QTWired_AddCollisionLogicToSprite(mySpriteData);
		
		// add the sprite to the key frame sample
		myErr = SpriteUtils_AddSpriteToSample(mySample, mySpriteData, kOldQTIconSpriteAtomID + myCount);
		if (myErr != noErr)
			goto bail;
	}

//	myErr = SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, kSpriteMediaFrameDurationIcon, true, NULL);	
	myErr = SpriteUtils_AddCompressedSpriteSampleToMedia(theMedia, mySample, kSpriteMediaFrameDurationIcon, true, zlibDataCompressorSubType, NULL);

bail:	
	if (mySample != NULL)
		QTDisposeAtomContainer(mySample);

	if (mySpriteData != NULL)
		QTDisposeAtomContainer(mySpriteData);
	
	return(myErr);
}


//////////
//
// QTWired_AddCollisionLogicToSprite
// If the specified sprite hits another sprite, have both recoil in the appropriate directions.
//
//////////

OSErr QTWired_AddCollisionLogicToSprite (QTAtomContainer theSprite)
{
	OSErr				myErr = noErr;

	myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, kOperandSpriteBoundsLeft, kOperandSpriteBoundsTop);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, kOperandSpriteBoundsRight, kOperandSpriteBoundsTop);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, kOperandSpriteBoundsLeft, kOperandSpriteBoundsBottom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTWired_AddCornerCollisionLogicToSprite(theSprite, kOperandSpriteBoundsRight, kOperandSpriteBoundsBottom);
	
bail:
	return(myErr);	
}


//////////
//
// QTWired_AddCornerCollisionLogicToSprite
// If the corner of the specified sprite hits another sprite, have both recoil in the appropriate directions.
//
// set variable kSpriteHitID to the value of kOperandSpriteTrackSpriteIDAtPoint[theHorizCorner,theVertCorner];
// if value of kSpriteHitID is equal to ID of other sprite,
//		if xdir[1] != xdir[2] then
//			xdir[1] = -xdir[1]
//			xdir[2] = -xdir[2]
//		if ydir[1] != ydir[2] then
//			ydir[1] = -ydir[1]
//			ydir[2] = -ydir[2]
//
// Keep in mind that calling kOperandSpriteTrackSpriteIDAtPoint with the parameters set to the corner of a sprite 
// will always return the ID of that sprite, UNLESS some other sprite at that location has a lower layer.
// 
//////////

OSErr QTWired_AddCornerCollisionLogicToSprite (QTAtomContainer theSprite, UInt32 theHorizCorner, UInt32 theVertCorner)
{
	QTAtom				myActionAtom = 0;
	QTAtom				myNewActionAtom = 0;
	QTAtom				myExpressionAtom = 0;
	QTAtom				myNewExpressionAtom = 0;
	QTAtom				myParamAtom = 0;
	QTAtom				myNewParamAtom = 0;
	QTAtom				myOperatorAtom = 0;
	QTAtom				myNewOperatorAtom = 0;
	QTAtom				myOperandAtom = 0;
	QTAtom				myNewOperandAtom = 0;
	QTAtom				myOperandTypeAtom = 0;
	QTAtom				myNewOperandTypeAtom = 0;
	QTAtom				myConditionalAtom = 0;
	QTAtom				myNewConditionalAtom = 0;
	QTAtom				myActionListAtom = 0;
	QTAtom				myNewActionListAtom = 0;
	QTAtomID			myVariableID;
	OSErr				myErr = noErr;
	
	//////////
	//
	// set variable kSpriteHitID to the value of kOperandSpriteTrackSpriteIDAtPoint[theHorizCorner, theVertCorner]
	//
	//////////

	myErr = WiredUtils_AddQTEventAndActionAtoms(theSprite, kParentAtomIsContainer, kQTEventIdle, kActionSpriteTrackSetVariable, &myActionAtom);
	if (myErr != noErr)
		goto bail;
	
	// add parameters to the set variable action: variable ID (QTAtomID) and value (float)
	myVariableID = EndianU32_NtoB(kSpriteHitID);
	myErr = QTInsertChild(theSprite, myActionAtom, kActionParameter, 0, (short)kFirstParam, sizeof(myVariableID), &myVariableID, NULL);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myActionAtom, kActionParameter, 0, (short)kSecondParam, 0, NULL, &myParamAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myParamAtom, &myExpressionAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myExpressionAtom, kOperandAtomType, 0, 1, 0, NULL, &myOperandAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myOperandAtom, kOperandSpriteTrackSpriteIDAtPoint, 1, 1, 0, NULL, &myOperandTypeAtom);
	if (myErr != noErr)
		goto bail;

	// add parameters to kOperandSpriteTrackSpriteIDAtPoint operand;
	// first param: theHorizCorner
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myOperandTypeAtom, kFirstParam, 0, NULL, &myNewParamAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myNewParamAtom, &myNewExpressionAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myNewExpressionAtom, kOperandAtomType, 0, 1, 0, NULL, &myNewOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myNewOperandAtom, theHorizCorner, 1, 1, 0, NULL, NULL);
	if (myErr != noErr)
		goto bail;

	// second param: theVertCorner
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myOperandTypeAtom, kSecondParam, 0, NULL, &myNewParamAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myNewParamAtom, &myNewExpressionAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myNewExpressionAtom, kOperandAtomType, 0, 1, 0, NULL, &myNewOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myNewOperandAtom, theVertCorner, 1, 1, 0, NULL, NULL);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// if value of variable kSpriteHitID is ID of other sprite (kOldQTIconSpriteAtomID)...
	//
	//////////

	// add an idle event "if" action
	myErr = WiredUtils_AddQTEventAndActionAtoms(theSprite, kParentAtomIsContainer, kQTEventIdle, kActionCase, &myActionAtom);
	if (myErr != noErr)
		goto bail;
	
	// add a parameter atom to the kActionCase action atom
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myActionAtom, kFirstParam, 0, NULL, &myParamAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = WiredUtils_AddConditionalAtom(theSprite, myParamAtom, 1, &myConditionalAtom);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myConditionalAtom, &myExpressionAtom);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddOperatorAtom(theSprite, myExpressionAtom, kOperatorEqualTo, &myOperatorAtom);
	if (myErr != noErr)
		goto bail;
		
	// first operand: the value of kSpriteHitID variable
	myErr = QTInsertChild(theSprite, myOperatorAtom, kOperandAtomType, 1, 1, 0, NULL, &myOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myOperandAtom, kOperandSpriteTrackVariable, 1, 1, 0, NULL, &myOperandTypeAtom);
	if (myErr != noErr)
		goto bail;

	myVariableID = EndianU32_NtoB(kSpriteHitID);
	myErr = QTInsertChild(theSprite, myOperandTypeAtom, kActionParameter, 1, 1, sizeof(myVariableID), &myVariableID, NULL);
	if (myErr != noErr)
		goto bail;

	// second operand: the sprite ID
	myErr = WiredUtils_AddOperandAtom(theSprite, myOperatorAtom, kOperandConstant, 2, NULL, (float)kOldQTIconSpriteAtomID);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// ...and if the two sprites are moving in different horizontal directions...
	//
	//////////

	myErr = WiredUtils_AddActionListAtom(theSprite, myConditionalAtom, &myActionListAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddActionAtom(theSprite, myActionListAtom, kActionCase, &myNewActionAtom);
	if (myErr != noErr)
		goto bail;

	// add a parameter atom to the kActionCase action atom
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myNewActionAtom, kFirstParam, 0, NULL, &myNewParamAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = WiredUtils_AddConditionalAtom(theSprite, myNewParamAtom, 1, &myNewConditionalAtom);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myNewConditionalAtom, &myNewExpressionAtom);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddOperatorAtom(theSprite, myNewExpressionAtom, kOperatorNotEqualTo, &myNewOperatorAtom);
	if (myErr != noErr)
		goto bail;
		
	// first operand: the value of kXMoveVarIDNew
	myErr = QTInsertChild(theSprite, myNewOperatorAtom, kOperandAtomType, 1, 1, 0, NULL, &myNewOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myNewOperandAtom, kOperandSpriteTrackVariable, 1, 1, 0, NULL, &myNewOperandTypeAtom);
	if (myErr != noErr)
		goto bail;

	myVariableID = EndianU32_NtoB(kXMoveVarIDNew);
	myErr = QTInsertChild(theSprite, myNewOperandTypeAtom, kActionParameter, 1, 1, sizeof(myVariableID), &myVariableID, NULL);
	if (myErr != noErr)
		goto bail;

	// second operand: the value of kXMoveVarIDOld
	myErr = QTInsertChild(theSprite, myNewOperatorAtom, kOperandAtomType, 2, 2, 0, NULL, &myNewOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myNewOperandAtom, kOperandSpriteTrackVariable, 1, 1, 0, NULL, &myNewOperandTypeAtom);
	if (myErr != noErr)
		goto bail;

	myVariableID = EndianU32_NtoB(kXMoveVarIDOld);
	myErr = QTInsertChild(theSprite, myNewOperandTypeAtom, kActionParameter, 1, 1, sizeof(myVariableID), &myVariableID, NULL);
	if (myErr != noErr)
		goto bail;
		
	// ...adjust the direction of travel...
	myErr = WiredUtils_AddActionListAtom(theSprite, myNewConditionalAtom, &myNewActionListAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTWired_AddNegateVariableAction(theSprite, myNewActionListAtom, kXMoveVarIDNew);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTWired_AddNegateVariableAction(theSprite, myNewActionListAtom, kXMoveVarIDOld);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// ...and if the two sprites are moving in different vertical directions...
	//
	//////////

	myErr = WiredUtils_AddActionAtom(theSprite, myActionListAtom, kActionCase, &myNewActionAtom);
	if (myErr != noErr)
		goto bail;

	// add a parameter atom to the kActionCase action atom
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myNewActionAtom, kFirstParam, 0, NULL, &myNewParamAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = WiredUtils_AddConditionalAtom(theSprite, myNewParamAtom, 1, &myNewConditionalAtom);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myNewConditionalAtom, &myNewExpressionAtom);
	if (myErr != noErr)
		goto bail;
		
	myErr = WiredUtils_AddOperatorAtom(theSprite, myNewExpressionAtom, kOperatorNotEqualTo, &myNewOperatorAtom);
	if (myErr != noErr)
		goto bail;
		
	// first operand: the value of kYMoveVarIDNew
	myErr = QTInsertChild(theSprite, myNewOperatorAtom, kOperandAtomType, 1, 1, 0, NULL, &myNewOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myNewOperandAtom, kOperandSpriteTrackVariable, 1, 1, 0, NULL, &myNewOperandTypeAtom);
	if (myErr != noErr)
		goto bail;

	myVariableID = EndianU32_NtoB(kYMoveVarIDNew);
	myErr = QTInsertChild(theSprite, myNewOperandTypeAtom, kActionParameter, 1, 1, sizeof(myVariableID), &myVariableID, NULL);
	if (myErr != noErr)
		goto bail;

	// second operand: the value of kYMoveVarIDOld
	myErr = QTInsertChild(theSprite, myNewOperatorAtom, kOperandAtomType, 2, 2, 0, NULL, &myNewOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myNewOperandAtom, kOperandSpriteTrackVariable, 1, 1, 0, NULL, &myNewOperandTypeAtom);
	if (myErr != noErr)
		goto bail;

	myVariableID = EndianU32_NtoB(kYMoveVarIDOld);
	myErr = QTInsertChild(theSprite, myNewOperandTypeAtom, kActionParameter, 1, 1, sizeof(myVariableID), &myVariableID, NULL);
	if (myErr != noErr)
		goto bail;
		
	// ...adjust the direction of travel...
	myErr = WiredUtils_AddActionListAtom(theSprite, myNewConditionalAtom, &myNewActionListAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTWired_AddNegateVariableAction(theSprite, myNewActionListAtom, kYMoveVarIDNew);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTWired_AddNegateVariableAction(theSprite, myNewActionListAtom, kYMoveVarIDOld);

bail:	
	return(myErr);
}
	
	
//////////
//
// QTWired_SetTrackProperties
// Set the track properties for the specified sample sprite media.
//
//////////

void QTWired_SetTrackProperties (Media theMedia, UInt32 theIdleFrequency)
{
	QTAtomContainer		myTrackProperties;
	RGBColor			myBackgroundColor;
	Boolean				hasActions;
	UInt32				myFrequency;
	OSErr				myErr = noErr;
		
	// add a background color to the sprite track
	myBackgroundColor.red = EndianU16_NtoB(0xffff);
	myBackgroundColor.green = EndianU16_NtoB(0xffff);
	myBackgroundColor.blue = EndianU16_NtoB(0xffff);
	
	myErr = QTNewAtomContainer(&myTrackProperties);
	if (myErr == noErr) {
		QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyBackgroundColor, 1, 1, sizeof(myBackgroundColor), &myBackgroundColor, NULL);

		// tell the movie controller that this sprite track has actions
		hasActions = true;
		QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyHasActions, 1, 1, sizeof(hasActions), &hasActions, NULL);
	
		// tell the sprite track to generate QTIdleEvents
		myFrequency = EndianU32_NtoB(theIdleFrequency);
		QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyQTIdleEventsFrequency, 1, 1, sizeof(myFrequency), &myFrequency, NULL);

		SetMediaPropertyAtom(theMedia, myTrackProperties);
		
		QTDisposeAtomContainer(myTrackProperties);
	}
}
	

//////////
//
// QTWired_AddNegateVariableAction
// Add an action atom that negates the specified sprite track variable.
//
//////////

static OSErr QTWired_AddNegateVariableAction (QTAtomContainer theSprite, QTAtom theParentAtom, QTAtomID theVariableID)
{
	QTAtom				myActionAtom = 0;
	QTAtom				myExpressionAtom = 0;
	QTAtom				myParamAtom = 0;
	QTAtom				myOperatorAtom = 0;
	QTAtom				myOperandAtom = 0;
	QTAtom				myOperandTypeAtom = 0;
	QTAtomID			myVariableID;
	OSErr				myErr = paramErr;

	if ((theSprite == NULL) || (theParentAtom == 0))
		goto bail;

	myErr = WiredUtils_AddActionAtom(theSprite, theParentAtom, kActionSpriteTrackSetVariable, &myActionAtom);
	if (myErr != noErr)
		goto bail;
	
	// add parameters to the set variable action: variable ID (QTAtomID) and value (float)
	myVariableID = EndianU32_NtoB(theVariableID);
	myErr = QTInsertChild(theSprite, myActionAtom, kActionParameter, 0, (short)kFirstParam, sizeof(myVariableID), &myVariableID, NULL);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myActionAtom, kActionParameter, 0, (short)kSecondParam, 0, NULL, &myParamAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myParamAtom, &myExpressionAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myExpressionAtom, kOperatorAtomType, kOperatorNegate, 1, 0, NULL, &myOperatorAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myOperatorAtom, kOperandAtomType, 0, 1, 0, NULL, &myOperandAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myOperandAtom, kOperandSpriteTrackVariable, 1, 1, 0, NULL, &myOperandTypeAtom);
	if (myErr != noErr)
		goto bail;

	myVariableID = EndianU32_NtoB(theVariableID);
	myErr = QTInsertChild(theSprite, myOperandTypeAtom, kActionParameter, 1, 1, sizeof(myVariableID), &myVariableID, NULL);

bail:
	return(myErr);
}
	

//////////
//
// QTWired_AddTranslateByVariablesAction
// Add an action atom that translates the specified sprite relatively by an amount specified by two variables.
//
//////////

static OSErr QTWired_AddTranslateByVariablesAction (QTAtomContainer theSprite, QTAtom theParentAtom, QTAtomID theXVariableID, QTAtomID theYVariableID, QTAtom *theActionAtom)
{
	QTAtom				myActionAtom = 0;
	QTAtom				myExpressionAtom = 0;
	QTAtom				myParamAtom = 0;
	QTAtom				myOperatorAtom = 0;
	QTAtom				myOperandAtom = 0;
	QTAtom				myOperandTypeAtom = 0;
	QTAtomID			myVariableID;
	Boolean				myBoolean;
	OSErr				myErr = paramErr;

	if (theSprite == NULL)
		goto bail;

	// add a translate action atom to the specified parent atom
	myErr = WiredUtils_AddActionAtom(theSprite, theParentAtom, kActionSpriteTranslate, &myActionAtom);
	if (myErr != noErr)
		goto bail;

	// add parameters to the translate action: Fixed x, Fixed y, Boolean isAbsolute

	// first parameter: get value of variable theXVariableID
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myActionAtom, kFirstParam, 0, NULL, &myParamAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myParamAtom, &myExpressionAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myExpressionAtom, kOperandAtomType, 0, 1, 0, NULL, &myOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myOperandAtom, kOperandSpriteTrackVariable, 1, 1, 0, NULL, &myOperandTypeAtom);
	if (myErr != noErr)
		goto bail;

	myVariableID = EndianU32_NtoB(theXVariableID);
	myErr = QTInsertChild(theSprite, myOperandTypeAtom, kActionParameter, 1, 1, sizeof(myVariableID), &myVariableID, NULL);
	if (myErr != noErr)
		goto bail;
	
	// second parameter: get value of variable theYVariableID
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myActionAtom, kSecondParam, 0, NULL, &myParamAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myParamAtom, &myExpressionAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myExpressionAtom, kOperandAtomType, 0, 1, 0, NULL, &myOperandAtom);
	if (myErr != noErr)
		goto bail;
	
	myErr = QTInsertChild(theSprite, myOperandAtom, kOperandSpriteTrackVariable, 1, 1, 0, NULL, &myOperandTypeAtom);
	if (myErr != noErr)
		goto bail;

	myVariableID = EndianU32_NtoB(theYVariableID);
	myErr = QTInsertChild(theSprite, myOperandTypeAtom, kActionParameter, 1, 1, sizeof(myVariableID), &myVariableID, NULL);
	if (myErr != noErr)
		goto bail;
	
	// third parameter: false (for relative translation)
	myBoolean = false;
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myActionAtom, kThirdParam, sizeof(myBoolean), &myBoolean, NULL);
	
bail:
	if (theActionAtom != NULL)
		*theActionAtom = myActionAtom;

	return(myErr);
}
	

//////////
//
// QTWired_AddGetValueOfVarIDAction
// Add an action atom that sets theTempVariableID to the value in the variable whose ID is the value of theVarID.
//
//////////

static OSErr QTWired_AddGetValueOfVarIDAction (QTAtomContainer theSprite, QTAtom theParentAtom, QTAtomID theTempVariableID, QTAtomID theVarID)
{
	QTAtom				myActionAtom = 0;
	QTAtom				myExpressionAtom = 0;
	QTAtom				myNewExpressionAtom = 0;
	QTAtom				myParamAtom = 0;
	QTAtom				myNewParamAtom = 0;
	QTAtom				myOperandAtom = 0;
	QTAtom				myNewOperandAtom = 0;
	QTAtom				myOperandTypeAtom = 0;
	QTAtom				myNewOperandTypeAtom = 0;
	QTAtomID			myVariableID;
	OSErr				myErr = paramErr;

	if (theSprite == NULL)
		goto bail;

#if 0
	{
		Str255			myString;
		
		NumToString(theTempVariableID, myString);
		
		myErr = WiredUtils_AddActionAtom(theSprite, theParentAtom, kActionDebugStr, &myActionAtom);
		if (myErr != noErr)
			goto bail;
			
		// add a target atom as the first parameter
		myErr = WiredUtils_AddActionParameterAtom(theSprite, myActionAtom, kFirstParam, myString[0], &myString, NULL);
		if (myErr != noErr)
			goto bail;
	}
#endif

	myErr = WiredUtils_AddActionAtom(theSprite, theParentAtom, kActionSpriteTrackSetVariable, &myActionAtom);
	if (myErr != noErr)
		goto bail;

	myVariableID = EndianU32_NtoB(theTempVariableID);
	myErr = WiredUtils_AddActionParameterAtom(theSprite, myActionAtom, kFirstParam, sizeof(myVariableID), &myVariableID, NULL);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddActionParameterAtom(theSprite, myActionAtom, kSecondParam, 0, NULL, &myParamAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myParamAtom, &myExpressionAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myExpressionAtom, kOperandAtomType, 0, 1, 0, NULL, &myOperandAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myOperandAtom, kOperandSpriteTrackVariable, 1, 1, 0, NULL, &myOperandTypeAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddActionParameterAtom(theSprite, myOperandTypeAtom, kFirstParam, 0, NULL, &myNewParamAtom);
	if (myErr != noErr)
		goto bail;

	myErr = WiredUtils_AddExpressionContainerAtomType(theSprite, myNewParamAtom, &myNewExpressionAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myNewExpressionAtom, kOperandAtomType, 0, 1, 0, NULL, &myNewOperandAtom);
	if (myErr != noErr)
		goto bail;

	myErr = QTInsertChild(theSprite, myNewOperandAtom, kOperandSpriteTrackVariable, 1, 1, 0, NULL, &myNewOperandTypeAtom);
	if (myErr != noErr)
		goto bail;

	myVariableID = EndianU32_NtoB(theVarID);
	myErr = QTInsertChild(theSprite, myNewOperandTypeAtom, kActionParameter, 1, 1, sizeof(myVariableID), &myVariableID, NULL);

bail:
	return(myErr);
}
	


