/*	SCCS Id: @(#)macwin.c	3.4	1996/01/15	*/
/* Copyright (c) Jon W{tte, Hao-Yang Wang, Jonathan Handler 1992. */
/* NetHack may be freely redistributed.  See license for details. */

#include "hack.h"
#include "func_tab.h"
#include "macwin.h"
#include "mactty.h"
#include "wintty.h"

#if !TARGET_API_MAC_CARBON
#include <LowMem.h>
#include <AppleEvents.h>
#include <Gestalt.h>
#include <TextUtils.h>
#include <DiskInit.h>
#include <Script.h>

#ifdef MAC_MPW
# include <ControlDefinitions.h>
#endif
#endif

NhWindow *theWindows = (NhWindow *) 0;
Cursor qdarrow;

#ifdef MAC_MPW
#define qdarrow qd.arrow
#define USESROUTINEDESCRIPTORS
#endif /* Macintosh Programmer's Workshop */

/* Borrowed from the Mac tty port */
extern WindowPtr _mt_window;

/* Some useful #defines for the scroll bar width and height */
#define		SBARWIDTH	15
#define		SBARHEIGHT	15

/*
 * We put a TE on the message window for the "top line" queries.
 * top_line is the TE that holds both the query and the user's
 * response.  The first topl_query_len characters in top_line are
 * the query, the rests are the response.  topl_resp is the valid
 * response to a yn query, while topl_resp[topl_def_idx] is the
 * default response to a yn query.
 */
static TEHandle top_line = (TEHandle) nil;
static int	topl_query_len;
static int	topl_def_idx = -1;
static char	topl_resp[10] = "";

#define CHAR_ANY '\n'

/*
 * inSelect means we have a menu window up for selection or
 * something similar. It makes the window with win number ==
 * inSelect a movable modal (unfortunately without the border)
 * and clicking the close box forces an RET into the key
 * buffer. Don't forget to set inSelect to WIN_ERR when you're
 * done...
 */
static winid inSelect = WIN_ERR;

/*
 * The key queue ring buffer where Read is where to take from, 
 * Write is where next char goes and count is queue depth.
 */
static unsigned char keyQueue [QUEUE_LEN];
static int keyQueueRead = 0,
	keyQueueWrite = 0,
	keyQueueCount = 0;

static Boolean gClickedToMove = 0;	/* For ObscureCursor */

static Point clicked_pos;	/* For nh_poskey */
static int clicked_mod;
static Boolean cursor_locked = false;

static ControlActionUPP MoveScrollUPP;		/* scrolling callback, init'ed in InitMac */

void
lock_mouse_cursor(Boolean new_cursor_locked) {
	cursor_locked = new_cursor_locked;
}


/*
 * Add key to input queue, force means flush left and replace if full
 */
void
AddToKeyQueue (unsigned char ch, Boolean force) {
	if (keyQueueCount < QUEUE_LEN) {
		keyQueue [keyQueueWrite++] = ch;
		keyQueueCount++;
	}
	else if (force) {
		keyQueue [keyQueueWrite++] = ch;
		keyQueueRead++;
		if (keyQueueRead >= QUEUE_LEN)
			keyQueueRead = 0;
		keyQueueCount = QUEUE_LEN;
	}
	if (keyQueueWrite >= QUEUE_LEN)
		keyQueueWrite = 0;
}


/*
 * Get key from queue
 */
unsigned char
GetFromKeyQueue (void) {
	unsigned char ret;

	if (keyQueueCount) {
		ret = keyQueue [keyQueueRead++];
		keyQueueCount--;
		if (keyQueueRead >= QUEUE_LEN)
			keyQueueRead = 0;
	}
	else
		ret = 0;
	return ret;
}


/*
 * Cursor movement
 */
static RgnHandle gMouseRgn = (RgnHandle) 0;

/*
 * _Gestalt madness - we rely heavily on the _Gestalt glue, since we
 * don't check for the trap...
 */
MacFlags macFlags;

/*
 * The screen layouts on the small 512x342 screen need special cares.
 */
Boolean small_screen = 0;

#ifdef NHW_BASE
# undef NHW_BASE
#endif
#define NHW_BASE 0

static int FDECL(filter_scroll_key,(const int, NhWindow *));

static void FDECL(GeneralKey, (EventRecord *, WindowPtr));
static void FDECL(macKeyMenu, (EventRecord *, WindowPtr));
static void FDECL(macKeyText, (EventRecord *, WindowPtr));

static void FDECL(macClickMessage, (EventRecord *, WindowPtr));
static void FDECL(macClickTerm, (EventRecord *, WindowPtr));
static void FDECL(macClickMenu, (EventRecord *, WindowPtr));
static void FDECL(macClickText, (EventRecord *, WindowPtr));

static short FDECL(macDoNull, (EventRecord *, WindowPtr));
static short FDECL(macUpdateMessage, (EventRecord *, WindowPtr));
static short FDECL(macUpdateMenu, (EventRecord *, WindowPtr));
static short FDECL(GeneralUpdate, (EventRecord *, WindowPtr));

static void FDECL(macCursorTerm, (EventRecord *, WindowPtr, RgnHandle));
static void FDECL(GeneralCursor, (EventRecord *, WindowPtr, RgnHandle));

static void FDECL(DoScrollBar,(Point, short, ControlHandle, NhWindow *));
static pascal void FDECL(MoveScrollBar, (ControlHandle, short));

typedef void (*CbFunc) (EventRecord *, WindowPtr);
typedef short (*CbUpFunc) (EventRecord *, WindowPtr);
typedef void (*CbCursFunc) (EventRecord *, WindowPtr, RgnHandle);

#define NUM_FUNCS 6
static const CbFunc winKeyFuncs [NUM_FUNCS] = {
	GeneralKey, GeneralKey, GeneralKey, GeneralKey, macKeyMenu, macKeyText
};

static const CbFunc winClickFuncs [NUM_FUNCS] = {
	(CbFunc)macDoNull, macClickMessage, macClickTerm, macClickTerm, macClickMenu,
	macClickText
};

static const CbUpFunc winUpdateFuncs [NUM_FUNCS] = {
	macDoNull, macUpdateMessage, image_tty, image_tty,
	macUpdateMenu, GeneralUpdate
};

static const CbCursFunc winCursorFuncs [NUM_FUNCS] = {
	(CbCursFunc) macDoNull, GeneralCursor, macCursorTerm, macCursorTerm,
	GeneralCursor, GeneralCursor
};


static NhWindow *
GetNhWin(WindowPtr mac_win) {
	if (mac_win == _mt_window)	/* term window is still maintained by both systems, and */
		return theWindows;		/* WRefCon still refers to tty struct, so we have to map it */
	else {
		NhWindow *aWin = (NhWindow *)GetWRefCon (mac_win);
		if (aWin >= theWindows && aWin < &theWindows[NUM_MACWINDOWS])
			return aWin;
	}
	return ((NhWindow *) nil);
}


Boolean CheckNhWin (WindowPtr mac_win) {
	return GetNhWin (mac_win) != nil;
}


static pascal OSErr
AppleEventHandler (const AppleEvent* inAppleEvent, AppleEvent* outAEReply, long inRefCon) {
#if defined(__SC__) || defined(__MRC__)
# pragma unused(outAEReply,inRefCon)
#endif
	Size     actualSize;
	DescType typeCode;
	AEEventID EventID;
	OSErr    err;

	/* Get Event ID */
	err = AEGetAttributePtr (inAppleEvent, keyEventIDAttr, typeType, &typeCode,
								&EventID, sizeof (EventID), &actualSize);
	if (err == noErr) {
		switch (EventID) {
			default :
			case kAEOpenApplication :
				macFlags.gotOpen = 1;
				/* fall through */
			case kAEPrintDocuments :
				err = errAEEventNotHandled;
				break;
			case kAEQuitApplication :
				/* Flush key queue */
				keyQueueCount = keyQueueWrite = keyQueueRead = 0;
				AddToKeyQueue ('S', 1);
				break;
			case kAEOpenDocuments : {
				FSSpec      fss;
				FInfo fndrInfo;
				AEKeyword   keywd;
				AEDescList  docList;
				long     index, itemsInList;

				if((err = AEGetParamDesc(inAppleEvent, keyDirectObject, typeAEList, &docList)) != noErr ||
					(err = AECountItems(&docList, &itemsInList)) != noErr){
					if (err == errAEDescNotFound)
						itemsInList = 0;
					else
						break;
				}

				for(index = 1; index <= itemsInList; index++){
					err = AEGetNthPtr(&docList, index, typeFSS, &keywd, &typeCode, (Ptr)&fss,
								sizeof(FSSpec), &actualSize);
					if(noErr != err)
						break;

					err = FSpGetFInfo (&fss, &fndrInfo);
					if (noErr != err)
						break;

					if (fndrInfo.fdType != SAVE_TYPE) 
						continue;	/* only look at save files */

					process_openfile (fss.vRefNum, fss.parID, fss.name, fndrInfo.fdType);
					if (macFlags.gotOpen)
						break;	/* got our save file */
				}
				err = AEDisposeDesc(&docList);
				break;
			}
		}
	}			

	/* Check to see if all required parameters for this type of event are present */
	if (err == noErr) {
		err = AEGetAttributePtr (inAppleEvent, keyMissedKeywordAttr, 
						  		typeWildCard, &typeCode, NULL, 0, &actualSize);
		if (err == errAEDescNotFound)											 
			err = noErr;		/* got all the required parameters */
		else if (err == noErr)	/* missed a required parameter */
			err = errAEEventNotHandled;
	}

	return err;
}


short win_fonts [NHW_TEXT + 1];

