//////////
//
//	File:		QTTimeCode.c
//
//	Contains:	QuickTime timecode media handler sample code.
//
//	Written by:	Tim Monroe
//				Based on AddTC.c sample code by QT Engineering and Apple Developer Technical Support.
//
//	Copyright:	© 1998-2000 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <9>	 	11/08/00	rtm		removed "\p" escape sequences from QTTC_OptionsUserItemProcedure
//	   <8>	 	10/15/00	rtm		added QTTC_OptionsUserItemProcedure, so user item draws on Windows;
//									added font pop-up support to Windows
//	   <7>	 	10/10/00	rtm		added font pop-up menu (currently Mac only)
//	   <6>	 	06/21/00	rtm		minor tweaks
//	   <5>	 	03/17/00	rtm		made changes to get things running under CarbonLib
//	   <4>	 	04/07/99	rtm		modified QTTC_AddTimeCodeToMovie to add big-endian time code data to movie
//									(fixes problems on Windows)
//	   <3>	 	06/05/98	rtm		fixed track geometry calculations in QTTC_AddTimeCodeToMovie; other minor
//									cleanup here and there
//	   <2>	 	05/21/98	rtm		added QTTC_ShowStringToUser
//	   <1>	 	05/18/98	rtm		first file; conversion to personal coding style; updated to latest headers;
//									added ability to toggle timecode display in a movie containing a timecode track;
//									added ability to extract timecode values and source info; ported to Windows
//	 
//	This source code shows how to add a timecode track to a movie. It also shows how to toggle the visibility
//	of a movie's timecode track and how to extract timecode values (and other info) from a timecode track. 
//
//////////

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

#include "QTTimeCode.h"


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

Str255					gSrcName;
Str255					gFontName;						// name of current font
long					gFontIndex = 1;					// index in font pop-up menu of current font
TimeScale				gTimeScale = 2997;				// typical for NTSC video (29.97 frames/sec)
TimeValue				gFrameDur = 100;				// duration of a single frame
long					gNumFrames = 30;				// frames per second
Boolean					gDisplayTimeCode = false;
Boolean					gDisplayBelowVideo = true;
Boolean					gDropFrameVal = true;
Boolean					g24Hour = true;
Boolean					gNegOK = false;
Boolean					gIsNeg = false;
Boolean					gUseTimeCode = true;			// ue timecode (true) or counter (false)?
long					gHours = 0;
long					gMinutes = 0;
long					gSeconds = 0;
long					gFrames = 0;
long					gCounterVal = 0;
UserItemUPP				gOptionsUserItemProcUPP = NULL;	// UPP to our options dialog user item procedure

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


//////////
//
// QTTC_DeleteTimeCodeTracks
// Remove all existing timecode tracks from the specified movie.
//
//////////

void QTTC_DeleteTimeCodeTracks (Movie theMovie)
{
	Track				myTrack = NULL;
	
	myTrack = GetMovieIndTrackType(theMovie, 1, TimeCodeMediaType, movieTrackMediaType);
	while (myTrack != NULL) {
		DisposeMovieTrack(myTrack);
		myTrack = GetMovieIndTrackType(theMovie, 1, TimeCodeMediaType, movieTrackMediaType);
	}
}


//////////
//
// QTTC_AddTimeCodeToMovie
// Add a timecode track to the specified movie.
//
//////////

