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

/*
 * MonitorInit()
 *
 *     Purpose: Sets up the linked list of screen devices, and sets their inverse color
 *					table resolutions to 5 bits per component
 *      Inputs: none
 *     Returns: nothing
 *
 */

void MonitorInit(void) 
{
	DeviceLoopDrawingUPP addDevice = NewDeviceLoopDrawingProc((ProcPtr)AddDevice);
	MonitorHandle theMonitor;
	GDHandle theDevice;

	if (!(theDevice = GetMainDevice())) FatalError(errNoDeviceInfo);
	if (!(theMonitor = gMainMonitor = NewMonitor(theDevice))) FatalError(errNoMemory);
	DeviceLoop(nil, addDevice, (long)&theMonitor, allDevices + singleDevices);
	DisposeRoutineDescriptor((UniversalProcPtr)addDevice);
	SetUpDevices();
	KeepSpinning();
}

/*
 * AddDevice(depth, deviceFlags, targetDevice, userData)
 *
 *     Purpose: DeviceLoop routine to add the current device to the linked list
 *      Inputs: depth = depth of the device
 *				deviceFlags = device flags for the device
 *				targetDevice = handle to the target device
 *				userData = a refCon we can use
 *     Returns: nothing
 *
 */

pascal void AddDevice(short depth, short deviceFlags, GDHandle targetDevice, 
	MonitorHandle *theMonitor)
{
#if applec
#pragma unused(depth, deviceFlags)
#endif
	if (targetDevice != (*gMainMonitor)->device) {
		if ((**theMonitor)->next = NewMonitor(targetDevice))
			*theMonitor = (**theMonitor)->next;
	}
}

/*
 * NewMonitor()
 *
 *     Purpose: Allocates and initializes a new monitor record based on the given device
 *      Inputs: theDevice = handle to the new device
 *     Returns: a pointer to the new monitor record
 *
 */

MonitorHandle NewMonitor(GDHandle theDevice)
{
	MonitorHandle theMonitor;
	Rect theRect;
	
	if (theMonitor = (MonitorHandle)NewHandle(sizeof(MonitorItem))) {
		if ((*theMonitor)->gray = NewRgn()) {
			if ((*theMonitor)->screen = NewRgn()) {
				(*theMonitor)->next = nil;
				(*theMonitor)->device = theDevice;
				(*theMonitor)->oldres = (*theDevice)->gdResPref;
				(*theMonitor)->color = IsColorDevice(theDevice);
				(*theMonitor)->depth = MonitorDepth(theMonitor);
				(*theMonitor)->full = false;
				theRect = (*theMonitor)->rect = (*theDevice)->gdRect;
				RectRgn((*theMonitor)->screen, &theRect);
				SectRgn((*theMonitor)->screen, GetGrayRgn(), (*theMonitor)->gray);
				return theMonitor;
			}
			DisposeHandle((Handle)(*theMonitor)->gray);
		}
		DisposeHandle((Handle)theMonitor);
	}
	return nil;
}	

/*
 * MonitorChanged()
 *
 *     Purpose: Checks to see if any monitors have changed depth/greys
 *      Inputs: none
 *     Returns: a pointer to the changed monitor, or nil if everything is the same
 *
 */
 
MonitorHandle MonitorChanged(void)
{
	MonitorHandle theMonitor;
	Boolean color;
	short depth;
	
	for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next) {
		color = IsColorDevice((*theMonitor)->device);
		depth = MonitorDepth(theMonitor);
		if (((*theMonitor)->color != color) || ((*theMonitor)->depth != depth)) {
			(*theMonitor)->color = color;
			(*theMonitor)->depth = depth;
			return theMonitor;
		}
	}
	return nil;
}

/*
 * SetUpDevices()
 *
 *     Purpose: Restores the new inverse table resolutions on the various devices
 *      Inputs: none
 *     Returns: nothing
 *
 */

void SetUpDevices(void) 
{
	MonitorHandle theMonitor;

	for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next) {
		/*(*(*theMonitor)->device)->gdResPref = 5;
		if ((*(*theMonitor)->device)->gdITable)
			(*(*(*theMonitor)->device)->gdITable)->iTabSeed = GetCTSeed();
		GDeviceChanged((*theMonitor)->device);*/
	}
}

/*
 * ResetDevices()
 *
 *     Purpose: Restores the original inverse table resolutions on the various devices
 *      Inputs: none
 *     Returns: nothing
 *
 */

void ResetDevices(void) 
{
	MonitorHandle theMonitor;
	
	if (!gMenuVisible && !(*gMainMonitor)->full) UnhideMenuBar();
	for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next)
		if ((*theMonitor)->full) NormalScreen(theMonitor);
}

