/*********************************************************/
/* 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

static void HandleUpdateError(ImageHandle theImage, OSErr theErr);
static void DoDragSelection(ImageHandle theImage, EventRecord *theEvent);
static pascal OSErr SendItemData(FlavorType theType, void *dragSendRefCon,
			ItemReference theItem, DragReference theDrag);

extern void DeferImageUpdate(ImageHandle theImage)
{
	Pattern whitePat = { 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00 };
	Pattern blackPat = { 0xff, 0xfd, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff };
	RgnHandle theRgn;
	Rect theRect;
	
	PushPort();
	MySetPort((CGrafPtr)(*theImage)->window);
	InvalRgn((*theImage)->update);
	BeginUpdate((*theImage)->window);
	PenNormal();
	RGBForeColor(&gBlack);
	RGBBackColor(&gWhite);
	theRect = (*theImage)->wrect;
	if (theRgn = NewRgn()) {
		RectRgn(theRgn, &theRect);
		if (gDrawing && theImage == gDrawing) 
			DiffRgn(theRgn, (*theImage)->update, theRgn);
		SetClip(theRgn);
		DisposeRgn(theRgn);
	} else ClipRect(&theRect);
	HideAnts((*theImage)->ants);
	FillRgn((*theImage)->window->visRgn, NeedsWhiteBG(theImage) ? &whitePat : &blackPat);
	ShowAnts((*theImage)->ants);
	ClipRect(&(*theImage)->window->portRect);
	UnionRgn((*theImage)->update, (*theImage)->window->visRgn, (*theImage)->update);
	EndUpdate((*theImage)->window);
	(*theImage)->flags &= ~ifAborted;
	PopPort();
}

extern OSErr UpdateImage(ImageHandle theImage) 
{
	Point thePoint = { 0, 0 };
	OSErr theErr = noErr;
	RgnHandle oldVisRgn;

	StartSpinning();
	PushPort();
	MySetPort((CGrafPtr)(*theImage)->window);
	if (oldVisRgn = NewRgn()) CopyRgn((*theImage)->window->visRgn, oldVisRgn);
	ClipRect(&(*theImage)->window->portRect);
	InvalRgn((*theImage)->update);
	BeginUpdate((*theImage)->window);
	SetClip((*theImage)->window->visRgn);
	EndUpdate((*theImage)->window);
	CopyRgn((*theImage)->window->clipRgn, (*theImage)->window->visRgn);
	GetMinMaxMonitor(theImage);
	gIntError = 0;
	if (!EmptyRgn(oldVisRgn)) {
		HideAnts((*theImage)->ants);
		theErr = DrawImage(theImage, bfOnscreen, nil);
		ShowAnts((*theImage)->ants);
	}
	CalcVis((WindowPeek)(*theImage)->window);
	if (IsAboutBox(theImage)) {
		Rect theRect = (*theImage)->wrect;
		FontInfo theFontInfo;
		Str63 theText;
		TextFont(geneva);
		TextSize(9);
		TextMode(srcOr);
		ClipRect(&theRect);
		GetFontInfo(&theFontInfo);
#if defined(powerc) || defined(__powerc)
		BlockMove(gString[strVersionPPC], theText, *gString[strVersionPPC] + 1);
#else
		BlockMove(gString[strVersion68k], theText, *gString[strVersion68k] + 1);
#endif
		StuffNumber(theText, 1, (kVersion >> 8) & 0xf);
		StuffNumber(theText, 2, (kVersion >> 4) & 0xf);
		MoveTo((*theImage)->wrect.right - StringWidth(theText) - 3, 
				(*theImage)->wrect.top + theFontInfo.ascent + theFontInfo.leading + 3);
		DrawString(theText);
	}
	if (oldVisRgn && EqualRgn(oldVisRgn, (*theImage)->window->visRgn) && !Aborted(theImage)) {
		SetEmptyRgn((*theImage)->update);
		DisposeRgn(oldVisRgn);
	}
	if (theErr) HandleUpdateError(theImage, theErr);
	SetStatistics();
	PopPort();
	StopSpinning(&qd.arrow);
	return theErr;
}

static void HandleUpdateError(ImageHandle theImage, OSErr theErr)
{
	if ((theErr == codecBadDataErr) || (gIntError == errCorruptImage)) {
		if (Corrupt(theImage)) return;
		(*theImage)->flags |= ifCorrupt;
	}
	HandleAEError(theErr, errNoDrawMemory, theImage ? FileName(theImage) : nil);
	if (theErr == memFullErr) DoCloseWindow((*theImage)->window, gThePrefs.restoreColors);
}

extern void HandleImageActivate(ImageHandle theImage, Boolean nowActive)
{
	if (theImage) {
		if (nowActive) ActivateAnts((*theImage)->ants);
		else DeactivateAnts((*theImage)->ants);
	}
}

extern void HandleImageClick(ImageHandle theImage, EventRecord *theEvent)
{
	Boolean inFront = (!gInBackground && (*theImage)->window == FWFrontWindow());
	Rect theRect = { 0, 0, 0, 0 }, selRect;
	Point where = theEvent->where;

	GetAntsSelection((*theImage)->ants, &selRect);
	PushPort();
	MySetPort((CGrafPtr)(*theImage)->window);
	
	// hidden feature: cmd-click forces redraw of window
	if (theEvent->modifiers & cmdKey) {
		KillGWorld(theImage);
		theRect = (*theImage)->wrect;
		InvalRect(&theRect);
		inFront = true;
		goto done;
	}

	// check to see if we're even inside the drawing area of the window
	GlobalToLocal(&where);
	if (!gSlideShow && !IsAboutBox(theImage) && PtInRect(where, &(*theImage)->wrect)) {

		// if we're in the size box area, resize the window
		if (inFront && (((*theImage)->wrect.right - where.h) < 16) && (((*theImage)->wrect.bottom - where.v) < 16)) {
			Boolean modified = (theEvent->modifiers & (optionKey + cmdKey + shiftKey)) != 0;
			if (TrackGrowWindow(where, theImage, !modified, &theRect)) {
				SendSetDrawBounds((*theImage)->window, &theRect);
				goto done;
			}
		} else if (!Full(theImage) || (*theImage)->dmon != gMainMonitor || !gMenuVisible) {
		
			// if we're selected, either drag or cancel the selection
			if (AntsHaveSelection((*theImage)->ants)) {
				if (PtInRect(where, &selRect) && !EqualRect(&selRect, &(*theImage)->wrect)) {
					if ((inFront && TrackDragRect(where, theImage) && gDragMgrPresent) ||
								(!inFront && gDragMgrPresent && WaitMouseMoved(theEvent->where))) {
						DoDragSelection(theImage, theEvent);
						inFront = true;
					}
					GetAntsSelection((*theImage)->ants, &theRect);
					SendSetSelection(theImage, &theRect);
					goto done;
				}
			}
			
			// if we fall through here, we need to track a new selection
			if (inFront) {
				SendSetSelection(theImage, &theRect);
				TrackAntsSelection((*theImage)->ants, where);
				GetAntsSelection((*theImage)->ants, &theRect);
				SendSetSelection(theImage, &theRect);
				if (AntsHaveSelection((*theImage)->ants)) goto done;
			}
		
		// special case: if full screen image overlapped by menubar, treat as window in background
		} else if (AntsHaveSelection((*theImage)->ants) && PtInRect(where, &selRect) && gDragMgrPresent && WaitMouseMoved(theEvent->where)) {
			DoDragSelection(theImage, theEvent);
			inFront = true;
		}
	}

	// if we fall all the way through here, we need to flip the menu bar
	AdjustMenus();
	if (Full(theImage) && (*theImage)->dmon == gMainMonitor && gSlideShow != kPaused) {
		if (gMenuVisible) {
			RehideMenuBar();
			ActivateAnts((*theImage)->ants);
		} else {
			DeactivateAnts((*theImage)->ants);
			UnhideMenuBar();
		}
	}

done:
	if (!inFront) ChangeActive((*theImage)->window);
	AdjustMenus();
	PopPort();
}

static void DoDragSelection(ImageHandle theImage, EventRecord *theEvent)
{
	static DragSendDataProc gSendItemData = nil;
	RgnHandle theRgn = NewRgn(), tempRgn = NewRgn();
	DragReference theDrag;
	Rect startRect;
	OSErr theErr;
	
	GetAntsSelection((*theImage)->ants, &startRect);
	if (!gSendItemData) gSendItemData = NewDragSendDataProc((ProcPtr)SendItemData);
	if (theRgn && tempRgn) {
		theErr = NewDrag(&theDrag);
		if (theErr == noErr) {
			AddDragItemFlavor(theDrag, (ItemReference)theImage, 'PICT', nil, 0, 0);
			GlobalRect(&startRect, (*theImage)->window);
			RectRgn(theRgn, &startRect);
			RectRgn(tempRgn, &startRect);
			InsetRgn(tempRgn, 1, 1);
			DiffRgn(theRgn, tempRgn, theRgn);
			SetDragItemBounds(theDrag, (ItemReference)theImage,
						&(*theRgn)->rgnBBox);
			SetDragSendProc(theDrag, gSendItemData, nil);
			SetCursor(&qd.arrow);
			theErr = TrackDrag(theDrag, theEvent, theRgn);
			DisposeDrag(theDrag);
		}
	}
	if (theRgn) DisposeRgn(theRgn);
	if (tempRgn) DisposeRgn(tempRgn);
}

static pascal OSErr SendItemData(FlavorType theType, void *dragSendRefCon,
			ItemReference theItem, DragReference theDrag)
{
#if applec
#pragma unused(dragSendRefCon)
#endif
	Boolean dontDispose = false;
	PicHandle thePicture;
	OSErr theErr;
	
	switch (theType) {
		case 'PICT':
			theErr = GetSelectedPICT((ImageHandle)theItem, &thePicture);
			if (theErr == noErr) {
				if (!thePicture) {
					thePicture = (PicHandle)(*(ImageHandle)theItem)->data;
					dontDispose = true;
				}
				HLock((Handle)thePicture);
				theErr = SetDragItemFlavorData(theDrag, theItem, 'PICT',
							*thePicture, GetHandleSize((Handle)thePicture), 0);
				if (!dontDispose) DisposeHandle((Handle)thePicture);
			}
			break;
		default:
			theErr = badDragFlavorErr;
			break;
	}
	return theErr;
}

/*
 * NewImage()
 *
 *     Purpose: Adds a new image to the linked list
 *      Inputs: none
 *     Returns: a pointer to the new image
 *
 */
 
