/*	XGAppCore_mac.cpp
 *
 *		This is the Macintosh specific routiens that define the
 *	application core class.
 */

/**********************************************************************

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#include <XConfig.h>

#if OPT_MACOS

#include <XApplication.h>
#include <XWindow.h>
#include <XMessages.h>
#include <XMenu.h>
#include <XError.h>
#include <XModifiers.h>
#include <XDataUtil.h>
#include <XStringUtil.h>
#include <XCursorUtil.h>
#include <XThread.h>
#include <XInline.h>
#include <XFile.h>
#include "MacFloater.h"

#include "appearance.h"

/************************************************************************/
/*																		*/
/*	Globals																*/
/*																		*/
/************************************************************************/

bool _GHasFSSpec;					// Have FSSpec trap? (Sys 6 support)
bool _GHasWNETrap;					// Have WaitNextEvent trap? (Sys 6 support)
bool _GAppFrontmost;				// active application?
bool _GHasDragDrop;					// has drag/drop manager?
long _GAppearance;					// appearance manager present?
long _GMacOSVersion;				// Mac OS Version number?

static AEEventHandlerUPP	GAppleEventProc;
EventRecord					XGAppCore::gLastMessage;

/************************************************************************/
/*																		*/
/*	Gestalt Trap Patching												*/
/*																		*/
/*		Why am I doing this? Because I'm INSANE! Bwwwaahhh ha ha ha ha!	*/
/*	When I started writing YAAF, I started developing it under System	*/
/*	6.0--and there is no good reason why YAAF can't still run under		*/
/*	such an old operating system. The only hitch is that Gestalt() is	*/
/*	not present before 6.0.5, and a lot of the Metrowerks libraries		*/
/*	freely call the Gestalt trap directly.								*/
/*																		*/
/*		To work around this, the following code, which is only called	*/
/*	when running on MacOS v6.0.4 or earlier, creates a simple			*/
/*	Gestalt() trap which returns results based on SysEnvirons() call,	*/
/*	which first came available with the 128K ROMS (the Mac Plus).		*/
/*																		*/
/*		While a lot of my drawing code expects a color Macintosh (and	*/
/*	Color QD availability), at least my core code should work for		*/
/*	models of Macintosh systems going back to System 5.0 or even		*/
/*	earlier...  *GRIN*													*/
/*																		*/
/*		What this means if YAAF runs under MacOS 6.0, it sure as hell	*/
/*	should run right without things like the Appearance Manager...		*/
/*																		*/
/************************************************************************/

/*
 *	Forwards
 */

extern pascal OSErr _GestaltPatch(OSType, long *);

#if __powerc

/*	GestaltPatch
 *
 *		This is the PowerPC version of my GestaltPatch routine, used for
 *	booting up YAAF. As I know the PowerPC always has Gestalt, this is
 *	rather brain dead...
 */

pascal OSErr _GestaltPatch(OSType sel, long *resp)
{
	return Gestalt(sel,resp);
}

#else

/*
 *	Globals
 */

static bool GPatchGestaltAddr;
static long (*GOldGestaltAddr)();

/*	GestaltTrap
 *
 *		My Gestalt trap routine; this is called ONLY on Sys 6.0.5 or
 *	earlier.
 */

static pascal OSErr GestaltTrap(OSType sel, long *resp)
{
	OSErr err;
	SysEnvRec env;
	
	if ((sel != gestaltSystemVersion) && (sel != gestaltQuickdrawVersion)) {
		return gestaltUndefSelectorErr;
	}
	err = SysEnvirons(1,&env);
	if (err) return gestaltUndefSelectorErr;
	if (sel == gestaltSystemVersion) {
		*resp = env.systemVersion;
		return noErr;
	} else {
		if (env.hasColorQD) {
			*resp = gestalt8BitQD;
		} else {
			*resp = gestaltOriginalQD;
		}
		return noErr;
	}
}

/*	_GestaltTrap
 *
 *		The trap itself; this shunts to GestaltTrap. This basically
 *	wrappers the arguments I get out of the Mac trap dispatcher engine,
 *	and calls my Pascal code above.
 */

static asm void _GestaltTrap(void)
{
	/*
	 *	This is called with
	 *
	 *		D0		selector
	 *		A1		pointer to response
	 *
	 *	on return
	 *
	 *		D0		result code
	 */
	
	LINK			A6,#0
	MOVEM.L			A0-A1/D1-D2, -(SP)
	
	SUBQ.W			#2,A7
	MOVE.L			D0,-(A7)
	MOVE.L			A1,-(A7)
	JSR				GestaltTrap
	MOVE.W			(A7)+,D0
	EXT.L			D0
	
	MOVEM.L			(SP)+,A0-A1/D1-D2
	UNLK			A6
	RTS
}

/*	PatchGestaltTrap
 *
 *		This requires a little explanation. If this is running on OS v6.0.4
 *	or earlier, Gestalt() is not present on the operating system. This
 *	can be a problem when trying to get code to work on earlier
 *	versions of the operating system, especially given that the 
 *	Metrowerks code calls Gestalt() everywhere.
 *
 *		Instead of recompiling everything, I figured that I could just
 *	add my own Gestalt() trap instead, if I'm running on an OS earlier
 *	than v6.0.4 that only answers to the gestaltSystemVersion and
 *	gestaltQuickdrawVersion selectors.
 */