OSErr QTTC_AddTimeCodeToMovie (Movie theMovie, OSType theType)
{
	Track						myTypeTrack = NULL;
	Track						myTrack = NULL;
	Media						myMedia = NULL;
	MediaHandler				myHandler = NULL;
	TimeCodeDef					myTCDef;
	TimeCodeRecord				myTCRec;
	Str63						myString;
	TimeValue					myDuration;
	MatrixRecord				myMatrix;
	Fixed						myWidth;
	Fixed						myHeight;
	Fixed						myTCHeight;
	long						myFlags = 0L;
	TCTextOptions				myTextOptions;
	FontInfo					myFontInfo;
	TimeCodeDescriptionHandle	myDesc = NULL;
	long						**myFrameHandle;
	OSErr						myErr = noErr;
	
	//////////
	//
	// find the target track
	//
	//////////

	// get the (first) track of the specified type; this track determines the width of the new timecode track
	myTypeTrack = GetMovieIndTrackType(theMovie, 1, theType, movieTrackMediaType);
	if (myTypeTrack == NULL) {
		myErr = trackNotInMovie;
		goto bail;
	}
	
	// get the dimensions of the target track
	GetTrackDimensions(myTypeTrack, &myWidth, &myHeight);
	
	//////////
	//
	// create the timecode track and media
	//
	//////////

	myTrack = NewMovieTrack(theMovie, myWidth, kTimeCodeTrackSize, kNoVolume);
	if (myTrack == NULL)
		goto bail;
		
	myMedia = NewTrackMedia(myTrack, TimeCodeMediaType, GetMovieTimeScale(theMovie), NULL, 0);
	if (myMedia == NULL)
		goto bail;
		
	myHandler = GetMediaHandler(myMedia);
	if (myHandler == NULL)
		goto bail;
	
	//////////
	//
	// fill in a timecode definition structure; this becomes part of the timecode description
	//
	//////////
	
	// set the timecode format information flags
	if (gUseTimeCode) {
		myFlags = 0L;
		if (gDropFrameVal)
			myFlags |= tcDropFrame;
		if (gIsNeg)
			myFlags |= tcNegTimesOK;
		if (g24Hour)
			myFlags |= tc24HourMax;
		
	} else {
		myFlags = tcCounter;
	}
	
	myTCDef.flags = myFlags;
	myTCDef.fTimeScale = gTimeScale;
	myTCDef.frameDuration = gFrameDur;
	myTCDef.numFrames = gNumFrames;

	//////////
	//
	// fill in a timecode record
	//
	//////////
	
	if (gUseTimeCode) {
		myTCRec.t.hours = (UInt8)gHours;
		myTCRec.t.minutes = (UInt8)gMinutes;		// negative flag is here
		myTCRec.t.seconds = (UInt8)gSeconds;
		myTCRec.t.frames = (UInt8)gFrames;
		if (gIsNeg)
			myTCRec.t.minutes |= tctNegFlag;
	} else {
		myTCRec.c.counter = gCounterVal;
	}

	//////////
	//
	// figure out the timecode track geometry
	//
	//////////
	
	// get display options to calculate box height
	TCGetDisplayOptions(myHandler, &myTextOptions);
	GetFNum(gFontName, &myTextOptions.txFont);
	TCSetDisplayOptions(myHandler, &myTextOptions);
	
	// use the starting time to figure out the dimensions of track	
	TCTimeCodeToString(myHandler, &myTCDef, &myTCRec, myString);
	TextFont(myTextOptions.txFont);
	TextFace(myTextOptions.txFace);
	TextSize(myTextOptions.txSize);
	GetFontInfo(&myFontInfo);
	
	// calculate track width and height based on text	
	myTCHeight = FixRatio(myFontInfo.ascent + myFontInfo.descent + 2, 1);
	SetTrackDimensions(myTrack, myWidth, myTCHeight);

	GetTrackMatrix(myTrack, &myMatrix);
	if (gDisplayBelowVideo)
		TranslateMatrix(&myMatrix, 0, myHeight);
	
	SetTrackMatrix(myTrack, &myMatrix);	
	SetTrackEnabled(myTrack, gDisplayTimeCode ? true : false);
		
	TCSetTimeCodeFlags(myHandler, gDisplayTimeCode ? tcdfShowTimeCode : 0, tcdfShowTimeCode);
	
	//////////
	//
	// edit the track media
	//
	//////////

	myErr = BeginMediaEdits(myMedia);	
	if (myErr == noErr) {
		long				mySize;
		UserData			myUserData;
		
		//////////
		//
		// create and configure a new timecode description handle
		//
		//////////

		mySize = sizeof(TimeCodeDescription);
		myDesc = (TimeCodeDescriptionHandle)NewHandleClear(mySize);
		if (myDesc == NULL)
			goto bail;
		
		(**myDesc).descSize = mySize;
		(**myDesc).dataFormat = TimeCodeMediaType;
		(**myDesc).timeCodeDef = myTCDef;
		
		//////////
		//
		// set the source identification information
		//
		//////////

		// the source identification information for a timecode track is stored
		// in a user data item of type TCSourceRefNameType
		myErr = NewUserData(&myUserData);
		if (myErr == noErr) {
			Handle 							myNameHandle = NULL;
			
			myErr = PtrToHand(&gSrcName[1], &myNameHandle, gSrcName[0]);
			if (myErr == noErr) {
				myErr = AddUserDataText(myUserData, myNameHandle, TCSourceRefNameType, 1, langEnglish);
				if (myErr == noErr)
					TCSetSourceRef(myHandler, myDesc, myUserData);
			}
			
			if (myNameHandle != NULL)
				DisposeHandle(myNameHandle);
				
			DisposeUserData(myUserData);
		}

		//////////
		//
		// add a sample to the timecode track
		//
		// each sample in a timecode track provides timecode information for a span of movie time;
		// here, we add a single sample that spans the entire movie duration
		//
		//////////

		// the sample data contains a frame number that identifies one or more content frames
		// that use the timecode; this value (a long integer) identifies the first frame that
		// uses the timecode
		myFrameHandle = (long **)NewHandle(sizeof(long));
		if (myFrameHandle == NULL)
			goto bail;
		
		myErr = TCTimeCodeToFrameNumber(myHandler, &(**myDesc).timeCodeDef, &myTCRec, *myFrameHandle);

		// the data in the timecode track must be big-endian		
		**myFrameHandle = EndianS32_NtoB(**myFrameHandle);
		
		myDuration = GetMovieDuration(theMovie);
		// since we created the track with the same timescale as the movie,
		// we don't need to convert the duration
		
		myErr = AddMediaSample(myMedia, (Handle)myFrameHandle, 0, GetHandleSize((Handle)myFrameHandle), myDuration, (SampleDescriptionHandle)myDesc, 1, 0, 0);
		if (myErr != noErr)
			goto bail;	
	}
	
	myErr = EndMediaEdits(myMedia);	
	if (myErr != noErr)
		goto bail;
			
	myErr = InsertMediaIntoTrack(myTrack, 0, 0, myDuration, fixed1);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// create a track reference from the target track to the timecode track
	//
	//////////
	
	myErr = AddTrackReference(myTypeTrack, myTrack, TimeCodeMediaType, NULL);
	
bail:
	if (myDesc != NULL)
		DisposeHandle((Handle)myDesc);
		
	if (myFrameHandle != NULL)
		DisposeHandle((Handle)myFrameHandle);

	return(myErr);
}