ImageHandle NewImage(void) 
{
	ImageHandle theImage;

	if (gImageRoot) {
		theImage = LastImage();
		theImage = (*theImage)->next = (ImageHandle)NewHandleClear(sizeof(ImageItem));
	} else
		theImage = gImageRoot = (ImageHandle)NewHandleClear(sizeof(ImageItem));
	(*theImage)->id = kImageID;
	(*theImage)->update = NewRgn();
	(*theImage)->ants = NewAnts();
	return theImage;
}

/*
 * DisposeImage(theImage)
 *
 *     Purpose: Removes the given image from the linked list
 *      Inputs: theImage = the image record to be removed
 *     Returns: nothing
 *
 */
 
void DisposeImage(ImageHandle theImage) 
{
	Boolean foundData = false, foundGWorld = false;
	ImageHandle tempImage;
	
	PushPort();
	MySetPort(nil);
	(*theImage)->id = 0;
	for (tempImage = gImageRoot; tempImage; tempImage = (*tempImage)->next)
		if (tempImage != theImage) {
			if ((*tempImage)->data == (*theImage)->data) foundData = true;
			if ((*tempImage)->gworld == (*theImage)->gworld) foundGWorld = true;
		}
	if (!foundData && (*theImage)->data) DisposeHandle((*theImage)->data);
	if (!foundGWorld) KillGWorld(theImage);
	if ((*theImage)->ipalette) DisposePalette((*theImage)->ipalette);
	if ((*theImage)->qpalette) DisposePalette((*theImage)->qpalette);
	if ((*theImage)->palette) DisposePalette((*theImage)->palette);
	if ((*theImage)->comments) DisposeHandle((*theImage)->comments);
	if ((*theImage)->window) {
		FWCloseWindow((*theImage)->window);
		DeallocateWindow((*theImage)->window);
	}
	if ((*theImage)->update) DisposeRgn((*theImage)->update);
	if ((*theImage)->format && (*theImage)->format->close)
		(*theImage)->format->close(theImage);
	if ((*theImage)->privateData) DisposeHandle((*theImage)->privateData);
	if (theImage != gImageRoot) (*PreviousImage(theImage))->next = (*theImage)->next;
	else gImageRoot = (*gImageRoot)->next;
	if ((*theImage)->ants) DisposeAnts((*theImage)->ants);
	DisposeHandle((Handle)theImage);
	PopPort();
}