static void PatchGestaltTrap()
{
	GOldGestaltAddr = NGetTrapAddress(_Gestalt,0);
	if (GOldGestaltAddr == NGetTrapAddress(_Unimplemented,0)) {
		GPatchGestaltAddr = true;
		NSetTrapAddress((long (*)())_GestaltTrap,_Gestalt,0);
	} else {
		GPatchGestaltAddr = false;
	}
}

/*	UnpatchGestaltTrap
 *
 *		Remove my gestalt patch.
 */

static void UnpatchGestaltTrap()
{
	if (GPatchGestaltAddr) {
		GPatchGestaltAddr = false;
		NSetTrapAddress(GOldGestaltAddr,_Gestalt,0);
	}
}


/*	_GestaltPatch
 *
 *		Call either Gestalt or GestaltTrap depending on if the Gestalt
 *	trap is available
 */

pascal OSErr _GestaltPatch(OSType sel, long *resp)
{
	if (NGetTrapAddress(_Gestalt,0) == NGetTrapAddress(_Unimplemented,0)) {
		return GestaltTrap(sel,resp);
	} else {
		return Gestalt(sel,resp);
	}
}

#endif

/************************************************************************/
/*																		*/
/*	Common Internals													*/
/*																		*/
/************************************************************************/

#pragma mark Initialization and Shutdown

/*	XGAppCore::InitOS
 *
 *		Initialize the operating system. This just does the basic
 *	initialization without the frills. This only gets run once.
 */

void XGAppCore::InitOS()
{
	long flags;
	int i;
	static bool gInit = false;
		
	if (gInit) return;					// run this only once.
	gInit = true;
	_GAppFrontmost = true;
	
	/*
	 *	Core startup
	 */
	
	::InitGraf(&qd.thePort);
	::InitFonts();
	::InitWindows();
	::InitMenus();
	::TEInit();
	::InitDialogs(NULL);
	::InitCursor();
	
	/*
	 *	Flush events
	 */
	
	::FlushEvents(everyEvent,0);
	
	/*
	 *	Set the stack size to 32K (on the 68K Macintosh), if available
	 */
	
#ifndef __powerc
	::SetApplLimit(GetApplLimit() - 32768L);
#endif

	/*
	 *	And add more memory master pointers
	 */
	
	for (i = 0; i < 5; i++) ::MoreMasters();
	::MaxApplZone();
		
	/*
	 *	Now query for different attributes
	 */
	
	// Get MacOS version in a Pre-Gestalt safe manner
	_GestaltPatch(gestaltSystemVersion,&_GMacOSVersion);

	// Are we before System 7? This tests for _Gestalt, and patches if not there
	// I do it before System 7 because the documents are unclear if _Gestalt
	// shows up with System 6.0.4 or System 6.0.5...
#ifndef __powerc
	if (_GMacOSVersion < 0x0700) PatchGestaltTrap();
#endif
	
	// Is Apple Events present? If so, install my procs
	fAppleEvents = false;
	if (!Gestalt(gestaltAppleEventsAttr,&flags)) {
		if (flags & 1) fAppleEvents = true;
	}
	
	if (fAppleEvents) {
		GAppleEventProc = NewAEEventHandlerUPP(AEHandleProc);
		
		::AEInstallEventHandler(kCoreEventClass,kAEOpenApplication,GAppleEventProc,0,false);
		::AEInstallEventHandler(kCoreEventClass,kAEOpenDocuments,GAppleEventProc,0,false);
		::AEInstallEventHandler(kCoreEventClass,kAEPrintDocuments,GAppleEventProc,0,false);
		::AEInstallEventHandler(kCoreEventClass,kAEQuitApplication,GAppleEventProc,0,false);
	}

	// What version of the Appearance Manager (if any) is present?
	_GAppearance = 0;
	if (!Gestalt(gestaltAppearanceAttr, &flags)
		&& (flags & (1 << gestaltAppearanceExists)))
	{
		if (noErr != Gestalt(gestaltAppearanceVersion, &_GAppearance)) {
			_GAppearance = 0x0100;		/* Version 1.0 of this */
		}
		RegisterAppearanceClient();
	}
	
	// Is WaitNextEvent() trap present?
#ifdef __powerc
	_GHasWNETrap = true;
#else
	if (_GMacOSVersion >= 0x0700) _GHasWNETrap = true;
	else {
		if (NGetTrapAddress(_WaitNextEvent,1) != NGetTrapAddress(_Unimplemented,1)) {
			_GHasWNETrap = true;
		} else {
			_GHasWNETrap = false;
		}
	}
#endif

	// Is FSSpec present? Test if we're running before Sys 7; Sys 7 and later
	// has FSSpec
#ifdef __powerc
	_GHasFSSpec = true;
#else
	if (_GMacOSVersion >= 0x0700) _GHasFSSpec = true;
	else {
		if (noErr == Gestalt(gestaltFSAttr,&flags)) {
			_GHasFSSpec = (flags & (1L << gestaltHasFSSpecCalls)) ? true : false;
		} else {
			_GHasFSSpec = false;
		}
	}
#endif

	// Test for drag and drop. 
	_GHasDragDrop = false;
	if (noErr == Gestalt(gestaltDragMgrAttr,&flags)) {
		_GHasDragDrop = (flags & (1L << gestaltDragMgrPresent)) ? true : false;
	}

	/*
	 *	If I'm running MacOS6 or earlier, get a few events; this causes
	 *	me to get activated frontmost when I start the app environment up
	 *	under Multifinder. This is a MacOS 5 and MacOS 6 kludge used to
	 *	make sure I'm brought frontmost when I'm launched, so my copyright
	 *	notice is brought frontmost.
	 */
	
	if ((_GMacOSVersion < 0x700) && (_GHasWNETrap)) {
		EventRecord e;
		int i;
		
		for (i = 0; i < 5; i++) {
			SystemTask();
			GetNextEvent(everyEvent,&e);
		}
	}
}