void
InitMac(void) {
	short i;
	long l;
	Str255 volName;


#if !TARGET_API_MAC_CARBON
	if (LMGetDefltStack() < 50 * 1024L) {
		SetApplLimit ((void *) ((long) LMGetCurStackBase() - (50 * 1024L)));
	}
	MaxApplZone ();
	for (i = 0; i < 5; i ++)
		MoreMasters ();

	InitGraf (&qd.thePort);
	InitFonts ();
	InitWindows ();
	InitMenus ();
	InitDialogs (0L);
	TEInit ();
#endif

	memset (&macFlags, 0, sizeof(macFlags));
	if (!Gestalt (gestaltOSAttr, & l)) {
		macFlags.processes = (l & (1 << gestaltLaunchControl)) ? 1 : 0;
		macFlags.tempMem = (l & (1 << gestaltRealTempMemory)) ? 1 : 0;
		macFlags.hasDebugger = (l & (1 << gestaltSysDebuggerSupport)) ? 1 : 0;
	}
	if (!Gestalt (gestaltQuickdrawVersion, & l))
		macFlags.color = (l >= gestalt8BitQD) ? 1 : 0;

	if (!Gestalt (gestaltFindFolderAttr, & l))
		macFlags.folders = (l & (1 << gestaltFindFolderPresent)) ? 1 : 0;
		
	if (!Gestalt (gestaltHelpMgrAttr, & l))
		macFlags.help = (l & (1 << gestaltHelpMgrPresent)) ? 1 : 0;

	if (!Gestalt (gestaltFSAttr, & l))
		macFlags.fsSpec = (l & (1 << gestaltHasFSSpecCalls)) ? 1 : 0;

	if (!Gestalt (gestaltFontMgrAttr, & l))
		macFlags.trueType = (l & (1 << gestaltOutlineFonts)) ? 1 : 0;

	if (!Gestalt (gestaltAUXVersion, & l))
		macFlags.aux = (l >= 0x200) ? 1 : 0;

	if (!Gestalt (gestaltAliasMgrAttr, & l))
		macFlags.alias = (l & (1 << gestaltAliasMgrPresent)) ? 1 : 0;

	if (!Gestalt (gestaltStandardFileAttr, & l))
		macFlags.standardFile = (l & (1 << gestaltStandardFile58)) ? 1 : 0;

	gMouseRgn = NewRgn ();
	InitCursor();

#if TARGET_API_MAC_CARBON
	GetQDGlobalsArrow(&qdarrow);
#endif

	ObscureCursor ();
	
	MoveScrollUPP = NewControlActionUPP(MoveScrollBar);

	/* Set up base fonts for all window types */
	GetFNum ("\pHackFont", &i);
	if (i == 0)
		i = kFontIDMonaco;
	win_fonts [NHW_BASE] = win_fonts [NHW_MAP] = win_fonts [NHW_STATUS] = i;
	GetFNum ("\pPSHackFont", &i);
	if (i == 0)
		i = kFontIDGeneva;
	win_fonts [NHW_MESSAGE] = i;
	win_fonts [NHW_TEXT] = kFontIDGeneva;

	macFlags.hasAE = 0;
	if(!Gestalt(gestaltAppleEventsAttr, &l) && (l & (1L << gestaltAppleEventsPresent))){
		if (AEInstallEventHandler (kCoreEventClass, typeWildCard,
							NewAEEventHandlerUPP(AppleEventHandler),
							0,
							FALSE) == noErr)
			macFlags.hasAE = 1;
	}

#if TARGET_API_MAC_CARBON
	HGetVol(volName, &theDirs.dataRefNum, &theDirs.dataDirID);
#else
	/*
	 * We should try to get this data from a rsrc, in the profile file
	 * the user double-clicked...  This data should be saved with the
	 * save file in the resource fork, AND be saveable in "stationary"
	 */
	GetVol (volName, &theDirs.dataRefNum );
	GetWDInfo (theDirs.dataRefNum, &theDirs.dataRefNum, &theDirs.dataDirID, &l);
#endif
	if (volName [0] > 31) volName [0] = 31;
	for (l = 1; l <= volName [0]; l++) {
		if (volName [l] == ':') {
			volName [l] = 0;
			volName [0] = l - 1;
			break;
		}
	}
	BlockMove (volName, theDirs.dataName, l);
	BlockMove (volName, theDirs.saveName, l);
	BlockMove (volName, theDirs.levelName, l);
	theDirs.saveRefNum = theDirs.levelRefNum = theDirs.dataRefNum;
	theDirs.saveDirID = theDirs.levelDirID = theDirs.dataDirID;

	/* Create the "record" file, if necessary */
	check_recordfile("");
	return;
}


/*
 * Change default window fonts.
 */
short
set_tty_font_name (int window_type, char *font_name) {
	short fnum;
	Str255 new_font;
	
	if (window_type < NHW_BASE || window_type > NHW_TEXT)
		return general_failure;
		
	C2P (font_name, new_font);
	GetFNum (new_font, &(fnum));
	if (!fnum)
		return general_failure;
	win_fonts [window_type] = fnum;
	return noErr;
}


static void
DrawScrollbar (NhWindow *aWin)
{
	WindowPtr theWindow = aWin->its_window;
	Rect crect, wrect;
	Boolean vis;
	short val, lin, win_height;


	if (!aWin->scrollBar)
		return;
#if !TGT_API_MAC_CARBON
	crect = (*aWin->scrollBar)->contrlRect;
#else
	GetControlBounds(aWin->scrollBar, &crect);
#endif
	GetWindowBounds(aWin->its_window, kWindowContentRgn, &wrect);
	OffsetRect(&wrect, -wrect.left, -wrect.top);
	win_height = wrect.bottom - wrect.top;

	if (crect.top != wrect.top - 1 ||
		 crect.left != wrect.right - SBARWIDTH) {
		MoveControl (aWin->scrollBar, wrect.right - SBARWIDTH, wrect.top - 1);
	}
	if (crect.bottom != wrect.bottom - SBARHEIGHT ||
		 crect.right != wrect.right + 1) {
		SizeControl (aWin->scrollBar, SBARWIDTH+1, win_height - SBARHEIGHT + 2);
	}
	vis = (win_height > (50 + SBARHEIGHT));
	if (vis != IsControlVisible(aWin->scrollBar)) {
		/* current status != control */
		if (vis)/* if visible, show */
			ShowControl (aWin->scrollBar);
		else	/* else hide */
			HideControl (aWin->scrollBar);
	}
	lin = aWin->y_size;
	if (aWin == theWindows + WIN_MESSAGE) {
		/* calculate how big scroll bar is for message window */
		lin -= (win_height - SBARHEIGHT) / aWin->row_height;
		if (lin < 0)
			lin = 0;		
		val = 0;			/* always have message scrollbar active */
	}
	else {
		/* calculate how big scroll bar is for other windows */
		lin -= win_height / aWin->row_height;
		if (lin < 0)
			lin = 0;
		if (lin) 	val = 0;	/* if there are 1+ screen lines, activate scrollbar */
		else		val = 255;	/* else grey it out */
	}
	SetControlMaximum (aWin->scrollBar, lin);
	HiliteControl (aWin->scrollBar, val);
	val = GetControlValue (aWin->scrollBar);
	if (val != aWin->scrollPos) {
		InvalWindowRect(theWindow, &wrect);
		aWin->scrollPos = val;
	}
}


#define MAX_HEIGHT 100
#define MIN_HEIGHT 50
#define MIN_WIDTH 300

/*
 * This function could be overloaded with any amount of intelligence...
 */
int
SanePositions (void)
{
#if TARGET_API_MAC_CARBON
	ConstrainWindowToScreen(_mt_window, kWindowStructureRgn,
		kWindowConstrainMoveRegardlessOfFit, NULL, NULL);
#else
	short left, top, width, height;
	int ix, numText = 0, numMenu = 0;
	int mbar_height = GetMBarHeight ();
	Rect screenArea = qd.screenBits.bounds;
	WindowPtr theWindow;
	NhWindow *nhWin;

	OffsetRect (&screenArea, - screenArea.left, - screenArea.top);

/* Map Window */
	height = _mt_window->portRect.bottom - _mt_window->portRect.top;
	width = _mt_window->portRect.right - _mt_window->portRect.left;

	if (!RetrievePosition (kMapWindow, &top, &left)) {
		top = mbar_height + (small_screen ? 2 : 20);
		left = (screenArea.right - width) / 2;
	}
	MoveWindow (_mt_window, left, top, 1);

/* Message Window */
	if (!RetrievePosition (kMessageWindow, &top, &left)) {
		top += height;
		if (!small_screen)
			top += 20;
	}

	if (!RetrieveSize (kMessageWindow, top, left, &height, &width)) {
		height = screenArea.bottom - top - (small_screen ? 2-SBARHEIGHT : 2);
		if (height > MAX_HEIGHT) {
			height = MAX_HEIGHT;
		} else if (height < MIN_HEIGHT) {
			height = MIN_HEIGHT;
			width = MIN_WIDTH;
			left = screenArea.right - width;
			top = screenArea.bottom - MIN_HEIGHT;
		}
	}

/* Move these windows */
	nhWin = theWindows + WIN_MESSAGE;
	theWindow = nhWin->its_window;

	MoveWindow (theWindow, left, top, 1);
	SizeWindow (theWindow, width, height, 1);
	if (nhWin->scrollBar)
		DrawScrollbar (nhWin);

	/* Handle other windows */
	for (ix = 0; ix < NUM_MACWINDOWS; ix ++) {
		if (ix != WIN_STATUS && ix != WIN_MESSAGE && ix != WIN_MAP && ix != BASE_WINDOW) {
			theWindow = theWindows [ix].its_window;
			if (theWindow && ((WindowPeek) theWindow)->visible) {
				int shift;
				if (((WindowPeek)theWindow)->windowKind == WIN_BASE_KIND + NHW_MENU) {
					if (!RetrievePosition (kMenuWindow, &top, &left)) {
						top = mbar_height * 2;
						left = 2;
					}
					top += (numMenu * mbar_height);
					numMenu++;
					shift = 20;
				} else {
					if (!RetrievePosition (kTextWindow, &top, &left)) {
						top = mbar_height * 2;
						left = screenArea.right - 3 - 
							(theWindow->portRect.right - theWindow->portRect.left);
					}
					top += (numText * mbar_height);
					numText++;
					shift = -20;
				}
				while (top > screenArea.bottom - MIN_HEIGHT) {
					top -= screenArea.bottom - mbar_height * 2;
					left += shift;
				}
				MoveWindow (theWindow, left, top, 1);
			}
		}
	}
#endif
	return (0);
}