//////////
//
// QTTC_GetTimeCodeOptions 
// Display our options dialog box and get the user's selections.
//
//////////

Boolean QTTC_GetTimeCodeOptions (void)
{
	DialogPtr			myDialog = NULL;
	short				myItem = kStdCancelItemIndex;
	short				myKind;
	ControlHandle		myControl = NULL;
	Handle				myHandle = NULL;
	MenuHandle			myMenu = NULL;
	Rect				myRect;
	GrafPtr				myPort;
	
	GetPort(&myPort);
	
	myDialog = GetNewDialog(kTimeCodeDialogID, NULL, (WindowPtr)-1L);
	if (myDialog == NULL)
		goto bail;
	
	SetDialogDefaultItem(myDialog, kStdOkItemIndex);
	SetDialogCancelItem(myDialog, kStdCancelItemIndex);

	myControl = QTTC_GetDItemHandle(myDialog, kItemDisplayTimeCode);
	SetControlValue(myControl, gDisplayTimeCode);
	myControl = QTTC_GetDItemHandle(myDialog, kItemBelowVideo);
	SetControlValue(myControl, gDisplayBelowVideo);
	myControl = QTTC_GetDItemHandle(myDialog, kItemDropFrame);
	SetControlValue(myControl, gDropFrameVal);
	myControl = QTTC_GetDItemHandle(myDialog, kItem24Hour);
	SetControlValue(myControl, g24Hour);
	myControl = QTTC_GetDItemHandle(myDialog, kItemNegOK);
	SetControlValue(myControl, gNegOK);

	// set a user-item drawing procedure for the pos/neg box
	GetDialogItem(myDialog, kItemIsNeg, &myKind, &myHandle, &myRect);						
	SetDialogItem(myDialog, kItemIsNeg, myKind, (Handle)gOptionsUserItemProcUPP, &myRect);

	myControl = QTTC_GetDItemHandle(myDialog, kItemUseTC);
	SetControlValue(myControl, gUseTimeCode ? 1 : 0);
	myControl = QTTC_GetDItemHandle(myDialog, kItemUseCounter);
	SetControlValue(myControl, gUseTimeCode ? 0 : 1);

	QTTC_SetDialogTextNumber(myDialog, kItemTimeScale, gTimeScale);
	QTTC_SetDialogTextNumber(myDialog, kItemFrameDur, gFrameDur);
	QTTC_SetDialogTextNumber(myDialog, kItemNumFrames, gNumFrames);
	QTTC_SetDialogTextNumber(myDialog, kItemHours, gHours);
	QTTC_SetDialogTextNumber(myDialog, kItemMinutes, gMinutes);
	QTTC_SetDialogTextNumber(myDialog, kItemSeconds, gSeconds);
	QTTC_SetDialogTextNumber(myDialog, kItemFrames, gFrames);

	QTTC_SetDialogTextNumber(myDialog, kItemCounter, gCounterVal);
	
	QTTC_SetDialogTextString(myDialog, kItemSrcName, gSrcName);

	// set the current value of the pop-up font menu
	myControl = QTTC_GetDItemRect(myDialog, kFontPopUpMenuControl, &myRect);
	if (myControl != NULL) {
		myMenu = MacGetMenu(kFontPopUpResID);
		if (myMenu != NULL) {
#if ACCESSOR_CALLS_ARE_FUNCTIONS
			// insert the menu into the menu list
			MacInsertMenu(myMenu, kInsertHierarchicalMenu);
			SetControlPopupMenuHandle(myControl, myMenu);
			SetControlPopupMenuID(myControl, kFontPopUpResID);
#else			
			PopupPrivateData	myPrivateData;

			// insert the menu into the menu list
			MacInsertMenu(myMenu, kInsertHierarchicalMenu);
			myPrivateData.mHandle = myMenu;
			myPrivateData.mID = kFontPopUpResID;
			(**(PopupPrivateDataHandle)(**myControl).contrlData) = myPrivateData;
#endif					
			// clean out existing menu
			while (CountMenuItems(myMenu))
				DeleteMenuItem(myMenu, 1);
				
			// add in the available fonts
			AppendResMenu(myMenu, FOUR_CHAR_CODE('FONT'));
			SetControlMaximum(myControl, CountMenuItems(myMenu));
			SetControlValue(myControl, gFontIndex);
		}
	}

#if TARGET_API_MAC_CARBON
	MacShowWindow(GetDialogWindow(myDialog));
#else
	MacShowWindow(myDialog);
#endif
	
noGood:
	do {	
		ModalDialog(gModalFilterUPP, &myItem);
		switch (myItem) {
			case kItemDisplayTimeCode:
				myControl = QTTC_GetDItemHandle(myDialog, kItemDisplayTimeCode);
				SetControlValue(myControl, !GetControlValue(myControl));
				break;
				
			case kItemBelowVideo:
				myControl = QTTC_GetDItemHandle(myDialog, kItemBelowVideo);
				SetControlValue(myControl, !GetControlValue(myControl));
				break;
				
			case kItemDropFrame:
				myControl = QTTC_GetDItemHandle(myDialog, kItemDropFrame);
				SetControlValue(myControl, !GetControlValue(myControl));
				break;
				
			case kItem24Hour:
				myControl = QTTC_GetDItemHandle(myDialog, kItem24Hour);
				SetControlValue(myControl, !GetControlValue(myControl));
				break;
				
			case kItemNegOK:
				myControl = QTTC_GetDItemHandle(myDialog, kItemNegOK);
				SetControlValue(myControl, !GetControlValue(myControl));
				break;
				
			case kItemUseTC:
				myControl = QTTC_GetDItemHandle(myDialog, kItemUseTC);
				SetControlValue(myControl, 1);
				myControl = QTTC_GetDItemHandle(myDialog, kItemUseCounter);
				SetControlValue(myControl, 0);
				break;
				
			case kItemUseCounter:
				myControl = QTTC_GetDItemHandle(myDialog, kItemUseCounter);
				SetControlValue(myControl, 1);
				myControl = QTTC_GetDItemHandle(myDialog, kItemUseTC);
				SetControlValue(myControl, 0);
				break;
				
			case kItemIsNeg:
				gIsNeg = !gIsNeg;
				myControl = QTTC_GetDItemRect(myDialog, kItemIsNeg, &myRect);

#if TARGET_API_MAC_CARBON
				InvalWindowRect(GetDialogWindow(myDialog), &myRect);
#else		
				InvalRect(&myRect);
				DrawDialog(myDialog);		// force a redraw (necessary on Windows)
#endif		
				break;
			
			default:
				break;
		}
	} while ((myItem != kStdOkItemIndex) && (myItem != kStdCancelItemIndex));
	
	if (myItem == kStdOkItemIndex) {
		myControl = QTTC_GetDItemHandle(myDialog, kItemSrcName);
		GetDialogItemText((Handle)myControl, gSrcName);
	
		myControl = QTTC_GetDItemHandle(myDialog, kItemDisplayTimeCode);
		gDisplayTimeCode = GetControlValue(myControl);
		myControl = QTTC_GetDItemHandle(myDialog, kItemBelowVideo);
		gDisplayBelowVideo = GetControlValue(myControl);
		myControl = QTTC_GetDItemHandle(myDialog, kItemDropFrame);
		gDropFrameVal = GetControlValue(myControl);
		myControl = QTTC_GetDItemHandle(myDialog, kItem24Hour);
		g24Hour = GetControlValue(myControl);
		myControl = QTTC_GetDItemHandle(myDialog, kItemNegOK);
		gNegOK = GetControlValue(myControl);
		myControl = QTTC_GetDItemHandle(myDialog, kItemUseTC);
		gUseTimeCode = (GetControlValue(myControl) != 0);

		myControl = QTTC_GetDItemHandle(myDialog, kFontPopUpMenuControl);
		if (myControl != NULL) {
			MenuHandle	myMenu = NULL;
			
#if ACCESSOR_CALLS_ARE_FUNCTIONS
			myMenu = GetControlPopupMenuHandle(myControl);
#else
			myMenu = (**((PopupPrivateDataHandle)(**myControl).contrlData)).mHandle;
#endif		
			gFontIndex = GetControlValue(myControl);
			GetMenuItemText(myMenu, gFontIndex, gFontName);
		}
		
		if (!QTTC_ValidateDialogLong(myDialog, kItemTimeScale, &gTimeScale))
			goto noGood;
		if (!QTTC_ValidateDialogLong(myDialog, kItemFrameDur, &gFrameDur))
			goto noGood;
		if (!QTTC_ValidateDialogLong(myDialog, kItemNumFrames, &gNumFrames))
			goto noGood;
		if (!QTTC_ValidateDialogLong(myDialog, kItemHours, &gHours))
			goto noGood;
		if (!QTTC_ValidateDialogLong(myDialog, kItemMinutes, &gMinutes))
			goto noGood;
		if (!QTTC_ValidateDialogLong(myDialog, kItemSeconds, &gSeconds))
			goto noGood;
		if (!QTTC_ValidateDialogLong(myDialog, kItemFrames, &gFrames))
			goto noGood;
		if (!QTTC_ValidateDialogLong(myDialog, kItemCounter, &gCounterVal))
			goto noGood;
	}
	
bail:
	// remove the pop-up menu from the menu list
	if (myMenu != NULL)
#if ACCESSOR_CALLS_ARE_FUNCTIONS
		MacDeleteMenu(GetMenuID(myMenu));
#else
		MacDeleteMenu((**myMenu).menuID);
#endif

	if (myDialog != NULL)
		DisposeDialog(myDialog);

	MacSetPort(myPort);
	
	return(myItem == kStdOkItemIndex);
}


