//////////
//
//	File:		QTStdCompr.c
//
//	Contains:	Sample code for using QuickTime's standard image compression dialog routines.
//
//	Written by:	Tim Monroe
//				Based on existing code by Apple Developer Technical Support, which was itself
//				based on the code in Chapter 3 of Inside Macintosh: QuickTime Components.
//
//	Copyright:	© 1998 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <4>	 	02/03/99	rtm		reworked prompt and filename handling to remove "\p" sequences
//	   <3>	 	04/23/98	rtm		added extended procedures support; everything seems to work
//									fine on both Mac and Windows
//	   <2>	 	04/22/98	rtm		revised to personal coding style and made cross-platform
//	   <1>	 	12/04/94	khs		first file
//	   
//	This sample code illustrates how to use QuickTime's standard image compression dialog routines
//	to get compression settings from the user and to compress an image using those settings. See
//	Chapter 3 of Inside Macintosh: QuickTime Components for complete information on the standard
//	image compression dialog routines.
//
//	In this sample, we prompt the user to open an image file; then we display the standard image
//	compression dialog box and use the settings selected by the user to compress the image. The 
//	Standard Image Compression Dialog Component currently supports three sources for the image:
//
//		(1) a PICT handle
//		(2) a PICT file
//		(3) a pixel map
//
//	The most general of these is the pixel map, so we'll use that throughout this sample. We can
//	create a pixel map by opening an image file and drawing it into an offscreen graphics world.
//	By using the graphics importer routines, we allow ourselves to handle ANY kind of image file for
//	which QuickTime supplies a graphics importer component. So, for free, we get support for PICT
//	files too.
//
//	This sample also shows how to extend the basic user interface by installing a modal-dialog filter
//	function and a hook function to handle the optional custom button in the dialog box. If you don't
//	want this extended behavior, set gUseExtendedProcs to false.
//
//	NOTES:
//
//	*** (1) ***
//	Using the SCCompressImage function to compress a pixmap using some of the available compression
//	types (for instance, BMP) results in a block of compressed data that does not contain the required
//	headers. As a result, saving that data into a file results in an invalid image file. This is a
//	known limitation of QuickTime 3 and may be fixed in the future. Currently the only way to generate
//	these headers is to use a graphics importer to export the file as a BMP (or whatever) file. This
//	is NOT illustrated in this sample code.
//	
//	*** (2) ***
//	You can use the SCSetInfo function with the scSettingsStateType selector to retrieve a handle
//	containing the current compression settings; this might be useful if you were allowing the user
//	to compress a series of images and wanted to preserve the user's settings from one image to the
//	next (instead of reverting to the defaults for every image). Note, however, that the data in
//	that handle is byte-ordered according to the platform the code is running on. As a result, you
//	should not store that data in a file and expect that file to be valid on other platforms. To
//	get a handle of data in a platform-independent format, use the function SCGetSettingsAsAtomContainer
//	(introduced in QuickTime 3); to restore the settings in that handle, use the related function
//	SCSetSettingsAsAtomContainer.
//	
//////////

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

#include "QTStdCompr.h"


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

Boolean							gUseExtendedProcs = true;	// do we use extended procs with our dialog box?
SCExtendedProcs 				gProcStruct;


// our application's window-updating function
extern void DoUpdateWindow (WindowRef theWindow, Rect *theRefreshArea);


//////////
//
// QTStdCompr_PromptUserForImageFileAndCompress
// Let the user select an image file and select its compression settings; then compress it.
//
//////////

