//////////
//
//	File:		QTShowEffect.c
//
//	Contains:	Code to generate a QuickTime movie with a QuickTime video effect in it,
//				and to preview an effect on zero, one, or two pictures.
//
//	Written by:	Tim Monroe
//				Based on existing ShowEffect code written by Dan Crow,
//				which was adapted from Peter Hoddie's original DoEffect code.
//
//	Copyright:	© 1996-1999 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <37>	 	03/19/01	rtm		added MacSetPort call to QTEffects_HandleEffectsDialogEvents (so GlobalToLocal
//									would work correctly)
//	   <36>	 	09/01/00	rtm		made gChooseDialog global, so we can just hide and show it instead of rebuilding
//									it every time; this fixes a problem with RezWacked files on Windows (menu was
//									getting lost...); commented out call to QTEffects_UninitializePopUpMenu
//	   <35>	 	06/08/00	rtm		fixed crashing bug in QTEffects_SetUpEffectSequence caused by illegal cast 
//	   <34>	 	06/07/00	rtm		fixed dynamic building of Effects pop-up menu, using info in Tech Note TB42;
//									added QTEffects_UninitializePopUpMenu
//	   <33>	 	03/20/00	rtm		made changes to get things running under CarbonLib
//	   <32>	 	05/24/99	rtm		fixed problem that caused image buffers to get corrupted on certain operations;
//									we were not careful in our locking and unlocking the image pixmaps, so sometimes
//									they would get unlocked and remain unlocked; now we lock the image pixmaps for
//									gGW1 and gGW2 when the GWorlds are created, and then we leave them locked forever
//	   <31>	 	05/03/99	rtm		made call to GetCCursor cross-platform again; changed name of main effects
//									display window; removed most support for compound effects; converted some
//									hard-coded strings into constants; on Windows, made main effects display window
//									a child of ghWndMDIClient (still some remaining cosmetic glitches, tho')
//	   <30>	 	03/23/99	rtm		started to add support for compound effects: renamed _CreateTwoSourceEffectDescription
//									as _CreateEffectDescription
//	   <29>	 	03/02/99	rtm		added support for MakeImageDescriptionForEffect (QT 4.0 and later)
//	   <28>	 	02/04/99	rtm		made call to GetCCursor Mac-only (crashes on Windows); filed bug report
//	   <27>	 	09/30/98	rtm		tweaked call to AddMovieResource to create single-fork movies
//	   <26>	 	05/21/98	rtm		removed conditionalized support for compound effects
//	   <25>	 	05/20/98	rtm		changed stacked calls to NewGWorld to a single QTNewGWorld call in the
//									functions QTEffects_GetPictResourceAsGWorld and QTEffects_GetPictureAsGWorld;
//									fixed crashing bug in QTEffects_HandleEffectsDialogEvents by adding the test
//									"&& (gSubPanelPopUpControl != NULL)" in control handle test
//	   <24>	 	04/23/98	rtm		changed gw->portPixMap to GetGWorldPixMap in CopyBits calls
//	   <23>	 	03/24/98	rtm		fixed minor problems with subpanels in custom dialog box
//	   <22>	 	03/23/98	rtm		added support for subpanels in custom dialog box
//	   <21>	 	03/09/98	rtm		changed kEffectSourceName constant in QTEffects_AddTrackReferenceToInputMap
//									back to kEffectDataSourceType; now "Build Movie" works again
//	   <20>	 	03/06/98	rtm		moved custom dialog item handling to before the call to
//									ImageCodecIsStandardParameterDialogEvent in QTEffects_HandleEffectsDialogEvents
//	   <19>	 	03/02/98	rtm		further clean-up on Windows
//	   <18>	 	02/26/98	rtm		added QTEffects_HandleEffectsDialogEvents, QTEffects_EffectsDialogCallback,
//									and QTEffects_CustomDialogWndProc to properly handle Windows dialog boxes;
//									miscellaneous other clean-up; added QTUtils_SetMovieFileLoopingInfo to
//									QTEffects_CreateEffectsMovie
//	   <17>	 	02/21/98	rtm		added palindrome looping and stepping backward
//	   <16>	 	02/20/98	rtm		revised custom dialog box handling; now works on Windows (yippee!)
//	   <15>	 	02/18/98	rtm		removed support for thumbnail movies
//	   <14>	 	02/17/98	rtm		conditionalized support for compound effects (not yet implemented by QT 3.0)
//	   <13>	 	02/16/98	rtm		added QTEffects_HandleEffectsWindowEvents and *Messages for platform-specific
//									event/message handling for main effects window
//	   <12>	 	02/13/98	rtm		reworked QTEffects_DrawEffectsWindow to support stepping through an effect
//	   <11>	 	02/12/98	rtm		removed all support for Preferences dialog box (Settings menu is enough)
//	   <10>	 	02/11/98	rtm		reworked QTEffects_CreateTwoSourceEffectDescription to return an effect
//									description instead of setting gCurrentState.fEffectDescription directly;
//									began adding support for compound effects
//	   <9>	 	02/10/98	rtm		added call to WriteResource to QTEffects_AddListOfEffects
//	   <8>	 	02/07/98	rtm		rewrote QTEffects_AddListOfEffects to use QTGetEffectsList
//	   <7>	 	02/04/98	rtm		fixed problems with custom dialog boxes; see the discussion in the
//									function QTEffects_LetUserCustomizeEffect for details
//	   <6>	 	02/03/98	rtm		modified QTEffects_DrawEffectsWindow and QTEffects_AddVideoTrackFromGWorld
//									to copy image(s) from existing GWorld(s); added ability to read pictures
//									from files (in addition to resources) using graphics importer routines
//	   <5>	 	01/31/98	rtm		added Settings menu to replace Preferences dialog box
//	   <4>	 	01/30/98	rtm		fixed video track durations; now everything works fine on MacOS
//	   <3>	 	12/19/97	rtm		added gModalFilterUPP to ModalDialog so that the Select Effect
//									dialog box can be moved
//	   <2>	 	12/18/97	rtm		continued clean-up: removed unused global variables and added
//									constants for dialog box item indices
//	   <1>	 	12/15/97	rtm		first file; integrated existing code with shell framework
//	   
//	This file defines functions that display a QuickTime video effect as a transition from one picture
//	to another, or a QuickTime video effect applied to a single picture. The user selects the effect
//	using a simple dialog box with a pop-up menu; then the user selects effect parameters using either
//	the standard effects parameter dialog box or a custom effects parameter dialog box. Once an effect
//	and some parameters are selected, the user can:
//
//		* run the effect once through from beginning to end
//		* run the effect continuously in a normal or palindrome loop
//		* run a single step of the effect, either forward or backward
//		* generate a QuickTime movie file containing the current effect
//		* select the picture(s) to which the effect is applied
//
//	Here we use the low-level QuickTime video effects APIs, for greater control over the process. The low-level
//	API includes functions beginning "ImageCodec", for example: ImageCodecCreateStandardParameterDialog.
//	The main advantage to using the low-level calls is the ability to embed the effect parameter dialog items
//	into a custom, application-specific dialog box. (We do however use the high-level function QTGetEffectsList
//	when building a list of the available effects.)
//	
//	NOTE:
//	If you set the compiler flag ALLOW_COMPOUND_EFFECTS to 1, then the Build Effect Movie menu item will add
//	the film noise filter to whatever two-source effect you've already chosen when it builds the movie; this is
//	intended to illustrate how to work with compound (or "stacked") effects. QTShowEffect does not yet provide
//	a mechanism to allow the user to add a compound effect to the one shown in the main effects display window.
//
//////////

//////////
//
// TO DO:
// + 
//
//////////

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

#include "QTShowEffect.h"


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

WindowPtr					gMainWindow = NULL;				// the main effect-display window
QTParameterDialog			gEffectsDialog = 0L;			// identifier for the standard parameter dialog box
DialogPtr					gCustomDialog = NULL;			// the dialog that incorporates the standard parameter dialog box user interface elements
DialogPtr					gChooseDialog = NULL;			// the dialog for choosing an effect
GWorldPtr					gGW1 = NULL;					// the GWorlds that hold the effect sources
GWorldPtr					gGW2 = NULL;
ImageDescriptionHandle		gGW1Desc = NULL;				// the image descriptions of the GWorlds
ImageDescriptionHandle		gGW2Desc = NULL;
ComponentInstance			gCompInstance = NULL;			// the instance of the current effect component
unsigned short				gLoopingState = kNormalLooping;	// the current looping state of effect display
unsigned short				gCurrentDir = kForward;			// the current direction of effect display
Boolean						gUseStandardDialog = true;		// if true, use the standard effect parameter dialog box; if false, use a custom effect parameter dialog box
Boolean						gFastEffectDisplay = false;		// if true, the effect is run to completion immediately;
															// if false, the effect runs as tickled by the event loop or message stream