//////////
//
// QTTC_OptionsUserItemProcedure
// A user-item procedure to draw a box in the options dialog box.
//
//////////

PASCAL_RTN void QTTC_OptionsUserItemProcedure (DialogPtr theDialog, short theItem)
{
	Handle						myItemHandle = NULL;
	Rect						myRect;
	ControlHandle				myControl = NULL;
	StringPtr					mySign = NULL;
	
	if (theItem != kItemIsNeg)
		return;

	MacSetPort(GetDialogPort(theDialog));

	myControl = QTTC_GetDItemRect(theDialog, kItemIsNeg, &myRect);
	MoveTo(myRect.left + 2, myRect.top + 17);
	MacFrameRect(&myRect);
	
	MacInsetRect(&myRect, 1, 1);
	EraseRect(&myRect);
	MacInsetRect(&myRect, -1, -1);

	TextSize(kTextBigSize);
	
	if (gIsNeg) 
		mySign = QTUtils_ConvertCToPascalString("-");	
	else
		mySign = QTUtils_ConvertCToPascalString("+");	
		
	DrawString(mySign);	
	
	TextSize(kTextRegSize);
	free(mySign);
}


//////////
//
// QTTC_SetDialogTextNumber 
// Set and highlight the text of a dialog item.
//
//////////