/*	XGAppCore::KillOS
 *
 *		Kill the operating system stuff
 */

void XGAppCore::KillOS()
{
	if (fAppleEvents) {
		DisposeAEEventHandlerUPP(GAppleEventProc);
	}
	if (_GAppearance)
		UnregisterAppearanceClient();

#ifndef __powerc
	if (_GMacOSVersion < 0x0605) UnpatchGestaltTrap();
#endif
}

/*	XGAppCore::GetAppArgs
 *
 *		This is called just before the event pump is started on the
 *	Macintosh.
 */

void XGAppCore::GetAppArgs()
{
	if (fAppleEvents) {
	} else {
		InitOS6Events();
	}
}

/************************************************************************/
/*																		*/
/*	Event Pump Window Support											*/
/*																		*/
/************************************************************************/

#pragma mark -
#pragma mark Window Support

/*	XGAppCore::GetWindowMinMax
 *
 *		Find the minimum, maximum window sizes
 */

void XGAppCore::GetWindowMinMax(WindowPtr w, XRect *r)
{
	XPoint pt;
	XGWindow *tw = XGWindow::GetXGWindow(w);
	
	if (tw == NULL) {
		SetRect(r,75,75,4096,4096);
	} else {
		pt = tw->MinWindowSize();
		if (pt.h == 0) pt.h = 75;
		if (pt.v == 0) pt.v = 75;
		r->left = pt.h;
		r->top = pt.v;
		
		pt = tw->MaxWindowSize();
		if (pt.h == 0) pt.h = 4096;
		if (pt.v == 0) pt.v = 4096;
		r->right = pt.h;
		r->bottom = pt.v;
	}
}

/*	XGAppCore::SetWindowZoom
 *
 *		Set the zoom rectangle for this window to the maximum
 *	zoom size.
 */

void XGAppCore::SetWindowZoom(WindowPtr w)
{
	XPoint pt;
	XGWindow *tw;
	WStateData **wHandle;
	
	tw = XGWindow::GetXGWindow(w);
	if (tw == NULL) return;
	pt = tw->ZoomWindowSize();				// zoom window size
	
	wHandle = (WStateData **)(((WindowPeek)w)->dataHandle);
	if (pt.h != 0) (**wHandle).stdState.right = (**wHandle).stdState.left + pt.h;
	if (pt.v != 0) (**wHandle).stdState.bottom = (**wHandle).stdState.top + pt.v;
}

/*	XGAppCore::DrawGrowIcon
 *
 *		Draw the grow icon in this window
 */

void XGAppCore::DrawGrowIcon(WindowPtr w)
{
	Rect r;
	
	::SetPort(w);
	::SetOrigin(0,0);
	r = w->portRect;
	r.top = r.bottom - 15;
	r.left = r.right - 15;
	::ClipRect(&r);
	::DrawGrowIcon(w);
}

/************************************************************************/
/*																		*/
/*	Event Translation													*/
/*																		*/
/************************************************************************/

#pragma mark -
#pragma mark Keyboard Translation

/*	XGAppCore::GetEventFlags
 *
 *		This formulates the modifier flag bit field depending on the
 *	current event
 */

short XGAppCore::GetEventFlags(EventRecord &e)
{
	short flags = 0;
	
	if (e.modifiers & shiftKey)			flags |= KKeyShift;
	if (e.modifiers & controlKey)		flags |= KKeyControl;
	if (e.modifiers & cmdKey)			flags |= KKeyCommand;
	if (e.modifiers & optionKey)		flags |= KKeyOption;
	if (e.modifiers & btnState)			flags |= KKeyButton;
	
	if ((e.what == keyDown) || (e.what == autoKey)) {
		char key;
		
		key = (char)(e.message);
		switch (key) {
			case 0x1C: case 0x1D: case 0x1E: case 0x1F:
			case 0x0B: case 0x0C: case 0x01: case 0x04:
			case 0x1B: case 0x7F:
			case 0x05: case 0x10:
				flags |= KSpecialKey;
		}
	}
	
	gLastMods = flags;					// remember this...
	return flags;
}

/*	XGAppCore::GetKeyCode
 *
 *		Get the keyboard character code
 */