PopUpMenuInformation		gSelectEffectPopup;				// holds information about the Select Effect popup menu
StateInformation			gCurrentState;					// holds information about the current state of effects processing
int							gNumberOfSteps = k30StepsCount;
MenuHandle					gSubPanelPopUpMenu = NULL;		// menu handle for subpanel pop-up menu in custom dialog box
ControlHandle				gSubPanelPopUpControl = NULL;	// control handle for subpanel pop-up menu in custom dialog box

extern ModalFilterUPP		gModalFilterUPP;

#if TARGET_OS_WIN32
char						gEffectsWindowClassName[] = kEffectsWindowClassName;
extern HANDLE				ghInst;							// the instance of this application
extern HWND					ghWndMDIClient; 				// the MDI client window
extern Boolean				gShuttingDown;
#endif


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Application start-up and shut-down functions.
//
// Use these functions to initialize and tear down effects processing for this application.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTEffects_Init
// Do application-specific initialization for effects processing.
//
//////////

OSErr QTEffects_Init (void)
{
	CCrsrHandle			myCursor = NULL;
	OSErr				myErr = noErr;

#if TARGET_OS_WIN32
	HWND				myWindow;
#endif	

#if TARGET_OS_MAC	
	StringPtr			myString;
	Rect				myRect;
	DialogPtr			myDialog = NULL;

	// show a splash screen while we set up the default values and other such things
	myDialog = GetNewDialog(kSplashDialogID, NULL, (WindowPtr)-1);
	if (myDialog != NULL) {
		MacSetPort(GetDialogPort(myDialog));
		MacShowWindow(GetDialogWindow(myDialog));
		DrawDialog(myDialog);
	}
#endif

	// get wristwatch cursor; this initialization might take a little while....
	myCursor = GetCCursor(kWatchCursorResID);
	if (myCursor != NULL)
		SetCCursor(myCursor);
	
	// set up the initial state
	gCurrentState.fSampleDescription = NULL;
	gCurrentState.fEffectDescription = NULL;
	gCurrentState.fEffectType        = kCrossFadeTransitionType;
	gCurrentState.fShowingEffect     = false;
	gCurrentState.fTime              = 0;
	gCurrentState.fEffectSequenceID  = 0L;
	gCurrentState.fTimeBase          = NULL;
	
	// create the pop-up menu for the Select Effect dialog box
	myErr = QTEffects_InitializePopUpMenu(&gSelectEffectPopup);
	if (myErr != noErr)
		goto bail;
	
	// add items to the menu
	myErr = QTEffects_AddListOfEffects();
	if (myErr != noErr) {
#if TARGET_OS_MAC	
		QTFrame_ShowWarning("\pCannot run this application from a locked volume; please copy to a different disk. Quitting.", 0);
		ExitToShell();
#endif
#if TARGET_OS_WIN32
		PostQuitMessage(0);
#endif
	}
		
	// create the GWorlds containing the pictures that will act as the sources for the effect to be displayed;
	// on application start-up, we'll use canned pictures in resources
	myErr = QTEffects_GetPictResourceAsGWorld(kFirstPICTResID, kWidth, kHeight, kDepth, &gGW1);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTEffects_GetPictResourceAsGWorld(kSecondPICTResID, kWidth, kHeight, kDepth, &gGW2);
	if (myErr != noErr)
		goto bail;
		
	// lock the pixmaps; henceforth, whenever we draw into or copy out of these GWorlds, we shall assume that
	// the pixmaps have previously been locked
	if (!LockPixels(GetGWorldPixMap(gGW1)))
		goto bail;
		
	if (!LockPixels(GetGWorldPixMap(gGW2)))
		goto bail;

	// ***create the main effects display window***
#if TARGET_OS_MAC	
	// on MacOS, we create a standard Macintosh window and keep track of it in the global variable gMainWindow
	myString = QTUtils_ConvertCToPascalString(kEffectsWindowTitle);
	MacSetRect(&myRect, kWindowOffset, kWindowOffset, kWindowOffset + kWidth, kWindowOffset + kHeight);
	gMainWindow = NewCWindow(NULL, &myRect, myString, true, 0, (WindowPtr)-1, true, 0);
	free(myString);
#endif
	
#if TARGET_OS_WIN32	
	// on Windows, we create an instance of a custom window class (gEffectsWindowClassName), associate a port
	// with it, and then keep track of that port in the global variable gMainWindow
	QTEffects_RegisterEffectsWindowClass(ghInst);
	myWindow = CreateWindow(gEffectsWindowClassName, kEffectsWindowTitle,
							WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_CHILD | WS_SYSMENU,
                            kWindowOffset, 
                            kWindowOffset,
                            kWidth + (2 * GetSystemMetrics(SM_CXBORDER)), 
                            kHeight + (2 * GetSystemMetrics(SM_CXBORDER)) + GetSystemMetrics(SM_CYCAPTION) - 1,
							ghWndMDIClient,
                            NULL, 
                            ghInst, 
                            NULL);
	if (myWindow != NULL)
		gMainWindow = (WindowPtr)CreatePortAssociation(myWindow, NULL, 0L);
		
#endif

bail:

#if TARGET_OS_MAC	
	// close down the splash screen
	if (myDialog != NULL)
		DisposeDialog(myDialog);
#endif

	// restore the cursor to the arrow
	InitCursor();
	if (myCursor != NULL)
		DisposeCCursor(myCursor);
		
	// now prompt the user for the first effect
	if ((gMainWindow != NULL) && (myErr == noErr))
		QTApp_HandleMenu(IDM_SELECT_EFFECT);

	return(myErr);
}


//////////
//
// QTEffects_Stop
// Do application-specific deinitialization for effects processing.
//
//////////