void QTTC_SetDialogTextNumber (DialogPtr theDialog, short theItem, long theNumber)
{
	Str255				myText;

	NumToString(theNumber, myText);
	SetDialogItemText((Handle)QTTC_GetDItemHandle(theDialog, theItem), myText);
	SelectDialogItemText(theDialog, theItem, 0, 32767);
}


//////////
//
// QTTC_SetDialogTextString 
// Set and highlight the text of a dialog item.
//
//////////

void QTTC_SetDialogTextString (DialogPtr theDialog, short theItem, StringPtr theString)
{
	SetDialogItemText((Handle)QTTC_GetDItemHandle(theDialog, theItem), theString);
	SelectDialogItemText(theDialog, theItem, 0, 32767);
}


//////////
//
// QTTC_ValidateDialogLong 
// Validate a long value.
//
//////////

Boolean QTTC_ValidateDialogLong (DialogPtr theDialog, short theItem, long *theResult)
{
	Str255				myText;
	ControlHandle		myHandle;
	Boolean				IsDigitFound = false;
	short				myIndex;

	myHandle = QTTC_GetDItemHandle(theDialog, theItem);
	GetDialogItemText((Handle)myHandle, myText);

	for (myIndex = 1; myIndex < myText[0]; myIndex++) {
		if (myText[myIndex] >= '0' && myText[myIndex] <= '9') {
			IsDigitFound = true;
		} else if (IsDigitFound) {
			myText[0] = myIndex - 1;
			break;
		} else if (myText[myIndex] != ' ') {
			SelectDialogItemText(theDialog, theItem, 0, 32767);
			SysBeep(1);
			return(false);
		}
	}

	StringToNum(myText, theResult);
	return(true);
}