winid
mac_create_nhwindow (int kind) {
	int i;
	NhWindow *aWin;
	FontInfo fi;

	if (kind < NHW_BASE || kind > NHW_TEXT) {
		error ("cre_win: Invalid kind %d.", kind);
		return WIN_ERR;
	}

	for (i = 0; i < NUM_MACWINDOWS; i ++) {
		if (!theWindows [i].its_window)
			break;
	}
	if (i >= NUM_MACWINDOWS) {
		error ("cre_win: Win full; freeing extras");
		for (i = 0; i < NUM_MACWINDOWS; i ++) {
			if (IsWindowVisible(theWindows [i].its_window) || i == WIN_INVEN ||
				GetWindowKind(theWindows [i].its_window) != WIN_BASE_KIND + NHW_MENU &&
				GetWindowKind(theWindows [i].its_window) != WIN_BASE_KIND + NHW_TEXT)
				continue;
			mac_destroy_nhwindow(i);
			goto got1;
		}
		error ("cre_win: Out of ids!");
		return WIN_ERR;
	}

got1 :
	aWin = &theWindows [i];
	aWin->windowTextLen = 0L;
	aWin->scrollBar = (ControlHandle) 0;
	aWin->menuInfo = 0;
	aWin->menuSelected = 0;
	aWin->miLen = 0;
	aWin->miSize = 0;
	aWin->menuChar = 'a';
	
	dprintf ("cre_win: New kind %d", kind);

	if (kind == NHW_BASE || kind == NHW_MAP || kind == NHW_STATUS) {
		short x_sz, x_sz_p, y_sz, y_sz_p;
		if (kind != NHW_BASE) {
			if (i != tty_create_nhwindow(kind)) {
				dprintf ("cre_win: error creating kind %d", kind);
			}
			if (kind == NHW_MAP) {
				wins[i]->offy = 0;	/* the message box is in a separate window */
			}
		}
		aWin->its_window = _mt_window;
		get_tty_metrics(aWin->its_window, &x_sz, &y_sz, &x_sz_p, &y_sz_p,
					 &aWin->font_number, &aWin->font_size,
					 &aWin->char_width, &aWin->row_height);
		return i;
	}

	aWin->its_window = GetNewWindow (WIN_BASE_RES + kind, (WindowPtr) 0L, (WindowPtr) -1L);
	SetWindowKind(aWin->its_window, WIN_BASE_KIND + kind);
	SetWRefCon(aWin->its_window, (long) aWin);
	if (!(aWin->windowText = NewHandle (TEXT_BLOCK))) {
		error ("cre_win: NewHandle fail(%ld)", (long) TEXT_BLOCK);
		DisposeWindow (aWin->its_window);
		aWin->its_window = (WindowPtr) 0;
		return WIN_ERR;
	}
	aWin->x_size = aWin->y_size = 0;
	aWin->x_curs = aWin->y_curs = 0;
	aWin->drawn = TRUE;
	mac_clear_nhwindow (i);

	SetPortWindowPort(aWin->its_window);

	if (kind == NHW_MESSAGE) {
		aWin->font_number = win_fonts [NHW_MESSAGE];
		aWin->font_size = iflags.wc_fontsiz_message? iflags.wc_fontsiz_message :
			iflags.large_font ? 12 : 9;
		if (!top_line) {
			const Rect out_of_scr = {10000, 10000, 10100, 10100};
			TextFont(aWin->font_number);
			TextSize(aWin->font_size);
			TextFace(bold);
			top_line = TENew(&out_of_scr, &out_of_scr);
			TEActivate(top_line);
			TextFace(normal);
		}
	} else {
		aWin->font_number = win_fonts [NHW_TEXT];
		aWin->font_size = iflags.wc_fontsiz_text ? iflags.wc_fontsiz_text : 9;
	}

	TextFont (aWin->font_number); 
	TextSize (aWin->font_size);

	GetFontInfo (&fi);
	aWin->ascent_height = fi.ascent + fi.leading;
	aWin->row_height = aWin->ascent_height + fi.descent;
	aWin->char_width = fi.widMax;

	if (kind == NHW_MENU || kind == NHW_TEXT || kind == NHW_MESSAGE) {
		Rect r;

		GetWindowBounds(aWin->its_window, kWindowContentRgn, &r);
		r.right -= (r.left - 1);
		r.left = r.right - SBARWIDTH;
		r.bottom -= (r.top + SBARHEIGHT);
		r.top = -1;
		aWin->scrollBar = NewControl (aWin->its_window, &r, "\p", (r.bottom > r.top + 50), 0, 0, 0, 16, 0L);
		aWin->scrollPos = 0;
	}
	return i;
}


void
mac_init_nhwindows (int *argcp, char **argv)
{
	Rect r;

#if !TARGET_API_MAC_CARBON
	Rect scr = (*GetGrayRgn())->rgnBBox;
	small_screen = scr.bottom - scr.top <= (iflags.large_font ? 12*40 : 9*40);
#endif

	InitMenuRes ();

	theWindows = (NhWindow *) NewPtrClear (NUM_MACWINDOWS * sizeof (NhWindow));
	if (MemError())
		error("mac_init_nhwindows: Couldn't allocate memory for windows.");

	DimMenuBar ();

	tty_init_nhwindows(argcp, argv);
	iflags.window_inited = TRUE;

	/* Some ugly hacks to make both interfaces happy:
	 * Mac port uses both tty interface (for main map) and extra windows.  The winids need to
	 * be kept in synch for both interfaces to map.  Also, the "blocked" display_nhwindow case
	 * for the map automatically calls the tty interface for the message box, so some version
	 * of the message box has to exist in the tty world to prevent a meltdown, even though most
	 * messages are handled in mac window.
	 */
	mac_create_nhwindow(NHW_BASE);
	tty_create_nhwindow(NHW_MESSAGE);
	RetrievePosition(kMessageWindow, &r.top, &r.left);
	RetrieveSize(kMessageWindow, r.top, r.left, &r.bottom, &r.right);
	MoveWindow(theWindows[NHW_MESSAGE].its_window, r.left, r.top, false);
	SizeWindow(theWindows[NHW_MESSAGE].its_window, r.right, r.bottom, true);
#if TARGET_API_MAC_CARBON
	ConstrainWindowToScreen(theWindows[NHW_MESSAGE].its_window, kWindowStructureRgn,
		kWindowConstrainMoveRegardlessOfFit, NULL, NULL);
#endif
	return;
}


void
mac_clear_nhwindow (winid win) {
	long l;
	Rect r;
	NhWindow *aWin = &theWindows [win];
	WindowPtr theWindow = aWin->its_window;

	if (win < 0 || win >= NUM_MACWINDOWS || !theWindow) {
		error ("clr_win: Invalid win %d.", win);
		return;
	}
	if (theWindow == _mt_window) {
		tty_clear_nhwindow(win);
		return;
	}
	if (!aWin->drawn)
		return;

	SetPortWindowPort(theWindow);
	GetWindowBounds(theWindow, kWindowContentRgn, &r);
	OffsetRect(&r, -r.left, -r.top);
	if (aWin->scrollBar)
		r.right -= SBARWIDTH;

	switch (GetWindowKind(theWindow) - WIN_BASE_KIND) {
	case NHW_MESSAGE :
		if (aWin->scrollPos == aWin->y_size - 1)	/* if no change since last clear */
			return;									/* don't bother with redraw */
		r.bottom -= SBARHEIGHT;
		for (l = 0; aWin->y_size > iflags.msg_history;) {
			const char cr = CHAR_CR;
			l = Munger(aWin->windowText, l, &cr, 1, nil, 0) + 1;
			--aWin->y_size;
		}
		if (l) {
			aWin->windowTextLen -= l;
			BlockMove(*aWin->windowText + l, *aWin->windowText, aWin->windowTextLen);
		}
		aWin->last_more_lin = aWin->y_size;
		aWin->save_lin	= aWin->y_size;
		aWin->scrollPos = aWin->y_size ? aWin->y_size - 1 : 0;
		break;
	case NHW_MENU:
		if (aWin->menuInfo) {
			DisposeHandle((Handle)aWin->menuInfo);
			aWin->menuInfo = NULL;
		}
		if (aWin->menuSelected) {
			DisposeHandle((Handle)aWin->menuSelected);
			aWin->menuSelected = NULL;
		}
		aWin->menuChar = 'a';
		aWin->miSelLen = 0;
		aWin->miLen = 0;
		aWin->miSize = 0;
		/* Fall-Through */
	default :
		SetHandleSize (aWin->windowText, TEXT_BLOCK);
		aWin->windowTextLen = 0L;
		aWin->x_size = 0;
		aWin->y_size = 0;
		aWin->scrollPos = 0;
		break;
	}
	if (aWin->scrollBar) {
		SetControlMaximum (aWin->scrollBar, aWin->y_size);
		SetControlValue(aWin->scrollBar, aWin->scrollPos);
	}
	aWin->y_curs = 0;
	aWin->x_curs = 0;
	aWin->drawn = FALSE;
	InvalWindowRect(theWindow, &r);
}


static Boolean
ClosingWindowChar(const int c) {
	return c == CHAR_ESC || c == CHAR_BLANK || c == CHAR_LF || c == CHAR_CR ||
			c == 'q';
}


static Boolean
in_topl_mode(void)
{
	Rect rect;


	GetWindowBounds(theWindows[WIN_MESSAGE].its_window, kWindowContentRgn, &rect);
	OffsetRect(&rect, -rect.left, -rect.top);
	return (WIN_MESSAGE != WIN_ERR && top_line &&
		(*top_line)->viewRect.left < rect.right);
}


#define BTN_IND 2
#define BTN_W	40
#define BTN_H	(SBARHEIGHT-3)

static void
topl_resp_rect(int resp_idx, Rect *r)
{
	Rect rect;


	GetWindowBounds(theWindows[WIN_MESSAGE].its_window, kWindowContentRgn, &rect);
	OffsetRect(&rect, -rect.left, -rect.top);
	r->left	  = (BTN_IND + BTN_W) * resp_idx + BTN_IND;
	r->right  = r->left + BTN_W;
	r->bottom = rect.bottom - 1;
	r->top	  = r->bottom - BTN_H;
	return;
}


void
enter_topl_mode(char *query) {
	if (in_topl_mode())
		return;

	putstr(WIN_MESSAGE, ATR_BOLD, query);

	topl_query_len = strlen(query);
	(*top_line)->selStart = topl_query_len;
	(*top_line)->selEnd = topl_query_len;
	(*top_line)->viewRect.left = 0;
	PtrToXHand(query, (*top_line)->hText, topl_query_len);
	TECalText(top_line);

	DimMenuBar();
	mac_display_nhwindow(WIN_MESSAGE, FALSE);
}


void
leave_topl_mode(char *answer) {
	unsigned char *ap, *bp;

	int ans_len = (*top_line)->teLength - topl_query_len;
	NhWindow *aWin = theWindows + WIN_MESSAGE;

	if (!in_topl_mode())
		return;

	/* Cap length of reply */
	if (ans_len >= BUFSZ)
		ans_len = BUFSZ-1;

	/* remove unprintables from the answer */
	for (ap = *(*top_line)->hText + topl_query_len, bp = answer; ans_len > 0; ans_len--, ap++) {
		if (*ap >= ' ' && *ap < 128) {
			*bp++ = *ap;
		}
	}
	*bp = 0;
	
	if (aWin->windowTextLen && (*aWin->windowText)[aWin->windowTextLen-1] == CHAR_CR) {
		-- aWin->windowTextLen;
		-- aWin->y_size;
	}
	putstr(WIN_MESSAGE, ATR_BOLD, answer);

	(*top_line)->viewRect.left += 10000;
	UndimMenuBar();
}

/*
 * TESetSelect flushes out all the pending key strokes.  I hate it.
 */