OSErr QTEffects_Stop (void)
{
	OSErr				myErr = noErr;

	// close the main effects window
#if TARGET_OS_WIN32
	if (gMainWindow != NULL)
		DestroyWindow(QTFrame_GetWindowReferenceFromPort(gMainWindow));
#endif
	
#if TARGET_OS_MAC
	if (gMainWindow != NULL)
		DisposeWindow(gMainWindow);
#endif

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

	// unhook the effects pop-up menu from the menu list and dispose of it
//	QTEffects_UninitializePopUpMenu(&gSelectEffectPopup);

	// deallocate any global storage
	if (gGW1Desc != NULL)
		DisposeHandle((Handle)gGW1Desc);
		
	if (gGW2Desc != NULL)
		DisposeHandle((Handle)gGW2Desc);

	if (gGW1 != NULL)
		DisposeGWorld(gGW1);
		
	if (gGW2 != NULL)
		DisposeGWorld(gGW2);
		
	if (gCurrentState.fSampleDescription != NULL)
		DisposeHandle((Handle)gCurrentState.fSampleDescription);
		
	if (gCurrentState.fEffectDescription != NULL)
		QTDisposeAtomContainer(gCurrentState.fEffectDescription);
		
	if (gCurrentState.fEffectSequenceID != 0L)
		CDSequenceEnd(gCurrentState.fEffectSequenceID);
		
	if (gCurrentState.fTimeBase != NULL)
		DisposeTimeBase(gCurrentState.fTimeBase);
		
	if (gCompInstance != NULL)
		CloseComponent(gCompInstance);

	return(myErr);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Effects window functions.
//
// Use these functions to set up and manage the main effects window displayed by this application.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTEffects_ProcessEffect
// Play one or more steps of the current effect.
// 
//////////

void QTEffects_ProcessEffect (void)
{
	OSErr		myErr = noErr;
	
	// if we are in "fast mode", play the effect forward thru to completion
	if (gCurrentState.fShowingEffect && gFastEffectDisplay) {
		for (gCurrentState.fTime = 1; gCurrentState.fTime <= gNumberOfSteps; gCurrentState.fTime++) {
			myErr = QTEffects_RunEffect(gCurrentState.fTime);
			if (myErr != noErr)
				return;
		}
		
		gCurrentState.fShowingEffect = false;
	}
	
	// otherwise, show the next step of the effect
	if (gCurrentState.fShowingEffect && !gFastEffectDisplay) {
	
		if (gCurrentDir == kForward) {
			gCurrentState.fTime++;
			if (gCurrentState.fTime > gNumberOfSteps) {
				switch (gLoopingState) {
					case kNoLooping:
						gCurrentState.fTime = 0;
						gCurrentDir = kForward;
						gCurrentState.fShowingEffect = false;
						break;
					case kNormalLooping:
						gCurrentState.fTime = 0;
						gCurrentDir = kForward;
						gCurrentState.fShowingEffect = true;
						break;
					case kPalindromeLooping:
						gCurrentState.fTime = gNumberOfSteps;
						gCurrentDir = kBackward;
						gCurrentState.fShowingEffect = true;
						break;
				}
			}
		} else {
			gCurrentState.fTime--;
			if (gCurrentState.fTime < 1) {
				switch (gLoopingState) {
					case kNoLooping:
						gCurrentState.fTime = 0;
						gCurrentDir = kForward;
						gCurrentState.fShowingEffect = false;
						break;
					case kPalindromeLooping:
					case kNormalLooping:		// (this should never actually happen, since we're going backward)
						gCurrentState.fTime = 0;
						gCurrentDir = kForward;
						gCurrentState.fShowingEffect = true;
						break;
				}
			}
		}
		
		// run the next step of the effect
		myErr = QTEffects_RunEffect(gCurrentState.fTime);
		if (myErr != noErr)
			return;

		if (gCurrentState.fSteppingEffect)
			gCurrentState.fShowingEffect = false;
	}
}


//////////
//
// QTEffects_DrawEffectsWindow
// Draw the contents of the main effects window.
// 
//////////

void QTEffects_DrawEffectsWindow (void)
{	
	if ((gCurrentState.fEffectSequenceID != 0L) && (gCurrentState.fTime != 1)) {
		QTEffects_RunEffect(gCurrentState.fTime);
	} else {
		// if we haven't set up an effect yet (which presumably happens only when the application is starting up)
		// or if we're at the first frame, just copy the first source image into the window
		CGrafPtr 		mySavedPort = NULL;
		GDHandle		mySavedGDevice = NULL;
		Rect			myRectGW1;
		Rect			myRectMain;
		
		// get the current port and device
		GetGWorld(&mySavedPort, &mySavedGDevice);
		
#if TARGET_OS_MAC
		MacSetPort((GrafPtr)GetWindowPort(gMainWindow));

		GetPortBounds(gGW1, &myRectGW1);
		GetPortBounds(GetWindowPort(gMainWindow), &myRectMain);
#endif
#if TARGET_OS_WIN32
		MacSetPort((GrafPtr)gMainWindow);

		myRectGW1 = gGW1->portRect;
		myRectMain = gMainWindow->portRect;
#endif
		
		// copy the image from the first source GWorld into the main window
		CopyBits(	(BitMapPtr)*GetGWorldPixMap(gGW1),
					(BitMapPtr)*GetGWorldPixMap(GetWindowPort(gMainWindow)),
					&myRectGW1,
					&myRectMain,
					srcCopy,
					NULL);

		// restore the original port and device
		SetGWorld(mySavedPort, mySavedGDevice);
	}
}


#if TARGET_OS_MAC
//////////
//
// QTEffects_HandleEffectsWindowEvents
// Handle Macintosh events for the main effects window.
// 
//////////

Boolean QTEffects_HandleEffectsWindowEvents (EventRecord *theEvent)
{
	Boolean			isHandled = false;
	short			myWindowPart;
	WindowPtr		myWindow;
	Rect			myRect;

	switch (theEvent->what) {
		case mouseDown:
			myWindowPart = MacFindWindow(theEvent->where, &myWindow);
			if (myWindow != gMainWindow)
				break;
				
			switch (myWindowPart) {
				case inDrag:
					GetRegionBounds(GetGrayRgn(), &myRect);
					DragWindow(myWindow, theEvent->where, &myRect);
					isHandled = true;
					break;
					
				case inContent:
					SelectWindow(myWindow);
					MacSetPort(GetWindowPort(myWindow));
					QTApp_HandleContentClick(myWindow, theEvent);
					isHandled = true;
					break;
					
				case inGoAway:
					// if the close box on the main window is clicked, dispose the window and quit the application
					if (TrackGoAway(myWindow, theEvent->where))
						if (myWindow == gMainWindow) {
							DisposeWindow(myWindow);
							gMainWindow = NULL;
							QTFrame_QuitFramework();
						}
					isHandled = true;
					break;
					
			} // end switch(myWindowPart)
			break;
	
		case updateEvt:
			myWindow = (WindowPtr)theEvent->message;
			if (myWindow == gMainWindow) {
				QTApp_Draw(myWindow);
				isHandled = true;
			}
			break;

		default:
			break;
	}
	
	return(isHandled);
}
#endif	// TARGET_OS_MAC


#if TARGET_OS_WIN32
//////////
//
// QTEffects_HandleEffectsWindowMessages
// Handle Windows messages for the main effects window.
// 
//////////

LRESULT CALLBACK QTEffects_HandleEffectsWindowMessages (HWND theWnd, UINT theMessage, UINT wParam, LONG lParam)
{
	PAINTSTRUCT		myPaintStruct;

	// run the next step(s) of the effect
	if (!gShuttingDown)
		QTEffects_ProcessEffect();
		
	switch (theMessage) {

		case WM_PAINT:
			BeginPaint(theWnd, &myPaintStruct);
			QTEffects_DrawEffectsWindow();
			EndPaint(theWnd, &myPaintStruct);
			break;

		case WM_NCLBUTTONDOWN:
		case WM_NCRBUTTONDOWN:
			SetForegroundWindow(theWnd);
			break;
			
		case WM_LBUTTONDOWN:
		case WM_RBUTTONDOWN:
			SetForegroundWindow(theWnd);
			QTApp_HandleContentClick(theWnd, NULL);
			return(0);

		case WM_CLOSE:
			if (theWnd != NULL)
				DestroyWindow(theWnd);
			gMainWindow = NULL;
			QTFrame_QuitFramework();
			break;

		case WM_DESTROY:
			DestroyPortAssociation((CGrafPtr)gMainWindow);
			break;

		default:
			break;
	}

	return(DefWindowProc(theWnd, theMessage, wParam, lParam));
}


//////////
//
// QTEffects_RegisterEffectsWindowClass
// Register the window procedure for the main effects window.
// 
//////////

BOOL QTEffects_RegisterEffectsWindowClass (HANDLE hInstance)
{
    WNDCLASSEX			myWC;

	// register the main effect window class
	myWC.cbSize        = sizeof(WNDCLASSEX);
	myWC.style         = CS_HREDRAW | CS_VREDRAW;
	myWC.lpfnWndProc   = (WNDPROC)QTEffects_HandleEffectsWindowMessages;
	myWC.cbClsExtra    = 0;
	myWC.cbWndExtra    = 0;
	myWC.hInstance     = hInstance;
	myWC.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPICON));
	myWC.hCursor       = LoadCursor(NULL, IDC_ARROW);
	myWC.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	myWC.lpszMenuName  = NULL;
	myWC.lpszClassName = gEffectsWindowClassName;
	myWC.hIconSm       = LoadImage(hInstance, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 16, 16, 0);
								 
	if (!RegisterClassEx(&myWC)) {
		if (!RegisterClass((LPWNDCLASS)&myWC.style))
    		return(false);
	}
	
	return(true);
}
#endif	// TARGET_OS_WIN32


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Dialog utilities.
//
// Use these functions to set up and manage the dialog boxes displayed by this application.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTEffects_LetUserChooseEffect
// Display a dialog box that contains a pop-up menu of all the effects currently available,
// and allow the user to select an effect.
// 
//////////

OSErr QTEffects_LetUserChooseEffect (void)
{
	GrafPtr 		mySavedPort = NULL;
	DialogPtr		myDialog = NULL;
	short			myItem;
	short			myType;
	Handle			myItemHandle = NULL;
	Rect			myRect;
	int				myIndex;
	OSErr			myErr = noErr;
	
	GetPort(&mySavedPort);

	if (gChooseDialog == NULL) {
		// get the dialog that lets the user select an effect component
		gChooseDialog = GetNewDialog(kSelectDialogID, NULL, (WindowPtr)-1);
		if (gChooseDialog == NULL)
			goto bail;
	} else {
#if TARGET_API_MAC_CARBON
		MacShowWindow(GetDialogWindow(gChooseDialog));
#else
		MacShowWindow(gChooseDialog);
#endif
	}
	
	myErr = SetDialogDefaultItem(gChooseDialog, kSelectButtonOK);
	if (myErr != noErr)
		goto bail;
		
	// find the currently selected effect in the list, so that it's showing when the dialog is displayed
	for (myIndex = 0; myIndex < gSelectEffectPopup.fNumberOfItems; myIndex++) {
		if (gSelectEffectPopup.fItemInfo[myIndex] == gCurrentState.fEffectType) {
			GetDialogItem(gChooseDialog, kSelectPopUpID, &myType, &myItemHandle, &myRect);
			SetControlValue((ControlHandle)myItemHandle, myIndex + 1);
		}
	}
	
	// now show the dialog
	MacShowWindow(GetDialogWindow(gChooseDialog));
	MacSetPort(GetDialogPort(gChooseDialog));
	
	do {
#if TARGET_OS_MAC
		ModalDialog(gModalFilterUPP, &myItem);
#endif
#if TARGET_OS_WIN32
		ModalDialog(NULL, &myItem);
#endif
	} while (myItem != kSelectButtonOK);
	
	// get the control handle for the pop-up menu and read its current value
	GetDialogItem(gChooseDialog, kSelectPopUpID, &myType, &myItemHandle, &myRect);
	gSelectEffectPopup.fLastChosen = GetControlValue((ControlHandle)myItemHandle);
		
	// now the user has hit OK and gSelectEffectPopup.fLastChosen contains the item chosen.
	gCurrentState.fEffectType = (OSType)gSelectEffectPopup.fItemInfo[gSelectEffectPopup.fLastChosen - 1];

bail:
	if (gChooseDialog != NULL)
#if TARGET_API_MAC_CARBON
		HideWindow(GetDialogWindow(gChooseDialog));
#else
		HideWindow(gChooseDialog);
#endif
	
	MacSetPort(mySavedPort);

	return(myErr);
}