short XGAppCore::GetKeyCode(EventRecord &e)
{
	char key;
	char code;
	
	key = (char)(e.message);
	code = (char)(e.message >> 8);
	
	switch (key) {
		case 0x1C: 
			return KKeyLeftCursor;
		case 0x1D: 
			return KKeyRightCursor;
		case 0x1E: 
			return KKeyUpCursor;
		case 0x1F:
			return KKeyDownCursor;
		case 0x0B: 
			return KKeyPageUp;
		case 0x0C: 
			return KKeyPageDown;
		case 0x01: 
			return KKeyHome;
		case 0x04:
			return KKeyEnd;
		case 0x1B: 
			if (code == 0x47) return KKeyClear;
			return KKeyEscape;
		case 0x03:
		case 0x0D: 
			return 0x0D;					// return key
		case 0x7F:
			return KKeyDelete;
		case 0x05:
			return KKeyHelp; 
		case 0x10:
			switch (code) {
				case 0x7A:		return KKeyF1;
				case 0x78:		return KKeyF2;
				case 0x63:		return KKeyF3;
				case 0x76:		return KKeyF4;
				case 0x60:		return KKeyF5;
				case 0x61:		return KKeyF6;
				case 0x62:		return KKeyF7;
				case 0x64:		return KKeyF8;
				case 0x65:		return KKeyF9;
				case 0x6D:		return KKeyF10;
				case 0x67:		return KKeyF11;
				case 0x6F:		return KKeyF12;
				case 0x69:		return KKeyF13;
				case 0x6B:		return KKeyF14;
				case 0x71:		return KKeyF15;
				default:		return key;
			}
		default:
			return key;						// normal key
	}
}

/************************************************************************/
/*																		*/
/*	Finder/Apple Events													*/
/*																		*/
/************************************************************************/

#pragma mark -
#pragma mark Finder Events

/*	XGAppCore::InitOS6Events
 *
 *		If I'm running on MacOS6, this gets the files
 */

void XGAppCore::InitOS6Events()
{
#ifndef __powerc							// only on 68K macs
	short what,count;
	short index;
	AppFile file;
	FSSpec fdata;
	XGFileSpecifier fspec;
	
	::CountAppFiles(&what,&count);
	if (count == 0) {
		XGDispatch::SendDispatch(KXGOpenApplication,0,NULL);
	} else {
		for (index = 1; index <= count; index++) {
			::GetAppFiles(index,&file);
			fdata.vRefNum = file.vRefNum;
			fdata.parID = 0;
			if (noErr == GetWDInfo(file.vRefNum,&fdata.vRefNum,&fdata.parID,NULL)) {
				pstrcpy(fdata.name,file.fName);
				
				/*
				 *	At this point, we've parsed the info file
				 */
				
				fspec = fdata;
				if (what == 0) {
					XGDispatch::SendDispatch(KXGDoOpenDocument,0,(void *)&fspec);
				} else {
					XGDispatch::SendDispatch(KXGDoPrintDocument,0,(void *)&fspec);
				}
			}
		}
		
		if (what == 1) {
			XGDispatch::SendDispatch(KXGQuitApplication,0,NULL);
		}
	}
#endif
}

/*	XGAppCore::GotRequiredParams
 *
 *		Check to see all the required parameters were fetched
 */

OSErr XGAppCore::GotRequiredParams(const AppleEvent *event)
{
	OSErr myErr;
	long size;
	DescType returnType;
	
	myErr = AEGetAttributePtr(event,keyMissedKeywordAttr,typeWildCard,&returnType,NULL,0,&size);
	if (myErr == noErr) return errAEParamMissed;
	else if (myErr == errAEDescNotFound) return noErr;
	else return myErr;
}

/*	XGAppCore::AEHandleProc
 *
 *		This is the code to handle the Apple Events for MacOS7 and later
 */

