/*********************************************************/
/* This source code copyright (c) 1991-2001, Aaron Giles */
/* See the Read Me file for licensing information.       */
/* Contact email: mac@aarongiles.com                     */
/*********************************************************/

//=====================================================================================
// Generic includes for Macintosh headers
//=====================================================================================

#if THINK_C
#include "THINK.Header"
#include <stddef.h>
#elif applec
#pragma load ":Headers:MPW.Header"
#elif __MWERKS__
//#include "MW.Header"
#else
#include "JPEGView.h"
#endif

//=====================================================================================
// Includes specific to this module
//=====================================================================================

#include "ProgressProcs.h"

//=====================================================================================
// Global variables local to this module
//=====================================================================================

static UserItemUPP gUpdateBar = nil, gUpdateBarIndef;
static Fixed gProgressPercent;

//=====================================================================================
// Prototypes for functions local to this module
//=====================================================================================

static pascal void UpdateBar(DialogPtr theDialog, short theItem);
static pascal void UpdateBarIndef(DialogPtr theDialog, short theItem);
static void InitDrawProgress(Point *thePoint, JVDrawParamsHandle theParams);
static void MakeDrawProgressText(short theMessage, StringPtr theNum, Fixed thePercent);

//=====================================================================================
// pascal OSErr DummyProgress(short theMessage, Fixed thePercent, long refCon)
//=====================================================================================
// Dummy progress proc; goes through all the motions, but is just used to make sure we
// give up time to other applications while providing no real feedback to the user.
//=====================================================================================

extern pascal OSErr DummyProgress(short theMessage, Fixed thePercent, long refCon)
{
#if applec
#pragma unused(thePercent)
#endif
	NestedProgressPtr theProgress = nil;
	JVDrawParamsHandle theParams = nil;
	OSErr theErr = noErr;
	char hState;
	
	// figure out of we've got a handle to drawing parameters, or just a pointer
	// to a progress proc as the parameter; if we have a handle, lock it and
	// extract the pointer to the progress proc
	if (((NestedProgressPtr)refCon)->prog.progressProc == gDummyProg.prog.progressProc)
		theProgress = (NestedProgressPtr)refCon;
	else {
		theParams = (JVDrawParamsHandle)refCon;
		hState = HGetState((Handle)theParams);
		HLock((Handle)theParams);
		theProgress = &(*theParams)->progress;
	}
	
	// keep the cursor spinning, and track the various nesting levels as necessary
	// when open and close messages are received
	KeepSpinning();
	switch (theMessage) {
		case codecProgressOpen:
			if (++theProgress->level <= 1) {
				theProgress->aborted = false;
			}
			break;
		case codecProgressClose:
			if (--theProgress->level > 0) break;
			break;
	}
	
	// check for aborts
	if (theProgress->aborted || (theProgress->aborted = CheckAbort(theParams))) 
		theErr = codecAbortErr;

	// restore the drawing parameters handle to its original state and exit
	if (theParams) HSetState((Handle)theParams, hState);
	return theErr;
}

//=====================================================================================
// pascal OSErr GenericProgress(short theMessage, Fixed thePercent, long refCon)
//=====================================================================================
// Generic progress procedure, which displays a dialog with a progress bar and a cancel
// button.  When calling with the open message, the thePercent parameter specifies
// special options.  If thePercent equals kModalProgress, then the dialog displayed is
// a modal dialog, centered on the screen, rather than the usual movable modal.  If
// thePercent is set to kIndefProgress, then the percentage parameter is ignored in the
// future, and the famous Finder "barber shop" progress bar is displayed instead.  This
// function can be called as often as desired; the display is never updated more than
// 3 times/second.
//=====================================================================================
 