//////////
//
// QTEffects_LetUserCustomizeEffect
// Display a dialog box that allows the user to select non-default parameters for an effect.
// 
//////////

OSErr QTEffects_LetUserCustomizeEffect (QTAtomContainer theEffectDesc)
{
	GrafPtr 				mySavedPort = NULL;
	ComponentDescription	myCD;
	Component				myComponent = NULL;
	QTAtomContainer			myParamDesc = NULL;
	MenuHandle				myMenu = NULL;
	OSErr					myErr = noErr;
		
	GetPort(&mySavedPort);

	// set up a component description
	myCD.componentType			= decompressorComponentType;	// effects are image decompressor components
	myCD.componentSubType		= gCurrentState.fEffectType;	// whichever subtype of effect we are looking for
	myCD.componentManufacturer	= 0;
	myCD.componentFlags			= 0;
	myCD.componentFlagsMask		= 0;
	
	// if the component has been previously opened, close it (since we may be customizing a different effect)
	if (gCompInstance != NULL) {
		CloseComponent(gCompInstance);
		gCompInstance = NULL;
	}
	
	// find the required component
	myComponent = FindNextComponent(myComponent, &myCD);
	if (myComponent == NULL)
		return(paramErr);
		
	// open the component
	gCompInstance = OpenComponent(myComponent);
	
	// get the list of parameters for the effect
	myErr = ImageCodecGetParameterList(gCompInstance, &myParamDesc);
	if (myErr != noErr)
		goto bail;
		
	// if the user has chosen the standard dialog box, then display it...
	if (gUseStandardDialog) {
		// set up the dialog box
		myErr = ImageCodecCreateStandardParameterDialog(
									gCompInstance,
									myParamDesc,
									theEffectDesc,
									0,							// dialog options
									NULL,						// no existing dialog
									0,							// no existing user item
									&gEffectsDialog);
		if ((myErr != noErr) || (gEffectsDialog == 0L))
			goto bail;
			
		gCustomDialog = NULL;
		
	} else {
	// ...otherwise, create the dialog box as a part of a custom application-supplied dialog box
	
		// Get the application-supplied dialog box; a few words of explanation are appropriate here:		
		// QuickTime expects this dialog box to be a color window, or the slider elements (and some
		// other controls) will not draw correctly; accordingly, your application needs to include
		// several resources that have the same ID as the dialog box resource (here, kCustomDialogID).
		// For MacOS 8 and/or Appearance Manager compatibility, the application must also a resource
		// of type 'dlgx', whose data (a long word) has at least the bits kDialogFlagsUseThemeBackground
		// and kDialogFlagsUseThemeControls set; we'll also set the bit kDialogFlagsHandleMovableModal.
		// So our entire 6-byte 'dlgx' resource is 00000000000D. (The first 2 bytes of the 'dlgx' resource
		// are a version number, which we set to 0.) For vanilla System 7 compatibility, the application
		// must include a resource of type 'dctb' whose ID is kCustomDialogID.

		gCustomDialog = GetNewDialog(kCustomDialogID, NULL, (WindowPtr)-1);
		if (gCustomDialog != NULL) {
		
			// set up the dialog box
			myErr = ImageCodecCreateStandardParameterDialog(
									gCompInstance,
									myParamDesc,
									theEffectDesc,
									0,							// dialog options
									gCustomDialog,				// the existing dialog
									kCustomUserItemID,			// the existing user item
									&gEffectsDialog);
			if ((myErr != noErr) || (gEffectsDialog == 0L))
				goto bail;
		
			SetDialogDefaultItem(gCustomDialog, kCustomButtonOK);
			
#if TARGET_OS_WIN32
			// on Windows, we need to explicitly tag the dialog box as a movable modal dialog box
			SetDialogMovableModal(gCustomDialog); 
#endif

			// see whether the current effect supports subpanels;
			// if so, display a pop-up menu that contains the names of those subpanels
			myErr = ImageCodecStandardParameterDialogDoAction(gCompInstance, gEffectsDialog, pdActionGetSubPanelMenu, &myMenu);
			if ((myErr == noErr) && (myMenu != NULL)) {
				short			myNumItems;
				short			myIndex;
				short			myItemKind;
				Handle			myItemHandle = NULL;
				Rect			myItemRect;
				
				// remove any existing menu items in gSubPanelPopUpMenu
				if (gSubPanelPopUpMenu != NULL) {
					myNumItems = CountMenuItems(gSubPanelPopUpMenu);
					for (myIndex = 0; myIndex < myNumItems; myIndex++)
						DeleteMenuItem(gSubPanelPopUpMenu, 1);
				}
				
				// get the control handle of the pop-up menu
				GetDialogItem(gCustomDialog, kCustomPopUpID, &myItemKind, &myItemHandle, &myItemRect);
				gSubPanelPopUpControl = (ControlHandle)myItemHandle;
				
				// get the menu handle of the pop-up menu
#if TARGET_API_MAC_CARBON
				gSubPanelPopUpMenu = GetControlPopupMenuHandle(gSubPanelPopUpControl);
#else
				gSubPanelPopUpMenu = (**(PopupPrivateDataHandle)(**gSubPanelPopUpControl).contrlData).mHandle;
#endif
				myNumItems = CountMenuItems(myMenu);
				for (myIndex = 1; myIndex <= myNumItems; myIndex++) {
					Str255		myString;
					
					// copy items into the pop-up menu
					GetMenuItemText(myMenu, myIndex, myString);
					MacInsertMenuItem(gSubPanelPopUpMenu, myString, myIndex + 1);
				}
				
				// set the minimum, maximum, and initial values of the pop-up menu
				SetControlMinimum(gSubPanelPopUpControl, 1);
				SetControlMaximum(gSubPanelPopUpControl, myNumItems);
				SetControlValue(gSubPanelPopUpControl, 1);
				ShowControl(gSubPanelPopUpControl);
			}

			MacShowWindow(GetDialogWindow(gCustomDialog));
			MacSetPort(GetDialogPort(gCustomDialog));
		}
	}
	
	// now, the frontmost window is a standard or custom effects parameter dialog box;
	// on the Mac, we call QTEffects_HandleEffectsDialogEvents in our main event loop
	// to find and process events targeted at the effects parameter dialog box; on Windows,
	// we need to use a different strategy: we install a modeless dialog callback procedure
	// that is called internally by QTML
#if TARGET_OS_WIN32
	SetModelessDialogCallbackProc(FrontWindow(), (QTModelessCallbackUPP)QTEffects_EffectsDialogCallback);
	QTMLSetWindowWndProc(FrontWindow(), QTEffects_CustomDialogWndProc);
#endif

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

	MacSetPort(mySavedPort);

	return(myErr);
}


#if TARGET_OS_WIN32
//////////
//
// QTEffects_EffectsDialogCallback
// This function is called by QTML when it processes events for the standard or custom effects parameter dialog box.
// 
//////////

static void QTEffects_EffectsDialogCallback (EventRecord *theEvent, DialogRef theDialog, DialogItemIndex theItemHit)
{
	QTParamDialogEventRecord	myRecord;

	myRecord.theEvent = theEvent;
	myRecord.whichDialog = theDialog;
	myRecord.itemHit = theItemHit;

	if (gEffectsDialog != 0L) {
		ImageCodecStandardParameterDialogDoAction(gCompInstance, gEffectsDialog, pdActionModelessCallback, &myRecord);
	
		// see if the event is meant for the effects parameter dialog box
		QTEffects_HandleEffectsDialogEvents(theEvent, theItemHit);
	}
}


//////////
//
// QTEffects_CustomDialogWndProc
// Handle messages for the custom effects parameters dialog box.
// 
//////////

LRESULT CALLBACK QTEffects_CustomDialogWndProc (HWND theWnd, UINT theMessage, UINT wParam, LONG lParam)
{
	EventRecord			myEvent = {0};
	
	// pass idle events thru to the dialog callback
	if (theMessage == 0x7FFF)
		QTEffects_EffectsDialogCallback(&myEvent, GetNativeWindowPort(theWnd), 0);

	return(DefWindowProc(theWnd, theMessage, wParam, lParam));
}
#endif


//////////
//
// QTEffects_HandleEffectsDialogEvents
// Process events that might be targeted at the standard or custom effects parameter dialog box.
// Return true if the event was completely handled.
// 
//////////