pascal OSErr XGAppCore::AEHandleProc(const AppleEvent *event, AppleEvent */*ret*/, long)
{
	OSErr myErr;
	long eClass,eType;
	DescType returnType;
	long size;
	long off,len;
	AEDescList docList;
	AEKeyword keyWord;
	FSSpec file;
	XGFileSpecifier fspec;
	
	/*
	 *	Get the Apple Event message type
	 */
	
	if (noErr != (myErr = AEGetAttributePtr(event,keyEventClassAttr,typeType,&returnType,(Ptr)&eClass,sizeof(eClass),&size))) return myErr;
	if (noErr != (myErr = AEGetAttributePtr(event,keyEventIDAttr,typeType,&returnType,(Ptr)&eType,sizeof(eType),&size))) return myErr;
	
	/*
	 *	We've now got the class and type of the event. Dispatch for the
	 *	common four entry points.
	 */
	
	try {
		if (eClass == kCoreEventClass) {
			switch (eType) {
				case kAEOpenApplication:
					/*
					 *	New application event
					 */
					
					myErr = GotRequiredParams(event);
					if (myErr == noErr) {				// new file
						XGDispatch::SendDispatch(KXGOpenApplication,0,NULL);
					}
					return myErr;
				case kAEOpenDocuments:
					/*
					 *	Open new documents
					 */
					
					if (noErr != (myErr = AEGetParamDesc(event,keyDirectObject,typeAEList,&docList))) return myErr;
					if (noErr != (myErr = AECountItems(&docList,&len))) {
						AEDisposeDesc(&docList);
						return myErr;
					}
					for (off = 1; off <= len; off++) {
						if (noErr != (myErr = AEGetNthPtr(&docList,off,typeFSS,&keyWord,&returnType,(Ptr)&file,sizeof(file),&size))) {
							AEDisposeDesc(&docList);
							return myErr;
						}
						
						fspec = file;
						XGDispatch::SendDispatch(KXGDoOpenDocument,0,(void *)&fspec);
					}
					AEDisposeDesc(&docList);
					break;
				case kAEPrintDocuments:
					/*
					 *	Print specified documents
					 */
					
					if (noErr != (myErr = AEGetParamDesc(event,keyDirectObject,typeAEList,&docList))) return myErr;
					if (noErr != (myErr = AECountItems(&docList,&len))) {
						AEDisposeDesc(&docList);
						return myErr;
					}
					for (off = 1; off <= len; off++) {
						if (noErr != (myErr = AEGetNthPtr(&docList,off,typeFSS,&keyWord,&returnType,(Ptr)&file,sizeof(file),&size))) {
							AEDisposeDesc(&docList);
							return myErr;
						}
						
						fspec = file;
						XGDispatch::SendDispatch(KXGDoPrintDocument,0,(void *)&fspec);
					}
					AEDisposeDesc(&docList);
					break;
				case kAEQuitApplication:
					/*
					 *	Quit this application
					 */
					
					myErr = GotRequiredParams(event);
					if (myErr == noErr) {
						if (-1 == XGDispatch::SendDispatch(KXGQuitApplication,0,NULL)) {
							return 1;
						} else {
							return noErr;
						}
					} else return myErr;
				default:
					return errAEEventNotHandled;			// Not able to handle event
			}
		
			return GotRequiredParams(event);				// Fall through to handle this		
		}
	}
	catch (XGPostMacOSError &e) {
		e.DisplayError();
		return e.GetMacOSError();
	}
	catch (XGError &e) {
		e.DisplayError();
		return noErr;										// huh?
	}
	catch (...) {
		Error(KXGRandomException);
		return noErr;
	}
	
	/*
	 *	Process the rest of the possible events
	 */
	
	return errAEEventNotHandled;
}

/*	XGAppCore::InitAppleEvents
 *
 *		Initialize apple events
 */

void XGAppCore::InitAppleEvents(void)
{
}

/************************************************************************/
/*																		*/
/*	Application Event Pump												*/
/*																		*/
/************************************************************************/

#pragma mark -
#pragma mark Event Pump and Dispatch

/*	XGAppCore::DoMenuKey
 *
 *		Handle the keyboard menu event
 */

bool XGAppCore::DoMenuKey(char c)
{
	return GetCurMenu()->MenuKey(c);
}

/*	XGAppCore::DoMenuClick
 *
 *		Handle the mouse in menu event
 */

void XGAppCore::DoMenuClick(XPoint pt)
{
	GetCurMenu()->MenuSelect(pt.h,pt.v);
}

/*	XGAppCore::DoWindowKey
 *
 *		Handle keyboard event that is being sent to a window.
 */

void XGAppCore::DoWindowKey(EventRecord &e)
{
	short event;
	short key;
	
	event = GetEventFlags(e);
	key   = GetKeyCode(e);
	
	if (event & KKeyCommand) {
		XGDispatch::SendDispatch(KXGMenuKey,
									MAKEDWORD(event,key),
									NULL);
	} else {
		XGDispatch::SendDispatch(KXGKeyEvent,
									MAKEDWORD(event,key),
									NULL);
	}
}

/*	XGAppCore::DoActivate
 *
 *		Handle activate event. This sends the activation/deactivation
 *	events
 */

void XGAppCore::DoActivate(WindowPtr w, bool flag)
{
	XGWindow *tw;
	
	tw = XGWindow::GetXGWindow(w);
	if (tw) {
		tw->ProcessActivate(flag);
		if (!(tw->GetWindowAttributes() & KAttrNoResize)) {
			DrawGrowIcon(w);
		}
	}
}

/*	XGRDoActivate
 *
 *		Insert this into my pump
 */

void XGRDoActivate(WindowPtr w, short f)
{
	XGAppCore::GetApplication()->DoActivate(w,f);
}

/*	XGAppCore::DoUpdate
 *
 *		Find the bounding rectangle for the update region of my
 *	window, and send the update event
 */

void XGAppCore::DoUpdate(WindowPtr w)
{
	RgnHandle rgn;
	Rect r;
	XGWindow *tw;
	Point p;
	
	/*
	 *	Force a reset of the window
	 */
	
	::SetPort(w);
	::SetOrigin(0,0);
	::ClipRect(&(w->portRect));
	
	/*
	 *	Now get the update rectangle
	 */
	
	rgn = ((WindowPeek)w)->updateRgn;
	r = (**rgn).rgnBBox;
	
	/*
	 *	And convert it to window coordinates
	 */
	
	p.h = r.left;
	p.v = r.top;
	GlobalToLocal(&p);
	r.left = p.h;
	r.top = p.v;
	p.h = r.right;
	p.v = r.bottom;
	GlobalToLocal(&p);
	r.right = p.h;
	r.bottom = p.v;
	
	/*
	 *	Now handle the udpate event
	 */
	
	tw = XGWindow::GetXGWindow(w);
	
	::BeginUpdate(w);
	if (tw) {
		if (!(tw->GetWindowAttributes() & KAttrNoResize)) {
			DrawGrowIcon(w);
		}
		tw->ProcessDraw(r);
		if (!(tw->GetWindowAttributes() & KAttrNoResize)) {
			DrawGrowIcon(w);
		}
	}
	::EndUpdate(w);
}