static void
topl_set_select(short selStart, short selEnd) {
	TEDeactivate(top_line);
	(*top_line)->selStart	= selStart;
	(*top_line)->selEnd	= selEnd;
	TEActivate(top_line);
}


static void
topl_replace(char *new_ans) {
	topl_set_select(topl_query_len, (*top_line)->teLength);
	TEDelete(top_line);
	TEInsert(new_ans, strlen(new_ans), top_line);
}


Boolean
topl_key(unsigned char ch, Boolean ext) {
	switch (ch) {
		case CHAR_ESC:
			topl_replace("\x1b");
		case CHAR_ENTER: case CHAR_CR: case CHAR_LF:
			return false;

		case 0x1f & 'P':
			mac_doprev_message();
			return true;
		case '\x1e'/* up arrow */:
			topl_replace ("");
			return true;
		case CHAR_BS: case '\x1c'/* left arrow */:
			if ((*top_line)->selEnd <= topl_query_len)
				return true;
			else if (ext) {
				topl_replace ("");
				return true;
			}
		default:
			TEKey(ch, top_line);
			if (ext) {
				int com_index = -1, oindex = 0;
				while(extcmdlist[oindex].ef_txt != (char *)0) {
					if(!strncmpi(*(*top_line)->hText + topl_query_len,
								 extcmdlist[oindex].ef_txt,
								 (*top_line)->teLength - topl_query_len)) {
						if(com_index == -1) /* No matches yet*/
							com_index = oindex;
						else /* More than 1 match */ {
							com_index = -2;
							break;
						}
					}
					oindex++;
				}
				if(com_index >= 0)
					topl_replace((char *) extcmdlist[com_index].ef_txt);
			}
			return true;
	}
}


static void
topl_flash_resp(int resp_idx) {
	unsigned long dont_care;
	Rect frame;
	SetPortWindowPort(theWindows[WIN_MESSAGE].its_window);
	topl_resp_rect(resp_idx, &frame);
	InsetRect(&frame, 1, 1);
	InvertRect(&frame);
	Delay(GetDblTime() / 2, &dont_care);
	InvertRect(&frame);
}


static void
topl_set_def(int new_def_idx) {
	Rect frame;
	SetPortWindowPort(theWindows[WIN_MESSAGE].its_window);
	topl_resp_rect(topl_def_idx, &frame);
	InvalWindowRect(theWindows[WIN_MESSAGE].its_window, &frame);
	topl_def_idx = new_def_idx;
	topl_resp_rect(new_def_idx, &frame);
	InvalWindowRect(theWindows[WIN_MESSAGE].its_window, &frame);
}


void
topl_set_resp(char *resp, char def) {
	char *loc;
	Rect frame;
	int r_len, r_len1;

	if (!resp) {
		const char any_str[2] = {CHAR_ANY, '\0'};
		resp = (char *) any_str;
		def = CHAR_ANY;
	}

	SetPortWindowPort(theWindows[WIN_MESSAGE].its_window);
	r_len1 = strlen(resp);
	r_len  = strlen(topl_resp);
	if (r_len < r_len1)
		r_len = r_len1;
	topl_resp_rect(0, &frame);
	frame.right = (BTN_IND + BTN_W) * r_len;
	InvalWindowRect(theWindows[WIN_MESSAGE].its_window, &frame);

	strcpy(topl_resp, resp);
	loc = strchr (resp, def);
	topl_def_idx = loc ? loc - resp : -1;
}


static char
topl_resp_key(char ch) {
	if (strlen(topl_resp) > 0) {
		char *loc = strchr(topl_resp, ch);

		if (!loc) {
			if (ch == '\x9'/* tab */) {
				topl_set_def(topl_def_idx<=0 ? strlen(topl_resp)-1 : topl_def_idx-1);
				ch = '\0';
			} else if (ch == CHAR_ESC) {
				loc = strchr(topl_resp, 'q');
				if (!loc) {
					loc = strchr(topl_resp, 'n');
					if (!loc && topl_def_idx >= 0)
						loc = topl_resp + topl_def_idx;
				}
			} else if (ch == (0x1f & 'P')) {
				mac_doprev_message();
				ch = '\0';
			} else if (topl_def_idx >= 0) {
				if (ch == CHAR_ENTER || ch == CHAR_CR || ch == CHAR_LF ||
					 ch == CHAR_BLANK || topl_resp[topl_def_idx] == CHAR_ANY)
					loc = topl_resp + topl_def_idx;

				else if (strchr(topl_resp, '#')) {
					if (digit(ch)) {
						topl_set_def(strchr(topl_resp, '#') - topl_resp);
						TEKey(ch, top_line);
						ch = '\0';

					} else if (topl_resp[topl_def_idx] == '#') {
						if (ch == '\x1e'/* up arrow */) {
							topl_set_select(topl_query_len, topl_query_len);
							ch = '\0';
						} else if (ch == '\x1d'/* right arrow */ ||
							   ch == '\x1f'/* down arrow */ ||
							   ch == CHAR_BS || ch == '\x1c'/* left arrow */ &&
							   (*top_line)->selEnd > topl_query_len) {
							TEKey(ch, top_line);
							ch = '\0';
						}
					}
				}
			}
		}

		if (loc) {
			topl_flash_resp(loc - topl_resp);
			if (*loc != CHAR_ANY)
				ch = *loc;
			TEKey(ch, top_line);
		}
	}

	return ch;
}


static void
adjust_window_pos(NhWindow *aWin, short width, short height)
{
	WindowRef theWindow = aWin->its_window;
#if TARGET_API_MAC_CARBON
	Rect r;


	GetWindowBounds(theWindow, kWindowContentRgn, &r);
	RetrieveWinPos(theWindow, &r.top, &r.left);
	MoveWindow(theWindow, r.left, r.top, false);
	SizeWindow(theWindow, width, height, true);
	ConstrainWindowToScreen(theWindow, kWindowStructureRgn,
		kWindowConstrainMoveRegardlessOfFit, NULL, NULL);
#else
	Rect scr_r = (*GetGrayRgn())->rgnBBox;
	const Rect win_ind = {2, 2, 3, 3};
	const short	min_w = theWindow->portRect.right - theWindow->portRect.left,
				max_w = scr_r.right - scr_r.left - win_ind.left - win_ind.right;
	Point pos;
	short max_h;

	SetPortWindowPort(theWindow);
	if (!RetrieveWinPos(theWindow, &pos.v, &pos.h)) {
		pos.v = 0;	/* take window's existing position */
		pos.h = 0;
		LocalToGlobal(&pos);
	}

	max_h = scr_r.bottom - win_ind.bottom - pos.v;
	if (height > max_h)		height = max_h;
	if (height < MIN_HEIGHT)	height = MIN_HEIGHT;
	if (width < min_w)		width = min_w;
	if (width > max_w)		width = max_w;
	SizeWindow(theWindow, width, height, true);

	if (pos.v + height + win_ind.bottom > scr_r.bottom)
		pos.v = scr_r.bottom - height - win_ind.bottom;
	if (pos.h + width + win_ind.right > scr_r.right)
		pos.h = scr_r.right	 - width - win_ind.right;
	MoveWindow(theWindow, pos.h, pos.v, false);
	if (aWin->scrollBar)	
		DrawScrollbar (aWin);
#endif
	return;
}


/*
 * display/select/update the window.
 * If f is true, this window should be "modal" - don't return
 * until presumed seen.
 */
void
mac_display_nhwindow (winid win, BOOLEAN_P f) {
	NhWindow *aWin = &theWindows [win];
	WindowPtr theWindow = aWin->its_window;

	if (win < 0 || win >= NUM_MACWINDOWS || !theWindow) {
		error ("disp_win: Invalid window %d.", win);
		return;
	}

	if (theWindow == _mt_window) {
		tty_display_nhwindow(win, f);
		return;
	}

	if (f && inSelect == WIN_ERR && win == WIN_MESSAGE) {
		topl_set_resp ((char *)0, 0);
		if (aWin->windowTextLen > 0 &&
			 (*aWin->windowText) [aWin->windowTextLen - 1] == CHAR_CR) {
			-- aWin->windowTextLen;
			-- aWin->y_size;
		}
		putstr (win, flags.standout ? ATR_INVERSE : ATR_NONE, " --More--");
	}

	if (!IsWindowVisible(theWindow)) {
		if (win != WIN_MESSAGE)
			adjust_window_pos(aWin, aWin->x_size + SBARWIDTH+1, aWin->y_size *aWin->row_height);

		SelectWindow (theWindow);
		ShowWindow (theWindow);
	}

	if (f && inSelect == WIN_ERR) {
		int ch;

		DimMenuBar();
		inSelect = win;
		do {
			ch = mac_nhgetch ();
		} while (!ClosingWindowChar (ch));
		inSelect = WIN_ERR;
		UndimMenuBar();

		if (win == WIN_MESSAGE)
			topl_set_resp ("", '\0');
		else
			HideWindow (theWindow);

	}
}


void
mac_destroy_nhwindow (winid win) {
	WindowPtr theWindow;
	NhWindow *aWin = &theWindows [win];
	int kind;

	if (win < 0 || win >= NUM_MACWINDOWS) {
		if (iflags.window_inited) error ("dest_win: Invalid win %d.", win);
		return;
	}
	theWindow = aWin->its_window;
	if (!theWindow) {
		error ("dest_win: Not allocated win %d.", win);
		return;
	}

	/*
	 * Check special windows.  The base window should never go away.
	 * Other "standard" windows should not go away unless we've exitted nhwindows.
	 */
	if (theWindow == _mt_window) {
		return;
	}
	if (win == WIN_INVEN || win == WIN_MESSAGE) {
		if (iflags.window_inited) {
			if (flags.tombstone && killer) {
				/* Prepare for the coming of the tombstone window. */
				win_fonts [NHW_TEXT] = kFontIDMonaco;
			}
			return;
		}
		if (win == WIN_MESSAGE)
			WIN_MESSAGE = WIN_ERR;
	}

	kind = GetWindowKind(theWindow) - WIN_BASE_KIND;

	if ((!IsWindowVisible(theWindow) || (kind != NHW_MENU && kind != NHW_TEXT))) {
		DisposeWindow (theWindow);
		if (aWin->windowText) {
			DisposeHandle(aWin->windowText);
		}
		aWin->its_window = (WindowPtr) 0;
		aWin->windowText = (Handle) 0;
	}
}


void
mac_number_pad (int pad) {
	iflags.num_pad = pad;
}