extern pascal OSErr GenericProgress(short theMessage, Fixed thePercent, long refCon) 
{
	static DialogPtr gTheDialog = nil;
	static long gLastTime;
	NestedProgressPtr theProgress = nil;
	JVDrawParamsHandle theParams = nil;
	OSErr theErr = noErr;
	char hState;
	long ticks;

	// figure out of we've got a handle to drawing parameters, or just a pointer
	// to a progress proc as the parameter; if we have a handle, lock it and
	// extract the pointer to the progress proc
	if (((NestedProgressPtr)refCon)->prog.progressProc == gGenericProgress)
		theProgress = (NestedProgressPtr)refCon;
	else {
		theParams = (JVDrawParamsHandle)refCon;
		hState = HGetState((Handle)theParams);
		HLock((Handle)theParams);
		theProgress = &(*theParams)->progress;
	}

	// save the port now, and calculate the scaled percentage
	KeepSpinning();
	PushPort();

	switch (theMessage) {
		case codecProgressOpen:
		
			// open the dialog if this is the first call
			if (++theProgress->level <= 1) {
				theProgress->aborted = false;
				MySetPort(nil);
				if (!(gTheDialog = AllocateDialog())) break;
				
				// we use the modal dialog if thePercent is set to kModalProgress
				if (gTheDialog = FWGetNewDialog((long)thePercent == kModalProgress ?
							rModalProgressDialog : rProgressDialog, nil, (WindowPtr)-1)) {
					MySetPort((CGrafPtr)gTheDialog);
					
					// set up the user item handler, either for indefinite progress
					// (thePercent == kIndefProgress) or for standard progress
					if (!gUpdateBar) {
						gUpdateBar = NewUserItemProc((ProcPtr)UpdateBar);
						gUpdateBarIndef = NewUserItemProc((ProcPtr)UpdateBarIndef);
					}
					SetItemHandle(gTheDialog, progressUser, 
								(long)thePercent == kIndefProgress ? 
								(Handle)gUpdateBarIndef : (Handle)gUpdateBar);
					gProgressPercent = 0L;
					PlaceWindow(gTheDialog, &gThePrefs.progressBounds, 
								CenterWindow(gTheDialog));
					HiliteMenu(0);
					MySetPort(nil);
					ShowWindow(gTheDialog);
					DrawDialog(gTheDialog);
					AdjustMenus();
				} else DeallocateDialog(gTheDialog), gTheDialog = nil;
			}
			
			// reset the last time so that we draw right away, and update the initial
			// percentage
			gLastTime = 0;
			theErr = GenericProgress(codecProgressUpdatePercent, 0, refCon);
			break;

		case codecProgressUpdatePercent:
			if (gTheDialog) {
			
				// update our user item no more than 3x a second; do this by setting the
				// gProgressPercent global, and forcing an update of the bar userItem
				if ((TickCount() - gLastTime) < 10L) break;
				gLastTime = TickCount();

				// scale the displayed percentage according to begin/end
				gProgressPercent = theProgress->begin + 
							FixMul(thePercent, theProgress->end - theProgress->begin);
				UpdateDialogItem(gTheDialog, progressUser);
			}
			break;

		case codecProgressClose:

			// display the final percentage and delay for an instant
			GenericProgress(codecProgressUpdatePercent, 0x10000, refCon);
			Delay(1, &ticks);
			if (--theProgress->level > 0) break;

			// if this is the final nested call, save the position and exit
			MySetPort(nil);
			if (gTheDialog) {
				if (GetWVariant(gTheDialog) != dBoxProc) 
					SaveWindowPosition(gTheDialog, &gThePrefs.progressBounds);
				if (gInBackground) FWHideFloatingWindows();
				FWCloseDialog(gTheDialog);
				DisposeHandle(((DialogPeek)gTheDialog)->items);
				DeallocateDialog(gTheDialog);
			}
			gTheDialog = nil;
			break;
	}

	// restore the original port and check for aborts
	PopPort();
	if (theErr == noErr) {
		if (theProgress->aborted || (theProgress->aborted = CheckAbort(theParams)))
			theErr = codecAbortErr;
	}

	// if there was an abort signalled, and the dialog is visible, hide it immediately
	// to give better user feedback
	if (theErr == codecAbortErr && gTheDialog && WindowVisible(gTheDialog))
		FWHideWindow(gTheDialog);

	// restore the drawing parameters handle to its original state and exit
	if (theParams) HSetState((Handle)theParams, hState);
	return theErr;
}

//=====================================================================================
// pascal void UpdateBar(DialogPtr theDialog, short theItem)
//=====================================================================================
// Draws the progress bar for the generic progress dialog, according to the percentage
// stored in the global gProgressPercent.
//=====================================================================================

static pascal void UpdateBar(DialogPtr theDialog, short theItem) 
{
	static RGBColor gBarBackground = { 0xcccc, 0xcccc, 0xffff };
	static RGBColor gBarForeground = { 0x4000, 0x4000, 0x4000 };
	Boolean useBW = ((CGrafPtr)theDialog)->portPixMap &&
					(*((CGrafPtr)theDialog)->portPixMap)->pixelSize == 1;
	Rect itemRect, leftRect, rightRect;
	Handle itemHandle;
	Fixed thePercent;
	short itemType;
	
	PushPort();
	MySetPort((CGrafPtr)theDialog);
	thePercent = gProgressPercent;
	GetDItem(theDialog, theItem, &itemType, &itemHandle, &itemRect);
	PenSize(1, 1);
	RGBForeColor(&gBlack);
	FrameRect(&itemRect);
	InsetRect(&itemRect, 1, 1);
	rightRect = leftRect = itemRect;
	leftRect.right = itemRect.left +  
				FixRound(FixMul(thePercent, Long2Fix(Width(&itemRect))));
	rightRect.left = leftRect.right;
	if (leftRect.right > leftRect.left) {
		RGBForeColor(&gBarForeground);
		PaintRect(&leftRect);
	}
	if (rightRect.left < rightRect.right) {
		if (useBW) RGBForeColor(&gWhite);
		else RGBForeColor(&gBarBackground);
		PaintRect(&rightRect);
	}
	RGBForeColor(&gBlack);
	PopPort();
}