/*	XGAppCore::DoSizeContents
 *
 *		Handle the resize of the content region of this window
 */

void XGAppCore::DoSizeContents(WindowPtr w)
{
	XGWindow *tw;
	
	tw = XGWindow::GetXGWindow(w);
	if (tw) tw->ProcessSizeWindow();
}

/*	XGAppCore::DoMouseMove
 *
 *		Handle mouse motions when the mouse is not down
 */

void XGAppCore::DoMouseMove(EventRecord &e)
{
	short i;
	WindowPtr w;
	XGWindow *tw;
	XGPopWindow *pw;
	Point pt;
	short flags;
		
	i = FindWindow(e.where,&w);
	if (i == inContent) {
		if ((w == ::MFrontWindow()) || (w == ::FrontWindow())) {
			tw = XGWindow::GetXGWindow(w);
			pt = e.where;
			flags = GetEventFlags(e);
					
			::SetPort(w);
			::SetOrigin(0,0);
			::GlobalToLocal(&pt);
			
			if (tw) {
				tw->ProcessMouseMove(pt,flags,NULL);
			} else {
				pw = XGPopWindow::GetPopWindow(w);
				if (pw) {
					pw->ProcessMouseMove(pt,flags);
				}
			}
			return;
		}
	}
	
	/*
	 *	If we get here, the mouse was not inside the content of a
	 *	window.
	 */
	
	XGWindow::ProcessMouseLeave();		// just in case it did leave.
	::SetCursorID(KNormalCursor);
}

/*	XGAppCore::DoWindowClick
 *
 *		Handle window click event
 */

void XGAppCore::DoWindowClick(WindowPtr w, EventRecord &e)
{
	Point pt;
	XGWindow *tw;
	XGPopWindow *pw;
	short flags;
	XGView *capture;
	bool hold;
	
	/*
	 *	Get the XGWidnow pointer
	 */
	
	tw = XGWindow::GetXGWindow(w);
	if (tw == NULL) {
		pw = XGPopWindow::GetPopWindow(w);
		if (pw == NULL) return;
	}
	
	/*
	 *	Get the location of the mouse
	 */
	
	pt = e.where;
	::SetPort(w);
	::GlobalToLocal(&pt);
	pt.h -= w->portRect.left;
	pt.v -= w->portRect.top;
	
	/*
	 *	Get the status flags
	 */
	
	flags = GetEventFlags(e);
	
	/*
	 *	Screen for double click
	 */
	
	if (fLastTime + ::GetDblTime() >= ::TickCount()) {
		XRect r;
		
		r.left = fLastClick.h - 3;
		r.right = fLastClick.h + 3;
		r.top = fLastClick.v - 3;
		r.bottom = fLastClick.v + 3;
		
		if (PtInRect(pt,&r)) flags |= KDoubleClick;
	}
	
	/*
	 *	Send the event and spin if the mouse is captured
	 */
	
	if (tw) {
		capture = tw->ProcessMouseDown(pt,flags);
	} else if (pw) {
		capture = pw->ProcessMouseDown(pt,flags);
	} else {
		return;			//???
	}
	
	/*
	 *	Run the loop, processing mouse drag events
	 */
	
	if (NULL != capture) {
		flags |= KCapturedMouse;
		DoPostEvent();
		
		while (::WaitMouseUp()) {
			DoPreEvent();
			RunUpdateOnce();
			
			::SetPort(w);
			::GetMouse(&pt);
			pt.h -= w->portRect.left;
			pt.v -= w->portRect.top;
			
			if (tw) {
				tw->ProcessMouseMove(pt,flags,capture);
			} else {
				pw->ProcessMouseMove(pt,flags);		// has only one view
			}
			DoPostEvent();
		}
			
		::SetPort(w);
		::GetMouse(&pt);
		pt.h -= w->portRect.left;
		pt.v -= w->portRect.top;
		
		DoPreEvent();
		if (tw) {
			hold = tw->ProcessMouseUp(pt,flags,capture);
		} else {
			hold = pw->ProcessMouseUp(pt,flags);
		}
		
		while (hold) {
			DoPostEvent();
			
			/*
			 *	View didn't want to give up the mouse. So
			 *	continue to act as if the mouse is down
			 */
			
			for (;;) {
				/*
				 *	Cycle until mouse down
				 */
				
				EventRecord event;
				if (::GetOSEvent(mDownMask,&event)) break;
				
				DoPreEvent();
				
				RunUpdateOnce();
				::SetPort(w);
				::GetMouse(&pt);
				pt.h -= w->portRect.left;
				pt.v -= w->portRect.top;
				
				if (tw) {
					tw->ProcessMouseMove(pt,flags,capture);
				} else {
					pw->ProcessMouseMove(pt,flags);		// has only one view
				}
				
				DoPostEvent();
			}
			while (::WaitMouseUp()) {
				DoPreEvent();
				
				/*
				 *	Now cycle until the mouse is released again
				 */
				
				RunUpdateOnce();
				
				::SetPort(w);
				::GetMouse(&pt);
				pt.h -= w->portRect.left;
				pt.v -= w->portRect.top;
				
				if (tw) {
					tw->ProcessMouseMove(pt,flags,capture);
				} else {
					pw->ProcessMouseMove(pt,flags);		// has only one view
				}
				
				DoPostEvent();
			}
			DoPreEvent();
			
			/*
			 *	And send the message that the mouse came up, on the
			 *	off chance the view wants to give up the cursor
			 */
				
			::SetPort(w);
			::GetMouse(&pt);
			pt.h -= w->portRect.left;
			pt.v -= w->portRect.top;
			
			if (tw) {
				hold = tw->ProcessMouseUp(pt,flags,capture);
			} else {
				hold = pw->ProcessMouseUp(pt,flags);
			}
		}
	}
	
	/*
	 *	Remember the last mouse down location
	 */
	
	fLastTime = ::TickCount();
	fLastClick = pt;
}