void QTStdCompr_PromptUserForImageFileAndCompress (void)
{
	SFTypeList					myTypeList;
	StandardFileReply			myReply;
	Rect						myRect;
	GraphicsImportComponent		myImporter = NULL;
	ComponentInstance			myComponent = NULL;
	GWorldPtr					myImageWorld = NULL;		// the graphics world we draw the image in
	PixMapHandle				myPixMap = NULL;
	ImageDescriptionHandle		myDesc = NULL;
	Handle						myHandle = NULL;
	OSErr						myErr = noErr;

	//////////
	//
	// have the user select an image file
	//
	//////////

	// kQTFileTypeQuickTimeImage means any image file readable by GetGraphicsImporterForFile
	myTypeList[0] = kQTFileTypeQuickTimeImage;

	StandardGetFilePreview(NULL, 1, myTypeList, &myReply);
	if (!myReply.sfGood)
		goto bail;
	
	//////////
	//
	// get a graphics importer for the image file and determine the natural size of the image
	//
	//////////

	myErr = GetGraphicsImporterForFile(&myReply.sfFile, &myImporter);
	if (myErr != noErr)
		goto bail;
	
	myErr = GraphicsImportGetNaturalBounds(myImporter, &myRect);
	if (myErr != noErr)
		goto bail;
		
	//////////
	//
	// create an offscreen graphics world and draw the image into it
	//
	//////////
	
	myErr = NewGWorld(&myImageWorld, 0, &myRect, NULL, NULL, 0L);
	if (myErr != noErr)
		goto bail;
	
	// get the pixmap of the GWorld; we'll lock the pixmap, just to be safe
	myPixMap = GetGWorldPixMap(myImageWorld);
	if (!LockPixels(myPixMap))
		goto bail;
	
	// set the current port and draw the image
	GraphicsImportSetGWorld(myImporter, (CGrafPtr)myImageWorld, NULL);
	GraphicsImportDraw(myImporter);
	
	//////////
	//
	// display the standard image compression dialog box
	//
	//////////
	
	// open the standard compression dialog component
	myComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
	if (myComponent == NULL)
		goto bail;
		
	// set the picture to be displayed in the dialog box; passing NULL for the rect
	// means use the entire image; passing 0 for the flags means to use the default
	// system method of displaying the test image, which is currently a combination
	// of cropping and scaling; personally, I prefer scaling (your milage may vary)
	SCSetTestImagePixMap(myComponent, myPixMap, NULL, scPreferScaling);

	// install the custom procs, if requested
	// we can install two kinds of custom procedures for use in connection with
	// the standard dialog box: (1) a modal-dialog filter function, and (2) a hook
	// function to handle the custom button in the dialog box
	if (gUseExtendedProcs)
		QTStdCompr_InstallExtendedProcs(myComponent, (long)myPixMap);
	
	// request image compression settings from the user; in other words, put up the dialog box
	myErr = SCRequestImageSettings(myComponent);
	if (myErr == scUserCancelled)
		goto bail;

	//////////
	//
	// compress the image
	//
	//////////
	
	myErr = SCCompressImage(myComponent, myPixMap, NULL, &myDesc, &myHandle);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// save the compressed image in a new file
	//
	//////////
	
	QTStdCompr_PromptUserForDiskFileAndSaveCompressed(myHandle, myDesc);
	
bail:
	if (gUseExtendedProcs)
		QTStdCompr_RemoveExtendedProcs();

	if (myPixMap != NULL)
		if (GetPixelsState(myPixMap) & pixelsLocked)
			UnlockPixels(myPixMap);
	
	if (myImporter != NULL)
		CloseComponent(myImporter);
			
	if (myComponent != NULL)
		CloseComponent(myComponent);

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

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

	if (myImageWorld != NULL)
		DisposeGWorld(myImageWorld);
}


//////////
//
// QTStdCompr_PromptUserForDiskFileAndSaveCompressed
// Let the user select a disk file, then write the compressed image into that file.
//
//////////

void QTStdCompr_PromptUserForDiskFileAndSaveCompressed (Handle theHandle, ImageDescriptionHandle theDesc)
{
	StandardFileReply			myReply;
	short						myRefNum = -1;
	StringPtr 					myMoviePrompt = QTUtils_ConvertCToPascalString(kSaveMoviePrompt);
	StringPtr 					myMovieFileName = QTUtils_ConvertCToPascalString(kSaveMovieFileName);
	OSErr						myErr = noErr;

	// do a little sanity-checking....
	if ((theHandle == NULL) || (theDesc == NULL))
		goto bail;
		
	if ((**theDesc).dataSize > GetHandleSize(theHandle))
		goto bail;

	// prompt the user for a file to put the compressed image into; in theory, the name
	// should have a file extension appropriate to the type of compressed data selected by the user;
	// this is left as an exercise for the reader
	StandardPutFile(myMoviePrompt, myMovieFileName, &myReply);
	if (!myReply.sfGood)
		goto bail;
	
	HLock(theHandle);

	// create and open the file
	myErr = FSpCreate(&myReply.sfFile, kImageFileCreator, (**theDesc).cType, 0);
	
	if (myErr == noErr)
		myErr = FSpOpenDF(&myReply.sfFile, fsRdWrPerm, &myRefNum);
		
	if (myErr == noErr)
		myErr = SetFPos(myRefNum, fsFromStart, 0);

	// now write the data in theHandle into the file
	if (myErr == noErr)
		myErr = FSWrite(myRefNum, &(**theDesc).dataSize, *theHandle);
	
	if (myErr == noErr)
		myErr = SetFPos(myRefNum, fsFromStart, (**theDesc).dataSize);

	if (myErr == noErr)
		myErr = SetEOF(myRefNum, (**theDesc).dataSize);
				 
	if (myRefNum != -1)
		myErr = FSClose(myRefNum);
		
bail:
	free(myMoviePrompt);
	free(myMovieFileName);

	HUnlock(theHandle);
}