Boolean QTEffects_HandleEffectsDialogEvents (EventRecord *theEvent, DialogItemIndex theItemHit)
{
#if TARGET_OS_MAC
#pragma unused(theItemHit)
#endif
	Boolean			isHandled = false;
	Boolean			myAcceptChanges = false;
	Boolean			myCloseDialog = false;
	OSErr			myErr = noErr;
	
	// handle events for our own items in the custom dialog box, if it exists;
	// we need to do this BEFORE we pass the event to ImageCodecIsStandardParameterDialogEvent
	if (gCustomDialog != NULL) {
		
#if TARGET_OS_WIN32
		// on Windows, QTML has already done any required control tracking for us, and
		// the theItemHit parameter contains the dialog item number of the selected item
		switch (theItemHit) {
			case kCustomPopUpID:
				ImageCodecStandardParameterDialogDoAction(gCompInstance, gEffectsDialog, pdActionActivateSubPanel, (void *)GetControlValue(gSubPanelPopUpControl));
				break;
			case kCustomButtonOK:
				myAcceptChanges = true;
				myCloseDialog = true;
				break;
			default:
				break;
		}
#endif
		
#if TARGET_OS_MAC
		// on the Mac, we need to do our own events processing
		switch (theEvent->what) {
			case mouseDown: {
				WindowPtr		myWindow;
				short			myPart;	
				ControlHandle	myControl;
				Point			myPoint;

				myPart = MacFindWindow(theEvent->where, &myWindow);
				if ((myWindow == GetDialogWindow(gCustomDialog)) && (myPart == inContent)) {
					// make sure we are the current drawing port (else GlobalToLocal won't work correctly)
					MacSetPort(GetDialogPort(gCustomDialog));
					
					myPoint = theEvent->where;
					GlobalToLocal(&myPoint);

					myPart = FindControl(myPoint, myWindow, &myControl);
					
					// first, see if the user clicked in the subpanel pop-up menu
					if ((myControl == gSubPanelPopUpControl) && (gSubPanelPopUpControl != NULL)) {
						TrackControl(myControl, myPoint, (ControlActionUPP)-1);
						ImageCodecStandardParameterDialogDoAction(gCompInstance, gEffectsDialog, pdActionActivateSubPanel, (void *)GetControlValue(myControl));
						return(true);
					}
					
					// next, see if the user clicked the OK button
					if (myPart == kControlButtonPart) {
						if (TrackControl(myControl, myPoint, NULL) != 0) {
							myAcceptChanges = true;
							myCloseDialog = true;
						}
					}
					
					// handle any other custom dialog box items here
					
				}
				break;
			}

			case keyDown:
			case autoKey:
				// handle the standard keyboard equivalents of OK button
				switch (theEvent->message & charCodeMask) {
					case kReturnKey:
					case kEnterKey:
						myAcceptChanges = true;
						myCloseDialog = true;
						break;
					default:
						break;
				}
				break;
		}
#endif
		
	}	// if (gCustomDialog != NULL)

	// pass the event to the standard effects parameter dialog box handler
	myErr = ImageCodecIsStandardParameterDialogEvent(gCompInstance, theEvent, gEffectsDialog);
	switch (myErr) {
	
		case codecParameterDialogConfirm:
			// the user has selected the OK button of the standard (not custom) effects parameter dialog box			
			myAcceptChanges = true;
			myCloseDialog = true;
			break;
			
		case userCanceledErr:
			// the user has selected the Cancel button of the standard (not custom) effects parameter dialog box
			myCloseDialog = true;
			break;
						
		case featureUnsupported:
			// the event was *not* handled by ImageCodecIsStandardParameterDialogEvent;
			// let the event be processed normally
			break;
		
		case noErr:
		default:
			// the event was completely handled by ImageCodecIsStandardParameterDialogEvent
			isHandled = true;
			break;
	}	

	if (myCloseDialog) {
        // retrieve the values from the parameters dialog box
		if (myAcceptChanges)
			ImageCodecStandardParameterDialogDoAction(gCompInstance, gEffectsDialog, pdActionConfirmDialog, NULL);
			
		// remove the dialog box
		ImageCodecDismissStandardParameterDialog(gCompInstance, gEffectsDialog);
		
		if (gCustomDialog != NULL)
			DisposeDialog(gCustomDialog);
			
		gCustomDialog = NULL;
		gEffectsDialog = 0L;
		
		isHandled = true;
		
		// now prepare the decompression sequence
		myErr = QTEffects_SetUpEffectSequence();
	}

	return(isHandled);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Pop-up menu utilities.
//
// Use these functions to set up and manage the global pop-up menu of effects used by this application.
// Here's our strategy: in our original resource file, the menu resource with ID kPopUpMenuID is empty;
// at run-time, we open the menu resource, remove any items from it, and then add a list of all effects
// to it. Then we write the new menu resource out to the resource file, whence GetNewDialog can find it.
// Very clever.
//
// Yeah, really clever...and not at all Carbonizable. Why? Apparently under Carbon, menu handles read from
// resources are not treated as resource handles. So calling ChangedResource and WriteResource no longer
// works. A better way to handle this is to follow the advice of Tech Note TB42 and insert the new menu
// into the menu list, whence the pop-up menu CDEF will retrieve it when GetNewDialog is called.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTEffects_InitializePopUpMenu
// Initialize the global pop-up effects menu.
// 
//////////

OSErr QTEffects_InitializePopUpMenu (PopUpMenuInformation *theMenuInfo)
{
	short			myIndex;
	short			myNumItems;
	OSErr			myErr = noErr;

	theMenuInfo->fMenu = MacGetMenu(kPopUpMenuID);
	if (theMenuInfo->fMenu == NULL) {
		myErr = ResError();
		goto bail;
	}
	
	// make sure the menu isn't purgeable
	HNoPurge((Handle)theMenuInfo->fMenu);
	
	// remove any existing menu items (since we're going to generate the effects list dynamically)
	myNumItems = CountMenuItems(theMenuInfo->fMenu);
	for (myIndex = 0; myIndex < myNumItems; myIndex++)
		DeleteMenuItem(theMenuInfo->fMenu, 1);
	
	theMenuInfo->fNumberOfItems = 0;
	theMenuInfo->fLastChosen = 1;

	// insert the menu into the menu list; the pop-up menu CDEF will find it there
	// when we open the dialog box containing this pop-up menu
	MacInsertMenu(theMenuInfo->fMenu, kInsertHierarchicalMenu);
	
bail:
	return(myErr);
}


//////////
//
// QTEffects_UninitializePopUpMenu
// Uninitialize the global pop-up effects menu.
// 
//////////

OSErr QTEffects_UninitializePopUpMenu (PopUpMenuInformation *theMenuInfo)
{
#if ACCESSOR_CALLS_ARE_FUNCTIONS
	MacDeleteMenu(GetMenuID(theMenuInfo->fMenu));
#else
	MacDeleteMenu((**theMenuInfo->fMenu).menuID);
#endif

	DisposeMenu(theMenuInfo->fMenu);
	
	return(noErr);
}


//////////
//
// QTEffects_AddItemToPopUpMenu
// Add an item to the global pop-up effects menu.
// 
//////////

OSErr QTEffects_AddItemToPopUpMenu (PopUpMenuInformation *theMenuInfo, char *theItemText, OSType theItemInfo)
{
	char		myCopy[256];
	StringPtr	myString = NULL;
	OSErr		myErr = noErr;

	strcpy(myCopy, theItemText);
	strcpy(theMenuInfo->fMenuText[theMenuInfo->fNumberOfItems], myCopy);
	
	myString = QTUtils_ConvertCToPascalString(myCopy);

	MacInsertMenuItem(theMenuInfo->fMenu, (unsigned char *)myString, theMenuInfo->fNumberOfItems);
	theMenuInfo->fItemInfo[theMenuInfo->fNumberOfItems] = theItemInfo;
	theMenuInfo->fNumberOfItems++;

	free(myString);
	return(myErr);
}


//////////
//
// QTEffects_AddListOfEffects
// Add a list of the available effects to the global pop-up effects menu.
// 
//////////

OSErr QTEffects_AddListOfEffects (void)
{
	QTAtomContainer			myEffectsList = NULL;
	short					myNumEffects;
	short					myIndex;
	OSErr					myErr = noErr;
	
	// get a list of the available effects
	myErr = QTNewAtomContainer(&myEffectsList);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTGetEffectsList(&myEffectsList, kNoMinNumSources, kNoMaxNumSources, 0L);
	if (myErr != noErr)
		goto bail;

	// the returned effects list contains (at least) two atoms for each available effect component,
	// a name atom and a type atom; happily, this list is already sorted alphabetically by effect name
	myNumEffects = QTCountChildrenOfType(myEffectsList, kParentAtomIsContainer, kEffectNameAtom);
	for (myIndex = 1; myIndex <= myNumEffects; myIndex++) {
		QTAtom				myNameAtom = 0L;
		QTAtom				myTypeAtom = 0L;

		myNameAtom = QTFindChildByIndex(myEffectsList, kParentAtomIsContainer, kEffectNameAtom, myIndex, NULL);
		myTypeAtom = QTFindChildByIndex(myEffectsList, kParentAtomIsContainer, kEffectTypeAtom, myIndex, NULL);
		if ((myNameAtom != 0L) && (myTypeAtom != 0L)) {
			char 			myName[256];
			OSType 			myType;
			long			mySize;

			// get the data from the type and name atoms
			QTCopyAtomDataToPtr(myEffectsList, myTypeAtom, false, sizeof(myType), &myType, NULL);
			QTCopyAtomDataToPtr(myEffectsList, myNameAtom, true, sizeof(myName), myName, &mySize);
			myName[mySize] = '\0';
			
			QTEffects_AddItemToPopUpMenu(&gSelectEffectPopup, myName, myType);
		}
	}
		
bail:
	QTDisposeAtomContainer(myEffectsList);	
	return(myErr);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Effects utilities.
//
// Use these functions to set up and run QuickTime video effects.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTEffects_CreateEffectDescription
// Create an effect description for zero, one, or two sources.
// 
// The effect description specifies which video effect is desired and the parameters for that effect.
// It also describes the source(s) for the effect. An effect description is simply an atom container
// that holds atoms with the appropriate information.
//
// Note that because we are creating an atom container, we must pass big-endian data (hence the calls
// to EndianU32_NtoB).
//
// The caller is responsible for disposing of the returned atom container, by calling QTDisposeAtomContainer.
//
//////////

QTAtomContainer QTEffects_CreateEffectDescription (OSType theEffectName, OSType theSourceName1, OSType theSourceName2)
{
	QTAtomContainer		myEffectDesc = NULL;
	OSType				myType;
	OSErr				myErr = noErr;

	// create a new, empty effect description
	myErr = QTNewAtomContainer(&myEffectDesc);
	if (myErr != noErr)
		goto bail;

	// create the effect ID atom: the atom type is kParameterWhatName, and the atom ID is kParameterWhatID
	myType = EndianU32_NtoB(theEffectName);
	myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, kParameterWhatName, kParameterWhatID, 0, sizeof(myType), &myType, NULL);
	if (myErr != noErr)
		goto bail;
		
	// add the first source, if it's not kSourceNoneName
	if (theSourceName1 != kSourceNoneName) {
		myType = EndianU32_NtoB(theSourceName1);
		myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, kEffectSourceName, 1, 0, sizeof(myType), &myType, NULL);
		if (myErr != noErr)
			goto bail;
	}
							
	// add the second source, if it's not kSourceNoneName
	if (theSourceName2 != kSourceNoneName) {
		myType = EndianU32_NtoB(theSourceName2);
		myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, kEffectSourceName, 2, 0, sizeof(myType), &myType, NULL);
	}