void
trans_num_keys(EventRecord *theEvent) {
#if defined(MAC_MPW)
# pragma unused(theEvent)
#endif
/* KMH -- Removed this translation.
 * Number pad keys should always emit digit characters.
 * That's consistent with the default MacOS behavior.
 * The number_pad option controls how digits are interpreted.
 */
#if 0
	if (iflags.num_pad) {
		Handle h = GetResource('Nump', theEvent->modifiers & shiftKey ? 129 : 128);
		if (h) {
			short inkey = (theEvent->message & keyCodeMask), *ab = (short *)*h;
			int i = ab[0];
			for (; i; i--) {
				if (inkey == (ab[i] & keyCodeMask)) {
					theEvent->message = ab[i];
					break;
				}
			}
		}
	}
#endif
}


/*
 * Note; theWindow may very well be null here, since keyDown may call
 * it when theres no window !!!
 */
static void
GeneralKey (EventRecord *theEvent, WindowPtr theWindow) {
#if defined(MAC_MPW)
# pragma unused(theWindow)
#endif
#if 0
	trans_num_keys (theEvent);
#endif
	AddToKeyQueue (topl_resp_key (theEvent->message & 0xff), TRUE);
}


/*
 * Routine used to select and de-select elements in a menu window, used by KeyMenu,
 * ClickMenu, and UpdateMenu.  Takes the NhWindow and a line ref relative to the scrollbar.
 */
static void ToggleMenuSelect (NhWindow *aWin, int line) {
	Rect r;


	GetWindowBounds(aWin->its_window, kWindowContentRgn, &r);
	OffsetRect(&r, -r.left, -r.top);
	if (aWin->scrollBar)
		r.right -= SBARWIDTH;			
	r.top = line * aWin->row_height;
	r.bottom = r.top + aWin->row_height;

	LMSetHiliteMode((UInt8) (LMGetHiliteMode() & 0x7F));
	InvertRect(&r);
}

/*
 * Check to see if given item is selected, return index if it is
 */
static int
ListItemSelected (NhWindow *aWin, int item) {
	int		i;

	HLock ((char**)aWin->menuSelected);
	/* Find item in selection list */
	for (i = aWin->miSelLen - 1; i >= 0; i--) {
		if ((*aWin->menuSelected) [i] == item)
			break;
	}
	HUnlock ((char**)aWin->menuSelected);
	return i;
}

/*
 * Add item to selection list if it's not selected already
 * If it is selected already, remove it from the list.
 */
static void
ToggleMenuListItemSelected (NhWindow *aWin, short item) {
	int i = ListItemSelected (aWin, item);

	HLock ((char**)aWin->menuSelected);
	if (i < 0) {	/* not there, so add */
		(*aWin->menuSelected) [aWin->miSelLen] = item;
		aWin->miSelLen++;
	}
	else {			/* there, so remove */
		short *mi = &(*aWin->menuSelected)[i];
		aWin->miSelLen --;
		memcpy (mi, mi + 1, (aWin->miSelLen - i)*sizeof(short));
	}
	HUnlock ((char**)aWin->menuSelected);
}


/*
 * Find menu item in list given a line number on the window
 */
static short
ListCoordinateToItem (NhWindow *aWin, short Row) {
	int					i, item = -1;
	MacMHMenuItem *		mi;

	HLock ((char**)aWin->menuInfo);
	for (i = 0, mi = *aWin->menuInfo; i < aWin->miLen; i++, mi++) {
		if (mi->line == Row + aWin->scrollPos) {
			item = i;
			break;
		}
	}
	HUnlock ((char**)aWin->menuInfo);
	return item;
}


static void
macKeyMenu (EventRecord *theEvent, WindowPtr theWindow) {
	NhWindow *aWin = GetNhWin(theWindow);
	MacMHMenuItem *mi;
	int l, ch = theEvent->message & 0xff;

	if (aWin && aWin->menuInfo) {
		HLock ((char**)aWin->menuInfo);
		for (l = 0, mi = *aWin->menuInfo; l < aWin->miLen; l++, mi++) {
			if (mi->accelerator == ch) {
				ToggleMenuListItemSelected (aWin, l);
				if (mi->line >= aWin->scrollPos && mi->line <= aWin->y_size) {
					SetPortWindowPort(theWindow);
					ToggleMenuSelect (aWin, mi->line - aWin->scrollPos);
				}				
				/* Dismiss window if only picking one item */
				if (aWin->how != PICK_ANY)
					AddToKeyQueue(CHAR_CR, 1);
				break;
			}
		}
		HUnlock ((char**)aWin->menuInfo);
		/* add key if didn't find it in menu and not filtered */
		if (l == aWin->miLen && filter_scroll_key (ch, aWin))
			GeneralKey (theEvent, theWindow);
	}
}


static void
macClickMenu (EventRecord *theEvent, WindowRef theWindow) {
	Point p;
	NhWindow *aWin = GetNhWin(theWindow);
	Rect wrect;


	GetWindowBounds(theWindow, kWindowContentRgn, &wrect);
	OffsetRect(&wrect, -wrect.left, -wrect.top);
	if (aWin->scrollBar && IsControlVisible(aWin->scrollBar)) {
		short code;
		ControlHandle theBar;

		p = theEvent->where;
		GlobalToLocal (&p);
		code = FindControl (p, theWindow, &theBar);
		if (code) {
			DoScrollBar (p, code, theBar, aWin);
			return;
		}
	}
	if (inSelect != WIN_ERR && aWin->how != PICK_NONE) {
		short		currentRow = -1, previousRow = -1;
		short		previousItem = -1, item = -1;
		Boolean		majorSelectState, firstRow = TRUE;

		do {
#if !TARGET_API_MAC_CARBON
			SystemTask ();
#endif
			GetMouse (&p);
			currentRow = p.v / aWin->row_height;
			if (p.h < wrect.left || p.h > wrect.right ||
				p.v < 0 || p.v > wrect.bottom || currentRow >= aWin->y_size) {
				continue;	/* not in window range */
			}

			item = ListCoordinateToItem (aWin, currentRow);

			if (item != previousItem) {
				/* Implement typical Mac multiple-selection behavior
				 * (ie, not the UI implemented by the Finder)
				 */
				Boolean	itemIsSelected = (ListItemSelected (aWin,item) >= 0);

				if (firstRow) {
					/* this is first valid row, so major state is opposite of what this row is */
					majorSelectState = !itemIsSelected;
					firstRow = FALSE;
				}

				if (aWin->how == PICK_ONE && previousItem != -1) {
					/* if previous row was selected and we're only selecting one object,
					 * deselect previous row!
					 */
					ToggleMenuListItemSelected (aWin, previousItem);
					ToggleMenuSelect (aWin, previousRow);
					previousItem = -1;
				}

				if (item == -1)
					continue;	/* header line */
					
				if (majorSelectState != itemIsSelected) {
					ToggleMenuListItemSelected (aWin, item);
					ToggleMenuSelect (aWin, currentRow);
				}

				previousRow		= currentRow;
				previousItem	= item;
			}
		} while (StillDown ());

		/* Dismiss window if only picking one item */
		if (aWin->how == PICK_ONE)
			AddToKeyQueue(CHAR_CR, 1);
	}
}


static void
macKeyText (EventRecord *theEvent, WindowPtr theWindow) {
	NhWindow *aWin = GetNhWin (theWindow);
	char c = filter_scroll_key (theEvent->message & 0xff, aWin);
	if (c) {
		if (inSelect == WIN_ERR && ClosingWindowChar (c)) {
			HideWindow (theWindow);
			mac_destroy_nhwindow (aWin - theWindows);
		} else {
			GeneralKey (theEvent, theWindow);
		}
	}
}


static void
macClickText (EventRecord *theEvent, WindowPtr theWindow) {
	NhWindow *aWin = GetNhWin (theWindow);

	if (aWin->scrollBar && IsControlVisible(aWin->scrollBar)) {
		short code;
		Point p = theEvent->where;
		ControlHandle theBar;

		GlobalToLocal (&p);
		code = FindControl (p, theWindow, &theBar);
		if (code) {
			DoScrollBar (p, code, theBar, aWin);
		}
	}
}


static void
macClickMessage (EventRecord *theEvent, WindowPtr theWindow) {
	int r_idx = 0;
	Point mouse = theEvent->where;

	GlobalToLocal(&mouse);
	while (topl_resp[r_idx]) {
		Rect frame;
		topl_resp_rect(r_idx, &frame);
		InsetRect(&frame, 1, 1);
		if (PtInRect(mouse, &frame)) {
			Boolean in_btn = true;

			InvertRect(&frame);
			while (WaitMouseUp()) {
#if !TARGET_API_MAC_CARBON
				SystemTask();
#endif
				GetMouse(&mouse);
				if (PtInRect(mouse, &frame) != in_btn) {
					in_btn = !in_btn;
					InvertRect(&frame);
				}
			}
			if (in_btn) {
				InvertRect(&frame);
				AddToKeyQueue (topl_resp [r_idx], 1);
			}
			return;

		}
		++r_idx;
	}

	macClickText(theEvent, theWindow);
}


static void
macClickTerm (EventRecord *theEvent, WindowPtr theWindow) {
	NhWindow *nhw = GetNhWin(theWindow);
	Point where = theEvent->where;

	GlobalToLocal(&where);
	where.h = where.h / nhw->char_width + 1;
	where.v = where.v / nhw->row_height;
	clicked_mod = (theEvent->modifiers & shiftKey) ? CLICK_2 : CLICK_1;

	if (strchr(topl_resp, *click_to_cmd(where.h, where.v, clicked_mod)))
		nhbell();
	else {
#if !TARGET_API_MAC_CARBON
		if (cursor_locked)
			while (WaitMouseUp())
				SystemTask();
#endif

		gClickedToMove = TRUE;
		clicked_pos = where;
	}
}

static pascal void
MoveScrollBar (ControlHandle theBar, short part) {
	EventRecord fake;
	Rect r;
	RgnHandle rgn;
	int now, amtToScroll;
	WindowPtr theWin;
	NhWindow *winToScroll;
	
	if (!part)
		return;

#if !TGT_API_MAC_CARBON
	theWin = (*theBar)->contrlOwner;
#else
	theWin = GetControlOwner(theBar);
#endif

	GetWindowBounds(theWin, kWindowContentRgn, &r);
	OffsetRect(&r, -r.left, -r.top);
	winToScroll = (NhWindow*)(GetWRefCon(theWin));
	now = GetControlValue (theBar);

	if (part == kControlPageUpPart || part == kControlPageDownPart)	
		amtToScroll = (r.bottom - r.top) / winToScroll->row_height;
	else
		amtToScroll = 1;

	if (part == kControlPageUpPart || part == kControlUpButtonPart) {
		int bound = GetControlMinimum (theBar);
		if (now - bound < amtToScroll)
			amtToScroll = now - bound;
		amtToScroll = -amtToScroll;
	} else {
		int bound = GetControlMaximum (theBar);
		if (bound - now < amtToScroll)
			amtToScroll = bound - now;
	}
	
	if (!amtToScroll)
		return;

	SetControlValue (theBar, now + amtToScroll);
	winToScroll->scrollPos = now + amtToScroll;
	r.right -= SBARWIDTH;
	if (winToScroll == theWindows + WIN_MESSAGE)
		r.bottom -= SBARHEIGHT;
	rgn = NewRgn ();
	ScrollRect (&r, 0, -amtToScroll * winToScroll->row_height, rgn);
	if (rgn) {
		InvalWindowRgn(theWin, rgn);
		BeginUpdate(theWin);
	}
	winUpdateFuncs [GetWindowKind(theWin) - WIN_BASE_KIND] (&fake, theWin);
	if (rgn) {
		EndUpdate(theWin);
		DisposeRgn(rgn);
	}
}