//=====================================================================================
// pascal void UpdateBarIndef(DialogPtr theDialog, short theItem)
//=====================================================================================
// Draws the progress bar for the generic progress dialog, in the Finder's "barber
// shop" style.  Used for progress display when the final count of things to be done is
// not initially known.
//=====================================================================================

static pascal void UpdateBarIndef(DialogPtr theDialog, short theItem) 
{
	static RGBColor gBarBackground = { 0xcccc, 0xcccc, 0xffff };
	static RGBColor gBarForeground = { 0x4000, 0x4000, 0x4000 };
	static short gBarOffset = 0;
	Boolean useBW = ((CGrafPtr)theDialog)->portPixMap &&
					(*((CGrafPtr)theDialog)->portPixMap)->pixelSize == 1;
	short itemType, left;
	Handle itemHandle;
	RgnHandle oldClip;
	Rect itemRect;
	
	if (!theDialog) return;
	PushPort();
	MySetPort((CGrafPtr)theDialog);
	GetDItem(theDialog, theItem, &itemType, &itemHandle, &itemRect);
	if (oldClip = NewRgn()) {
		GetClip(oldClip);
		PenSize(1, 1);
		RGBForeColor(&gBlack);
		FrameRect(&itemRect);
		InsetRect(&itemRect, 1, 1);
		ClipRect(&itemRect);
		left = itemRect.left - (3 * Height(&itemRect)) + 
					(gBarOffset * Height(&itemRect) / 2);
		PenSize(Height(&itemRect), 1);
		while (left < itemRect.right) {
			RGBForeColor(&gBarForeground);
			MoveTo(left, itemRect.top);
			LineTo(left += Height(&itemRect), itemRect.bottom);
			if (useBW) RGBForeColor(&gWhite);
			else RGBForeColor(&gBarBackground);
			MoveTo(left, itemRect.top);
			LineTo(left += Height(&itemRect), itemRect.bottom);
		}
		PenSize(1, 1);
		SetClip(oldClip);
		DisposeRgn(oldClip);
		gBarOffset = (gBarOffset + 1) & 3;
	}
	RGBForeColor(&gBlack);
	PopPort();
}

//=====================================================================================
// pascal OSErr DrawProgress(short theMessage, Fixed thePercent, 
//			JVDrawParamsHandle theParams)
//=====================================================================================
// Handles progress that is displayed during onscreen drawing.  This is shown as a
// small percentage in the bottom-right corner of the screen.  There is a lot that we
// need to be concerned with here, especially the possibility of overwriting an area
// that has already been drawn, and calculating the best place to put the percentage.
//=====================================================================================