//////////
//
// QTTC_GetDItemHandle 
// Get the handle of the specified dialog item.
//
//////////

ControlHandle QTTC_GetDItemHandle (DialogPtr theDialog, short theItem)
{
	short				myKind;
	ControlHandle		myHandle;
	Rect				myRect;

	GetDialogItem(theDialog, theItem, &myKind, (Handle *)&myHandle, &myRect);
	return(myHandle);
}


//////////
//
// QTTC_GetDItemRect 
// Get the rectangle surrounding the specified dialog item.
//
//////////

ControlHandle QTTC_GetDItemRect (DialogPtr theDialog, short theItem, Rect *theRect)
{
	short				myKind;
	ControlHandle		myHandle;

	GetDialogItem(theDialog, theItem, &myKind, (Handle *)&myHandle, theRect);
	return(myHandle);
}


//////////
//
// QTTC_ShowCurrentTimeCode 
// Show (in an alert box) the timecode value for the current movie time.
//
//////////

void QTTC_ShowCurrentTimeCode (Movie theMovie)
{
	MediaHandler		myHandler = NULL;
	HandlerError		myErr = noErr;
	TimeCodeDef			myTCDef;
	TimeCodeRecord		myTCRec;
	
	myHandler = QTTC_GetTimeCodeMediaHandler(theMovie);
	if (myHandler != NULL) {
	
		// get the timecode for the current movie time
		myErr = TCGetCurrentTimeCode(myHandler, NULL, &myTCDef, &myTCRec, NULL);
		if (myErr == noErr) {
			Str255		myString;
			
			myErr = TCTimeCodeToString(myHandler, &myTCDef, &myTCRec, myString);
			if (myErr == noErr)
				QTTC_ShowStringToUser(myString);
		}
	}
}