/*
 * CopyImage(theImage)
 *
 *     Purpose: Makes a copy of the given image
 *      Inputs: theImage = the image record to be copied
 *     Returns: a pointer to a new copy of the image
 *
 */
 
ImageHandle CopyImage(ImageHandle srcImage)
{
	ImageHandle dstImage, theImage;
	PaletteHandle newPalette;
	AntsReference oldAnts;
	RgnHandle oldUpdate;
	Handle tmpHandle;
	
	if (dstImage = NewImage()) {
		oldUpdate = (*dstImage)->update;
		oldAnts = (*dstImage)->ants;
		BlockMove(*srcImage, *dstImage, sizeof(ImageItem));
		(*dstImage)->update = oldUpdate;
		(*dstImage)->ants = oldAnts;
		(*dstImage)->next = nil;
		(*dstImage)->window = nil;
		if ((*srcImage)->palette) {
			newPalette = NewPalette((*(*srcImage)->palette)->pmEntries, nil, pmTolerant, 0);
			if (newPalette) {
				CopyPalette((*srcImage)->palette, newPalette, 0, 0, 
							(*(*srcImage)->palette)->pmEntries);
				(*dstImage)->palette = newPalette;
			}
		}
		if ((*srcImage)->qpalette) {
			newPalette = NewPalette((*(*srcImage)->qpalette)->pmEntries, nil, pmTolerant, 0);
			if (newPalette) {
				CopyPalette((*srcImage)->qpalette, newPalette, 0, 0, 
							(*(*srcImage)->qpalette)->pmEntries);
				(*dstImage)->qpalette = newPalette;
			}
		}
		if ((*srcImage)->ipalette) {
			newPalette = NewPalette((*(*srcImage)->ipalette)->pmEntries, nil, pmTolerant, 0);
			if (newPalette) {
				CopyPalette((*srcImage)->ipalette, newPalette, 0, 0, 
							(*(*srcImage)->ipalette)->pmEntries);
				(*dstImage)->ipalette = newPalette;
			}
		}
		tmpHandle = (*dstImage)->comments;
		if (tmpHandle) {
			HandToHand(&tmpHandle);
			(*dstImage)->comments = tmpHandle;
		}
		tmpHandle = (*dstImage)->privateData;
		if (tmpHandle) {
			HandToHand(&tmpHandle);
			(*dstImage)->privateData = tmpHandle;
		}
		if ((*srcImage)->format->clone) (*srcImage)->format->clone(srcImage, dstImage);
		(*dstImage)->flags &= ~(ifSelected + ifSelVisible + ifAborted + ifScreenSize + 
								ifFilename);
		for (theImage = gImageRoot; theImage; theImage = (*theImage)->next)
			if ((theImage != dstImage) && ((*theImage)->num >= (*dstImage)->num)) 
				(*dstImage)->num = (*theImage)->num + 1;
	}
	return dstImage;
}

