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

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

/*
 * Local variables:
 *   lTimeInstalled = count of the number of times the task record has been installed
 *   lTimeTask = Time Manager task record for calculating elapsed times
 *
 */

static short gTimeInstalled = 0;
static long gStartTicks;

static Boolean HandleAbortMouseDown(EventRecord *theEvent);
static Boolean HandleAbortKeyDown(EventRecord *theEvent);

/*
 * CheckAbort()
 *
 *     Purpose: Scans the event queue for an aborting keypress
 *      Inputs: none
 *     Returns: true if we should abort
 *
 */

Boolean CheckAbort(JVDrawParamsHandle theParams)
{
	static long gLastTime = 0;
	static short gCount = 0;
	Boolean aborted = false, QDxInstalled;
	ImageHandle theImage;
	EventRecord theEvent;
	EvQElPtr eventPtr;
	short sleepTime;
	Point newPoint;
	
	// get the proper sleep time: according to preferences if in foreground, or the
	// maximum if we're in the background or running as a screen saver
	if (gInBackground || gScreenSaver ||
				gThePrefs.backgroundTime == bgMaximal) sleepTime = kSleepTime;
	else if (gThePrefs.backgroundTime == bgMinimal) sleepTime = 0;
	else sleepTime = 3;

	// if we're in screen saver mode, make sure we're frontmost
	if (gScreenSaver) EnsureFrontmost();
	
	// if we're not in the background, ensure that we get at least 2 ticks of continuous
	// operation before giving up a full amount of time to others
	if (!gInBackground && (TickCount() - gLastTime) < 2) sleepTime = 0;
	PushPort();
	MySetPort((CGrafPtr)FrontWindow());
	QDxInstalled = GetQDxDispatchPatchState();

	// get the next event; if sleepTime is zero, check event availability first
	if (sleepTime || EventAvail(everyEvent, &theEvent)) {
	
		// if we're in screen saver mode, and the mouse moved, quit immediately
		newPoint = GlobalMouse();
		if (gSlideShow && gScreenSaver &&
			(abs(newPoint.h - gScreenSaverPoint.h) > 2 ||
			 abs(newPoint.v - gScreenSaverPoint.v) > 2)) {
			SendQuitApplication();
			aborted = true;
		}
		
		// otherwise, handle any pending events normally
		else while (WaitNextEvent(everyEvent - highLevelEventMask, &theEvent, sleepTime, nil)) {
		
			// dispatch the event as appropriate
			switch (theEvent.what) {
				case mouseDown:
					aborted = HandleAbortMouseDown(&theEvent);
					break;
				case keyDown:
					if (theEvent.message == kCreator) break;
				case autoKey:
					aborted = HandleAbortKeyDown(&theEvent);
					break;
				case updateEvt:
					if (theImage = FindImage((WindowPtr)theEvent.message)) {
						if (gDrawing && theImage == gDrawing &&
									theParams && (*theParams)->on.port) {
							aborted = true;
							MySetPort((CGrafPtr)(*theImage)->window);
							BeginUpdate((*theImage)->window);
							UnionRgn((*theImage)->update, (*theImage)->window->visRgn, (*theImage)->update);
							EndUpdate((*theImage)->window);
							(*theImage)->flags |= ifNeedToRedraw;
						} else DeferImageUpdate(theImage);
					} else HandleUpdateEvent(&theEvent);
					break;
				case activateEvt:
					HandleActivateEvent(&theEvent);
					break;
				case osEvt:
					HandleOSEvent(&theEvent);
					break;
			}
			StartSpinning();

			// if we got a pending button, abort (unless it's during offscreen draw of
			// user-controlled slide show)
			if (gPendingButton != -1)
				if (gPendingButton != kForwardButton ||
					(theParams && (*theParams)->on.port))
					aborted = true;
		}
	} else {
		if (!(gCount = (gCount + 1) & 0x1f) && !gInBackground) 
			PPostEvent(keyDown, kCreator, &eventPtr);
	}
	SetQDxDispatchPatchState(QDxInstalled);
	PopPort();
	if (sleepTime) gLastTime = TickCount();
	return aborted;
}