/*
 * ResetColors()
 *
 *	   Purpose: Restores the colors of all active screens to their original CLUTs
 *	    Inputs: none
 *	   Returns: nothing
 *
 */
 
void ResetColors(void)
{
	MonitorHandle theMonitor;
	RgnHandle theRgn;
	
	if (gInBackground) return;
	if (theRgn = NewRgn()) {
		for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next) {
			if (!(*(*theMonitor)->device)->gdType) {
				RestoreDeviceClut((*theMonitor)->device);
				UnionRgn(theRgn, (*theMonitor)->gray, theRgn);
			}
		}
		if (!EmptyRgn(theRgn)) {
			LMSetPaintWhite(true);
			PaintBehind((WindowPeek)GetFirstWindow(), theRgn);
			LMSetPaintWhite(false);
			PaintOne(nil, theRgn);
			LMSetPaintWhite(true);
		}
		DisposeRgn(theRgn);
	}
}

/*
 * FullScreen(theMonitor)
 *
 *	   Purpose: Fixes up theMonitor to allow full-screen windows
 *	    Inputs: theMonitor = pointer to the monitor we're interested in, or nil for all
 *	   Returns: nothing
 *
 */
 
void FullScreen(MonitorHandle theMonitor)
{
	RgnHandle updateRgn;
	
	if (!theMonitor) {
		for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next)
			FullScreen(theMonitor);
		return;
	}
	if ((*theMonitor)->full) return;
	UnionRgn(GetGrayRgn(), (*theMonitor)->screen, GetGrayRgn());
	if (theMonitor == gMainMonitor && gInBackground && (updateRgn = GetMenuRgn()))
		DiffRgn(GetGrayRgn(), updateRgn, GetGrayRgn());
	if (updateRgn = NewRgn()) {
		DiffRgn((*theMonitor)->screen, (*theMonitor)->gray, updateRgn);
		PaintOne((WindowPeek)FrontWindow(), updateRgn);
		PaintBehind((WindowPeek)FrontWindow(), updateRgn);
		CalcVis((WindowPeek)FrontWindow());
		CalcVisBehind((WindowPeek)FrontWindow(), updateRgn);
		DisposeRgn(updateRgn);
		if (theMonitor == gMainMonitor && !gInBackground) {
			LMSetMBarHeight(0);
			gMenuVisible = false;
			RestoreMenuArea();
		}
		(*theMonitor)->full = true;
	}
}

/*
 * NormalScreen(theMonitor)
 *
 *	   Purpose: Fixes up theMonitor to remove the effects of FullScreen
 *	    Inputs: theMonitor = pointer to the monitor we're interested in, or nil for all
 *	   Returns: nothing
 *
 */
 
void NormalScreen(MonitorHandle theMonitor)
{
	Boolean oldMenuVis = gMenuVisible;
	RgnHandle updateRgn;
	GrafPort tempPort;
	
	if (!theMonitor) {
		for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next)
			NormalScreen(theMonitor);
		return;
	}
	if (!(*theMonitor)->full) return;
	if (updateRgn = NewRgn()) {
		if (theMonitor == gMainMonitor) SaveMenuArea();
		DiffRgn((*theMonitor)->screen, (*theMonitor)->gray, updateRgn);
		PushPort();
		OpenPort(&tempPort);
		CopyRgn(GetGrayRgn(), tempPort.visRgn);
		MySetPort((CGrafPtr)&tempPort);
		RGBForeColor(&gBlack);
		PaintRgn(updateRgn);
		ClosePort(&tempPort);
		PopPort();
		DiffRgn(GetGrayRgn(), updateRgn, GetGrayRgn());
		CalcVis((WindowPeek)FrontWindow());
		CalcVisBehind((WindowPeek)FrontWindow(), updateRgn);
		DisposeRgn(updateRgn);
		if (theMonitor == gMainMonitor) {
			LMSetMBarHeight(gMenuHeight);
			gMenuVisible = true;
			AdjustMenus();
			DrawMenuBar();
			NudgeWindoids();
		}
	}
	(*theMonitor)->full = false;
}

/*
 * NudgeWindoids()
 *
 *	   Purpose: Check all windoids to be sure they're not under the menubar
 *	    Inputs: none
 *	   Returns: nothing
 *
 */
 
void NudgeWindoids(void)
{
	WindowPtr theWindow = GetFirstWindow();
	Rect theRect, menuRect, tempRect;
	
	menuRect = (*gMainMonitor)->rect;
	menuRect.bottom = menuRect.top + GetMBarHeight();
	while (theWindow) {
		if (WindowKind((WindowPtr)theWindow) == floatingWindowKind) {
			theRect = theWindow->portRect;
			theRect.top -= gTitleBarHeight * 2 / 3;
			GlobalRect(&theRect, theWindow);
			if (SectRect(&theRect, &menuRect, &tempRect)) {
				theRect.top = GetMBarHeight() + gTitleBarHeight * 2 / 3;
				MoveWindow(theWindow, theRect.left, theRect.top, false);
			}
		}
		theWindow = NextWindow(theWindow);
	}
}