/*
 * LastImage()
 *
 *     Purpose: Returns a pointer to the last image in the linked list
 *      Inputs: none
 *     Returns: a pointer to the last image, or nil if no images exist
 *
 */
 
ImageHandle LastImage(void)
{
	ImageHandle theImage;
	
	if (!gImageRoot) return nil;
	for (theImage = gImageRoot; (*theImage)->next; theImage = (*theImage)->next);
	return theImage;
}

/*
 * PreviousImage(theImage)
 *
 *     Purpose: Returns a pointer to the image preceding theImage in the linked list
 *      Inputs: none
 *     Returns: a pointer to the preceding image, or nil if theImage is gImageRoot
 *
 */
 
ImageHandle PreviousImage(ImageHandle theImage)
{
	ImageHandle curImage;
	
	if (theImage == gImageRoot) return nil;
	for (curImage = gImageRoot; curImage && ((*curImage)->next != theImage);
		 curImage = (*curImage)->next);
	return curImage;
}

/*
 * AnotherFull(theImage)
 *
 *     Purpose: Determines if there is another full-screen image on the same monitor as
 *					theImage
 *      Inputs: theImage = a handle to the image
 *     Returns: true if there is another full-screen image; false otherwise
 *
 */
 
Boolean AnotherFull(ImageHandle theImage)
{
	MonitorHandle imageMon = theImage ? (*theImage)->dmon : gMainMonitor;
	ImageHandle fullImage;
	
	if (imageMon == gMainMonitor && GetSlideBackWindow()) return true;
	for (fullImage = gImageRoot; fullImage; fullImage = (*fullImage)->next)
		if ((fullImage != theImage) && Full(fullImage) && (*fullImage)->dmon == imageMon) break;
	return (fullImage != nil);
}

/*
 * FindImage(theWindow)
 *
 *     Purpose: Scans the list of images for one who owns the given window
 *      Inputs: theWindow = a pointer to the window record
 *     Returns: a pointer to the associated image, or nil if none found
 *
 */
 
ImageHandle FindImage(WindowPtr theWindow)
{
	ImageHandle theImage;
	
	if (theWindow)
		for (theImage = gImageRoot; theImage; theImage = (*theImage)->next)
			if ((*theImage)->window == theWindow) return theImage;
	return nil;
}

/*
 * UpdateImages(theMonitor)
 *
 *     Purpose: Updates any images whose deepest monitor happens to be theMonitor
 *      Inputs: theMonitor = the monitor whose state has changed
 *     Returns: nothing
 *
 */
 
void UpdateImages(MonitorHandle theMonitor)
{
#if applec
#pragma unused(theMonitor)
#endif
	short depth, palette;
	ImageHandle theImage;
	Boolean grey;
	
	for (theImage = gImageRoot; theImage; theImage = (*theImage)->next) {
		GetMinMaxMonitor(theImage);
		depth = (*(*theImage)->dmon)->depth;
		grey = !(*(*theImage)->dmon)->color;
		if (grey || (depth < 4)) palette = plGrey;
		else if (depth > 8) palette = plSys;
		else palette = (*theImage)->npalette;
		switch (palette) {
			case plSys:
				DoSetPalette((*theImage)->window, kAEPSystem);
				break;
			case plGrey:
				DoSetPalette((*theImage)->window, kAEPGrayscale);
				break;
			case plImage:
				if ((*(*theImage)->ipalette)->pmEntries <= (1L << depth))
					DoSetPalette((*theImage)->window, kAEPImage);
				else DoSetPalette((*theImage)->window, kAEPSystem);
				break;
			case plQuant:
				if ((*theImage)->qpalette &&
					((1L << depth) != ((*(*theImage)->qpalette)->pmEntries))) {
					DisposePalette((*theImage)->qpalette);
					(*theImage)->qpalette = nil;
					DoSetPalette((*theImage)->window, kAEPSystem);
				} else DoSetPalette((*theImage)->window, kAEPQuantized);
				break;
		}
		if (!gThePrefs.ditherQuant && ((*theImage)->npalette == plQuant ||
			 ((*theImage)->npalette == plImage && (*theImage)->depth >= 16))) 
			(*theImage)->flags &= ~ifDithered;
		else (*theImage)->flags |= ifDithered;
	}
}

/*
 * DrawImage(theImage)
 *
 *     Purpose: Draws the given image in its window
 *      Inputs: theImage = the image record to be drawn
 *				bitsFlags = the initial set of bitsFlags
 *     Returns: nothing
 *
 */
 