bail:
	return(myEffectDesc);
}


//////////
//
// QTEffects_AddTrackReferenceToInputMap
// Add a track reference to the specified input map.
// 
//////////

OSErr QTEffects_AddTrackReferenceToInputMap (QTAtomContainer theInputMap, Track theTrack, Track theSrcTrack, OSType theSrcName)
{
	OSErr				myErr = noErr;
	QTAtom				myInputAtom;
	long				myRefIndex;
	OSType				myType;

	myErr = AddTrackReference(theTrack, theSrcTrack, kTrackModifierReference, &myRefIndex);
	if (myErr != noErr)
		goto bail;
			
	// add a reference atom to the input map
	myErr = QTInsertChild(theInputMap, kParentAtomIsContainer, kTrackModifierInput, myRefIndex, 0, 0, NULL, &myInputAtom);
	if (myErr != noErr)
		goto bail;
	
	// add two child atoms to the parent reference atom
	myType = EndianU32_NtoB(kTrackModifierTypeImage);
	myErr = QTInsertChild(theInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(myType), &myType, NULL);
	if (myErr != noErr)
		goto bail;
	
	myType = EndianU32_NtoB(theSrcName);
	myErr = QTInsertChild(theInputMap, myInputAtom, kEffectDataSourceType, 1, 0, sizeof(myType), &myType, NULL);
		
bail:
	return(myErr);
}
 
 
//////////
//
// QTEffects_SetUpEffectSequence
// Set up an effects sequence.
// 
//////////

OSErr QTEffects_SetUpEffectSequence (void)
{
	OSErr						myErr = noErr;
	ImageSequenceDataSource		mySrc1 = 0;
	ImageSequenceDataSource		mySrc2 = 0;
	ImageSequenceDataSource		mySrc3 = 0;
	PixMapHandle				mySrcPixMap;
	PixMapHandle				myDstPixMap;
 	
	// if an effect sequence is already set up, end it
	if (gCurrentState.fEffectSequenceID != 0L) {
		CDSequenceEnd(gCurrentState.fEffectSequenceID);
		gCurrentState.fEffectSequenceID = 0L;
	}
	
	// if there is a timebase already set up, dispose of it
	if (gCurrentState.fTimeBase != NULL) {
		DisposeTimeBase(gCurrentState.fTimeBase);
		gCurrentState.fTimeBase = NULL;
	}
		
	// make an effects sequence
	HLock((Handle)gCurrentState.fEffectDescription);

	// prepare the decompression sequence for playback
	myErr = DecompressSequenceBeginS(
							&gCurrentState.fEffectSequenceID,
							gCurrentState.fSampleDescription,
#if TARGET_CPU_68K
							StripAddress(*gCurrentState.fEffectDescription),
#else
							*gCurrentState.fEffectDescription,
#endif
							GetHandleSize(gCurrentState.fEffectDescription),
							(CGrafPtr)GetWindowPort(gMainWindow),
							NULL,
							NULL,
							NULL,
							ditherCopy,
							NULL,
							0,
							codecNormalQuality,
							NULL);

	HUnlock((Handle)gCurrentState.fEffectDescription);
	if (myErr != noErr)
		goto bail;

	// get the pixel maps for the GWorlds
	mySrcPixMap = GetGWorldPixMap(gGW1);
	myDstPixMap = GetGWorldPixMap(gGW2);
	
	if ((mySrcPixMap == NULL) || (myDstPixMap == NULL))
		goto bail;

	// make the first effect source
	if (gGW1 == NULL)
		goto bail;
	myErr = MakeImageDescriptionForPixMap(mySrcPixMap, &gGW1Desc);
	if (myErr != noErr)
		goto bail;

	myErr = CDSequenceNewDataSource(gCurrentState.fEffectSequenceID, &mySrc1, kSourceOneName, 1, (Handle)gGW1Desc, NULL, 0);
	if (myErr != noErr)
		goto bail;

	CDSequenceSetSourceData(mySrc1, GetPixBaseAddr(mySrcPixMap), (**gGW1Desc).dataSize);

	// make the second effect source
	if (gGW2 == NULL)
		goto bail;
	myErr = MakeImageDescriptionForPixMap(myDstPixMap, &gGW2Desc);
	if (myErr != noErr)
		goto bail;

	myErr = CDSequenceNewDataSource(gCurrentState.fEffectSequenceID, &mySrc2, kSourceTwoName, 1, (Handle)gGW2Desc, NULL, 0);
	if (myErr != noErr)
		goto bail;

	CDSequenceSetSourceData(mySrc2, GetPixBaseAddr(myDstPixMap), (**gGW2Desc).dataSize);

	// create a new time base and associate it with the decompression sequence
	gCurrentState.fTimeBase = NewTimeBase();
	myErr = GetMoviesError();
	if (myErr != noErr)
		goto bail;

	SetTimeBaseRate(gCurrentState.fTimeBase, 0);
	myErr = CDSequenceSetTimeBase(gCurrentState.fEffectSequenceID, gCurrentState.fTimeBase);

bail:
	return(myErr);
}


//////////
//
// QTEffects_MakeSampleDescription
// Return a new image description with default and specified values.
// 
//////////

ImageDescriptionHandle QTEffects_MakeSampleDescription (OSType theEffectType, short theWidth, short theHeight)
{
	ImageDescriptionHandle		mySampleDesc = NULL;

#if USES_MAKE_IMAGE_DESC_FOR_EFFECT
	OSErr						myErr = noErr;
	
	// create a new sample description
	myErr = MakeImageDescriptionForEffect(theEffectType, &mySampleDesc);
	if (myErr != noErr)
		return(NULL);
#else
	// create a new sample description
	mySampleDesc = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
	if (mySampleDesc == NULL)
		return(NULL);
		
	// fill in the fields of the sample description
	(**mySampleDesc).cType = theEffectType;
	(**mySampleDesc).idSize = sizeof(ImageDescription);
	(**mySampleDesc).hRes = 72L << 16;
	(**mySampleDesc).vRes = 72L << 16;
	(**mySampleDesc).frameCount = 1;
	(**mySampleDesc).depth = 0;
	(**mySampleDesc).clutID = -1;
#endif
	
	(**mySampleDesc).vendor = kAppleManufacturer;
	(**mySampleDesc).temporalQuality = codecNormalQuality;
	(**mySampleDesc).spatialQuality = codecNormalQuality;
	(**mySampleDesc).width = theWidth;
	(**mySampleDesc).height = theHeight;
	
	return(mySampleDesc);
}