/*
 * UnhideMenuBar()
 *
 *	   Purpose: Displays the menubar on the main screen, if it's currently full-screen
 *	    Inputs: none
 *	   Returns: nothing
 *
 */
 
void UnhideMenuBar(void)
{
	Boolean oldMenuVis = gMenuVisible;
	RgnHandle menuRgn = GetMenuRgn();
	
	if (!(*gMainMonitor)->full || gMenuVisible) return;
	SaveMenuArea();
	DiffRgn(GetGrayRgn(), menuRgn, GetGrayRgn());
	CalcVis((WindowPeek)FrontWindow());
	CalcVisBehind((WindowPeek)FrontWindow(), menuRgn);
	LMSetMBarHeight(gMenuHeight);
	gMenuVisible = true;
	if (oldMenuVis) MyInvalMenuBar();
	else {
		AdjustMenus();
		DrawMenuBar();
	}
}

/*
 * RehideMenuBar()
 *
 *	   Purpose: Removes the menu bar from the main screen, if it's currently full-screen
 *	    Inputs: none
 *	   Returns: nothing
 *
 */
 
void RehideMenuBar(void)
{
	RgnHandle menuRgn = GetMenuRgn();
	
	if (!(*gMainMonitor)->full || !gMenuVisible || gInBackground) return;
	UnionRgn(GetGrayRgn(), menuRgn, GetGrayRgn());
	PaintOne((WindowPeek)FrontWindow(), menuRgn);
	PaintBehind((WindowPeek)FrontWindow(), menuRgn);
	CalcVis((WindowPeek)FrontWindow());
	CalcVisBehind((WindowPeek)FrontWindow(), menuRgn);
	LMSetMBarHeight(0);
	gMenuVisible = false;
	RestoreMenuArea();
}

/*
 * FrontWindowRgn(theRgn)
 *
 *	   Purpose: Finds the frontmost window that lies unobscured in theRgn
 *	    Inputs: theRgn = the region of interest, in global coordinates
 *	   Returns: a pointer to the window found, or nil if none qualify
 *
 */

WindowPtr FrontWindowRgn(RgnHandle theRgn)
{
	WindowPtr theWindow = FWFrontWindow();
	WindowPtr theResult = nil;
	RgnHandle testRgn;
	
	if (testRgn = NewRgn()) {
		for ( ; theWindow; theWindow = NextWindow(theWindow)) {
			CopyRgn(theWindow->visRgn, testRgn);
			GlobalRgn(testRgn, theWindow);
			SectRgn(theRgn, testRgn, testRgn);
			if (EqualRgn(theRgn, testRgn)) {
				theResult = theWindow;
				break;
			} else if (!EmptyRgn(testRgn)) break;
		}
		DisposeRgn(testRgn);
	}
	return theResult;
}
 
/*
 * GetMenuRgn()
 *
 *     Purpose: Calculates the region occupied by the menubar
 *      Inputs: none
 *     Returns: a handle to the region of the menu bar
 *
 */

RgnHandle GetMenuRgn(void)
{
	static RgnHandle gMenuRgn = nil;

	if (!gMenuRgn) {
		gMenuRgn = NewRgn();
		gMenuHeight = GetMBarHeight();
	}
	if (gMenuRgn) SetRectRgn(gMenuRgn, (*gMainMonitor)->rect.left, (*gMainMonitor)->rect.top,
							(*gMainMonitor)->rect.right, (*gMainMonitor)->rect.top + gMenuHeight);
	return gMenuRgn;
}

/*
 * GetActiveRect(theMonitor, theRect)
 *
 *	   Purpose: Determines the active area of the monitor, with or without menu bar as
 *					appropriate
 *	    Inputs: theMonitor = a handle to the monitor record
 *				theRect = a pointer to the destination rectangle
 *	   Returns: nothing
 *
 */
 
void GetActiveRect(MonitorHandle theMonitor, Rect *theRect)
{
	if ((theMonitor != gMainMonitor) || !gMenuVisible) *theRect = (*theMonitor)->rect;
	else *theRect = (*(*theMonitor)->gray)->rgnBBox;
}

/*
 * GetMinMaxMonitor(theImage)
 *
 *	   Purpose: Calculates the shallowest and deepest monitors intersecting the given
 *					image's window
 *	    Inputs: theImage = a pointer to the image
 *	   Returns: nothing
 *
 */
 