OSErr DrawImage(ImageHandle theImage, short bitsFlags, NestedProgressPtr progProc) 
{
	Rect theRect = (*theImage)->wrect;
	OSErr theErr = noErr;
	RgnHandle tmpRgn;

	PushPort();
	MySetPort((CGrafPtr)(*theImage)->window);
	/* If we're drawing onscreen, erase the region to be updated, draw a border around
	 * full screen images, reset the clipping rectangle, and limit the visRgn to the
	 * clipped area (since we assume we're doing this from within an update event */
	if (bitsFlags & bfOnscreen) {
		CopyRgn((*theImage)->window->visRgn, (*theImage)->update);
		if (Full(theImage) && (tmpRgn = NewRgn())) {
			ClipRect(&(*theImage)->window->portRect);
			RectRgn(tmpRgn, &theRect);
			DiffRgn((*theImage)->window->clipRgn, tmpRgn, (*theImage)->window->clipRgn);
			if (NeedsWhiteBG(theImage) && !Full(theImage)) EraseRgn((*theImage)->window->visRgn);
			else PaintRgn((*theImage)->window->visRgn);
			DrawFullBorder(theImage);
			DisposeRgn(tmpRgn);
			DiffRgn((*theImage)->update, (*theImage)->window->clipRgn, (*theImage)->update);
			DiffRgn((*theImage)->window->visRgn, (*theImage)->window->clipRgn, 
					(*theImage)->window->visRgn);
		}
	}
	ClipRect(&theRect);
	/* If we have anything to draw, then set up the global bitsFlags, point to our
	 * special drawing procedures, and do the appropriate method of drawing */
	if (bitsFlags != bfOnscreen || !EmptyRgn((*theImage)->window->visRgn)) {
		if ((*theImage)->gworld) LockPixels(GetGWorldPixMap((*theImage)->gworld));
		if ((*theImage)->gworld && !(bitsFlags & bfForceFull)) 
			theErr = DrawImageGWorld(theImage, bitsFlags, progProc);
		else theErr = DrawImageFull(theImage, bitsFlags, progProc);
	}
	if ((theErr == noErr) && Full(theImage) && (bitsFlags & bfOnscreen) && 
		ShowFilename(theImage)) DrawFileName(theImage);
	if (bitsFlags & bfOnscreen) {
		if (theErr == codecAbortErr && !(bitsFlags & bfQuantize)) {
			if (NeedToRedraw(theImage)) (*theImage)->flags &= ~(ifNeedToRedraw + ifAborted);
			else {
				Pattern whitePat = { 0x82, 0x44, 0x28, 0x10, 0x28, 0x44, 0x82, 0x00 };
				Pattern blackPat = { 0x7d, 0xbb, 0xd7, 0xef, 0xd7, 0xbb, 0x7d, 0xff };
				RGBForeColor(&gBlack);
				RGBBackColor(&gWhite);
				SetClip((*theImage)->update);
				FillRgn((*theImage)->update, NeedsWhiteBG(theImage) ? &whitePat : &blackPat);
				(*theImage)->flags |= ifAborted;
			}
		} else (*theImage)->flags &= ~ifAborted;
	}
	if ((*theImage)->gworld) UnlockPixels(GetGWorldPixMap((*theImage)->gworld));
	PopPort();
	return theErr;
}

/*
 * DrawImageGWorld(theImage, bitsFlags)
 *
 *     Purpose: Handles drawing from the offscreen GWorld
 *      Inputs: theImage = the image record to be drawn
 *				bitsFlags = set of flags to be passed on to the custom copybits
 *     Returns: nothing
 *
 */
 
OSErr DrawImageGWorld(ImageHandle theImage, short bitsFlags, NestedProgressPtr progProc)
{
	PixMapHandle src = GetGWorldPixMap((*theImage)->gworld);
	Boolean deepGWorld = ((*(*theImage)->dmon)->depth < PixMapDepth(src)) && 
							!gThePrefs.origSize;
	Rect srcRect = GWOrigSize(theImage) ? (*theImage)->crect : (*theImage)->gworld->portRect;
	GWorldPtr oldGWorld = (*theImage)->gworld;
	Rect theRect = (*theImage)->wrect;
	JVDrawParamsHandle theParams;
	OSErr theErr = noErr;

	KeepSpinning();
	if (theParams = NewDrawParams(&(*theImage)->gworld->portRect, (*theImage)->quality, 
				(bitsFlags & bfQuantize) != 0, progProc, (*theImage)->privateData)) {
		SetUpDrawPort(theParams, kOnscreenPort, (CGrafPtr)(*theImage)->window, &srcRect,
					&theRect, Dithered(theImage));
		(*theParams)->newupdates = (*theImage)->update;

		// if the GWorld is too deep, we need to delete it and make a new one
		if (deepGWorld) {
			(*theImage)->gworld = nil;
			(*theImage)->flags &= ~ifGWOrigSize;
			if (!MakeImageGWorld(theImage, false)) {
				DisposeGWorld(oldGWorld);
				DisposeHandle((Handle)theParams);
				if (gThePrefs.useBitmaps == ubAlways && !gSlideShow)
					return gIntError = errNoOffscreenMemory, memFullErr;
				else return DrawImageFull(theImage, bitsFlags, progProc);
			}
			SetUpDrawPort(theParams, kOffscreenPort1, (*theImage)->gworld, &srcRect,
						&(*theImage)->gworld->portRect, true);
			LockPixels(GetGWorldPixMap((*theImage)->gworld));
		}
		
		// save the port, lock the handles, and initialize the drawing
		theErr = PreflightDrawing(theParams);

		// If everything's okay, CopyBits to the dummy port and clean up
		if (theErr == noErr) {
			SpinIndef();
			CopyGWorldToGWorld(oldGWorld, (*theParams)->dummy, &srcRect, &srcRect, 
						srcCopy, nil);
			theErr = PostflightDrawing(theParams);
		}
		KeepSpinning();
		if (deepGWorld) {
			UnlockPixels(GetGWorldPixMap((*theImage)->gworld));
			DisposeGWorld(oldGWorld);
		}
		if (progProc) *progProc = (*theParams)->progress;
		DisposeHandle((Handle)theParams);
	} else theErr = MemError();
	if (theErr == codecAbortErr && (bitsFlags & bfOffscreen)) KillGWorld(theImage);
	return theErr;
}