//////////
//
// QTStdCompr_InstallExtendedProcs
// Install the modal-dialog filter function and the hook function.
//
//////////

void QTStdCompr_InstallExtendedProcs (ComponentInstance theComponent, long theRefCon)
{
	StringPtr 		myButtonTitle = QTUtils_ConvertCToPascalString(kButtonTitle);

	// the modal-dialog filter function can be used to handle any events that
	// the standard image compression dialog handler doesn't know about, such
	// as any update events for windows owned by the application
	gProcStruct.filterProc = NewSCModalFilterProc(QTStdCompr_FilterProc);
	
	// the hook function can be used to handle clicks on the custom button
	gProcStruct.hookProc = NewSCModalHookProc(QTStdCompr_ButtonProc);

	// in this example, we pass the pixel map handle as a refcon
	gProcStruct.refcon = theRefCon;
	
	// copy the string for our custom button into the extended procs structure
	BlockMove(myButtonTitle, gProcStruct.customName, 9);

	// set the current extended procs
	SCSetInfo(theComponent, scExtendedProcsType, &gProcStruct);
	
	free(myButtonTitle);
}


//////////
//
// QTStdCompr_RemoveExtendedProcs
// Remove the modal-dialog filter function and the hook function.
//
//////////

void QTStdCompr_RemoveExtendedProcs (void)
{
	// clear out the extended procedures
	SCSetInfo((ComponentInstance)gProcStruct.refcon, scExtendedProcsType, NULL);
	
	// dispose of routine descriptors
	DisposeRoutineDescriptor(gProcStruct.filterProc);
	DisposeRoutineDescriptor(gProcStruct.hookProc);
}


//////////
//
// QTStdCompr_FilterProc
// Filter events for a standard modal dialog box. 
//
//////////

PASCAL_RTN Boolean QTStdCompr_FilterProc (DialogPtr theDialog, EventRecord *theEvent, short *theItemHit, long theRefCon)
{
#pragma unused(theDialog, theItemHit, theRefCon)
	Boolean			myEventHandled = false;
	WindowRef		myWindow = NULL;
		
	switch (theEvent->what) {
		case updateEvt:
			// update the specified window, if it's behind the modal dialog
			myWindow = (WindowRef)theEvent->message;
			if ((myWindow != NULL) && (myWindow != theDialog)) {
				DoUpdateWindow(myWindow, &(**(myWindow->visRgn)).rgnBBox);
				myEventHandled = false;		// so sayeth IM
			}
			break;
	}
	
	return(myEventHandled);
}


//////////
//
// QTStdCompr_ButtonProc
// Handle item selections in the standard image compression dialog box.
//
// The theParams parameter is the component instance of the standard image compression
// dialog component. Also, the theRefCon paramter is handle to our pixel map.
//
//////////

PASCAL_RTN short QTStdCompr_ButtonProc (DialogPtr theDialog, short theItemHit, void *theParams, long theRefCon)
{
#pragma unused(theDialog)
	// in this sample code, we'll have the settings revert to their default values
	// when the user clicks on the custom button
	if (theItemHit == scCustomItem)
		SCDefaultPixMapSettings(theParams, (PixMapHandle)theRefCon, false);

	// always return the item passed in
	return(theItemHit);
}
