/*********************************************************/
/* 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"
#elif applec
#pragma load ":Headers:MPW.Header"
#elif __MWERKS__
//#include "MW.Header"
#else
#include "JPEGView.h"
#endif

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

#include "SlideControlsWindow.h"

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

static short gButtonStatus[4] = { standard, standard, standard, standard };
static CIconHandle gStandardButton[4] = { nil, nil, nil, nil };
static CIconHandle gDepressedButton[4] = { nil, nil, nil, nil };
static CIconHandle gDisabledButton[4] = { nil, nil, nil, nil };
static WindowPtr gSlideControlsWindow = nil;
static Str255 gSlideControlsText = "\p";
static Rect gButtonRect[4];

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

static OSErr LoadButtons(void);

//=====================================================================================
// OSErr OpenSlideControls(void)
//
// Creates and initializes the slide show controls floating window.
//
// External calls: AllocateWindow, PushPort, MySetPort, GetActiveRect, PlaceWindow,
//					PopPort
//=====================================================================================

extern OSErr OpenSlideControls(void) 
{
	Rect theRect, mainRect;
	OSErr theErr = noErr;
	short width, height;
	FontInfo theInfo;
	Point where;
	short i;
	
	if (gSlideControlsWindow) return noErr;
	if (!gStandardButton[0]) theErr = LoadButtons();
	if (!(gSlideControlsWindow = AllocateWindow())) theErr = memFullErr;
	if (theErr != noErr) return theErr;
	PushPort();
	MySetPort(nil);
	if (gSlideControlsWindow = FWGetNewFloatingWindow(rSlideControlsWindow, 
							   (Ptr)gSlideControlsWindow, (WindowPtr)-1)) {
		MySetPort((CGrafPtr)gSlideControlsWindow);
		TextFont(geneva);
		TextSize(9);
		GetFontInfo(&theInfo);
		width = Width(&(*gStandardButton[0])->iconPMap.bounds);
		height = Height(&(*gStandardButton[0])->iconPMap.bounds);
		theRect.top = theRect.left = 0;
		theRect.right = width * 4;
		theRect.bottom = height + theInfo.ascent + theInfo.descent + theInfo.leading;
		SizeWindow(gSlideControlsWindow, Width(&theRect), Height(&theRect), true);
		GetActiveRect(gMainMonitor, &mainRect);
		where.h = mainRect.right - kWindowBorderWidth - Width(&theRect);
		where.v = mainRect.bottom - kWindowBorderHeight - Height(&theRect);
		PlaceWindow(gSlideControlsWindow, &gThePrefs.slideControlsBounds, where);
		for (i = 0; i < 4; i++) {
			gButtonRect[i].bottom = theRect.bottom;
			gButtonRect[i].top = theRect.bottom - height;
			gButtonRect[i].left = i * width;
			gButtonRect[i].right = i * width + width;
		}
		if (!*gSlideControlsText)
			SetSlideControlsText(gString[strSlideReady], gNullString, gNullString, gNullString);
		PopPort();
		return noErr;
	} else theErr = memFullErr;
	CloseSlideControls();
	PopPort();
	return theErr;
}

//=====================================================================================
// OSErr LoadButtons(void)
//
// Loads up the color icons used for buttons.
//
// External calls: none
//=====================================================================================

static OSErr LoadButtons(void)
{
	short buttonType[4] = { rReverseButton, rStopButton, rPauseButton, rForwardButton };
	short button;
	
	for (button = 0; button < 4; button++) {
		if (!(gStandardButton[button] = GetCIcon(buttonType[button] + rStandardButton)) ||
			!(gDepressedButton[button] = GetCIcon(buttonType[button] + rDepressedButton)) ||
			!(gDisabledButton[button] = GetCIcon(buttonType[button] + rDisabledButton)))
			return memFullErr;
		HLockHi((Handle)gStandardButton[button]);
		HLockHi((Handle)gDepressedButton[button]);
		HLockHi((Handle)gDisabledButton[button]);
	}
	return noErr;
}

//=====================================================================================
// OSErr CloseSlideControls(void)
//
// Closes the slide show controls window.
//
// External calls: none
//=====================================================================================

extern void CloseSlideControls(void)
{
	PushPort();
	MySetPort(nil);
	if (gSlideControlsWindow) {
		SaveWindowPosition(gSlideControlsWindow, &gThePrefs.slideControlsBounds);
		FWCloseWindow(gSlideControlsWindow);
		DeallocateWindow(gSlideControlsWindow);
	}
	gSlideControlsWindow = nil;
	PopPort();
}

//=====================================================================================
// WindowPtr GetSlideControlsWindow(void)
//
// Returns a pointer to the slide show controls window.
//
// External calls: none
//=====================================================================================

extern WindowPtr GetSlideControlsWindow(void)
{
	return gSlideControlsWindow;
}

//=====================================================================================
// void SetSlideControlButton(short id, short status)
//
// Sets the status of a given control button.
//
// External calls: none
//=====================================================================================

extern void SetSlideControlButton(short id, short status)
{
	gButtonStatus[id] = status;
}

//=====================================================================================
// short GetSlideControlButton(short id)
//
// Returns the status of the given control button.
//
// External calls: none
//=====================================================================================

extern short GetSlideControlButton(short id)
{
	return gButtonStatus[id];
}

//=====================================================================================
// void DrawSlideControlsWindow(void)
//
// Draws the contents of the slide show controls window.
//
// External calls: none
//=====================================================================================

extern void DrawSlideControlsWindow(void)
{
	FontInfo theInfo;
	Rect theRect;
	short i;

	if (!gSlideControlsWindow) return;
	PushPort();
	MySetPort((CGrafPtr)gSlideControlsWindow);
	theRect = gSlideControlsWindow->portRect;
	theRect.bottom = gButtonRect[0].top;
	TextFont(geneva);
	TextSize(9);
	GetFontInfo(&theInfo);
	MoveTo(2, theInfo.ascent + theInfo.leading);
	EraseRect(&theRect);
	TruncString(Width(&gSlideControlsWindow->portRect) - 4, gSlideControlsText, smTruncEnd);
	DrawString(gSlideControlsText);
	for (i = 0; i < 4; i++) {
		if (gButtonStatus[i] == depressed || gPendingButton == i) 
			PlotCIcon(&gButtonRect[i], gDepressedButton[i]);
		else if (gButtonStatus[i] == standard) PlotCIcon(&gButtonRect[i], gStandardButton[i]);
		else PlotCIcon(&gButtonRect[i], gDisabledButton[i]);
	}
	PopPort();
}	

//=====================================================================================
// void HandleSlideControlsClick(Point where, short modifiers, Boolean actNow)
//
// Draws the contents of the slide show controls window.
//
// External calls: none
//=====================================================================================

extern void HandleSlideControlsClick(Point where, short modifiers, Boolean actNow)
{
	long startTime = TickCount();
	Boolean isDepressed = false;
	short button, buttonFunction;
	
	gPendingButton = -1;
	PushPort();
	MySetPort((CGrafPtr)gSlideControlsWindow);
	
	// convert to local coordinates, and look for a match to a button rectangle
	GlobalToLocal(&where);
	for (button = 0; button < 4; button++)
		if (gButtonStatus[button] == standard && PtInRect(where, &gButtonRect[button])) break;
	
	if (gSlideShow == kPaused && PtInRect(where, &gButtonRect[kPauseButton]))
		button = kPauseButton;
	
	// if we found one, track it until the mouse is released
	if (button < 4) {
		PlotCIcon(&gButtonRect[button], gDepressedButton[button]);
		isDepressed = true;
		while (StillDown()) {
			GetMouse(&where);
			if (PtInRect(where, &gButtonRect[button])) {
				if (!isDepressed) {
					PlotCIcon(&gButtonRect[button], gDepressedButton[button]);
					isDepressed = true;
				}
			} else if (isDepressed) {
				PlotCIcon(&gButtonRect[button], gStandardButton[button]);
				isDepressed = false;
			}
		}
		
		// if button was depressed when released, and we're allowed to act immediately,
		// do the appropriate action
		if (isDepressed) {
			buttonFunction = button;
			if ((modifiers & optionKey) && button == kPauseButton) buttonFunction = kHideButton;
			if (actNow && gSlideShow != kPaused) {
				while ((TickCount() - startTime) < 8);
				PlotCIcon(&gButtonRect[button], gStandardButton[button]);
				if ((*gSlideOptions)->userControl) gSlideShow = HandleUserControlButton(buttonFunction);
				else gSlideShow = HandleAutomaticButton(buttonFunction);
				if (!gSlideShow) CleanUpSlideShow();
			} else gPendingButton = buttonFunction;
		}
	}
	PopPort();
	if (HMIsBalloon()) HMRemoveBalloon();
}

//=====================================================================================
// void HandleSlideControlsKey(char theChar, short modifiers, Boolean actNow)
//
// Draws the contents of the slide show controls window.
//
// External calls: none
//=====================================================================================

extern void HandleSlideControlsKey(char theChar, short modifiers, Boolean actNow)
{
	long startTime = TickCount();
	short button, buttonFunction;
	
	gPendingButton = -1;
	// associate the keypress with one of the buttons
	if (theChar == kEscapeChar || (theChar == '.' && (modifiers & cmdKey)))
		button = kStopButton;
	else if (theChar == kLeftArrowChar) button = kReverseButton;
	else if (theChar == kUpArrowChar) button = kPauseButton;
	else button = kForwardButton;

	// if the button is disabled, beep
	if (gButtonStatus[button] == disabled) SysBeep(1);

	// otherwise, depress the button (if visible)
	else {
		PushPort();
		if (gSlideControlsWindow) {
			MySetPort((CGrafPtr)gSlideControlsWindow);
			PlotCIcon(&gButtonRect[button], gDepressedButton[button]);
		}
		
		buttonFunction = button;
		if ((modifiers & optionKey) && button == kPauseButton) buttonFunction = kHideButton;
		
		// if we're to act now, dispatch the button to the appropriate handler
		if (actNow && gSlideShow != kPaused) {
			while ((TickCount() - startTime) < 8);
			if (gSlideControlsWindow) PlotCIcon(&gButtonRect[button], gStandardButton[button]);
			if ((*gSlideOptions)->userControl) gSlideShow = HandleUserControlButton(buttonFunction);
			else gSlideShow = HandleAutomaticButton(buttonFunction);
			if (!gSlideShow) CleanUpSlideShow();
		} else gPendingButton = buttonFunction;
		PopPort();
	}
	if (HMIsBalloon()) HMRemoveBalloon();
}

//=====================================================================================
// void Boolean HandleUserControlButton(short button)
//
// Handles button commands during a user-controlled slide show.
//
// External calls: none
//=====================================================================================

extern Boolean HandleUserControlButton(short button)
{
	Boolean advance;

	switch (button) {
		case kStopButton:
			return false;
		case kPauseButton:
			gSlideShow = PauseSlideShow(&advance);
			return gSlideShow;
		case kHideButton:
			gSlideShow = HideSlideShow(&advance);
			return gSlideShow;
		case kReverseButton:
			PointToPreviousSlide();
		case kForwardButton:
			do {
				advance = false;
				gSlideShow = NextSlide(&advance);
				if (gSlideShow) gSlideShow = LoadSlide(&advance);
			} while (advance && gSlideShow);
			return gSlideShow;
	}
	return true;
}

//=====================================================================================
// Boolean HandleAutomaticButton(short button)
//
// Handles button commands during an automatic slide show.
//
// External calls: none
//=====================================================================================

extern Boolean HandleAutomaticButton(short button)
{
	Boolean advance;

	switch (button) {
		case kStopButton:
			return false;
		case kPauseButton:
			gSlideShow = PauseSlideShow(&advance);
			return gSlideShow;
		case kHideButton:
			gSlideShow = HideSlideShow(&advance);
			return gSlideShow;
		case kReverseButton:
			PointToPreviousSlide();
		case kForwardButton:
			do {
				advance = false;
				gSlideShow = NextSlide(&advance);
				if (gSlideShow) gSlideShow = LoadSlide(&advance);
			} while (advance && gSlideShow);
			return gSlideShow;
	}
	return true;
}

//=====================================================================================
// Boolean PauseSlideShow(Boolean *advance)
//
// Oversees the pausing of the slide show.
//
// External calls: UnhideMenuBar, DispatchMainEvent, RehideMenuBar
//=====================================================================================

extern Boolean PauseSlideShow(Boolean *advance)
{
	Boolean result, isNullEvent, isDialogEvent, wasHidden = false;
	short oldBackwardStatus = gButtonStatus[kReverseButton];
	short oldForwardStatus = gButtonStatus[kForwardButton];
	short oldPauseStatus = gButtonStatus[kPauseButton];
	EventRecord theEvent;
	Str255 oldText;

	if (HMIsBalloon()) HMRemoveBalloon();
	*advance = false;
	gSlideShow = kPaused;
	if (!gMenuVisible) UnhideMenuBar();
	if (!gSlideControlsWindow && OpenSlideControls() == noErr) {
		ChangeActive(gSlideControlsWindow);
		wasHidden = true;
	}
	gButtonStatus[kPauseButton] = depressed;
	gButtonStatus[kForwardButton] = disabled;
	gButtonStatus[kReverseButton] = disabled;
	BlockMove(gSlideControlsText, oldText, *gSlideControlsText + 1);
	SetSlideControlsText(gString[strSlidePaused], gNullString, gNullString, gNullString);
	gPendingButton = -1;
	PushPort();
	MySetPort((CGrafPtr)FWFrontWindow());
	while (true) {
		isNullEvent = !WaitNextEvent(everyEvent - highLevelEventMask, &theEvent, kSleepTime, nil);
		isDialogEvent = FWIsDialogEvent(&theEvent);
		if (isNullEvent) {
			DialogPtr theDialog;
			short itemHit;
			theDialog = FWFrontWindow();
			if (theDialog && WindowKind(theDialog) == dialogKind) 
				FWDialogSelect(&theEvent, &theDialog, &itemHit);
			if (HMGetBalloons()) DispatchBalloonHelp();
		} else DispatchMainEvent(&theEvent, isDialogEvent);
		if (gPendingButton != -1 || gDone) {
			short button = gPendingButton;
			gPendingButton = -1;
			if (button == kStopButton || gDone) {
				result = false;
				break;
			} else if (button == kPauseButton) {
				result = true;
				break;
			}
		}
	}
	if (GetSlideOptionsWindow()) CloseSlideOptions();
	if (GetPrefsWindow()) ClosePrefs();
	if (GetHelpWindow()) CloseHelp();
	gSlideShow = kRunning;
	ChangeActive(FWFrontWindow());
	BlockMove(oldText, gSlideControlsText, *oldText + 1);
	if (wasHidden && gSlideControlsWindow) CloseSlideControls();
	gButtonStatus[kPauseButton] = oldPauseStatus;
	gButtonStatus[kForwardButton] = oldForwardStatus;
	gButtonStatus[kReverseButton] = oldBackwardStatus;
	DrawSlideControlsWindow();
	PopPort();
	return result;
}

//=====================================================================================
// extern Boolean HideSlideShow(void)
//
// Hides the slide show by hiding all open windows, and then moving another process to
// the front and pausing the slide show.
//
// External calls: 
//=====================================================================================

extern Boolean HideSlideShow(Boolean *advance)
{
	ProcessSerialNumber currentProcess = { 0, kCurrentProcess };
	ProcessSerialNumber noProcess = { 0, kNoProcess };
	ProcessSerialNumber newFrontProcess;
	ImageHandle theImage, frontImage;
	ProcessInfoRec theInfo;
	Boolean isSame, result;
	WindowPtr theWindow;
	OSErr theErr;
	
	// hide the slide background and all image windows; hide the front image last
	gHidden = true;
	if (theWindow = GetSlideBackWindow()) FWHideWindow(theWindow);
	frontImage = FrontImage();
	for (theImage = gImageRoot; theImage; theImage = (*theImage)->next)
		if ((*theImage)->window && theImage != frontImage) FWHideWindow((*theImage)->window);
	if (frontImage) FWHideWindow((*frontImage)->window);
	ResetColors();
	
	// move the process that launched us into the foreground; if we can't find it,
	// then move some other process to the front
	theInfo.processInfoLength = sizeof(ProcessInfoRec);
	theInfo.processName = nil;
	theInfo.processAppSpec = nil;
	theErr = GetProcessInformation(&currentProcess, &theInfo);
	if (theErr == noErr) {
		theErr = SameProcess(&noProcess, &theInfo.processLauncher, &isSame);
		if (!isSame) newFrontProcess = theInfo.processLauncher;
		else {
			newFrontProcess = noProcess;
			do {
				theErr = GetNextProcess(&newFrontProcess);
				theErr = SameProcess(&currentProcess, &newFrontProcess, &isSame);
			} while (isSame);
		}
		theErr = SetFrontProcess(&newFrontProcess);
	}
	
	// pause the slide show now
	result = PauseSlideShow(advance);
	return result;
}

//=====================================================================================
// void SetSlideControlsText(StringPtr string0, StringPtr string1, StringPtr string2,
//     StringPtr string3)
//
// Sets the text of the slide show controls status.
//
// External calls: none
//=====================================================================================

extern void SetSlideControlsText(StringPtr string0, StringPtr string1, StringPtr string2,
	StringPtr string3)
{
	Str255 newString;
	
	BlockMove(string0, newString, *string0 + 1);
	StuffString(newString, 1, string1);
	StuffString(newString, 2, string2);
	StuffString(newString, 3, string3);
	if (!EqualString(newString, gSlideControlsText, true, true)) {
		BlockMove(newString, gSlideControlsText, *newString + 1);
		if (gSlideControlsWindow) DrawSlideControlsWindow();
	}
}

//=====================================================================================
// void DoSlideControlsHelp(void)
//
// Handles balloon help in the slide controls floating window.
//
// External calls: none
//=====================================================================================

extern void DoSlideControlsHelp(Point globalPt)
{
	Point tip = { 0, 0 }, localPt = globalPt;
	HMMessageRecord theRecord;
	short index = -1, i;
	Rect aRect;
	
	if (HMIsBalloon() || gInBackground) return;
	PushPort();
	MySetPort((CGrafPtr)gSlideControlsWindow);
	GlobalToLocal(&localPt);
	if (localPt.v < gButtonRect[0].top) {
		index = 1;
		aRect = gSlideControlsWindow->portRect;
		aRect.bottom = gButtonRect[0].top;
		tip.h = aRect.right - 5;
		tip.v = aRect.bottom - 5;
	} else
		for (i = 0; i < 4; i++) {
			if (PtInRect(localPt, &gButtonRect[i])) {
				if (gButtonStatus[i] == standard) index = 2 + i;
				else if (gButtonStatus[i] == depressed) index = 6 + i;
				else if (gButtonStatus[i] == disabled) index = 10 + i;
				aRect = gButtonRect[i];
				tip.h = aRect.right - 5;
				tip.v = aRect.bottom - 5;
			}
		}
	if (index &&
		HMExtractHelpMsg(kHMRectListResType, rSlideControlsWindow, index, kHMEnabledItem, 
						 &theRecord) == noErr) {
		LocalToGlobal(&tip);
		GlobalRect(&aRect, gSlideControlsWindow);
		HMShowBalloon(&theRecord, tip, &aRect, nil, 0, 0, kHMRegularWindow);
	}
	PopPort();
}