static Boolean HandleAbortMouseDown(EventRecord *theEvent)
{
	Boolean aborted = false;
	
	// if we're in screen saver mode, always abort
	if (gSlideShow && gScreenSaver) {
		SendQuitApplication();
		return true;
	}
	
	// handle dialog events properly; if button 3 was hit on the progress dialog,
	// we abort the drawing
	if (IsDialogEvent(theEvent)) {
		DialogPtr theDialog;
		short itemHit;

		StopSpinning(&qd.arrow);
		if (DialogSelect(theEvent, &theDialog, &itemHit) && 
			GetWRefCon(theDialog) == kProgressDialogID &&
			itemHit == progressCancel) aborted = true;

	// otherwise, do it the hard way....
	} else {
		WindowPtr theWindow;
		short thePart = FindWindow(theEvent->where, &theWindow);

		// if a slide show is happening and the mouse was clicked in a
		// non-floating window, toggle the visible state of the slide show controls
		if (gSlideShow && WindowKind(theWindow) != floatingWindowKind) {
			theWindow = GetSlideControlsWindow();
			if (!theWindow) {
				if (OpenSlideControls() == noErr) 
					ChangeActive(GetSlideControlsWindow());
			} else CloseSlideControls();

		// otherwise, track dragging and menus as normal; check specially for clicks in
		// the slide show controls floating window, and beep about everything else
		} else {
			switch (thePart) {
				case inDrag:
					StopSpinning(&qd.arrow);
					HandleDragClick(theEvent, theWindow);
					break;
				case inMenuBar:
					StopSpinning(&qd.arrow);
					if (gMenuVisible) HandleMenuClick(theEvent);
					break;
				case inGrow:
				case inContent:
					if (theWindow == GetSlideControlsWindow()) {
						StopSpinning(&qd.arrow);
						HandleSlideControlsClick(theEvent->where, theEvent->modifiers, false);
						break;
					}
				default:
					SysBeep(1);
					break;
			}
		}
	}
	return aborted;
}

static Boolean HandleAbortKeyDown(EventRecord *theEvent)
{
	uchar theChar = theEvent->message & charCodeMask;
	Boolean aborted = false;
	DialogPtr theDialog;

	for (theDialog = FWFrontWindow(); theDialog; theDialog = NextWindow(theDialog))
		if (GetWRefCon(theDialog) == kProgressDialogID) break;

	// if we're in screen saver mode, always abort
	if (gSlideShow && gScreenSaver) {
		SendQuitApplication();
		return true;
	}
	
	// for cmd-0, cmd-`, and cmd-\, dispatch to the standard menu handler;
	// if cmd-. set the proper abort flag
	if ((theEvent->modifiers & cmdKey) && theChar != '.') {
		if (theChar == '0' || theChar == '`' || theChar == '\\' || 
			theChar == 'Q' || theChar == 'q') {
			HandleMenuChoice(PowerMenuKey(theEvent->message, theEvent->modifiers, 
										  GetMHandle(rFileMenu)));
			if (gDone) aborted = true;
		}
	}
	
	// if escape, set the proper abort flag
	else if (((theEvent->modifiers & cmdKey) && theChar == '.') || theChar == kEscapeChar) {

		// if the we're in a slide show & the stop button is disabled, simply abort
		if (gSlideShow == kPaused && GetSlideControlsWindow() && 
			GetSlideControlButton(kStopButton) == disabled) aborted = 0x4d;
		else if (gSlideShow) HandleSlideControlsKey(theChar, theEvent->modifiers, false);
		else aborted = 0x4d;
		
	// if we're in a slide show, pass the key through the controls handler
	} else if (gSlideShow) HandleSlideControlsKey(theChar, theEvent->modifiers, false);
	
	if (aborted == 0x4d && theDialog) {
		PushPort();
		MySetPort((CGrafPtr)theDialog);
		HandleDialogCancelKey(theDialog);
		aborted = true;
		PopPort();
	}
	return aborted;
}

/*
 * StartTimer()
 *
 *     Purpose: Starts a Time Manager task to calculate an elapsed time
 *      Inputs: none
 *     Returns: nothing
 *
 */
 
void StartTimer(void)
{
	if (++gTimeInstalled != 1) return;
	gStartTicks = TickCount();
}

/*
 * StopTimer()
 *
 *     Purpose: Stops the Time Manager task to calculate an elapsed time
 *      Inputs: none
 *     Returns: the elapsed time, in milliseconds
 *
 */
 
long StopTimer(void)
{
	if (--gTimeInstalled) return 0L;
	return ((TickCount() - gStartTicks) * 1000L / 60L);
}