/*	XGAppCore::DoCloseWindow
 *
 *		Handle the window close event
 */

void XGAppCore::DoCloseWindow(WindowPtr w)
{
	short type;
	XGWindow *tw;
	
	type = ((WindowPeek)w)->windowKind;
	if (type < 0) {
		/*
		 *	Close desk accessory
		 */
		
		CloseDeskAcc(type);
	} else {
		tw = XGWindow::GetXGWindow(w);
		Assert(tw != NULL);						// it's not mine???
		tw->CloseWindow();
	}
}

/*	XGAppCore::DoMouseClick
 *
 *		Handle mouse down event by figuring out where this happened
 */

void XGAppCore::DoMouseClick(EventRecord &e)
{
	short i;
	WindowPtr w;
	XRect r;
	Rect mr;
	long l;
	bool flag;
	XGWindow *tw;
	XGPopWindow *pw;
	unsigned short attr;
	
	w = NULL;
	i = ::FindWindow(e.where,&w);
	tw = XGWindow::GetXGWindow(w);
	pw = XGPopWindow::GetPopWindow(w);
	switch (i) {
		case inDesk:
			break;
		case inMenuBar:
			DoMenuClick(e.where);
			break;
		case inSysWindow:				// click in system window
			XGInline::Close();
			::SystemClick(&e,w);
			break;
		case inContent:					// click in content region of window
			if (fModalWindow) {
				/*
				 *	If modal window, clicks only in the modal window
				 *	or in a popup window (which could be part of a control
				 *	in that modal window
				 */
				
				if ((tw && (tw == fModalWindow)) || (pw != NULL)) {
					DoWindowClick(w,e);
				}
				break;
			}
			if ((w != ::MFrontWindow()) && (w != ::FrontWindow())) {
				/*
				 *	Select window if not frontmost. Pass click onwards
				 *	unless this has been set not to.
				 */
				
				MSelectWindow(w);	// modal dialog support
				
				if (tw) {
					attr = tw->GetWindowAttributes();
					if (attr & KAttrNoClick) break;
				} else break;
			}
			DoWindowClick(w,e);
			break;
		case inDrag:
			XGInline::Close();
			if (fModalWindow) {
				if (tw != fModalWindow) break;
			}
			::SetRect(&mr,-16384,-16384,16384,16384);
			::MDragWindow(w,e.where,&mr);
			break;
		case inGrow:
			XGInline::Close();
			if (fModalWindow) {
				if (tw != fModalWindow) break;
			}
			GetWindowMinMax(w,&r);
			mr = r.MacRect();
			l = ::GrowWindow(w,e.where,&mr);
			if (l) {
				::SizeWindow(w,(short)l,(short)(l >> 16),false);
				::SetPort(w);
				::InvalRect(&(w->portRect));
				DoSizeContents(w);
			}
			break;
		case inGoAway:
			XGInline::Close();
			if (fModalWindow) {
				if (tw != fModalWindow) break;
			}
			flag = ::TrackGoAway(w,e.where);
			if (flag) DoCloseWindow(w);
			break;
		case inZoomIn:
		case inZoomOut:
			XGInline::Close();
			if (fModalWindow) {
				if (tw != fModalWindow) break;
			}
			SetWindowZoom(w);
			flag = ::TrackBox(w,e.where,i);
			if (flag) {
				::SetPort(w);
				::EraseRect(&(w->portRect));
				::ZoomWindow(w,i,true);
				::InvalRect(&(w->portRect));
				DoSizeContents(w);
			}
			break;
	}
}

/*	XGAppCore::DoEvent
 *
 *		Run the event loop pump.
 */