//////////
//
// QTEffects_RunEffect
// Run the effect: decompress a single step of the effect sequence.
// 
//////////

OSErr QTEffects_RunEffect (TimeValue theTime)
{
	OSErr						myErr = noErr;
	ICMFrameTimeRecord			myFrameTime;

	// assertions
	if ((gCurrentState.fEffectDescription == NULL) || (gCurrentState.fEffectSequenceID == 0L))
		goto bail;

	// set the timebase time to the step of the sequence to be rendered
	SetTimeBaseValue(gCurrentState.fTimeBase, theTime, gNumberOfSteps);

	myFrameTime.value.hi				= 0;
	myFrameTime.value.lo				= theTime;
	myFrameTime.scale					= gNumberOfSteps;
	myFrameTime.base					= 0;
	myFrameTime.duration				= gNumberOfSteps;
	myFrameTime.rate					= 0;
	myFrameTime.recordSize				= sizeof(myFrameTime);
	myFrameTime.frameNumber				= 1;
	myFrameTime.flags					= icmFrameTimeHasVirtualStartTimeAndDuration;
	myFrameTime.virtualStartTime.lo		= 0;
	myFrameTime.virtualStartTime.hi		= 0;
	myFrameTime.virtualDuration			= gNumberOfSteps;
	
	HLock((Handle)gCurrentState.fEffectDescription);

	myErr = DecompressSequenceFrameWhen(
										gCurrentState.fEffectSequenceID,
#if TARGET_CPU_68K
										StripAddress(*((Handle)gCurrentState.fEffectDescription)),
#else
										*((Handle)gCurrentState.fEffectDescription),
#endif
										GetHandleSize((Handle)gCurrentState.fEffectDescription),
										0,
										0,
										NULL,
										&myFrameTime);
										
	HUnlock((Handle)gCurrentState.fEffectDescription);
	
	if (myErr != noErr)
		goto bail;
	
bail:
	return(myErr);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// General imaging utilities.
//
// Use these functions to draw pictures into GWorlds and do other imaging operations.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTEffects_GetPictResourceAsGWorld
// Create a new GWorld of the specified size and bit depth; then draw the specified PICT resource into it.
// The new GWorld is returned through the theGW parameter.
//
//////////

OSErr QTEffects_GetPictResourceAsGWorld (short theResID, short theWidth, short theHeight, short theDepth, GWorldPtr *theGW)
{
	PicHandle				myHandle = NULL;
	PixMapHandle			myPixMap = NULL;
	CGrafPtr				mySavedPort;
	GDHandle				mySavedDevice;
	Rect					myRect;
	OSErr					myErr = noErr;

	// get the current drawing environment
	GetGWorld(&mySavedPort, &mySavedDevice);

	// read the specified PICT resource from the application's resource file
	myHandle = GetPicture(theResID);
	if (myHandle == NULL) {
		myErr = ResError();
		if (myErr == noErr)
			myErr = resNotFound;
		goto bail;
	}

	// set the size of the GWorld
	MacSetRect(&myRect, 0, 0, theWidth, theHeight);

	// allocate a new GWorld
	myErr = QTNewGWorld(theGW, theDepth, &myRect, NULL, NULL, kICMTempThenAppMemory);
	if (myErr != noErr)
		goto bail;
	
	SetGWorld(*theGW, NULL);

	// get a handle to the offscreen pixel image and lock it
	myPixMap = GetGWorldPixMap(*theGW);
	LockPixels(myPixMap);

	EraseRect(&myRect);
	DrawPicture(myHandle, &myRect);
	
	if (myPixMap != NULL)
		UnlockPixels(myPixMap);
	
bail:
	// restore the previous port and device
	SetGWorld(mySavedPort, mySavedDevice);

	if (myHandle != NULL)
		ReleaseResource((Handle)myHandle);
	
	return(myErr);
}


//////////
//
// QTEffects_GetPictureAsGWorld
// Prompt the user to select a picture, create a new GWorld of the specified size and bit depth,
// and then draw the picture into it. The new GWorld is returned through the theGW parameter.
//
//////////

OSErr QTEffects_GetPictureAsGWorld (short theWidth, short theHeight, short theDepth, GWorldPtr *theGW)
{
	FSSpec						myFSSpec;
	OSType 						myTypeList[] = {kQTFileTypeQuickTimeImage};
	short						myNumTypes = 1;
	GraphicsImportComponent		myImporter = NULL;
	Rect						myRect;
	QTFrameFileFilterUPP		myFileFilterUPP = NULL;
	OSErr						myErr = paramErr;

#if TARGET_OS_MAC
	myNumTypes = 0;
#endif

	// have the user select an image file
	myFileFilterUPP = QTFrame_GetFileFilterUPP((ProcPtr)QTFrame_FilterFiles);
	myErr = QTFrame_GetOneFileWithPreview(myNumTypes, (QTFrameTypeListPtr)myTypeList, &myFSSpec, myFileFilterUPP);
	if (myErr != noErr)
		goto bail;
		
	// get a graphics importer for the image file
	myErr = GetGraphicsImporterForFile(&myFSSpec, &myImporter);
	if (myErr != noErr)
		goto bail;

	if (*theGW != NULL) {
		DisposeGWorld(*theGW);
		*theGW = NULL;
	}
	
	// set the size of the GWorld
	MacSetRect(&myRect, 0, 0, theWidth, theHeight);

	// allocate a new GWorld
	myErr = QTNewGWorld(theGW, theDepth, &myRect, NULL, NULL, kICMTempThenAppMemory);
	if (myErr != noErr)
		goto bail;
	
	// draw the picture into the GWorld
	GraphicsImportSetGWorld(myImporter, *theGW, NULL);
	GraphicsImportSetBoundsRect(myImporter, &myRect);
	GraphicsImportDraw(myImporter);

bail:
	if (myFileFilterUPP != NULL)
		DisposeNavObjectFilterUPP(myFileFilterUPP);

	if (myImporter != NULL)
		CloseComponent(myImporter);
	
	return(myErr);
}


//////////
//
// QTEffects_AddVideoTrackFromGWorld
// Add to the specified movie a video track for the specified picture resource.
//
//////////

OSErr QTEffects_AddVideoTrackFromGWorld (Movie *theMovie, GWorldPtr theGW, Track *theSourceTrack, long theStartTime, short theWidth, short theHeight)
{
	Media						myMedia;
	ImageDescriptionHandle		myDesc = NULL;
	Rect						myRect;
	Rect						myRect2;
	Rect						myRect3;
	long						mySize;
	Handle						myData = NULL;
	Ptr							myDataPtr = NULL;
	GWorldPtr					myGWorld = NULL;
	CGrafPtr 					mySavedPort = NULL;
	GDHandle 					mySavedGDevice = NULL;
	PicHandle					myHandle = NULL;
	PixMapHandle				mySrcPixMap = NULL;
	PixMapHandle				myDstPixMap = NULL;
	OSErr						myErr = noErr;
	
	// get the current port and device
	GetGWorld(&mySavedPort, &mySavedGDevice);
	
	// create a video track in the movie
	*theSourceTrack = NewMovieTrack(*theMovie, FixRatio(theWidth, 1), FixRatio(theHeight, 1), kNoVolume);
	myMedia = NewTrackMedia(*theSourceTrack, VideoMediaType, kOneSecond, NULL, 0);
	
	// get the rectangle for the movie
	GetMovieBox(*theMovie, &myRect);
	
	// begin editing the new track
	BeginMediaEdits(myMedia);
		
	// create a new GWorld; we draw the picture into this GWorld and then compress it
	// (note that we are creating a picture with the maximum bit depth)
	myErr = NewGWorld(&myGWorld, 32, &myRect, NULL, NULL, 0L);
	if (myErr != noErr)
		goto bail;
	
	mySrcPixMap = GetGWorldPixMap(theGW);
	// LockPixels(mySrcPixMap);
	
	myDstPixMap = GetGWorldPixMap(myGWorld);
	LockPixels(myDstPixMap);
	
	// create a new image description; CompressImage will fill in the fields of this structure
	myDesc = (ImageDescriptionHandle)NewHandle(4);
	
	SetGWorld(myGWorld, NULL);
#if TARGET_OS_MAC
	GetPortBounds(theGW, &myRect2);
	GetPortBounds(myGWorld, &myRect3);
#endif
#if TARGET_OS_WIN32
	myRect2 = theGW->portRect;
	myRect3 = myGWorld->portRect;
#endif

	// copy the image from the specified GWorld into the new GWorld
	CopyBits(	
				(BitMapPtr)*mySrcPixMap,
				(BitMapPtr)*myDstPixMap,
				&myRect2,
				&myRect3,
				srcCopy,
				NULL);

	// restore the original port and device
	SetGWorld(mySavedPort, mySavedGDevice);
	
	myErr = GetMaxCompressionSize(myDstPixMap, &myRect, 0, codecNormalQuality, kAnimationCodecType, anyCodec, &mySize);
	if (myErr != noErr)
		goto bail;
		
	myData = NewHandle(mySize);
	if (myData == NULL)
		goto bail;
		
	HLockHi(myData);
#if TARGET_CPU_68K
	myDataPtr = StripAddress(*myData);
#else
	myDataPtr = *myData;
#endif
	myErr = CompressImage(myDstPixMap, &myRect, codecNormalQuality, kAnimationCodecType, myDesc, myDataPtr);
	if (myErr != noErr)
		goto bail;
		
	myErr = AddMediaSample(myMedia, myData, 0, (**myDesc).dataSize, kEffectMovieDuration, (SampleDescriptionHandle)myDesc, 1, 0, NULL);
	if (myErr != noErr)
		goto bail;

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

	myErr = InsertMediaIntoTrack(*theSourceTrack, theStartTime, 0, GetMediaDuration(myMedia), fixed1);
	
bail:
	// restore the original port and device
	SetGWorld(mySavedPort, mySavedGDevice);
	
	if (myData != NULL) {
		HUnlock(myData);
		DisposeHandle(myData);
	}

	if (myDesc != NULL)
		DisposeHandle((Handle)myDesc);
		
	// if (mySrcPixMap != NULL)
	// 	UnlockPixels(mySrcPixMap);
		
	if (myDstPixMap != NULL)
		UnlockPixels(myDstPixMap);
		
	if (myGWorld != NULL)
		DisposeGWorld(myGWorld);
	
	return(myErr);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Movie utilities.
//
// Use these functions to create movie files and do other movie operations.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////
//
// QTEffects_CreateEffectsMovie
// Create a movie containing the current video effect transition from one picture to another.
//
//////////

void QTEffects_CreateEffectsMovie (OSType theEffectType, QTAtomContainer theEffectDesc, short theWidth, short theHeight)
{
	ImageDescriptionHandle	mySampleDesc = NULL;
	short					myResRefNum = 0;
	short					myResID = movieInDataForkResID;
	Movie					myMovie = NULL;
	Track					myTrack = NULL;
	Track					mySrc1Track = NULL;
	Track					mySrc2Track = NULL;
	Media					myMedia;
	FSSpec					myFile;
	Boolean					myIsSelected = false;
	Boolean					myIsReplacing = false;	
	QTAtomContainer			myInputMap = NULL;
	TimeValue				mySampleTime;
	long					myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
	StringPtr 				myMoviePrompt = QTUtils_ConvertCToPascalString(kSaveEffectMoviePrompt);
	StringPtr 				myMovieFileName = QTUtils_ConvertCToPascalString(kSaveEffectMovieFileName);
	OSErr					myErr = noErr;
	
	// create an effect sample description
	mySampleDesc = QTEffects_MakeSampleDescription(theEffectType, theWidth, theHeight);
	if (mySampleDesc == NULL)
		goto bail;

	// prompt user for new file name
	QTFrame_PutFile(myMoviePrompt, myMovieFileName, &myFile, &myIsSelected, &myIsReplacing);
	if (!myIsSelected)
		goto bail;				// deal with user cancelling

	// create a movie file for the destination movie
	myErr = CreateMovieFile(&myFile, sigMoviePlayer, smCurrentScript, myFlags, &myResRefNum, &myMovie);
	if (myErr != noErr)
		goto bail;
	
	// add the video tracks of the source pictures to the effects movie;
	// the video tracks used as sources for the effect should start at the same time as the effect track
	// and end at the same time as the effect track
	myErr = QTEffects_AddVideoTrackFromGWorld(&myMovie, gGW1, &mySrc1Track, 0, theWidth, theHeight);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTEffects_AddVideoTrackFromGWorld(&myMovie, gGW2, &mySrc2Track, 0, theWidth, theHeight);
	if (myErr != noErr)
		goto bail;

	// create the video effect track and media
	myTrack = NewMovieTrack(myMovie, FixRatio(theWidth, 1), FixRatio(theHeight, 1), kNoVolume);
	myMedia = NewTrackMedia(myTrack, VideoMediaType, kOneSecond, NULL, 0);

	// add the effect description as a sample to the effect track media
	BeginMediaEdits(myMedia);

	myErr = AddMediaSample(myMedia, (Handle)theEffectDesc, 0, GetHandleSize((Handle)theEffectDesc), kEffectMovieDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
	if (myErr != noErr)
		goto bail;

	EndMediaEdits(myMedia);
	
	// create the input map and add references for the two video tracks
	myErr = QTNewAtomContainer(&myInputMap);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTEffects_AddTrackReferenceToInputMap(myInputMap, myTrack, mySrc1Track, kSourceOneName);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTEffects_AddTrackReferenceToInputMap(myInputMap, myTrack, mySrc2Track, kSourceTwoName);
	if (myErr != noErr)
		goto bail;

	// add the input map to the effects track
	myErr = SetMediaInputMap(myMedia, myInputMap);
	if (myErr != noErr)
		goto bail;

	// add the media to the track
	myErr = InsertMediaIntoTrack(myTrack, 0, mySampleTime, GetMediaDuration(myMedia), fixed1);
	if (myErr != noErr)
		goto bail;
		
#if ALLOW_COMPOUND_EFFECTS
	QTEffects_AddFilmNoiseToMovie(myMovie, myTrack);
#endif

#ifdef __QTUtilities__
	// save the current looping state
	myErr = QTUtils_SetMovieFileLoopingInfo(myMovie, (gLoopingState - (kSettingsMenuResID << 8)) - 2);
#endif

	// put the movie resource into the file
	myErr = AddMovieResource(myMovie, myResRefNum, &myResID, NULL);
	
bail:		
	if (mySampleDesc != NULL)
		DisposeHandle((Handle)mySampleDesc);
	
	if (myResRefNum != 0)
		CloseMovieFile(myResRefNum);

	if (myMovie != NULL)
		DisposeMovie(myMovie);

	if (myInputMap != NULL)
		QTDisposeAtomContainer(myInputMap);
	
	free(myMoviePrompt);
	free(myMovieFileName);

	return;
}


//////////
//
// QTEffects_AddFilmNoiseToMovie
// Add the film noise filter to the specified track.
//
//////////

void QTEffects_AddFilmNoiseToMovie (Movie theMovie, Track theSrcTrack)
{
	ImageDescriptionHandle	mySampleDesc = NULL;
	Track					myTrack = NULL;
	Media					myMedia = NULL;
	QTAtomContainer			myInputMap = NULL;
	QTAtomContainer			myEffectDesc = NULL;
	TimeValue				mySampleTime;
	Fixed					myWidth, myHeight;
	OSErr					myErr = noErr;

	// get width and height of track
	GetTrackDimensions(theSrcTrack, &myWidth, &myHeight);	
	
	// create an effect sample description
	mySampleDesc = QTEffects_MakeSampleDescription(kFilmNoiseImageFilterType, myWidth >> 16, myHeight >> 16);
	if (mySampleDesc == NULL)
		goto bail;

	// create the video effect track and media
	myTrack = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
	myMedia = NewTrackMedia(myTrack, VideoMediaType, kOneSecond, NULL, 0);

	// create an effect description
	myEffectDesc = QTEffects_CreateEffectDescription(kFilmNoiseImageFilterType, kSourceThreeName, kSourceNoneName);

	// add the effect description as a sample to the effect track media
	BeginMediaEdits(myMedia);

	myErr = AddMediaSample(myMedia, (Handle)myEffectDesc, 0, GetHandleSize((Handle)myEffectDesc), kEffectMovieDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
	if (myErr != noErr)
		goto bail;

	EndMediaEdits(myMedia);
	
	// create the input map and add references for the first effect track
	myErr = QTNewAtomContainer(&myInputMap);
	if (myErr != noErr)
		goto bail;
		
	myErr = QTEffects_AddTrackReferenceToInputMap(myInputMap, myTrack, theSrcTrack, kSourceThreeName);
	if (myErr != noErr)
		goto bail;
		
	// add the input map to the effects track
	myErr = SetMediaInputMap(myMedia, myInputMap);
	if (myErr != noErr)
		goto bail;

	// add the media to the track
	myErr = InsertMediaIntoTrack(myTrack, 0, mySampleTime, GetMediaDuration(myMedia), fixed1);
	if (myErr != noErr)
		goto bail;
	
bail:		
	if (mySampleDesc != NULL)
		DisposeHandle((Handle)mySampleDesc);
	
	if (myInputMap != NULL)
		QTDisposeAtomContainer(myInputMap);
	
	return;
}