extern pascal OSErr DrawProgress(short theMessage, Fixed thePercent, 
			JVDrawParamsHandle theParams) 
{
	static Fixed gLastPercent;
	static Point gThePoint;
	static long gLastTime;
	static short gLastH;
	Boolean state = (thePercent != 0);
	OSErr theErr = noErr;
	RgnHandle tempRgn;
	FontInfo theFont;
	Str255 theNum;
	Rect theRect;
	short i;

	// if we're not drawing onscreen, don't do this at all; otherwise, set port to the
	// onscreen port and go for it
	if (!(*theParams)->on.port) return noErr;
	KeepSpinning();
	PushPort();
	MySetPort((*theParams)->on.port);

	// calculate the scaled percentage, according to the nested progress we use
	thePercent = (*theParams)->progress.begin +
				FixMul(thePercent, (*theParams)->progress.end - (*theParams)->progress.begin);
	switch (theMessage) {
		case codecProgressOpen:
		
			// if this is the first nesting level, set the gDrawing flag, readjust the
			// menus, and calculate the best place to draw the progress percentage
			if (++(*theParams)->progress.level > 1) break;
			if (!gDrawing) gDrawing = (ImageHandle)-1;
			HiliteMenu(0);
			AdjustMenus();
			InitDrawProgress(&gThePoint, theParams);
			(*theParams)->progress.aborted = false;
			GetFontInfo(&theFont);
			(*theParams)->endprogress = gThePoint.v - theFont.ascent - 2;
			gLastTime = 0;
			gLastH = 0;
			DrawProgress(codecProgressUpdatePercent, 0L, theParams);
			break;

		// if we get scaling or dithering messages, we use the last percentage
		case codecProgressUpdateScaling:
		case codecProgressUpdateDithering:
			thePercent = gLastPercent;

		case codecProgressUpdatePercent:
			gLastPercent = thePercent;
			
			// if we haven't drawn over the area where the progress is, we check to be
			// sure there's been at least 1/3 sec. since the last update; of course, this
			// doesn't apply if we've received the UpdateScaling or UpdateDithering msgs.
			if (!(*theParams)->endprogress ||
						((TickCount() - gLastTime) < 10L && 
						theMessage == codecProgressUpdatePercent)) break;
			gLastTime = TickCount();
			
			// here's where we actually determine the string and draw it onscreen
			SetClip(qd.thePort->visRgn);
			RGBForeColor(&gBlack);
			RGBBackColor(&gWhite);
			MakeDrawProgressText(state ? theMessage : codecProgressUpdatePercent, theNum, 
						thePercent);
			i = gThePoint.h - StringWidth(theNum);
			MoveTo(i, gThePoint.v - 2);
			DrawString(theNum);
			MySetRect(&theRect, gLastH ? gLastH : i - 1, (*theParams)->endprogress, i, 
					  (*theParams)->drawProgressRect.bottom);
			PaintRect(&theRect);
			gLastH = i;
			break;

		case codecProgressClose:
		
			// if this isn't the last nesting level, just ignore for now
			if (--(*theParams)->progress.level > 0) break;
			gDrawing = nil;
			AdjustMenus();
			
			// if we never drew over the area where the progress is, make sure we erase
			// it; otherwise it'll look funny
			if (!(*theParams)->endprogress) break;
			if (tempRgn = NewRgn()) {
				GetClip(tempRgn);
				SetClip((*theParams)->newupdates);
				MySetRect(&theRect, gLastH ? gLastH : (*theParams)->drawProgressRect.left, 
							(*theParams)->endprogress, (*theParams)->drawProgressRect.right, 
							(*theParams)->drawProgressRect.bottom);
				PaintRect(&theRect);
				SetClip(tempRgn);
				DisposeRgn(tempRgn);
			}
			break;
	}
	PopPort();
	
	// restore the original port and check for aborts
	if ((*theParams)->progress.aborted || 
				((*theParams)->progress.aborted = CheckAbort(theParams)))
		theErr = codecAbortErr;
	return theErr;
}

//=====================================================================================
// void InitProgress(Point *thePoint)
//=====================================================================================
// Handles progress that is displayed during onscreen drawing.  This is shown as a
// small percentage in the bottom-right corner of the screen.  There is a lot that we
// need to be concerned with here, especially the possibility of overwriting an area
// that has already been drawn, and calculating the best place to put the percentage.
//=====================================================================================

static void InitDrawProgress(Point *thePoint, JVDrawParamsHandle theParams)
{
	RgnHandle theRgn, textRgn;
	short height, width;
	FontInfo theFont;
	Str255 tempStr;
	Rect fullRect;
	
	PenNormal();
	TextFont(geneva);
	TextMode(notSrcCopy);
	TextSize(9);
	BlockMove(gString[strDecompressing], tempStr, gString[strDecompressing][0] + 1);
	GetFontInfo(&theFont);
	height = theFont.leading + theFont.ascent + theFont.descent;
	width = StringWidth(tempStr);
	fullRect = (*theParams)->drawProgressRect;
	fullRect.bottom = (*qd.thePort->visRgn)->rgnBBox.bottom;
	if (fullRect.bottom > (*theParams)->drawProgressRect.bottom) 
		fullRect.bottom = (*theParams)->drawProgressRect.bottom;
	MySetPt(thePoint, fullRect.right, fullRect.bottom);
	if (theRgn = NewRgn()) {
		if (textRgn = NewRgn()) {
			while (true) {
				while (true) {
					SetRectRgn(theRgn, fullRect.left, fullRect.bottom - 1, 
								fullRect.right,	fullRect.bottom);
					SectRgn(theRgn, qd.thePort->visRgn, theRgn);
					SetRectRgn(textRgn, (*theRgn)->rgnBBox.right - width, 
								(*theRgn)->rgnBBox.bottom - height, 
								(*theRgn)->rgnBBox.right, (*theRgn)->rgnBBox.bottom);
					SectRgn(textRgn, qd.thePort->visRgn, theRgn);
					if (EqualRgn(textRgn, theRgn)) break;
					if (EmptyRgn(theRgn)) break;
					fullRect.right = (*theRgn)->rgnBBox.right - 1;
					if (Width(&fullRect) < width) {
						fullRect.right = (*theParams)->drawProgressRect.right;
						break;
					}
				}
				if (EqualRgn(textRgn, theRgn)) break;
				fullRect.bottom -= 4;
				if (Height(&fullRect) < (height << 2)) {
					fullRect = (*theParams)->drawProgressRect;
					break;
				}
			}
			MySetPt(thePoint, (*theRgn)->rgnBBox.right - 1, (*theRgn)->rgnBBox.bottom);
			DisposeRgn(textRgn);
		}
		DisposeRgn(theRgn);
	}
}