void XGAppCore::DoEvent()
{
	RgnHandle rgn;
	Point pt;
	Rect r;
	unsigned long wneTime = ::GetCaretTime();
	
	/*
	 *	If we have several threads running, we need to yield as much
	 *	CPU to those threads as we can get away with, without degrading
	 *	the performance of the MacOS app.
	 */

	if (XGThread::GetThreadCount() > 1) {
		unsigned long tickcount;
		
		wneTime = 1;						// minimize time in WaitNextEvent
		tickcount = ::TickCount() + 10;		// give 1/6th second to other threads
		while (tickcount >= ::TickCount()) {
			GYieldThread = true;			// does everyone else want to give up
			XGThread::YieldThread(true);	// their timeslice? If so, screw this.
			if (GYieldThread) break;		// and give up time to MacOS system.
		}
	}
	
	if (_GHasWNETrap) {
		/*
		 *	Find the move mouse region
		 */
		
		::GetMouse(&pt);
		::LocalToGlobal(&pt);
		rgn = ::NewRgn();
		::SetRect(&r,pt.h,pt.v,pt.h+1,pt.v+1);
		::RectRgn(rgn,&r);
		
		/*
		 *	Now poll for events. The region I set up will give me
		 *	null events if the mouse ever moves
		 */
		
		::WaitNextEvent(everyEvent,&gLastMessage,wneTime,rgn);
		::DisposeRgn(rgn);
	} else {
		/*
		 *	GetNextEvent? We're running on an __OLD__ system...
		 */
		
		SystemTask();
		GetNextEvent(everyEvent,&gLastMessage);
	}
	
	/*
	 *	Send the event event
	 */
	
	if (FilterEvent((void *)&gLastMessage)) {
		/* Event was processed. Bail. */
		return;
	}
	if (1 == XGDispatch::SendDispatch(KXGAppEvent,0,(void *)&gLastMessage)) {
		/* Event was processed. Bail */
		return;
	}
	
	/*
	 *	Now process the event
	 */
	
	switch (gLastMessage.what) {
		case mouseDown:
			DoMouseClick(gLastMessage);
			break;
		case keyDown:
			if (gLastMessage.modifiers & cmdKey) {
				if (DoMenuKey((char)gLastMessage.message)) break;
			}
			/* Fall through to */
		case autoKey:
			DoWindowKey(gLastMessage);
			break;
		case updateEvt:
			DoUpdate((WindowPtr)(gLastMessage.message));
			break;
		case osEvt:
			if (0x01 == (unsigned char)(gLastMessage.message >> 24)) {
				/* Application suspend/resume */
				if (gLastMessage.message & 0x01) {
					_GAppFrontmost = true;
					DoActivate(MFrontWindow(),true);
				} else {
					DoActivate(MFrontWindow(),false);
					_GAppFrontmost = false;
					
					/*
					 *	The following is a kludge to get my
					 *	cursor utilities into a known state so
					 *	I can reset the cursor when I'm frontmost
					 *	again.
					 */
					
					SetCursorID(KNormalCursor);
				}
			}
			break;
		case activateEvt:
//			DoActivate((WindowPtr)(gLastMessage.message),
//					   (gLastMessage.modifiers & 1) ? true : false);
			break;
			
		case kHighLevelEvent:
			if (fAppleEvents) {
				AEProcessAppleEvent(&gLastMessage);
			}
			break;
		
		default:
			DoMouseMove(gLastMessage);
			break;
	}
	
	/* Make sure menu bar is cleaned up */
	::HiliteMenu(0);
}

/*	XGAppCore::RunUpdateOnce
 *
 *		This runs the event loop once for update events onnly
 */

void XGAppCore::RunUpdateOnce(void)
{	
	::GetOSEvent(updateMask,&gLastMessage);
	
	/*
	 *	Send the event event
	 */
	 
	if (FilterEvent((void *)&gLastMessage)) {
		/* Event was processed. Bail. */
		return;
	}
	if (1 == XGDispatch::SendDispatch(KXGAppEvent,0,(void *)&gLastMessage)) {
		/* Event was processed. Bail */
		return;
	}
	
	/*
	 *	Now process the update event only.
	 */
	
	switch (gLastMessage.what) {
		case updateEvt:
			DoUpdate((WindowPtr)(gLastMessage.message));
			break;
	}
	
	/* Make sure menu bar is cleaned up */
	::HiliteMenu(0);
}

/*	XGAppCore::CancelCheck
 *
 *		This runs the event loop once for update events onnly
 */

bool XGAppCore::CancelCheck(void)
{
	bool status = false;
	
	if (_GHasWNETrap) {
		::WaitNextEvent(everyEvent,&gLastMessage,1,NULL);
	} else {
		SystemTask();
		GetNextEvent(everyEvent,&gLastMessage);
	}
	
	/*
	 *	Send the event event
	 */
	 
	if (FilterEvent((void *)&gLastMessage)) {
		/* Event was processed. Bail. */
		return false;
	}
	if (1 == XGDispatch::SendDispatch(KXGAppEvent,0,(void *)&gLastMessage)) {
		/* Event was processed. Bail */
		return false;
	}
	
	/*
	 *	Now process the update event only.
	 */
	
	switch (gLastMessage.what) {
		case updateEvt:
			DoUpdate((WindowPtr)(gLastMessage.message));
			break;
		case keyDown:
			if (((gLastMessage.modifiers & cmdKey) != 0) &&
					((gLastMessage.message & charCodeMask) == '.'))
				status = true;
			break;
	}
	
	/* Make sure menu bar is cleaned up */
	::HiliteMenu(0);
	return status;
}

#endif // OPT_MACOS