static void
DoScrollBar (Point p, short code, ControlHandle theBar, NhWindow *aWin)
{
	ControlActionUPP func = NULL;
	Rect rect;

	if (code == kControlUpButtonPart || code == kControlPageUpPart ||
		code == kControlDownButtonPart || code == kControlPageDownPart)
		func = MoveScrollUPP;
	(void) TrackControl(theBar, p, func);
	if (!func) {
		if (aWin->scrollPos != GetControlValue (theBar)) {
			aWin->scrollPos = GetControlValue (theBar);
			GetWindowBounds(aWin->its_window, kWindowContentRgn, &rect);
			OffsetRect(&rect, -rect.left, -rect.top);
			InvalWindowRect(aWin->its_window, &rect);
		}
	}
}


static int
filter_scroll_key(const int ch, NhWindow *aWin) {
	if (aWin->scrollBar && GetControlValue(aWin->scrollBar) < GetControlMaximum(aWin->scrollBar)) {
		short part = 0;
		if (ch == CHAR_BLANK) {
			part = kControlPageDownPart;
		}
		else if (ch == CHAR_CR || ch == CHAR_LF) {
			part = kControlDownButtonPart;
		}
		if (part) {
			SetPortWindowPort(aWin->its_window);
			MoveScrollBar(aWin->scrollBar, part);
			return 0;
		}
	}
	return ch;
}


int
mac_doprev_message(void) {
	if (WIN_MESSAGE) {
		NhWindow *winToScroll = &theWindows[WIN_MESSAGE];
		mac_display_nhwindow(WIN_MESSAGE, FALSE);
		SetPortWindowPort(winToScroll->its_window);
		MoveScrollBar(winToScroll->scrollBar, kControlUpButtonPart);
	}	
	return 0;
}


static short
macDoNull (EventRecord *theEvent, WindowPtr theWindow) {
	return 0;
}


static void
draw_growicon_vert_only(WindowPtr wind)
{
	GrafPtr org_port;
	RgnHandle org_clip = NewRgn();
	Rect r;

	GetPort(&org_port);
	SetPortWindowPort(wind);
	GetClip(org_clip);
	GetWindowBounds(wind, kWindowContentRgn, &r);
	OffsetRect(&r, -r.left, -r.top);
	r.left = r.right - SBARWIDTH;
	ClipRect(&r);
	DrawGrowIcon(wind);
	SetClip(org_clip);
	DisposeRgn(org_clip);
	SetPort(org_port);
}


static short
macUpdateMessage (EventRecord *theEvent, WindowPtr theWindow)
{
	RgnHandle org_clip = NewRgn(), clip = NewRgn();
	Rect r;
	NhWindow *aWin = GetNhWin(theWindow);
	int l;

	if (!theEvent)
		return 0;

	GetClip(org_clip);
	GetWindowBounds(theWindow, kWindowContentRgn, &r);
	OffsetRect(&r, -r.left, -r.top);

	DrawControls(theWindow);
	DrawGrowIcon(theWindow);

	for (l = 0; topl_resp[l]; l++) {
		StringPtr name;
		unsigned char tmp[2];
		FontInfo font;
		Rect frame;
		topl_resp_rect(l, &frame);
		switch (topl_resp[l]) {
			case 'y':
				name = "\pyes";
				break;
			case 'n':
				name = "\pno";
				break;
			case 'N':
				name = "\pNone";
				break;
			case 'a':
				name = "\pall";
				break;
			case 'q':
				name = "\pquit";
				break;
			case CHAR_ANY:
				name = "\pany key";
				break;
			default:
				tmp[0] = 1;
				tmp[1] = topl_resp[l];
				name = tmp;
				break;
		}
		TextFont(kFontIDGeneva);
		TextSize(9);
		GetFontInfo(&font);
		MoveTo ((frame.left + frame.right - StringWidth(name)) / 2,
			(frame.top + frame.bottom + font.ascent-font.descent-font.leading-1) / 2);
		DrawString(name);
		PenNormal();
		if (l == topl_def_idx)
			PenSize(2, 2);
		FrameRoundRect(&frame, 4, 4);
	}

	r.right -= SBARWIDTH;
	r.bottom -= SBARHEIGHT;
	/* Clip to the portrect - scrollbar/growicon *before* adjusting the rect
		to be larger than the size of the window (!) */
	RectRgn(clip, &r);
	SectRgn(clip, org_clip, clip);
	if (r.right < MIN_RIGHT)
		r.right = MIN_RIGHT;
	r.top -= aWin->scrollPos * aWin->row_height;

#if 0
	/* If you enable this band of code (and disable the next band), you will get
	   fewer flickers but a slower performance while drawing the dot line. */
	{	RgnHandle dotl_rgn = NewRgn();
		Rect dotl;
		dotl.left	= r.left;
		dotl.right	= r.right;
		dotl.bottom = r.top + aWin->save_lin * aWin->row_height;
		dotl.top	= dotl.bottom - 1;
		FillRect(&dotl, &qd.gray);
		RectRgn(dotl_rgn, &dotl);
		DiffRgn(clip, dotl_rgn, clip);
		DisposeRgn(dotl_rgn);
		SetClip(clip);
	}
#endif

	if (in_topl_mode()) {
		RgnHandle topl_rgn = NewRgn();
		Rect topl_r = r;
		topl_r.top += (aWin->y_size - 1) * aWin->row_height;
		l = (*top_line)->destRect.right - (*top_line)->destRect.left;
		(*top_line)->viewRect = topl_r;
		(*top_line)->destRect = topl_r;
		if (l != topl_r.right - topl_r.left)
			TECalText(top_line);
		TEUpdate(&topl_r, top_line);
		RectRgn(topl_rgn, &topl_r);
		DiffRgn(clip, topl_rgn, clip);
		DisposeRgn(topl_rgn);
		SetClip(clip);
	}

	DisposeRgn(clip);

	TextFont (aWin->font_number);
	TextSize (aWin->font_size);
	HLock (aWin->windowText);
	TETextBox (*aWin->windowText, aWin->windowTextLen, &r, teJustLeft);
	HUnlock (aWin->windowText);

#if !TARGET_API_MAC_CARBON
	r.bottom = r.top + aWin->save_lin * aWin->row_height;
	r.top	 = r.bottom - 1;
	FillRect(&r, (void *) &qd.gray);
#endif

	SetClip(org_clip);
	DisposeRgn(org_clip);
	return 0;
}


static short 
macUpdateMenu (EventRecord *theEvent, WindowPtr theWindow) {
	NhWindow *aWin = GetNhWin (theWindow);
	int i, line;
	MacMHMenuItem *mi;
	
	GeneralUpdate (theEvent, theWindow);
	HLock ((char**)aWin->menuInfo);
	HLock ((char**)aWin->menuSelected);
	for (i = 0; i < aWin->miSelLen; i++) {
		mi = &(*aWin->menuInfo) [(*aWin->menuSelected) [i]];
		line = mi->line;
		if (line > aWin->scrollPos && line <= aWin->y_size)
			ToggleMenuSelect (aWin, line - aWin->scrollPos);
	}
	HUnlock ((char**)aWin->menuInfo);
	HUnlock ((char**)aWin->menuSelected);
	return 0;
}


static short
GeneralUpdate (EventRecord *theEvent, WindowPtr theWindow) {
	Rect r, r2;
	NhWindow *aWin = GetNhWin (theWindow);
	RgnHandle h;
	Boolean vis;


	if (!theEvent)
		return 0;

	GetWindowBounds(theWindow, kWindowContentRgn, &r);
	OffsetRect(&r, -r.left, -r.top);
	r2 = r;
	r2.left = r2.right - SBARWIDTH;
	r2.right += 1;
	r2.top -= 1;
	vis = (r2.bottom > r2.top + 50);

	draw_growicon_vert_only(theWindow);
	DrawControls (theWindow);

	h = (RgnHandle) 0;
	if (vis && (h = NewRgn ())) {
		RgnHandle tmp = NewRgn ();
		if (!tmp) {
			DisposeRgn (h);
			h = (RgnHandle) 0;
		} else {
			GetClip (h);
			RectRgn (tmp, &r2);
			DiffRgn (h, tmp, tmp);
			SetClip (tmp);
			DisposeRgn (tmp);
		}
	}
	if (r.right < MIN_RIGHT)
		r.right = MIN_RIGHT;
	r.top -= aWin->scrollPos * aWin->row_height;
	r.right -= SBARWIDTH;
	HLock (aWin->windowText);
	TETextBox (*aWin->windowText, aWin->windowTextLen, &r, teJustLeft);
	HUnlock (aWin->windowText);
	if (h) {
		SetClip (h);
		DisposeRgn (h);
	}
	return 0;
}


static void
macCursorTerm (EventRecord *theEvent, WindowPtr theWindow, RgnHandle mouseRgn) {
	char *dir_bas, *dir;
	CursHandle ch;
	GrafPtr gp;
	NhWindow *nhw = GetNhWin (theWindow);
	Rect r = {0, 0, 1, 1};

	GetPort (&gp);
	SetPortWindowPort(theWindow);

	if (cursor_locked)
		dir = (char *)0;
	else {
		Point where = theEvent->where;

		GlobalToLocal (&where);
		dir_bas = iflags.num_pad ? (char *) ndir : (char *) sdir;
		dir = strchr (dir_bas, *click_to_cmd (where.h / nhw->char_width + 1 ,
							where.v / nhw->row_height, CLICK_1));
	}
	ch = GetCursor (dir ? dir - dir_bas + 513 : 512);
	if (ch) {
		HLock ((Handle) ch);
		SetCursor (*ch);
		HUnlock ((Handle) ch);

	} else {
		SetCursor(&qdarrow);
	}
	OffsetRect (&r, theEvent->where.h, theEvent->where.v);
	RectRgn (mouseRgn, &r);
	SetPort (gp);
}