/*
 * DirName(dirID, vRefNum, s)
 *
 *     Purpose: Returns the name of the directory given by the dirID and vRefNum
 *      Inputs: dirID = the directory ID of the directory
 *				vRefNum = the vRef of the directory
 *				s = a pointer to a string to hold the final name
 *     Returns: s
 *
 */
 
char *DirName(long dirID, short vRefNum, char *s) 
{
	Str255 directoryName;
	CInfoPBRec block;

	*s = 0;
	block.dirInfo.ioNamePtr = directoryName;
	block.dirInfo.ioDrDirID = block.dirInfo.ioDrParID = dirID;
	block.dirInfo.ioVRefNum = vRefNum;
	block.dirInfo.ioFDirIndex = -1;
	if (PBGetCatInfo(&block, false) == noErr) {
		if (directoryName[0]) BlockMove(directoryName, s, directoryName[0] + 1);
		else s[0] = 0;
	} else s[0] = 0;
	return s;
}

/*
 * GetDirectoryID(dirID, vRefNum)
 *
 *     Purpose: Gets the dirID of the given directory
 *      Inputs: theSpec = pointer to the directory spec
 *				theID = pointer to a long to store the ID
 *     Returns: an OSErr describing what went wrong
 *
 */

OSErr GetDirectoryID(FSSpec *theSpec, long *theID)
{
	CInfoPBRec block;
	Str255 fileName;
	OSErr theErr;

	block.hFileInfo.ioNamePtr = fileName;
	BlockMove(theSpec->name, fileName, *theSpec->name + 1);
	block.hFileInfo.ioVRefNum = theSpec->vRefNum;
	block.hFileInfo.ioFDirIndex = 0;
	block.hFileInfo.ioDirID = theSpec->parID;
	theErr = PBGetCatInfo(&block, false);
	if (theErr != noErr) return theErr;
	if (!(block.hFileInfo.ioFlAttrib & 0x10)) return errNotADirectory;
	*theID = block.dirInfo.ioDrDirID;
	return theErr;
}

/*
 * GetFullPath(theSpec, thePath)
 *
 *     Purpose: Gets the full pathname from a given FSSpec
 *      Inputs: theSpec = pointer to the directory spec
 *				thePath = pointer to a handle to receive the path
 *     Returns: an OSErr describing what went wrong
 *
 */
 
OSErr GetFullPath(FSSpec *theSpec, Handle *thePath)
{
	OSErr theErr = noErr;
	Boolean isDirectory;
	long size, tempID;
	Str255 dirName;
	DirInfo	block;

	theErr = GetDirectoryID(theSpec, &tempID);
	isDirectory = (theErr == noErr);
	*thePath = NewHandle(size = *theSpec->name);
	if (*thePath) {
		BlockMove(&theSpec->name[1], **thePath, *theSpec->name);
		block.ioDrParID = theSpec->parID;
		block.ioNamePtr = dirName;
		do {
			block.ioVRefNum = theSpec->vRefNum;
			block.ioFDirIndex = -1;
			block.ioDrDirID = block.ioDrParID;
			theErr = PBGetCatInfo((CInfoPBPtr)&block, false);
			if (theErr != noErr) {
				if (isDirectory) return noErr;
				break;
			}
			SetHandleSize(*thePath, size + 1 + *dirName);
			theErr = MemError();
			if (theErr != noErr) break;
			BlockMove(**thePath, **thePath + *dirName + 1, size);
			size += 1 + *dirName;
			BlockMove(&dirName[1], **thePath, *dirName);
			*(**thePath + *dirName) = ':';
		} while (block.ioDrDirID != 2);
	} else theErr = memFullErr;
	if ((theErr != noErr) && *thePath) {
		DisposeHandle(*thePath);
		*thePath = nil;
	}
	return theErr;
}

/*
 * ForceFolderUpdate(dirID, vRefNum)
 *
 *     Purpose: Forces the Finder to update the directory information of the given folder
 *      Inputs: dirID = the directory ID of the directory
 *				vRefNum = the vRef of the directory
 *     Returns: nothing
 *
 */
 
void ForceFolderUpdate(long dirID, short vRefNum)
{
	CInfoPBRec block;
	
	block.hFileInfo.ioCompletion = nil;
	block.hFileInfo.ioNamePtr = nil;
	block.hFileInfo.ioVRefNum = vRefNum;
	block.hFileInfo.ioDirID = dirID;
	block.hFileInfo.ioFDirIndex = -1;
	PBGetCatInfo(&block, false);
	GetDateTime(&block.dirInfo.ioDrMdDat);
	PBSetCatInfo(&block, false);
}

