/*********************************************************/
/* 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 "SlideShow.h"

//=====================================================================================
// Constants local to this module
//=====================================================================================

enum {
	kEndSlideShow = -1,
	kAdvanceSlide = -2,
	kReloadSlide = -3,
	kReverseSlide = -4
};

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

static Boolean gSlideWarned = false, gStatsHidden, gColorsHidden, gCommentsHidden;
static ImageHandle gSlideNextImage = nil;
static WindowPtr gSlideBackWindow = nil;
static SlideHandle gSlideNext = nil;
static SlideHandle gSlideRoot = nil;
static long gSlideTimer = 0L;
static long gSlideCount = 0L;

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

static void OpenSlideBackWindow(void);
static SlideHandle NewSlide(Boolean memory, FSSpec *spec);
static void DisposeSlide(SlideHandle theSlide);
static SlideHandle LastSlide(void);
static SlideHandle PreviousSlide(SlideHandle theSlide);
static SlideHandle DisplayedSlide(void);
static void ShuffleSlides(void);
static ImageHandle LoadSlideFromMem(void);
static ImageHandle LoadSlideFromDisk(void);
static ImageHandle HandleLoadError(OSErr theErr, StringPtr fileName);
static Boolean WarnSlideShowMemory(void);

//=====================================================================================
// void OpenSlideBackWindow(void)
//
// Creates and displays the background window for the slide show.
//
// External calls: GetDeepMonitor, PushPort, MySetPort, FullScreen, PopPort
//=====================================================================================

static void OpenSlideBackWindow(void)
{
	MonitorHandle deepMonitor;
	PaletteHandle thePalette;

	deepMonitor = GetDeepMonitor(true);
	thePalette = SystemPalette((*deepMonitor)->depth <= 8 ? (*deepMonitor)->depth : 8);
	PushPort();
	MySetPort(nil);
	FullScreen(gMainMonitor);
	gSlideBackWindow = FWGetNewCWindow(rFullImageWindow, (Ptr)gSlideBackWindow, (WindowPtr)-1);
	SizeWindow(gSlideBackWindow, Width(&(*GetGrayRgn())->rgnBBox), 
				Height(&(*GetGrayRgn())->rgnBBox), true);
	FWMoveWindow(gSlideBackWindow, (*GetGrayRgn())->rgnBBox.left, (*GetGrayRgn())->rgnBBox.top, true);
	SetPalette(gSlideBackWindow, thePalette, true);
	ActivatePalette(gSlideBackWindow);
	MySetPort((CGrafPtr)gSlideBackWindow);
	SetOrigin((*GetGrayRgn())->rgnBBox.left, (*GetGrayRgn())->rgnBBox.top);
	FWShowWindow(gSlideBackWindow);
	DrawSlideBackWindow();
	PopPort();
}

//=====================================================================================
// WindowPtr GetSlideBackWindow(void)
//
// Returns a pointer to the slide backdrop window.
//
// External calls: GetDeepMonitor, PushPort, MySetPort, FullScreen, PopPort
//=====================================================================================

extern WindowPtr GetSlideBackWindow(void)
{
	return gSlideBackWindow;
}

//=====================================================================================
// void CloseSlideBackWindow(void)
//
// Closes the slide backdrop window.
//
// External calls: none
//=====================================================================================

extern void CloseSlideBackWindow(void)
{
	if (gSlideBackWindow) {
		PushPort();
		MySetPort(nil);
		FWDisposeWindow(gSlideBackWindow);
		DeallocateWindow(gSlideBackWindow);
		gSlideBackWindow = nil;
		if (!AnotherFull(nil)) NormalScreen(gMainMonitor);
		PopPort();
	}
}

//=====================================================================================
// void DrawSlideBackWindow(void)
//
// Draws a JPEGView icon in the center of each screen.
//
// External calls: none
//=====================================================================================

extern void DrawSlideBackWindow(void)
{
	MonitorHandle theMonitor;
	Rect theRect;
	
	for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next) {
		theRect = (*theMonitor)->rect;
		theRect.left += (Width(&theRect) - 32) / 2;
		theRect.top += (Height(&theRect) - 32) / 2;
		theRect.right = theRect.left + 32;
		theRect.bottom = theRect.top + 32;
		PlotIconSuite(&theRect, atAbsoluteCenter, ttNone, gIconSuite);
	}
}

//=====================================================================================
// void ResetSlideTimer(void)
//
// Resets the slide timer, maintained by HandleSlideNullEvent.
//
// External calls: none
//=====================================================================================

extern void ResetSlideTimer(void)
{
	gSlideTimer = TickCount();
}

//=====================================================================================
// void HandleSlideNullEvent(void)
//
// Handles null events while a slide show is active.
//
// External calls: none
//=====================================================================================

extern void HandleSlideNullEvent(void)
{
	ulong newCount = TickCount();
	EvQElPtr theEvent;
	Boolean advance;

	// if we're in screen saver mode, make sure the mouse didn't move
	if (gScreenSaver) {
		Point newMouse = GlobalMouse();
		if (abs(newMouse.h - gScreenSaverPoint.h) > 2 ||
			abs(newMouse.v - gScreenSaverPoint.v) > 2) {
			SendQuitApplication();
			return;
		}
	}

	// if we're automatic, enable the play button here
	if (!(*gSlideOptions)->userControl && GetSlideControlButton(kForwardButton) == disabled) {
		SetSlideControlButton(kForwardButton, standard);
		DrawSlideControlsWindow();
	}
	
	// if the slide show is paused, don't do this bit
	if (gSlideShow == kPaused) return;

	if ((*gSlideOptions)->userControl) 
		SetSlideControlsText(gString[strSlideReady], gNullString, gNullString, gNullString);

	// if the timer isn't up yet, and we're in the foreground, post a fake
	// key down event to prevent screen savers from kicking in
	else if (((newCount - gSlideTimer) / kClockFreq) <= (*gSlideOptions)->delay &&
		!gInBackground) {
		Str32 theCount;
		PPostEvent(keyDown, kCreator, &theEvent);
		NumToString((*gSlideOptions)->delay - (newCount - gSlideTimer) / kClockFreq, theCount);
		SetSlideControlsText(gString[strSlideDelaying], theCount, gNullString, gNullString);
		return;
	}

	// if we're under user control, or if we're paused
	if ((*gSlideOptions)->userControl || gSlideShow == kPaused) return;
	
	// otherwise, reset the slide timer, draw this slide and then start loading the next
	gSlideTimer = newCount;
	do {
		advance = false;
		gSlideShow = NextSlide(&advance);
		if (gSlideShow) gSlideShow = LoadSlide(&advance);
	} while (advance && gSlideShow);
}

//=====================================================================================
// SlideHandle NewSlide(Boolean memory, FSSpec *spec)
//
// Creates a new slide handle and appends it to the beginning of the linked list.
//
// External calls: none
//=====================================================================================

static SlideHandle NewSlide(Boolean memory, FSSpec *spec) 
{
	long theSize = sizeof(SlideItem);
	SlideHandle theSlide;

	if (!IsMemAvailable(sizeof(SlideItem))) return nil;
	if (gSlideRoot) {
		for (theSlide = gSlideRoot; (*theSlide)->next; theSlide = (*theSlide)->next);
		theSlide = (*theSlide)->next = (SlideHandle)NewHandle(sizeof(SlideItem));
	} else
		theSlide = gSlideRoot = (SlideHandle)NewHandle(sizeof(SlideItem));
	if (theSlide) {
		(*theSlide)->next = nil;
		(*theSlide)->mem = memory;
		(*theSlide)->spec = *spec;
		gSlideCount++;
	}
	return theSlide;
}

//=====================================================================================
// void DisposeSlide(SlideHandle theSlide)
//
// Removes a slide handle from the linked list.
//
// External calls: none
//=====================================================================================

static void DisposeSlide(SlideHandle theSlide) 
{
	Boolean found = false;
	SlideHandle tempSlide;
	
	if (theSlide != gSlideRoot) {
		for (tempSlide = gSlideRoot; 
		     tempSlide && ((*tempSlide)->next != theSlide); 
		     tempSlide = (*tempSlide)->next);
		if (tempSlide) (*tempSlide)->next = (*theSlide)->next;
	} else
		gSlideRoot = (*gSlideRoot)->next;
	DisposeHandle((Handle)theSlide);
	gSlideCount--;
}

//=====================================================================================
// SlideHandle PreviousSlide(SlideHandle theSlide)
//
// Returns the slide in the list previous to the given slide.
//
// External calls: none
//=====================================================================================

static SlideHandle LastSlide(void)
{
	SlideHandle theSlide;

	if (!gSlideRoot) return nil;
	for (theSlide = gSlideRoot; (*theSlide)->next; theSlide = (*theSlide)->next);
	return theSlide;
}

static SlideHandle PreviousSlide(SlideHandle theSlide)
{
	SlideHandle slide;
	
	if (!theSlide || theSlide == gSlideRoot || theSlide == (SlideHandle)kEndSlideShow)
		return LastSlide();
	for (slide = gSlideRoot; slide; slide = (*slide)->next)
		if ((*slide)->next == theSlide) return slide;
	return nil;
}

static SlideHandle DisplayedSlide(void)
{
	SlideHandle theSlide = PreviousSlide(gSlideNext);
	ImageHandle theImage = FrontImage();
	short imageIndex;
	
	if (!theImage) return nil;
	imageIndex = GetDocumentIndex((*theImage)->window);
	do {
		if ((*theSlide)->mem) {
			if ((*theSlide)->spec.vRefNum == imageIndex) return theSlide;
		} else 
			if (SameFile(&(*theImage)->file, &(*theSlide)->spec)) return theSlide;
		theSlide = PreviousSlide(theSlide);
	} while (theSlide != gSlideNext);
	return nil;
}

//=====================================================================================
// void PointToPreviousSlide(void)
//
// Backs up the gSlideNext pointer by the specified count.
//
// External calls: none
//=====================================================================================

extern void PointToPreviousSlide(void)
{
	SlideHandle currentSlide = DisplayedSlide();
	ImageHandle frontImage = FrontImage();

	if (currentSlide) {
		if ((*currentSlide)->next == gSlideNext || (*(*currentSlide)->next)->next == gSlideNext) 
			gSlideNext = PreviousSlide(currentSlide);
		else if (gSlideNext != gSlideRoot) gSlideNext = PreviousSlide(gSlideNext);
		else SysBeep(1);
	} else SysBeep(1);
	if (gSlideNextImage && gSlideNextImage != frontImage) 
		DoCloseWindow((*gSlideNextImage)->window, false);
	gSlideNextImage = nil;
}

//=====================================================================================
// void InitSlideShow(void)
//
// Initializes a new slide show; does not properly set up for a resumed slide show.
//
// External calls: none
//=====================================================================================

extern void InitSlideShow(void)
{
	ImageHandle theImage;
	WindowPtr theWindow;
	Boolean advance;
	FSSpec theSpec;
	
	gSlideShow = false;
	
	// if we're doing a memory-based slide show, generate the slide entries here
	if (!gSlideRoot && (*gSlideOptions)->fromMemory)
		for (theImage = gImageRoot; theImage; theImage = (*theImage)->next) {
			theSpec.vRefNum = GetDocumentIndex((*theImage)->window);
			NewSlide(true, &theSpec);
		}

	// if for some reason we have no slides, signal the error and return
	if (!gSlideCount) {
		MinorError(errNoImagesFound);
		MyInvalMenuBar();
		return;
	}

	// if we are to hide the floating windows, do it now
	gStatsHidden = gCommentsHidden = gColorsHidden = false;
	if ((*gSlideOptions)->hideWindoids) {
		if (theWindow = GetStatWindow()) gStatsHidden = true, CloseStats();
		if (theWindow = GetCommentsWindow()) gCommentsHidden = true, CloseComments();
		if (theWindow = GetColorsWindow()) gColorsHidden = true, CloseColors();
	}
	
	// if the slide background window has not been opened, we allocate and open it now
	if (!gSlideBackWindow && (gSlideBackWindow = AllocateWindow())) OpenSlideBackWindow();
	
	// set up the slide control buttons ahead of time here
	SetSlideControlButton(kReverseButton, disabled);
	SetSlideControlButton(kStopButton, standard);
	SetSlideControlButton(kPauseButton, standard);
	SetSlideControlButton(kForwardButton, standard);
	
	// if this is user-controlled, pop up the controls window now
	if (!(*gSlideOptions)->hideControls && OpenSlideControls() == noErr) 
		ChangeActive(GetSlideControlsWindow());
	
	// if this is a disk-based slide show, close all other open images
	if (!(*gSlideOptions)->fromMemory) 
		while (gImageRoot) 
			DoCloseWindow((*LastImage())->window, false);

	// reset all the appropriate variables, then load, draw, and load the second slide
	if ((*gSlideOptions)->random) ShuffleSlides();
	gSlideNextImage = nil;
	gSlideWarned = false;
	gSlideNext = gSlideRoot;
	gSlideShow = true;
	advance = false;
	if (gSlideShow = LoadSlide(&advance)) {
		do {
			advance = false;
			gSlideShow = NextSlide(&advance);
			if (gSlideShow) gSlideShow = LoadSlide(&advance);
		} while (advance && gSlideShow);
	}
	
	// if we got shoved in the background, start in the paused state
	if (gInBackground && !gScreenSaver) {
		Boolean dummy;
		gSlideShow = PauseSlideShow(&dummy);
	}
}

//=====================================================================================
// void ReinitSlideShow(PrefsData *oldPrefs)
//
// Initializes a new slide show; does not properly set up for a resumed slide show.
//
// External calls: none
//=====================================================================================

extern void ReinitSlideShow(SlideOptionsPtr oldOptions)
{
	WindowPtr theWindow;
	
	if (oldOptions->hideWindoids != (*gSlideOptions)->hideWindoids) {
		if ((*gSlideOptions)->hideWindoids) {
			if (theWindow = GetStatWindow()) gStatsHidden = true, CloseStats();
			if (theWindow = GetCommentsWindow()) gCommentsHidden = true, CloseComments();
			if (theWindow = GetColorsWindow()) gColorsHidden = true, CloseColors();
		} else {
			if (gStatsHidden && OpenStats() == noErr) ChangeActive(GetStatWindow());
			if (gCommentsHidden && OpenComments() == noErr) ChangeActive(GetCommentsWindow());
			if (gColorsHidden && OpenColors() == noErr) ChangeActive(GetColorsWindow());
		}
	}
	if (oldOptions->hideControls != (*gSlideOptions)->hideControls) {
		if (!(*gSlideOptions)->hideControls && OpenSlideControls() == noErr) 
			ChangeActive(GetSlideControlsWindow());
		else if ((*gSlideOptions)->hideControls && GetSlideControlsWindow())
			CloseSlideControls();
	}
	
	gPendingButton = kForwardButton;
}

//=====================================================================================
// void AddSlideDir(FSSpec *spec)
//
// Adds a given file or directory to the list of slides to be displayed, recognizing
// the request for recursive directory scans.
//
// External calls: none
//=====================================================================================

extern OSErr AddSlideDir(FSSpec *spec, Boolean recursive, ICMProgressProcRecordPtr theProg,
		LoopCheckPtr loopList)
{
	FSSpec dirSpec = *spec, theSpec;
	Boolean isFolder, wasAliased;
	OSErr theErr = noErr;
	LoopCheck thisDir;
	CInfoPBRec block;
	Str255 fileName;
	long dir;
	
	if (ResolveAliasFile(&dirSpec, true, &isFolder, &wasAliased) != noErr) return noErr;
	if (isFolder) {
		theErr = GetDirectoryID(&dirSpec, &dir);
		if (theErr != noErr) return noErr;
		thisDir.prev = loopList;
		thisDir.vRefNum = dirSpec.vRefNum;
		thisDir.dirID = dir;
		while (loopList) {
			if (loopList->vRefNum == dirSpec.vRefNum && loopList->dirID == dir)
				return noErr;
			loopList = loopList->prev;
		}
		UpdateParamText(gString[strScanningDirectory], gNullString, gNullString, spec->name);
	} else {
		if (!NewSlide(false, &dirSpec)) return memFullErr;
		return noErr;
	}
	block.hFileInfo.ioNamePtr = fileName;
	block.hFileInfo.ioFDirIndex = 1;
	block.hFileInfo.ioVRefNum = dirSpec.vRefNum;
	block.hFileInfo.ioDirID = dir;
	if (theProg && (OSErr)CallICMProgressProc(theProg->progressProc, 
				codecProgressUpdatePercent, kIndefProgress,
				theProg->progressRefCon) == codecAbortErr)
		return codecAbortErr;
	while (PBGetCatInfo(&block, false) == noErr && theErr == noErr) {
		KeepSpinning();
		FSMakeFSSpec(dirSpec.vRefNum, dir, fileName, &theSpec);
		if (ResolveAliasFile(&theSpec, true, &isFolder, &wasAliased) == noErr) {
			if (isFolder) {
				if (recursive) {
					theErr = AddSlideDir(&theSpec, (*gSlideOptions)->recursive, theProg, &thisDir);
					if (theErr != noErr) break;
				}
			} else if ((*gSlideOptions)->allFiles) {
				short theFile, fileType;
				if (FSpOpenDF(&theSpec, fsRdPerm, &theFile) == noErr) {
					fileType = IDImage(theFile, &theSpec);
					FSClose(theFile);
					if (fileType != kInvalidType)
						if (!NewSlide(false, &theSpec)) {
							theErr = memFullErr;
							break;
						}
				}
			} else if (ValidType(block.hFileInfo.ioFlFndrInfo.fdType))
				if (!NewSlide(false, &theSpec)) {
					theErr = memFullErr;
					break;
				}
			if (!(block.hFileInfo.ioFDirIndex & 0x0f))
				if (theProg && (OSErr)CallICMProgressProc(theProg->progressProc,
							codecProgressUpdatePercent, kIndefProgress,
							theProg->progressRefCon) == codecAbortErr)
					return codecAbortErr;
		}
		block.hFileInfo.ioFDirIndex++;
		block.hFileInfo.ioDirID = dir;
	}
	return theErr;
}

//=====================================================================================
// void CleanUpSlideShow(void)
//
// Cleans up for when the slide show is completely finished.
//
// External calls: none
//=====================================================================================

extern void CleanUpSlideShow(void) 
{
	ImageHandle theImage, nextImage;
	short choice = 0;
	
	while (gSlideRoot) DisposeSlide(gSlideRoot);
	if (GetSlideControlsWindow()) CloseSlideControls();
	if (GetSlideBackWindow()) CloseSlideBackWindow();
	gSlideShow = false;
	if (!(*gSlideOptions)->fromMemory)
		for (theImage = gImageRoot; theImage; theImage = nextImage) {
			nextImage = (*theImage)->next;
			if (theImage != FrontImage()) 
				DoCloseWindow((*theImage)->window, gThePrefs.restoreColors);
		}
	if ((*gSlideOptions)->hideWindoids) {
		if (gStatsHidden && OpenStats() == noErr) ChangeActive(GetStatWindow());
		if (gCommentsHidden && OpenComments() == noErr) ChangeActive(GetCommentsWindow());
		if (gColorsHidden && OpenColors() == noErr) ChangeActive(GetColorsWindow());
	}
	for (theImage = gImageRoot; theImage; theImage = (*theImage)->next)
		if (GWTempMem(theImage)) KillGWorld(theImage);
	EnableMenus();
	if (theImage = FrontImage()) (*theImage)->flags &= ~ifAborted;
}

//=====================================================================================
// void ShuffleSlides(void)
//
// Shuffles the linked list of slides in a pseudo-random order.
//
// External calls: none
//=====================================================================================

static void ShuffleSlides(void) 
{
	SlideHandle theSlide, tempSlide;
	long i, j, k;
	
	for (i = 0; i < (gSlideCount * 4); i++) {
		j = ((ushort)Random()) % gSlideCount;
		if (j > 0) {
			for (theSlide = gSlideRoot, k = 1; (k < j) && theSlide; k++) 
				theSlide = (*theSlide)->next;
			if (theSlide) {
				tempSlide = (*theSlide)->next;
				(*theSlide)->next = (*tempSlide)->next;
				(*tempSlide)->next = gSlideRoot;
				gSlideRoot = tempSlide;
			}
		}
	}
}

//=====================================================================================
// void LoadSlide(void)
//
// Loads the next slide, skipping over any that cause errors.
//
// External calls: none
//=====================================================================================

extern Boolean LoadSlide(Boolean *advance) 
{
	SlideHandle theSlide;

	// loop until we get a good slide
	while (!gSlideNextImage) {
		if (!(*gSlideOptions)->userControl) ObscureCursor();
		
		// enable the reverse button if we're not loading the first two slides
		if (gSlideNext != gSlideRoot &&
			(theSlide = DisplayedSlide()) && theSlide != gSlideRoot)
			SetSlideControlButton(kReverseButton, standard);
		else SetSlideControlButton(kReverseButton, disabled);
		
		// load the next slide, either from memory, or from disk
		if ((*gSlideNext)->mem) gSlideNextImage = LoadSlideFromMem();
		else gSlideNextImage = LoadSlideFromDisk();

		// if we've ended the show, clean up and return false 
		if (gSlideNextImage == (ImageHandle)kEndSlideShow) {
			CleanUpSlideShow();
			return false;
		
		// if we're supposed to advance forward one, set the flag
		} else if (gSlideNextImage == (ImageHandle)kAdvanceSlide) {
			gSlideNextImage = nil;
			*advance = true;
		}

		// if we're supposed to reload the current slide, skip this next part
		if (gSlideNextImage == (ImageHandle)kReloadSlide) {
			gSlideNextImage = nil;
			*advance = true;

		// if we're backing up, back gSlideNext up one and reload, skipping next
		} else if (gSlideNextImage == (ImageHandle)kReverseSlide) {
			gSlideNextImage = nil;
			PointToPreviousSlide();
			*advance = true;
			
		// save the handle to the current slide, and find the next one, restarting
		// and reshuffling as appropriate
		} else {
			theSlide = gSlideNext;
			if (!(gSlideNext = (*gSlideNext)->next)) {
				if (!(*gSlideOptions)->repeat) {
					if (gSlideNextImage) {
						gSlideNext = (SlideHandle)kEndSlideShow;
						return true;
					} else {
						EndSlideShow();
						CleanUpSlideShow();
						return false;
					}
				} else {
					if ((*gSlideOptions)->random) ShuffleSlides();
					gSlideNext = gSlideRoot;
				}
			}
	
			// if we got an unprocessed forward arrow, set the advance flag
			if (gPendingButton == kForwardButton) {
				*advance = true;
				gPendingButton = -1;
			}

			// if there was an error in setting up the last slide, kill it from the list
			if (!gSlideNextImage) {
				if (gSlideNext == theSlide) {
					gSlideNext = (*gSlideNext)->next;
					if (!gSlideNext && gSlideRoot == theSlide) {
						CleanUpSlideShow();
						EndSlideShow();
						return false;
					} else if (!gSlideNext) gSlideNext = gSlideRoot;
				}
				DisposeSlide(theSlide);
			}
		}
	}
	return true;
}

//=====================================================================================
// ImageHandle LoadSlideFromMem(void)
//
// Sets up the next slide for an in-memory slide show.
//
// External calls: FindImage, IndexedDocument, DoSetWindowFull
//=====================================================================================

static ImageHandle LoadSlideFromMem(void)
{
	ImageHandle theImage;
	OSErr theErr = noErr;
	
	theImage = FindImage(IndexedDocument((*gSlideNext)->spec.vRefNum));
	if (!Full(theImage)) theErr = DoSetWindowFull((*theImage)->window, true);
	if (theErr == noErr) return theImage;
	if (!(*gSlideOptions)->noErrors) HandleAEError(theErr, errNoDrawMemory, (*theImage)->file.name);
	return nil;
}

//=====================================================================================
// ImageHandle LoadSlideFromDisk(SlideHandle theSlide)
//
// Sets up the next slide for a disk-based slide show; returns nil if the image failed
// to load; returns (ImageHandle)-1 if the slide show should end.
//
// External calls: GetOpenDefaults, DoOpenDocument, StopSpinning, HandleAEError
//=====================================================================================

static ImageHandle LoadSlideFromDisk(void)
{
	DescType openScreen, openFull, openPalette, drawQuality;
	FSSpec theSpec = (*gSlideNext)->spec;
	ImageHandle theImage;
	OSErr theErr;

	do {
		SetSlideControlsText(gString[strSlideLoading], theSpec.name, gNullString, gNullString);
		GetOpenDefaults(&openScreen, &openFull, &openPalette, &drawQuality);
		StartSpinning();
		theErr = DoOpenDocument(&theSpec, true, openScreen, kAEOFAlways, 
				 gThePrefs.expandSmall, true, openPalette, drawQuality, 
				 (*gSlideOptions)->offscreen, (*gSlideOptions)->autoComments, 
				 gThePrefs.autoFix, gThePrefs.changeCreator);
		StopSpinning(&qd.arrow);
		if (theErr == noErr) {
			for (theImage = gImageRoot, gSlideNextImage = nil; theImage; 
				 theImage = (*theImage)->next)
				if (SameFile(&(*theImage)->file, &theSpec)) return theImage;
		} else theImage = HandleLoadError(theErr, theSpec.name);
	} while (theImage == (ImageHandle)kReloadSlide);
	return theImage;
}
	
//=====================================================================================
// ImageHandle HandleLoadError(OSErr theErr)
//
// Handles errors that occur during a load; abort errors are assumed to be the result
// of the user pressing a control button.
//
// External calls: GetOpenDefaults, DoOpenDocument, StopSpinning, HandleAEError
//=====================================================================================

static ImageHandle HandleLoadError(OSErr theErr, StringPtr fileName)
{
	Boolean advance;
	
	switch (theErr) {
		case codecAbortErr:
			if (gDone) return (ImageHandle)kEndSlideShow;
			else if (gPendingButton != -1) {
				short button = gPendingButton;
				gPendingButton = -1;
				if (button == kPauseButton || button == kHideButton) {
					gSlideShow = (button == kPauseButton) ? PauseSlideShow(&advance) :
															HideSlideShow(&advance);
					if (!gSlideShow) return (ImageHandle)kEndSlideShow;
					else return (ImageHandle)kReloadSlide;
				} else if (button == kForwardButton)// && (*gSlideOptions)->userControl)
					return (ImageHandle)kAdvanceSlide;
				else if (button == kStopButton) return (ImageHandle)kEndSlideShow;
				else if (button == kReverseButton) return (ImageHandle)kReverseSlide;
			}
			break;
		case memFullErr:
			if (WarnSlideShowMemory()) return (ImageHandle)kEndSlideShow;
			break;
		default:
			if (!(*gSlideOptions)->noErrors) HandleAEError(theErr, errNoLoadMemory, fileName);
			break;
	}
	return nil;
}

//=====================================================================================
// Boolean WarnSlideShowMemory(void)
//
// Display the warning about not being able to see all slides due to low memory.
//
// External calls: PushPort, MySetPort, StopSpinning, CenterAlert, PopPort
//=====================================================================================

static Boolean WarnSlideShowMemory(void)
{
	short choice;
	
	ShowCursor();
	if (!gSlideWarned && !(*gSlideOptions)->noErrors) {
		gSlideWarned = true;
		PushPort();
		MySetPort(nil);
		StopSpinning(&qd.arrow);
		choice = FWCautionAlert(CenterAlert(rSlideMemAlert), gGenericFilter);
		PopPort();
		if (choice == 3 || choice == kDialogCancel) return true;
	}
	return false;
}

//=====================================================================================
// Boolean EndSlideShow(void)
//
// Display the "Slide show has ended" alert.
//
// External calls: PushPort, MySetPort, StopSpinning, CenterAlert, PopPort
//=====================================================================================

extern Boolean EndSlideShow(void)
{
	ShowCursor();
	if (!gSlideRoot) {
		MinorError(errNoImagesFound);
		MyInvalMenuBar();
	} else {
		PushPort();
		MySetPort(nil);
		StopSpinning(&qd.arrow);
		FWNoteAlert(CenterAlert(rEndSlidesAlert), gGenericFilter);
		PopPort();
	}
	return false;
}

//=====================================================================================
// Boolean NextSlide(void)
//
// Switches to the next slide and forces it to be drawn.
//
// External calls: ChangeActive, DoCloseWindow, UpdateImage
//=====================================================================================

extern Boolean NextSlide(Boolean *advance) 
{
	ImageHandle loadErrorResult;
	OSErr theErr;
	
	// if no slide is ready tell the caller to advance
	if (!gSlideNextImage) *advance = true;
	
	// otherwise, process & draw the next slide
	else {

		// obscure the cursor unless it's under user control
		if (!(*gSlideOptions)->userControl && !GetSlideControlsWindow()) ObscureCursor();
	
		// bring the next window to the front
		ChangeActive((*gSlideNextImage)->window);
	
		// if we're doing a disk-based slide show, close all other images
		if (!(*gSlideRoot)->mem) {
			ImageHandle oldImage, nextImage;
			for (oldImage = gImageRoot; oldImage; oldImage = nextImage) {
				nextImage = (*oldImage)->next;
				if (oldImage != gSlideNextImage) 
					DoCloseWindow((*oldImage)->window, gThePrefs.restoreColors);
			}
		}
		
		// handle the update immediately, so we can begin loading the next guy; we
		// do this in a loop so that if we get a pause/resume sequence, we can redraw
		// the image
		do {
			SetSlideControlsText(gString[strSlideDrawing], (*gSlideNextImage)->file.name, 
								 gNullString, gNullString);
			theErr = UpdateImage(gSlideNextImage);
			ResetSlideTimer();
	
			// clear out the next slide pointer and handle any error conditions
			if (theErr != noErr) {
				loadErrorResult = HandleLoadError(theErr, nil);
				if (loadErrorResult == (ImageHandle)kAdvanceSlide) *advance = true;
				else if (loadErrorResult == (ImageHandle)kEndSlideShow) {
					CleanUpSlideShow();
					return false;
				} else if (loadErrorResult == (ImageHandle)kReverseSlide) {
					PointToPreviousSlide();
					*advance = true;
				}
			} else loadErrorResult = nil;
			if (loadErrorResult != (ImageHandle)kReloadSlide) gSlideNextImage = nil;
		} while (loadErrorResult == (ImageHandle)kReloadSlide);
	}
	
	// if we hit the end of the slide show, end it now
	if (!(*gSlideOptions)->repeat && DisplayedSlide() == LastSlide()) {
		SetSlideControlButton(kForwardButton, disabled);
		DrawSlideControlsWindow();
		EndSlideShow();
		CleanUpSlideShow();
		return false;
	}

	return true;
}