static void
GeneralCursor (EventRecord *theEvent, WindowPtr theWindow, RgnHandle mouseRgn) {
#if defined(MAC_MPW)
# pragma unused(theWindow)
#endif
	Rect r = {-1, -1, 2, 2};

	SetCursor(&qdarrow);
	OffsetRect (&r, theEvent->where.h, theEvent->where.v);
	RectRgn (mouseRgn, &r);
}


static void
HandleKey (EventRecord *theEvent) {
	WindowPtr theWindow = FrontWindow ();

	if (theEvent->modifiers & cmdKey) {
		if (theEvent->message & 0xff == '.') {
			/* Flush key queue */
			keyQueueCount = keyQueueWrite = keyQueueRead = 0;
			theEvent->message = '\033';
		} else {
			UndimMenuBar ();
			DoMenuEvt (MenuKey (theEvent->message & 0xff));
			return;
		}
	} else if (theEvent->modifiers & optionKey) {
			if ((theEvent->message & charCodeMask) == '#') {
				/* On the British keyboard layout the # key is option+3 */
				theEvent->modifiers -= optionKey;
			} else {
	   			Ptr     KCHRPtr;
	   			UInt16  keycode;
	   			unsigned long    state = 0;
	   			KCHRPtr = (char*)GetScriptManagerVariable(smKCHRCache);
				keycode = (theEvent->message & keyCodeMask) >> 8;
				keycode |= (theEvent->modifiers - optionKey) & 0xFF00;
				keycode = KeyTranslate(KCHRPtr, keycode, &state);
				theEvent->message &= ~charCodeMask;
				theEvent->message |= keycode | 0x80;
			}
	}
	if (theWindow) {
		int kind = ((WindowPeek)theWindow)->windowKind - WIN_BASE_KIND;
		winKeyFuncs [kind] (theEvent, theWindow);
	} else {
		GeneralKey (theEvent, (WindowPtr) 0);
	}
}


static void
WindowGoAway (EventRecord *theEvent, WindowPtr theWindow) {
	NhWindow *aWin = GetNhWin(theWindow);

	if (!theEvent || TrackGoAway (theWindow, theEvent->where)) {
		if (aWin - theWindows == BASE_WINDOW && !iflags.window_inited) {
			AddToKeyQueue ('\033', 1);
		} else {
			HideWindow (theWindow);
			if (aWin - theWindows != inSelect)
				mac_destroy_nhwindow (aWin - theWindows);
			else								/* if this IS the inSelect window put a close char */
				AddToKeyQueue (CHAR_CR, 1);		/* in queue to exit and maintain inSelect */
		}
	}
}


static void
HandleClick (EventRecord *theEvent) {
	int code;
	unsigned long l;
	WindowPtr theWindow;
	NhWindow *aWin;
	Rect r;
	Boolean not_inSelect;

#if !TARGET_API_MAC_CARBON
	r = (*GetGrayRgn ())->rgnBBox;
	InsetRect (&r, 4, 4);
#else
	InsetRect(GetRegionBounds(GetGrayRgn(), &r), 4, 4);
#endif

	code = FindWindow (theEvent->where, &theWindow);
	aWin = GetNhWin (theWindow);
	not_inSelect = (inSelect == WIN_ERR || aWin - theWindows == inSelect);
	
	switch (code) {
	case inContent :
		if (not_inSelect) {
			int kind = GetWindowKind(theWindow) - WIN_BASE_KIND;
			winCursorFuncs [kind] (theEvent, theWindow, gMouseRgn);
			SelectWindow (theWindow);
			SetPortWindowPort(theWindow);
			winClickFuncs [kind] (theEvent, theWindow);
		} else {
			nhbell ();
		}
		break;

	case inDrag :
		if (not_inSelect) {
			SetCursor(&qdarrow);
			DragWindow (theWindow, theEvent->where, &r);
			SaveWindowPos(theWindow);
		} else {
			nhbell ();
		}
		break;

	case inGrow :
		if (not_inSelect) {
			SetCursor(&qdarrow);
			SetRect (&r, 80, 2 * aWin->row_height + 1, r.right, r.bottom);
			if (aWin == theWindows + WIN_MESSAGE)
				r.top += SBARHEIGHT;
			l = GrowWindow (theWindow, theEvent->where, &r);
			SizeWindow (theWindow, l & 0xffff, l >> 16, FALSE);
			SaveWindowSize(theWindow);
			SetPortWindowPort(theWindow);
			GetWindowBounds(theWindow, kWindowContentRgn, &r);
			OffsetRect(&r, -r.left, -r.top);
			InvalWindowRect(theWindow, &r);
			if (aWin->scrollBar) {
				DrawScrollbar (aWin);
			}
		} else {
			nhbell ();
		}
		break;

	case inGoAway :
		WindowGoAway(theEvent, theWindow);
		break;

	case inMenuBar :
		DoMenuEvt (MenuSelect (theEvent->where));
		break;

#if !TARGET_API_MAC_CARBON
	case inSysWindow :
		SystemClick(theEvent, theWindow);
#endif
	default :
		break;
	}
}


static void
HandleUpdate (EventRecord *theEvent) {
	WindowPtr theWindow = (WindowPtr) theEvent->message;
	NhWindow *aWin = GetNhWin (theWindow);
	Rect r;


	char existing_update_region = FALSE;
	Rect rect;
	
	if (theWindow == _mt_window) {
		existing_update_region = (get_invalid_region (theWindow, &rect) == noErr);
	}
	BeginUpdate (theWindow);
	SetPortWindowPort(theWindow);
	GetWindowBounds(theWindow, kWindowContentRgn, &r);
	OffsetRect(&r, -r.left, -r.top);
	EraseRect(&r);
	winUpdateFuncs [GetWindowKind(theWindow) - WIN_BASE_KIND] 
				(theEvent, theWindow);

	if (theWindow == _mt_window && existing_update_region) {
		set_invalid_region (theWindow, &rect);
	}
	aWin->drawn = TRUE;
	EndUpdate (theWindow);
}


static void
DoOsEvt (EventRecord *theEvent) {
	WindowRef win;
	short code;

	if ((theEvent->message & 0xff000000) == 0xfa000000) {
		/* Mouse Moved */

		code = FindWindow (theEvent->where, &win);
		if (code != inContent) {
			Rect r = {-1, -1, 2, 2};

			SetCursor(&qdarrow);
			OffsetRect (&r, theEvent->where.h, theEvent->where.v);
			RectRgn (gMouseRgn, &r);
		} else {
			int kind = GetWindowKind(win) - WIN_BASE_KIND;
			if (kind >= 0 && kind <= NHW_TEXT) {
				winCursorFuncs [kind] (theEvent, win, gMouseRgn);
			}
		}
	}
}


void
HandleEvent (EventRecord *theEvent) {
	switch (theEvent->what) {
	case autoKey :
	case keyDown :
		HandleKey (theEvent);
		break;
	case updateEvt :
		HandleUpdate (theEvent);
		break;
	case mouseDown :
		HandleClick (theEvent);
		break;
#if !TARGET_API_MAC_CARBON
	case diskEvt :
		if ((theEvent->message & 0xffff0000) != 0) {
			Point p = {150, 150};
			(void) DIBadMount (p, theEvent->message);
		}
		break;
#endif
	case osEvt :
		DoOsEvt (theEvent);
		break;
	case kHighLevelEvent:
		AEProcessAppleEvent(theEvent);
	default :
		break;
	}
}


void
mac_get_nh_event(void) {
	EventRecord anEvent;

	/* KMH -- Don't proceed if the window system isn't set up */
	if (!iflags.window_inited)
		return;

	(void) WaitNextEvent (everyEvent, &anEvent, -1, gMouseRgn);
	HandleEvent(&anEvent);
}


int
mac_nhgetch(void) {
	int ch;
	long doDawdle;
	EventRecord anEvent;

	/* We want to take care of keys in the buffer as fast as
	 * possible
	 */
	if (keyQueueCount)
		doDawdle = 0L;
	else {
		long total, contig;
		static char warn = 0;

		doDawdle = (in_topl_mode() ? GetCaretTime () : 120L);
		/* Since we have time, check memory */
		PurgeSpace (&total, &contig);
		if (contig < 25000L || total < 50000L) {
			if (!warn) {
				pline ("Low Memory!");
				warn = 1;
			}
		} else {
			warn = 0;
		}
	}

	do {
		(void) WaitNextEvent (everyEvent, &anEvent, doDawdle, gMouseRgn);
		HandleEvent (&anEvent);
		ch = GetFromKeyQueue ();
	} while (!ch && !gClickedToMove);

	if (!gClickedToMove)
		ObscureCursor ();
	else
		gClickedToMove = 0;

#ifdef THINK_C
	if (ch == '\r') ch = '\n';
#endif

	return ch;
}


void
mac_delay_output(void) {
	long destTicks = TickCount () + 1;

	while (TickCount () < destTicks) {
		mac_get_nh_event ();
	}
}


#ifdef CLIPPING
static void
mac_cliparound (int x, int y) {
#if defined(MAC_MPW)
# pragma unused(x,y)
#endif
	/* TODO */
}
#endif

void
mac_exit_nhwindows (const char *s) {
	clear_screen ();
	tty_exit_nhwindows (s);
	mac_destroy_nhwindow (WIN_MESSAGE);
	mac_destroy_nhwindow (WIN_INVEN);
}


/*
 * Don't forget to decrease in_putstr before returning...
 */