/*
 * DrawImageFull(theImage, bitsFlags)
 *
 *     Purpose: Handles drawing from scratch
 *      Inputs: theImage = the image record to be drawn
 *				bitsFlags = set of flags to be passed on to the custom copybits
 *				progProc = pointer to the progress procedure, or nil to use the default
 *     Returns: nothing
 *
 */
 
OSErr DrawImageFull(ImageHandle theImage, short bitsFlags, NestedProgressPtr progProc)
{
	NestedProgress localProg = { { nil, 0L }, 0, 0L, 0x10000L, false };
	Rect crect = (*theImage)->crect, theRect = (*theImage)->wrect;
	JVDrawParamsHandle theParams;
	OSErr theErr;
	long dtime;
			
	KeepSpinning();
	
	// erase the area we're drawing into before doing anything else
	if (bitsFlags & bfOnscreen) {
		if (NeedsWhiteBG(theImage)) EraseRgn((*theImage)->window->visRgn);
		else PaintRgn((*theImage)->window->visRgn);
	}

	// set up the progress procedure, according to flags & current situation & such
	if (progProc) localProg = *progProc;
	else {
		if (bitsFlags & bfOnscreen) {
			if (NoOnscreenProg(theImage))
				localProg.prog.progressProc = gDummyProg.prog.progressProc;
			else localProg.prog.progressProc = gDrawProgress;
		} else if (bitsFlags & bfUseSlideProgress) 
			localProg.prog.progressProc = gSlideProgress;
		else localProg.prog.progressProc = gGenericProgress;
	}

	// allocate and initialize the drawing parameters
	if (theParams = NewDrawParams(&(*theImage)->grect, (*theImage)->quality, 
				(bitsFlags & bfQuantize) != 0, &localProg, (*theImage)->privateData)) {
		if (localProg.prog.progressProc == gDrawProgress)
			(*theParams)->drawProgressRect = (*theImage)->wrect;

		// set up the onscreen port
		if (bitsFlags & bfOnscreen) {
			SetUpDrawPort(theParams, kOnscreenPort, (CGrafPtr)(*theImage)->window, 
						&crect, &theRect, Dithered(theImage));
			(*theParams)->newupdates = (*theImage)->update;
		}
		
		// try to make a GWorld for this image; if successful, set the bitsFlags
		// and ClipRgn appropriately; otherwise, if we've required bitmaps, close the
		// image and return an error
		if (!(*theImage)->gworld) {
			if (MakeImageGWorld(theImage, bitsFlags & bfQuantize)) {
				LockPixels(GetGWorldPixMap((*theImage)->gworld));
				bitsFlags |= bfOffscreen;
			} else if (gThePrefs.useBitmaps == ubAlways && !gSlideShow &&
						((*theImage)->compression || !gThePrefs.noUncomp))
				return gIntError = errNoOffscreenMemory, memFullErr;
		}
		
		// set up the offscreen port
		if ((bitsFlags & bfOffscreen) && (*theImage)->gworld)
			SetUpDrawPort(theParams, kOffscreenPort1, (*theImage)->gworld,
				GWOrigSize(theImage) ? &(*theImage)->gworld->portRect : &crect,
				&(*theImage)->gworld->portRect, !GWOrigSize(theImage));

		// spin the cursor, start the timer, and preflight our drawing
		KeepSpinning();
		StartTimer();
		theErr = PreflightDrawing(theParams);
		KeepSpinning();
		PushPort();
		if (theErr == noErr) {
		
			// indicate globally that we are drawing, and actually call the function
			gDrawing = theImage;
			MySetPort((*theParams)->dummy);
			CallICMProgressProc((*theParams)->progress.prog.progressProc,
						codecProgressOpen, 0L, 
						(*theParams)->progress.prog.progressRefCon);
			theErr = (*theImage)->format->draw((*theImage)->data, theParams);
			CallICMProgressProc((*theParams)->progress.prog.progressProc,
						codecProgressClose, 0L,
						(*theParams)->progress.prog.progressRefCon);
						
			// reset everything now and stop the timer
			gDrawing = nil;
			KeepSpinning();
			dtime = StopTimer();
			
			// if we were drawing a full-size image, save the time from the timer
			if (EqualSizeRect(&theRect, &(*(*theImage)->window->visRgn)->rgnBBox) &&
				theErr == noErr && (bitsFlags & (bfOnscreen + bfOffscreen))) 
				(*theImage)->dtime = dtime;
			
			// postflight the drawing, saving the error
			if (theErr == noErr) theErr = PostflightDrawing(theParams);
			else PostflightDrawing(theParams);
		}
		PopPort();
		if (progProc) *progProc = (*theParams)->progress;
		DisposeHandle((Handle)theParams);
	} else theErr = memFullErr;
	if (theErr == memFullErr) gIntError = errNoDrawMemory;
	else if (theErr == codecAbortErr && (bitsFlags & bfOffscreen)) KillGWorld(theImage);
	return theErr;
}