void GetMinMaxMonitor(ImageHandle theImage)
{
	MonitorHandle theMonitor, deepMon = nil, shallowMon = nil;
	ushort pass, depth, shallowest = 100, deepest = 0;
	Rect winRect, tmpRect;

	if (!WindowVisible((*theImage)->window)) {
		if (!(*theImage)->dmon) (*theImage)->dmon = gMainMonitor;
		if (!(*theImage)->smon) (*theImage)->smon = gMainMonitor;
		return;
	}
	for (pass = 1; pass <= 2; pass++) {
		winRect = (pass == 1) ? (*GetContRgn((*theImage)->window))->rgnBBox :
								(*GetStrucRgn((*theImage)->window))->rgnBBox;
		InsetRect(&winRect, 1, 1);
		for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next) {
			if (SectRect(&winRect, &(*theMonitor)->rect, &tmpRect)) {
				depth = (*theMonitor)->depth;
				if (depth > deepest) {
					deepest = depth;
					deepMon = theMonitor;
				}
				if (depth < shallowest) {
					shallowest = depth;
					shallowMon = theMonitor;
				}
			}
		}
		if (deepMon && shallowMon) break;
	}
	if (deepMon && shallowMon) {
		(*theImage)->dmon = deepMon;
		(*theImage)->smon = shallowMon;
	} else if (deepMon) (*theImage)->dmon = (*theImage)->smon = deepMon;
	else if (shallowMon) (*theImage)->dmon = (*theImage)->smon = shallowMon;
	else (*theImage)->dmon = (*theImage)->smon = gMainMonitor;
}

/*
 * GetDeepMonitor(pickColor)
 *
 *	   Purpose: Returns a pointer to the current deepest monitor
 *	    Inputs: pickColor = flag: true to pick color monitors first
 *	   Returns: a MonitorHandle pointing to the deepest monitor
 *
 */
 
MonitorHandle GetDeepMonitor(Boolean pickColor)
{
	ushort depth, deepest = 0, color = pickColor;
	MonitorHandle theMonitor, deepMon = nil;
	
	for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next) {
		depth = (*theMonitor)->depth;
		if ((depth > deepest) || 
			((depth == deepest) && ((pickColor && !color) || (!pickColor && color)))) {
			deepest = depth;
			deepMon = theMonitor;
			color = (*theMonitor)->color;
		}
	}
	return deepMon;
}

/*
 * GetBigMonitor(pickColor)
 *
 *	   Purpose: Returns a handle to the current biggest monitor
 *	    Inputs: pickColor = flag: true to pick color monitors first
 *	   Returns: a MonitorHandle pointing to the biggest monitor
 *
 */
 
MonitorHandle GetBigMonitor(Boolean pickColor)
{
	ulong biggest = 0, size, color = pickColor;
	MonitorHandle theMonitor, bigMon = nil;
	
	for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next) {
		size = (long)Height(&(*theMonitor)->rect) * (long)Width(&(*theMonitor)->rect);
		if ((size > biggest) || 
			((size == biggest) && ((pickColor && !color) || (!pickColor && color)))) {
			biggest = size;
			bigMon = theMonitor;
			color = (*theMonitor)->color;
		}
	}
	return bigMon;
}

/*
 * GetImageMonitor(theImage)
 *
 *     Purpose: Returns a pointer to the deepest monitor intersected by the image window
 *      Inputs: theImage = pointer to the image we're interested in
 *     Returns: a MonitorHandle of the deepest intersecting monitor
 *
 */
 
MonitorHandle GetImageMonitor(ImageHandle theImage) 
{
	MonitorHandle theMonitor, deepMon = nil;
	ushort depth, deepest = 0;
	Rect theRect, tmpRect;
	
	if (!theImage || !(*theImage)->window) return nil;
	theRect = (*theImage)->wrect;
	GlobalRect(&theRect, (*theImage)->window);
	for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next) {
		if (SectRect(&theRect, &(*theMonitor)->rect, &tmpRect)) {
			depth = (*theMonitor)->depth;
			if (depth > deepest) {
				deepest = depth;
				deepMon = theMonitor;
			}
		}
	}
	return deepMon;
}

/*
 * GetMostDevice(theRect)
 *
 *     Purpose: Returns a pointer to the monitor containing the most area of the window
 *      Inputs: theRect = pointer to a global rectangle
 *     Returns: a MonitorHandle of the relevant intersecting monitor
 *
 */
 
MonitorHandle GetMostDevice(Rect *theRect) 
{
	MonitorHandle theMonitor, mostMon = gMainMonitor;
	long area, largest = 0;
	Rect tmpRect;

	for (theMonitor = gMainMonitor; theMonitor; theMonitor = (*theMonitor)->next) {
		SectRect(&(*theMonitor)->rect, theRect, &tmpRect);
		area = (long)Width(&tmpRect) * (long)Height(&tmpRect);
		if (area > largest) {
			largest = area;
			mostMon = theMonitor;
		}
	}
	return mostMon;
}