void
mac_putstr (winid win, int attr, const char *str) {
	long len, slen;
	NhWindow *aWin = &theWindows [win];
	static char in_putstr = 0;
	short newWidth, maxWidth;
	Rect r;
	char *src, *sline, *dst, ch;

	if (win < 0 || win >= NUM_MACWINDOWS || !aWin->its_window) {
		error ("putstr: Invalid win %d (Max %d).", win, NUM_MACWINDOWS, attr);
		return;
	}

	if (aWin->its_window == _mt_window) {
		tty_putstr(win, attr, str);
		return;
	}

	if (in_putstr > 3)
		return;

	in_putstr ++;
	slen = strlen (str);

	SetPortWindowPort(aWin->its_window);
	GetWindowBounds(aWin->its_window, kWindowContentRgn, &r);
	OffsetRect(&r, -r.left, -r.top);
	if (win == WIN_MESSAGE) {
		r.right  -= SBARWIDTH;
		r.bottom -= SBARHEIGHT;
		if (flags.page_wait && 
			aWin->last_more_lin <= aWin->y_size - (r.bottom - r.top) / aWin->row_height) {
			aWin->last_more_lin = aWin->y_size;
			mac_display_nhwindow(win, TRUE);
		}
	}

	/*
	 * A "default" text window - uses TETextBox
	 * We just add the text, without attributes for now
	 */
	len = GetHandleSize (aWin->windowText);
	while (aWin->windowTextLen + slen + 1 > len) {
		len = (len > 2048) ? (len + 2048) : (len * 2);
		SetHandleSize (aWin->windowText, len);
		if (MemError ()) {
			error ("putstr: SetHandleSize");
			aWin->windowTextLen = 0L;
			aWin->save_lin = 0;
			aWin->y_curs = 0;
			aWin->y_size = 0;
		}
	}
	
	len = aWin->windowTextLen;
	dst = *(aWin->windowText) + len;
	sline = src = (char *)str;
	maxWidth = newWidth = 0;
	for (ch = *src; ch; ch = *src) {
		if (ch == CHAR_LF)
			ch = CHAR_CR;
		*dst++ = ch;
		if (ch == CHAR_CR) {
			aWin->y_curs ++;
			aWin->y_size ++;
			aWin->x_curs = 0;
			newWidth = TextWidth (sline, 0, src - sline);
			if (newWidth > maxWidth) {
				maxWidth = newWidth;
			}
			sline = src+1;	/* keep track of where new line begins */
		}
		else
			aWin->x_curs ++;
		src++;
	}

	newWidth = TextWidth (sline, 0, src - sline);
	if (newWidth > maxWidth) {
		maxWidth = newWidth;
	}

	aWin->windowTextLen += slen;
	
	if (ch != CHAR_CR) {
		(*(aWin->windowText)) [len + slen] = CHAR_CR;
		aWin->windowTextLen ++;
		aWin->y_curs ++;
		aWin->y_size ++;
		aWin->x_curs = 0;
	}

	if (win == WIN_MESSAGE) {
		short min = aWin->y_size - (r.bottom - r.top) / aWin->row_height;
		if (aWin->scrollPos < min) {
			aWin->scrollPos = min;
			SetControlMaximum (aWin->scrollBar, aWin->y_size);
			SetControlValue(aWin->scrollBar, min);
		}
		InvalWindowRect(aWin->its_window, &r);
	}
	else	/* Message has a fixed width, other windows base on content */
		if (maxWidth > aWin->x_size)
			aWin->x_size = maxWidth;
	in_putstr --;
}


void
mac_curs (winid win, int x, int y) {
	NhWindow *aWin = &theWindows [win];

	if (aWin->its_window == _mt_window) {
		tty_curs(win, x, y);
		return;
	}

	SetPortWindowPort(aWin->its_window);
	MoveTo (x * aWin->char_width, (y * aWin->row_height) + aWin->ascent_height);
	aWin->x_curs = x;
	aWin->y_curs = y;
}


int
mac_nh_poskey (int *a, int *b, int *c) {
	int ch = mac_nhgetch();
	*a = clicked_pos.h;
	*b = clicked_pos.v;
	*c = clicked_mod;
	return ch;
}


void
mac_start_menu (winid win) {
	HideWindow (theWindows [win].its_window);
	mac_clear_nhwindow (win);
}


void
mac_add_menu (winid win, int glyph, const anything *any, CHAR_P menuChar, CHAR_P groupAcc, int attr, const char *inStr, int preselected) {
#if defined(MAC_MPW)
# pragma unused(glyph)
#endif
	NhWindow *aWin = &theWindows [win];
	const char *str;
	char locStr[4+BUFSZ];
	MacMHMenuItem *item;

	if (!inStr) return;

	if (any->a_void != 0) {

#define kMenuSizeBump 26
		if (!aWin->miSize) {
			aWin->menuInfo = (MacMHMenuItem **)NewHandle(sizeof(MacMHMenuItem) * kMenuSizeBump);
			if (!aWin->menuInfo) {
				error("Can't alloc menu handle");
				return;
			}
			aWin->menuSelected = (short **)NewHandle(sizeof(short) * kMenuSizeBump);
			if (!aWin->menuSelected) {
				error("Can't alloc menu select handle");
				return;
			}
			aWin->miSize = kMenuSizeBump;
		}

		if (aWin->miLen >= aWin->miSize) {
			SetHandleSize((Handle)aWin->menuInfo, sizeof(MacMHMenuItem) * (aWin->miLen+kMenuSizeBump));
			if (MemError()) {
				error("Can't resize menu handle");
				return;
			}
			SetHandleSize((Handle)aWin->menuSelected, sizeof(short) * (aWin->miLen+kMenuSizeBump));
			if (MemError()) {
				error("Can't resize menu select handle");
				return;
			}
			aWin->miSize += kMenuSizeBump;
		}

		if (menuChar == 0) {	
			if (('a' <= aWin->menuChar && aWin->menuChar <= 'z') ||
				('A' <= aWin->menuChar && aWin->menuChar <= 'Z')) {
				menuChar = aWin->menuChar++;
				if (menuChar == 'z')
					aWin->menuChar = 'A';
			}
		}
		
		Sprintf(locStr, "%c - %s", (menuChar ? menuChar : ' '), inStr);
		str = locStr;
		HLock ((char**)aWin->menuInfo);
		HLock ((char**)aWin->menuSelected);
		(*aWin->menuSelected)[aWin->miLen] = preselected;
		item = &(*aWin->menuInfo)[aWin->miLen];
		aWin->miLen++;
		item->id = *any;
		item->accelerator = menuChar;
		item->groupAcc = groupAcc;
		item->line = aWin->y_size;
		HUnlock ((char**)aWin->menuInfo);
		HUnlock ((char**)aWin->menuSelected);
	} else
		str = inStr;

	putstr (win, attr, str);
}


/*
 * End a menu in this window, window must a type NHW_MENU.
 * str is a list of cancel characters (values that may be input)
 * morestr is a prompt to display, rather than the default.
 * str and morestr might be ignored by some ports.
 */
void
mac_end_menu (winid win, const char *morestr) {
	Str255 buf;
	NhWindow *aWin = &theWindows [win];

	buf [0] = 0;
	if (morestr) 
		C2P (morestr, buf);
	SetWTitle (aWin->its_window, buf);
}


int
mac_select_menu (winid win, int how, menu_item **selected_list) {
	int c;
	NhWindow *aWin = &theWindows [win];
	WindowPtr theWin = aWin->its_window;

	inSelect = win;

	mac_display_nhwindow (win, FALSE);

	aWin->how = (short) how;
	for (;;) {
		c = map_menu_cmd (mac_nhgetch());
		if (c == CHAR_ESC) {
			/* deselect everything */
			aWin->miSelLen = 0;
			break;
		} else if (ClosingWindowChar(c)) {
			break;
		} else {
			nhbell();
		}
	}

	HideWindow (theWin);

	if (aWin->miSelLen) {
		menu_item *mp;
		MacMHMenuItem *mi;
		*selected_list = mp = (menu_item *) alloc(aWin->miSelLen * sizeof(menu_item));
		HLock ((char**)aWin->menuInfo);
		HLock ((char**)aWin->menuSelected);
		for (c = 0; c < aWin->miSelLen; c++) {
			mi = &(*aWin->menuInfo)[(*aWin->menuSelected) [c]];
			mp->item = mi->id;
			mp->count = -1L;
			mp++;
		}
		HUnlock ((char**)aWin->menuInfo);
		HUnlock ((char**)aWin->menuSelected);
	} else
		*selected_list = 0;

	inSelect = WIN_ERR;

	return aWin->miSelLen;
}

#include "dlb.h"

static void
mac_display_file (name, complain)
const char *name;	/* not ANSI prototype because of boolean parameter */
boolean complain;
{
	Ptr buf;
	int win;
	dlb *fp = dlb_fopen(name, "r");
	
	if (fp) {
		long l = dlb_fseek(fp, 0, SEEK_END);
		(void) dlb_fseek(fp, 0, 0L);
		buf = NewPtr(l+1);
		if (buf) {
			l = dlb_fread(buf, 1, l, fp);
			if (l > 0) {
				buf[l] = '\0';
				win = create_nhwindow(NHW_TEXT);
				if (WIN_ERR == win) {
					if (complain) error ("Cannot make window.");
				} else {
					putstr(win, 0, buf);
					display_nhwindow(win, FALSE);
				}
			}
			DisposePtr(buf);
		}
		dlb_fclose(fp);
	} else if (complain)
		error("Cannot open %s.", name);
}


void
port_help () {
	display_file (PORT_HELP, TRUE);
}


static void
mac_unimplemented (void) {
}


static void
mac_suspend_nhwindows (const char *foo) {
#if defined(MAC_MPW)
# pragma unused(foo)
#endif
	/*	Can't really do that :-)		*/
}


int
try_key_queue (char *bufp) {
	if (keyQueueCount) {
		char ch;
		for (ch = GetFromKeyQueue(); ; ch = GetFromKeyQueue()) {
			if (ch == CHAR_LF || ch == CHAR_CR)
				ch = 0;
			*bufp++ = ch;
			if (ch == 0)
				break;
		}
		return 1;
	}
	return 0;
}

/* Interface definition, for windows.c */
struct window_procs mac_procs = {
	"mac",
	WC_COLOR | WC_HILITE_PET |
	WC_FONT_MAP | WC_FONT_MENU | WC_FONT_MESSAGE | WC_FONT_STATUS | WC_FONT_TEXT |
	WC_FONTSIZ_MAP | WC_FONTSIZ_MENU | WC_FONTSIZ_MESSAGE | WC_FONTSIZ_STATUS | WC_FONTSIZ_TEXT,
	0L,
	mac_init_nhwindows,
	mac_unimplemented,	/* see macmenu.c:mac_askname() for player selection */
	mac_askname,
	mac_get_nh_event,
	mac_exit_nhwindows,
	mac_suspend_nhwindows,
	mac_unimplemented,
	mac_create_nhwindow,
	mac_clear_nhwindow,
	mac_display_nhwindow,
	mac_destroy_nhwindow,
	mac_curs,
	mac_putstr,
	mac_display_file,
	mac_start_menu,
	mac_add_menu,
	mac_end_menu,
	mac_select_menu,
	genl_message_menu,
	mac_unimplemented,
	mac_get_nh_event,
	mac_get_nh_event,
#ifdef CLIPPING
	mac_cliparound,
#endif
#ifdef POSITIONBAR
	donull,
#endif
	tty_print_glyph,
	tty_raw_print,
	tty_raw_print_bold,
	mac_nhgetch,
	mac_nh_poskey,
	tty_nhbell,
	mac_doprev_message,
	mac_yn_function,
	mac_getlin,
	mac_get_ext_cmd,
	mac_number_pad,
	mac_delay_output,
#ifdef CHANGE_COLOR
	tty_change_color,
	tty_change_background,
	set_tty_font_name,
	tty_get_color_string,
#endif
/* other defs that really should go away (they're tty specific) */
	0, //    mac_start_screen,
	0, //    mac_end_screen,
	genl_outrip,
	genl_preference_update,
};

/*macwin.c*/