/*
 * DrawFullBorder(theImage)
 *
 *     Purpose: Draws the border for a full-screen window
 *      Inputs: theImage = the image record to be drawn
 *     Returns: nothing
 *
 */
 
void DrawFullBorder(ImageHandle theImage)
{
	Rect outerRect = (*theImage)->wrect;
	
	InsetRect(&outerRect, -1, -1);
	RGBForeColor(&gFGrey);
	RGBBackColor(&gWhite);
	FrameRect(&outerRect);
	MoveTo(outerRect.left + 1, outerRect.bottom);
	LineTo(outerRect.right, outerRect.bottom);
	LineTo(outerRect.right, outerRect.top + 1);
	RGBForeColor(&gBlack);
}

/*
 * DrawFileName(theImage)
 *
 *     Purpose: Draws a filename on a full-screen window for the slide show
 *      Inputs: theImage = the image record to be drawn
 *     Returns: nothing
 *
 */
 
void DrawFileName(ImageHandle theImage)
{
	short height, width, i, j;
	FontInfo theInfo;
	Str255 theText;
	Point start;
	
	if (!theImage || !(*theImage)->window) return;
	PushPort();
	MySetPort((CGrafPtr)(*theImage)->window);
	ClipRect(&(*theImage)->window->portRect);
	CalcVis((WindowPeek)(*theImage)->window);
										// Do this to get the real visRgn of the window
	TextFont(systemFont);
	TextFace(normal);
	TextSize(12);
	TextMode(srcOr);
	GetFontInfo(&theInfo);
	BlockMove((*theImage)->file.name, theText, *(*theImage)->file.name + 1);
	height = theInfo.ascent + theInfo.descent + theInfo.leading;
	width = StringWidth(theText);
	/* If there's enough room, simply draw the file name below the image's rect */
	if (((*theImage)->wrect.bottom + height) < (*(*theImage)->dmon)->rect.bottom) {
		start.h = (*theImage)->wrect.right - width - theInfo.leading;
		start.v = (*theImage)->wrect.bottom + height - theInfo.leading;
		RGBForeColor(&gWhite);
		MoveTo(start.h, start.v);
		DrawString(theText);
	/* Or we could draw it to the right of the image's rect */
	} else if (((*theImage)->wrect.right + width + 4) < (*(*theImage)->dmon)->rect.right) {
		start.h = (*theImage)->wrect.right + 4;
		start.v = (*theImage)->wrect.bottom - theInfo.descent - theInfo.leading;
		RGBForeColor(&gWhite);
		MoveTo(start.h, start.v);
		DrawString(theText);
	/* If there's not enough room, we draw the filename on top of the lower-right corner */
	} else {
		start.h = (*theImage)->wrect.right - width - theInfo.leading;
		start.v = (*theImage)->wrect.bottom - theInfo.descent - theInfo.leading;
		RGBForeColor(&gBlack);
		for (i = -1; i <= 1; i++)
			for (j = -1; j <= 1; j++) {
				MoveTo(start.h + i, start.v + j);
				DrawString(theText);
			}
		RGBForeColor(&gWhite);
		MoveTo(start.h, start.v);
		DrawString(theText);
	}
	RGBForeColor(&gBlack);
	PopPort();
}

/*
 * MakeImageGWorld(theImage)
 *
 *     Purpose: Attempts to create a new offscreen bitmap for the given image
 *      Inputs: theImage = the image record
 *				quantizing = flag: true if we're quantizing
 *     Returns: true if it worked; false otherwise
 *
 */
 