//=====================================================================================
// void MakeProgressText(short theMessage, StringPtr theNum, Fixed thePercent)
//=====================================================================================
// Assemble the string to display for the drawing progress.
//=====================================================================================
 
static void MakeDrawProgressText(short theMessage, StringPtr theNum, Fixed thePercent)
{
	theNum[0] = 0;
	if (theMessage == codecProgressUpdateScaling && thePercent)
		AddString(theNum, gString[strScaling]);
	else if (theMessage == codecProgressUpdateDithering && thePercent)
		AddString(theNum, gString[strDithering]);
	else AddString(theNum, gString[strDecompressing]);
	StuffNumber(theNum, 1, FixRound(FixMul(thePercent, Long2Fix(100L))));
}

//=====================================================================================
// pascal OSErr SlideProgress(short theMessage, Fixed thePercent, long refCon)
//=====================================================================================
// Progress function for slide shows; draws a rotating little 2x1 dot in the
// upper-right corner of the screen.
//=====================================================================================

extern pascal OSErr SlideProgress(short theMessage, Fixed thePercent, long refCon) 
{
#if applec
#pragma unused(thePercent)
#endif
	static long gLastTime, gLastPos;
	JVDrawParamsHandle theParams = (JVDrawParamsHandle)refCon;
	ImageHandle theImage = FrontImage();
	WindowPtr theWindow;
	Rect theRect;

	theWindow = (theImage && Full(theImage)) ? (*theImage)->window : GetSlideBackWindow();
	KeepSpinning();
	if (theWindow) {
		theRect = gImageRoot ? (*(*gImageRoot)->dmon)->rect : theWindow->portRect;
		PushPort();
		MySetPort((CGrafPtr)theWindow);
		ClipRect(&theRect);
		switch (theMessage) {
		
			case codecProgressRealOpen:
				gLastTime = gLastPos = 0;
				SetCPixel(theRect.right - 1, theRect.top + 0, &gWhite);
				SetCPixel(theRect.right - 2, theRect.top + 0, &gWhite);
				SetCPixel(theRect.right - 1, theRect.top + 1, &gBlack);
				SetCPixel(theRect.right - 2, theRect.top + 1, &gBlack);
				break;
				
			case codecProgressUpdatePercent:
				if ((TickCount() - gLastTime) < 20L) break;
				gLastTime = TickCount();
				switch (gLastPos = (gLastPos + 1) & 3) {
					case 0:
						SetCPixel(theRect.right - 2, theRect.top + 1, &gBlack);
						SetCPixel(theRect.right - 1, theRect.top + 0, &gWhite);
						break;
					case 1:
						SetCPixel(theRect.right - 2, theRect.top + 0, &gBlack);
						SetCPixel(theRect.right - 1, theRect.top + 1, &gWhite);
						break;
					case 2:
						SetCPixel(theRect.right - 1, theRect.top + 0, &gBlack);
						SetCPixel(theRect.right - 2, theRect.top + 1, &gWhite);
						break;
					case 3:
						SetCPixel(theRect.right - 1, theRect.top + 1, &gBlack);
						SetCPixel(theRect.right - 2, theRect.top + 0, &gWhite);
						break;
				}
				break;
				
			case codecProgressRealClose:
				SetCPixel(theRect.right - 1, theRect.top + 0, &gBlack);
				SetCPixel(theRect.right - 2, theRect.top + 0, &gBlack);
				SetCPixel(theRect.right - 1, theRect.top + 1, &gBlack);
				SetCPixel(theRect.right - 2, theRect.top + 1, &gBlack);
				break;
		}
		PopPort();
	}
	if (theParams &&
		((*theParams)->progress.aborted || ((*theParams)->progress.aborted = CheckAbort(theParams))))
		return codecAbortErr;
	else return noErr;
}