//////////
//
// QTTC_ShowTimeCodeSource
// Show (in an alert box) the timecode source for the specified movie.
//
//////////

void QTTC_ShowTimeCodeSource (Movie theMovie)
{
	MediaHandler		myHandler = NULL;
	HandlerError		myErr = noErr;
	UserData			myUserData;
	
	myHandler = QTTC_GetTimeCodeMediaHandler(theMovie);
	if (myHandler != NULL) {
	
		// get the timecode source for the current movie time
		myErr = TCGetCurrentTimeCode(myHandler, NULL, NULL, NULL, &myUserData);
		if (myErr == noErr) {
			Str255		myString = " [No source name!]";
			Handle 		myNameHandle = NewHandleClear(0);
			
			GetUserDataText(myUserData, myNameHandle, TCSourceRefNameType, 1, langEnglish);
			if (GetHandleSize(myNameHandle) > 0) {
				BlockMove(*myNameHandle, &myString[1], GetHandleSize(myNameHandle));
				myString[0] = GetHandleSize(myNameHandle);
			}
			
			if (myNameHandle != NULL)
				DisposeHandle(myNameHandle);
			
			QTTC_ShowStringToUser(myString);
			
			DisposeUserData(myUserData);
		}
	}
}


//////////
//
// QTTC_ShowStringToUser 
// Display a string in an alert box.
//
//////////

void QTTC_ShowStringToUser (StringPtr theString)
{
	short 		mySavedResFile;

	// get the current resource file and set the application's resource file
	mySavedResFile = CurResFile();
	UseResFile(gAppResFile);

	ParamText(theString, NULL, NULL, NULL);
	Alert(kTimeCodeAlertID, NULL);
	
	// restore the original resource file
	UseResFile(mySavedResFile);
}


//////////
//
// QTTC_ToggleTimeCodeDisplay 
// Toggle the current state of timecode display.
//
//////////

void QTTC_ToggleTimeCodeDisplay (MovieController theMC)
{
	Movie				myMovie = MCGetMovie(theMC);
	Track				myTrack = NULL;
	MediaHandler		myHandler = NULL;
	long				myFlags = 0L;
	
	// get the (first) timecode track in the specified movie
	myTrack = GetMovieIndTrackType(myMovie, 1, TimeCodeMediaType, movieTrackMediaType);
	if (myTrack != NULL) {
	
		// get the timecode track's media handler
		myHandler = QTTC_GetTimeCodeMediaHandler(myMovie);
		if (myHandler != NULL) {
		
			// toggle the show-timecode flag
			TCGetTimeCodeFlags(myHandler, &myFlags);
			myFlags ^= tcdfShowTimeCode;
			TCSetTimeCodeFlags(myHandler, myFlags, tcdfShowTimeCode);
			
			// toggle the track enabled state
			SetTrackEnabled(myTrack, !GetTrackEnabled(myTrack));
			
			// now tell the movie controller the movie has changed,
			// so that the movie rectangle gets updated correctly
			MCMovieChanged(theMC, myMovie);
		}
	}
}


//////////
//
// QTTC_GetTimeCodeMediaHandler 
// Get the media handler for the first timecode track in the specified movie.
//
//////////

MediaHandler QTTC_GetTimeCodeMediaHandler (Movie theMovie)
{
	Track				myTrack = NULL;
	Media				myMedia = NULL;
	MediaHandler		myHandler = NULL;
	
	// get the (first) timecode track in the specified movie
	myTrack = GetMovieIndTrackType(theMovie, 1, TimeCodeMediaType, movieTrackMediaType);
	if (myTrack != NULL) {
		// get the timecode track's media and media handler
		myMedia = GetTrackMedia(myTrack);
		if (myMedia != NULL)
			myHandler = GetMediaHandler(myMedia);
	}
	
	return(myHandler);
}


//////////
//
// QTTC_MovieHasTimeCodeTrack
// Determine whether the specified movie contains a timecode track.
// 
//////////

Boolean QTTC_MovieHasTimeCodeTrack (Movie theMovie)
{
	return(GetMovieIndTrackType(theMovie, 1, TimeCodeMediaType, movieTrackMediaType) != NULL);
}