Boolean MakeImageGWorld(ImageHandle theImage, Boolean quantizing)
{
	short imageDepth = ((*theImage)->depth < 8) ? 8 : (*theImage)->depth;
	short theDepth = (*(*theImage)->dmon)->depth;
	Rect theRect = (*theImage)->wrect;
	CTabHandle theColors = nil;

	if (gThePrefs.noUncomp && !(*theImage)->compression) return false;
	if (gThePrefs.useBitmaps == ubNever && (!gSlideShow || !(*gSlideOptions)->offscreen))
		return false;
	if (gThePrefs.origSize && (!Banded(theImage) || !Cropped(theImage))) 
		theRect = (*theImage)->grect;
	if (theDepth > imageDepth || gThePrefs.origSize || quantizing) theDepth = imageDepth;
	if (theDepth == 24) theDepth = 32;
	else if (theDepth <= 8) theColors = GetGWorldColors(theImage, theDepth);
	OffsetRect(&theRect, (*theImage)->grect.left - theRect.left,
						 (*theImage)->grect.top - theRect.top);
	(*theImage)->gworld = nil;
	if (IsMemAvailable(EstGWorldSize(&theRect, theDepth)))
		(*theImage)->gworld = MyNewGWorld(&theRect, theDepth, theColors, 
							  nil, NeedsWhiteBG(theImage), false);
	if (!(*theImage)->gworld && gSlideShow && (*gSlideOptions)->offscreen && !HasVirtualMemory()) {
		(*theImage)->gworld = MyNewGWorld(&theRect, theDepth, theColors,
							  nil, NeedsWhiteBG(theImage), true);
		if ((*theImage)->gworld) (*theImage)->flags |= ifGWTempMem;
	}
	if ((*theImage)->gworld) {
		(*theImage)->flags &= ~ifGWOrigSize;
		if (EqualSizeRect(&theRect, &(*theImage)->grect) && 
			theDepth >= (*theImage)->depth &&
			(gThePrefs.origSize || theDepth >= 16)) (*theImage)->flags |= ifGWOrigSize;
		if (theColors) DisposeHandle((Handle)theColors);
		return true;
	}
	if (theColors) DisposeHandle((Handle)theColors);
	return false;
}

/*
 * KillGWorld(theImage)
 *
 *     Purpose: Kills the given image's GWorld and checks for others who might share it
 *      Inputs: theImage = the image record whose GWorld we are about to kill
 *     Returns: nothing
 *
 */
 
void KillGWorld(ImageHandle theImage)
{
	GWorldPtr theGWorld, curPort = (GWorldPtr)qd.thePort;
	Boolean foundOthers = false;
	ImageHandle image;
	
	if (theImage && (theGWorld = (*theImage)->gworld)) {
		if (curPort == theGWorld) MySetPort(nil);
		for (image = gImageRoot; image; image = (*image)->next)
			if (image != theImage && (*image)->gworld == theGWorld) foundOthers = true;
		if (!foundOthers) DisposeGWorld(theGWorld);
		(*theImage)->gworld = nil;
	}
}

/*
 * GetGWorldColors(theImage, gwDepth)
 *
 *     Purpose: Creates an appropriate color table for the given image's GWorld at the
 *					given depth
 *      Inputs: theImage = the image record
 *				gwDepth = the depth of the GWorld we'd like to create
 *     Returns: a CTabHandle containing the color table
 *
 */
 
CTabHandle GetGWorldColors(ImageHandle theImage, short gwDepth)
{
	short imDepth = (*theImage)->depth;
	PaletteHandle thePalette = nil;
	CTabHandle theColors = nil;
	
	if (!gThePrefs.origSize) {		// can only copy from screen if not original size
		if ((theImage == FrontImage()) && !gInBackground && (*theImage)->window && 
			WindowVisible((*theImage)->window) &&
			(gwDepth == (*(*theImage)->dmon)->depth) && (gwDepth <= 8)) {
			theColors = (*(*(*(*theImage)->dmon)->device)->gdPMap)->pmTable;
			if (HandToHand((Handle *)&theColors) != noErr) theColors = nil;
			return theColors;
		}
	}
	if (gwDepth >= imDepth) {
		if (imDepth <= 8) {
			if (gwDepth <= 8) thePalette = (*theImage)->ipalette ? (*theImage)->ipalette :
																   (*theImage)->palette;
			else thePalette = nil;
		} else thePalette = nil;
	} else {
		if (imDepth <= 8) thePalette = (*theImage)->palette;
		else {
			if (gwDepth <= 8) thePalette = (*theImage)->palette;
			else thePalette = nil;
		}
	}
	if (thePalette && ((1L << gwDepth) == (*thePalette)->pmEntries)) {
		if (theColors = (CTabHandle)NewHandle(4)) {
			Palette2CTab(thePalette, theColors);
			(*theColors)->ctSeed = GetCTSeed();
		}
	}
	return theColors;
}

/*
 * DoImageHelp(theWindow, globalPt)
 *
 *     Purpose: Pops up help for an image window; needed because they can be called anything
 *      Inputs: theWindow = the window whose help we're getting
 *				globalPt = point, in global coordinates, where the mouse is
 *     Returns: nothing
 *
 */
 
void DoImageHelp(WindowPtr theWindow, Point globalPt)
{
	ImageHandle theImage = FindImage(theWindow);
	HMMessageRecord theRecord;
	Point tip = { 0, 0 };
	Rect aRect;
	
	if (HMIsBalloon() || gInBackground) return;
	PushPort();
	SetPort(theWindow);
	if (HMExtractHelpMsg(kHMRectListResType, rImageWindow, 1, kHMEnabledItem, &theRecord) == noErr) {
		tip = globalPt;
		aRect = theWindow->portRect;
		GlobalRect(&aRect, theWindow);
		HMShowBalloon(&theRecord, tip, &aRect, nil, 0, 0, kHMRegularWindow);
	}
	PopPort();
}