/*
 * AddString(theString, theAddition)
 *
 *     Purpose: Appends theAddition, a pascal string, to the pascal string theString
 *      Inputs: theString = the destination string in pascal format
 *				theAddition = the text of the addition
 *     Returns: nothing
 *
 */

void AddString(StringPtr theString, StringPtr theAddition) 
{
	BlockMove((uchar *)theAddition + 1, theString + theString[0] + 1, *theAddition);
	*theString += *theAddition;
}

/*
 * AddCString(theString, theAddition, length)
 *
 *     Purpose: Appends theAddition, a C string of length length, to the pascal string
 *					theString
 *      Inputs: theString = the destination string in pascal format
 *				theAddition = the text of the addition
 *				length = the length of the addition
 *     Returns: nothing
 *
 */

void AddCString(StringPtr theString, char *theAddition, int length) 
{
	BlockMove(theAddition, theString + theString[0] + 1, length);
	*theString += length;
}

/*
 * AddNumber(theString, theNum)
 *
 *     Purpose: Appends an ASCII representation of theNum to the pascal string theString
 *      Inputs: theString = the destination string in pascal format
 *				theNum = the number to append
 *     Returns: nothing
 *
 */

void AddNumber(StringPtr theString, long theNum) 
{
	Str255 tempNum, theAddition;
	int i;
	
	NumToString(theNum, tempNum);
	*theAddition = 0;
	for (i = 1; i <= *tempNum; i++) {
		AddChar(theAddition, tempNum[i]);
		if ((*tempNum > 4) && (*tempNum != i) && (((*tempNum - i) % 3) == 0)) 
			AddChar(theAddition, gThousandsSep);
	}
	AddString(theString, theAddition);
}

void StuffNumber(StringPtr theString, short theItem, long theNum)
{
	Str32 tempNum, fixedNum;
	StringPtr src, dst;
	short j;

	NumToString(theNum, tempNum);
	src = &tempNum[1];
	dst = &fixedNum[1];
	*fixedNum = *tempNum;
	for (j = 1; j <= *tempNum; j++) {
		*dst++ = *src++;
		if ((*tempNum > 4) && (*tempNum != j) && (((*tempNum - j) % 3) == 0))
			*dst++ = gThousandsSep, (*fixedNum)++;
	}
	StuffString(theString, theItem, fixedNum);
}

void StuffNumber0(StringPtr theString, short theItem, long theNum, short theLength)
{
	Str32 tempNum;

	NumToString(theNum, tempNum);
	while (*tempNum < theLength) {
		BlockMove(&tempNum[1], &tempNum[2], 30);
		tempNum[1] = '0';
		(*tempNum)++;
	}
	StuffString(theString, theItem, tempNum);
}

void StuffDepth(StringPtr theString, short theItem, short theDepth)
{
	Str32 tempNum;

	if (theDepth > 32) theDepth -= 32;
	if (theDepth <= 8) NumToString(1L << theDepth, tempNum);
	else if (theDepth == 16)
		BlockMove(gString[strThousands], tempNum, *gString[strThousands] + 1);
	else BlockMove(gString[strMillions], tempNum, *gString[strMillions] + 1);
	StuffString(theString, theItem, tempNum);
}

void StuffString(StringPtr theString, short theItem, StringPtr addString)
{
	Str255 tempStr;
	StringPtr src = &theString[1], dst = &tempStr[1], num = &addString[1];
	short i, j, len = *theString;

	theItem += '0';
	for (i = 0; i < len; i++) {
		if ((*src == '~') && (*(src+1) == theItem)) break;
		else *dst++ = *src++;
	}
	if (*src != '~') return;
	num = &addString[1];
	for (j = 0; j < *addString; j++) *dst++ = *num++;
	src += 2, i += 2;
	for ( ; i < len; i++) *dst++ = *src++;
	*tempStr = len + *addString - 2;
	BlockMove(tempStr, theString, *tempStr + 1);
}

/*
 * GlobalMouse()
 *
 *     Purpose: Gets the global coordinates of the mouse
 *      Inputs: none
 *     Returns: the mouse location in global coordinates
 *
 */

Point GlobalMouse(void) 
{
	Point thePoint;
	
	PushPort();
	MySetPort(nil);
	GetMouse(&thePoint);
	LocalToGlobal(&thePoint);
	PopPort();
	return thePoint;
}
