/*	SCCS Id: @(#)macmenu.c	3.4	1999/11/24	*/
/*      Copyright (c) Macintosh NetHack Port Team, 1993.          */
/* NetHack may be freely redistributed.  See license for details. */

/****************************************\
 * Extended Macintosh menu support
 *
 * provides access to all keyboard commands from cmd.c
 * provides control key functionality for classic keyboards
 * provides key equivalent references and logical menu groups
 * supports various menu highlighting modes
\****************************************/

/****************************************\
 * Edit History:
 *
 * 930512	- More bug fixes and getting tty to work again, Jon W{tte
 * 930508	- Bug fixes in-flight, Jon W{tte
 * 04/29/93 - 1st Release Draft, David Hairston
 * 04/11/93 - 1st Draft, David Hairston
\****************************************/

/******** Application Defines ********/
#include "hack.h"
#include "mactty.h"
#include "macwin.h"
#include "macpopup.h"
#include "patchlevel.h"

/******** Toolbox Defines ********/
#if !TARGET_API_MAC_CARBON
#include <Menus.h>
#include <Devices.h>
#include <Resources.h>
#include <TextUtils.h>
#include <ToolUtils.h>
#include <Sound.h>
#endif

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

/******** Local Defines ********/

/* 'MNU#' (menu list record) */
typedef union menuRefUnn
{
	short		mresID;		/* MENU resource ID (before GetMenu) */
	MenuHandle	mhnd;		/* MENU handle (after GetMenu) */
} menuRefUnn;

typedef struct menuListRec
{
	short		firstMenuID;
	short		numMenus;
	menuRefUnn	mref[];
} menuListRec, *menuListPtr, **menuListHandle;

/* indices and resource IDs of the menu list data */
enum
{
	listMenubar,
	listSubmenu,

	menuBarListID = 128,
	subMenuListID
};

/* the following mref[] indices are reserved */
enum
{
	/* menu bar */
	menuApple,
	menuFile,
	menuEdit,

	/* submenu */
	menuWizard = 0
};

/* the following menu items are reserved */
enum
{
	/* apple */
	menuAppleAboutBox = 1,
	____Apple__1,

	/* File */
	menuFileRedraw = 1,
	menuFilePrevMsg,
	menuFileCleanup,
	____File___1,
	menuFilePlayMode,
	menuFileEnterExplore,
	____File___2,
	menuFileSave,
	____File___3,
	menuFileQuit,

	/* standard minimum Edit menu items */

	/* Wizard */
	menuWizardAttributes = 1
};


/*
 * menuListRec data (preloaded and locked) specifies the number of menus in
 * the menu bar, the number of hierarchal or submenus and the menu IDs of
 * all of those menus.  menus that go into in the menu bar are specified by
 * 'MNU#' 128 and submenus are specified by 'MNU#' 129.  the fields of the
 * menuListRec are:
 * firstMenuID - the menu ID (not resource ID) of the 1st menu.  subsequent
 *     menus in the list are _forced_ to have consecutively incremented IDs.
 * numMenus - the total count of menus in a given list (and the extent of
 *     valid menu IDs).
 * mref[] - initially the MENU resource ID is stored in the placeholder for
 *     the resource handle.  after loading (GetResource), the menu handle
 *     is stored and the menu ID, in memory, is set as noted above.
 *
 * NOTE: a ResEdit template editor is supplied to edit the 'MNU#' resources.
 *
 * NOTE: the resource IDs do not need to match the menu IDs in a menu list
 * record although they have been originally set that way.
 *
 * NOTE: the menu ID's of menus in the submenu list record may be reset, as
 * noted above.  it is the programmers responsibility to make sure that
 * submenu references/IDs are valid.
 *
 * WARNING: the existence of the submenu list record is assumed even if the
 * number of submenus is zero.  also, no error checking is done on the
 * extents of the menu IDs.  this must be correctly setup by the programmer.
 */

#define ID1_MBAR	pMenuList[listMenubar]->firstMenuID
#define ID1_SUBM	pMenuList[listSubmenu]->firstMenuID

#define NUM_MBAR	pMenuList[listMenubar]->numMenus
#define NUM_SUBM	pMenuList[listSubmenu]->numMenus

#define MHND_APPLE	pMenuList[listMenubar]->mref[menuApple].mhnd
#define MHND_FILE	pMenuList[listMenubar]->mref[menuFile].mhnd
#define MHND_EDIT	pMenuList[listMenubar]->mref[menuEdit].mhnd

#define MBARHND(x)	pMenuList[listMenubar]->mref[(x)].mhnd

#define MHND_WIZ	pMenuList[listSubmenu]->mref[menuWizard].mhnd


/* mutually exclusive (and prioritized) menu bar states */
enum
{
	mbarDim,
	mbarNoWindows,
	mbarDA,
	mbarNoMap,
	mbarRegular,
	mbarSpecial					/* explore or debug mode */
};

#define WKND_MAP		(WIN_BASE_KIND + NHW_MAP)


/* menu routine error numbers */
enum
{
	errGetMenuList,
	errGetMenu,
	errGetANDlogTemplate,
	errGetANDlogItems,
	errGetANDialog,
	errANNewMenu,
	err_Menu_total
};


/* menu 'STR#' comment char */
#define mstrEndChar		0xA5		/* '\245' or option-* or "bullet" */

/* 'ALRT' */
enum
{
	alrt_Menu_start = 5000,
	alrtMenuNote = alrt_Menu_start,
	alrtMenu_NY,
	alrt_Menu_limit
};

#define beepMenuAlertErr	1		/* # of SysBeep()'s before exitting */
enum
{
	bttnMenuAlertNo = 1,
	bttnMenuAlertYes
};


/******** Globals ********/
static	unsigned char *menuErrStr[err_Menu_total] = 
	{
		"\pAbort: Bad \'MNU#\' resource!",		/* errGetMenuList */
		"\pAbort: Bad \'MENU\' resource!",		/* errGetMenu */
		"\pAbort: Bad \'DLOG\' resource!",		/* errGetANDlogTemplate */
		"\pAbort: Bad \'DITL\' resource!",		/* errGetANDlogItems */
		"\pAbort: Bad Dialog Allocation!",		/* errGetANDialog */
		"\pAbort: Bad Menu Allocation!",		/* errANNewMenu */
	};
static	menuListPtr	pMenuList[2];
static	short		theMenubar = mbarDA;	/* force initial update */
static	short		kAdjustWizardMenu = 1;


/******** Prototypes ********/
#if !TARGET_API_MAC_CARBON
static	void alignAD(Rect *, short);
#endif
static	void mustGetMenuAlerts(void);
static	void menuError(short);
static	void aboutNetHack(void);
static	void askSave(void);
static	void askQuit(void);


/*** Askname dialog box ***/

#define RSRC_ASK			6000	/* Askname dialog and item list */
#define RSRC_ASK_PLAY			1	/*	Play button */
#define RSRC_ASK_QUIT			2	/*	Quit button */
#define RSRC_ASK_DEFAULT		3	/*	Default ring */
#define RSRC_ASK_ROLE			4	/*	Role popup menu */
#define RSRC_ASK_RACE			5	/*	Race popup menu */
#define RSRC_ASK_GEND			6	/*	Gender popup menu */
#define RSRC_ASK_ALIGN			7	/*	Alignment popup menu */
#define RSRC_ASK_MODE			8	/*	Mode popup menu */
#define RSRC_ASK_NAME			9	/*	Name text field */
#define RSRC_ASK_MAX			10	/*	Maximum enabled item */

#define KEY_MASK	0xff00
#define KEY_RETURN	0x2400
#define KEY_ENTER	0x4c00
#define KEY_ESCAPE	0x3500
#define CH_MASK		0x00ff
#define CH_RETURN	0x000d
#define CH_ENTER	0x0003
#define CH_ESCAPE	0x001b

static void ask_restring(const char *cstr, unsigned char *pstr);
static void ask_enable(DialogRef wind, short item, int enable);
static pascal void ask_redraw(DialogRef wind, DialogItemIndex item);
static pascal Boolean ask_filter(DialogRef wind, EventRecord *event, DialogItemIndex *item);
#define noresource(t,n)	{SysBeep(3); ExitToShell();}
#define fatal(s)	{SysBeep(3); ExitToShell();}

static MenuHandle askmenu[RSRC_ASK_MAX];
static int askselect[RSRC_ASK_MAX];
#define currrole	askselect[RSRC_ASK_ROLE]
#define currrace	askselect[RSRC_ASK_RACE]
#define currgend	askselect[RSRC_ASK_GEND]
#define curralign	askselect[RSRC_ASK_ALIGN]
#define currmode	askselect[RSRC_ASK_MODE]

static RGBColor
	blackcolor = {0x0000, 0x0000, 0x0000},
//	indentcolor = {0x4000, 0x4000, 0x4000},
	darkcolor = {0x8000, 0x8000, 0x8000},
	backcolor = {0xdddd, 0xdddd, 0xdddd},
	lightcolor = {0xffff, 0xffff, 0xffff},
	whitecolor = {0xffff, 0xffff, 0xffff};


/* Convert a mixed-case C string to a Capitalized Pascal string */
static void
ask_restring (const char *cstr, unsigned char *pstr)
{
	int i;


	for (i = 0; *cstr && (i < 255); i++)
	    pstr[i+1] = *cstr++;
	pstr[0] = i;
	if ((pstr[1] >= 'a') && (pstr[1] <= 'z'))
	    pstr[1] += 'A' - 'a';
	return;
}


/* Enable the dialog item with the given index */
static void
ask_enable (DialogRef wind, short item, int enable)
{
	short type;
	Handle handle;
	Rect rect;


	/* Enable or disable the appropriate item */
	GetDialogItem(wind, item, &type, &handle, &rect);
	if (enable)	type &= ~itemDisable;
	else		type |= itemDisable;
	HiliteControl((ControlHandle)handle, enable ? 0 : 255);
	SetDialogItem(wind, item, type, handle, &rect);
	return;
}


static pascal void
ask_redraw (DialogRef wind, DialogItemIndex item)
{
	short type;
	Handle handle;
	Rect rect;
	static char	*modechar = "NED";


	/* Which item shall we redraw? */
	GetDialogItem(wind, item, &type, &handle, &rect);
	switch (item) {
		case RSRC_ASK_DEFAULT:
			PenSize(3, 3);
			FrameRoundRect(&rect, 16, 16);
			break;

		case RSRC_ASK_ROLE:
		case RSRC_ASK_RACE:
		case RSRC_ASK_GEND:
		case RSRC_ASK_ALIGN:
		case RSRC_ASK_MODE:
			if (macFlags.color) {
				RGBForeColor(&blackcolor);
				RGBBackColor(&backcolor);
			}
			PenNormal();
			TextMode(srcOr);
			EraseRect(&rect);

			/* Draw the frame and drop shadow */
			rect.right--;
			rect.bottom--;
			FrameRect(&rect);
			MoveTo(rect.right, rect.top+1);
			LineTo(rect.right, rect.bottom);
			LineTo(rect.left+1, rect.bottom);

			/* Draw the menu character */
			MoveTo(rect.left+4, rect.top+12);
			switch (item) {
			case RSRC_ASK_ROLE:
				DrawText(roles[askselect[item]].filecode, 0, 3);
				break;
			case RSRC_ASK_RACE:
				DrawText(races[askselect[item]].filecode, 0, 3);
				break;
			case RSRC_ASK_GEND:
				DrawText(genders[askselect[item]].filecode, 0, 3);
				break;
			case RSRC_ASK_ALIGN:
				DrawText(aligns[askselect[item]].filecode, 0, 3);
				break;
			case RSRC_ASK_MODE:
				DrawChar(modechar[askselect[item]]);
				break;
			}

			/* Draw the popup symbol */
			MoveTo(rect.right - 16, rect.top + 5);
			LineTo(rect.right -  6, rect.top + 5);
			LineTo(rect.right - 11, rect.top + 10);
			LineTo(rect.right - 15, rect.top + 6);
			LineTo(rect.right -  8, rect.top + 6);
			LineTo(rect.right - 11, rect.top + 9);
			LineTo(rect.right - 13, rect.top + 7);
			LineTo(rect.right - 10, rect.top + 7);
			LineTo(rect.right - 11, rect.top + 8);

			/* Draw the shadow */
			InsetRect(&rect, 1, 1);
			if (macFlags.color) {
				RGBColor color;


				/* Save the foreground color */
				GetForeColor(&color);

				/* Draw the top and left */
				RGBForeColor(&lightcolor);
				MoveTo(rect.left, rect.bottom-1);
				LineTo(rect.left, rect.top);
				LineTo(rect.right-1, rect.top);

				/* Draw the bottom and right */
				RGBForeColor(&darkcolor);
				MoveTo(rect.right-1, rect.top+1);
				LineTo(rect.right-1, rect.bottom-1);
				LineTo(rect.left+1, rect.bottom-1);

				/* Restore the foreground color */
				RGBForeColor(&color);
			}
			break;

		case RSRC_ASK_NAME:
			PenNormal();
			if (macFlags.color) {
				RGBForeColor(&whitecolor);
				RGBBackColor(&whitecolor);
				TextMode(srcOr);
			} else {
				PenMode(notPatCopy);
				TextMode(srcBic);
			}
			InsetRect(&rect, -1, -1);
			FrameRect(&rect);
			InsetRect(&rect, -1, -1);
			FrameRect(&rect);
			InsetRect(&rect, -2, -2);
			if (macFlags.color) {
				/* Draw the top and left */
				RGBForeColor(&darkcolor);
				MoveTo(rect.left, rect.bottom-1);
				LineTo(rect.left, rect.top);
				LineTo(rect.right-1, rect.top);

				/* Draw the bottom and right */
				RGBForeColor(&lightcolor);
				MoveTo(rect.right-1, rect.top+1);
				LineTo(rect.right-1, rect.bottom-1);
				LineTo(rect.left+1, rect.bottom-1);

				/* Restore the colors */
				RGBForeColor(&blackcolor);
				RGBBackColor(&backcolor);
			}
			break;
	}
	return;
}


static pascal Boolean
ask_filter (DialogRef wind, EventRecord *event, DialogItemIndex *item)
{
	short ch, key;


	switch (event->what) {
		case keyDown:
		case autoKey:
			ch = event->message & CH_MASK;
			key = event->message & KEY_MASK;
			/* Handle equivalents for OK */
			if ((ch == CH_RETURN) || (key == KEY_RETURN) ||
				(ch == CH_ENTER) || (key == KEY_ENTER)) {
				if (GetDialogTextEditHandle(wind)[0]->teLength) {
					FlashButton(wind, RSRC_ASK_PLAY);
					*item = RSRC_ASK_PLAY;
				} else
					*item = 0;
				return (TRUE);
			}
			/* Handle equivalents for Normal/Explore/Debug */
			if ((event->modifiers & cmdKey) && (ch == 'n')) {
				currmode = 0;
				ask_redraw(wind, RSRC_ASK_MODE);
				*item = RSRC_ASK_MODE;
				return (TRUE);
			}
			if ((event->modifiers & cmdKey) && (ch == 'e')) {
				currmode = 1;
				ask_redraw(wind, RSRC_ASK_MODE);
				*item = RSRC_ASK_MODE;
				return (TRUE);
			}
			if ((event->modifiers & cmdKey) && (ch == 'd')) {
				currmode = 2;
				ask_redraw(wind, RSRC_ASK_MODE);
				*item = RSRC_ASK_MODE;
				return (TRUE);
			}
			/* Handle equivalents for Cancel and Quit */
			if ((ch == CH_ESCAPE) || (key == KEY_ESCAPE) ||
				((event->modifiers & cmdKey) && (ch == 'q')) ||
				((event->modifiers & cmdKey) && (ch == '.'))) {
				FlashButton(wind, RSRC_ASK_QUIT);
				*item = RSRC_ASK_QUIT;
				return (TRUE);
			}
			return (FALSE);
		case updateEvt:
			ask_redraw(wind, RSRC_ASK_NAME);
			return (FALSE);
		default:
			return (FALSE);
	}
}


void mac_askname ()
{
	GrafPtr oldport;
	DialogRef askdialog;
	short i, j, item, type;
	Handle handle;
	Rect rect;
	Str255 str;
	Point pt;
	UserItemUPP redraw = NewUserItemUPP(ask_redraw);
	ModalFilterUPP filter = NewModalFilterUPP(ask_filter);


	/* Create the dialog */
	if (!(askdialog = GetNewDialog(RSRC_ASK, NULL, (WindowRef)-1)))
	    noresource('DLOG', RSRC_ASK);
	GetPort(&oldport);
	SetPortDialogPort(askdialog);

	/* Initialize the name text item */
	ask_restring(plname, str);
	if (plname[0]) {
	    GetDialogItem(askdialog, RSRC_ASK_NAME, &type, &handle, &rect);
	    SetDialogItemText(handle, str);
	}
#if 0
	{
	Str32 pName;
		pName [0] = 0;
		if (plname && plname [0]) {
			strcpy ((char *) pName, plname);
			c2pstr ((char *) pName);
		} else {
			Handle h;
			h = GetResource ('STR ', -16096);
			if (((Handle) 0 != h) && (GetHandleSize (h) > 0)) {
				DetachResource (h);
				HLock (h);
				if (**h > 31) {
					**h = 31;
				}
				BlockMove (*h, pName, **h + 1);
				DisposeHandle (h);
			}
		}
		if (pName [0]) {
			GetDialogItem(askdialog, RSRC_ASK_NAME, &type, &handle, &rect);
			SetDialogItemText(handle, pName);
			if (pName [0] > 2 && pName [pName [0] - 1] == '-') {
			    short role = (*pANR).anMenu[anRole];
			    char suffix = (char) pName[pName[0]],
				*sfxindx = strchr(pl_classes, suffix);

			    if (sfxindx)
				role = (short) (sfxindx - pl_classes);
			    else if (suffix == '@')
				role = (short) rn2((int) strlen(pl_classes));
			    (*pANR).anMenu[anRole] = role;
			}
		}
	}
#endif
	SelectDialogItemText(askdialog, RSRC_ASK_NAME, 0, 32767);

	/* Initialize the role popup menu */
	if (!(askmenu[RSRC_ASK_ROLE] = NewMenu(RSRC_ASK_ROLE, "\p")))
	    fatal("\pCannot create role menu");
	for (i = 0; roles[i].name.m; i++) {
	    ask_restring(roles[i].name.m, str);
	    AppendMenu(askmenu[RSRC_ASK_ROLE], str);
	}
	InsertMenu(askmenu[RSRC_ASK_ROLE], hierMenu);
	if (flags.initrole >= 0)
	    currrole = flags.initrole;
	/* Check for backward compatibility */
	else if ((currrole = str2role(pl_character)) < 0)
	    currrole = randrole();

	/* Initialize the race popup menu */
	if (!(askmenu[RSRC_ASK_RACE] = NewMenu(RSRC_ASK_RACE, "\p")))
	    fatal("\pCannot create race menu");
	for (i = 0; races[i].noun; i++) {
	    ask_restring(races[i].noun, str);
	    AppendMenu(askmenu[RSRC_ASK_RACE], str);
	}
	InsertMenu(askmenu[RSRC_ASK_RACE], hierMenu);
	if (flags.initrace >= 0)
	    currrace = flags.initrace;
	else
	    currrace = randrace(currrole);

	/* Initialize the gender popup menu */
	if (!(askmenu[RSRC_ASK_GEND] = NewMenu(RSRC_ASK_GEND, "\p")))
	    fatal("\pCannot create gender menu");
	for (i = 0; i < ROLE_GENDERS; i++) {
	    ask_restring(genders[i].adj, str);
	    AppendMenu(askmenu[RSRC_ASK_GEND], str);
	}
	InsertMenu(askmenu[RSRC_ASK_GEND], hierMenu);
	if (flags.initgend >= 0)
	    currgend = flags.initgend;
	else if (flags.female)
	    currgend = 1;
	else
	    currgend = randgend(currrole, currrace);

	/* Initialize the alignment popup menu */
	if (!(askmenu[RSRC_ASK_ALIGN] = NewMenu(RSRC_ASK_ALIGN, "\p")))
	    fatal("\pCannot create alignment menu");
	for (i = 0; i < ROLE_ALIGNS; i++) {
	    ask_restring(aligns[i].adj, str);
	    AppendMenu(askmenu[RSRC_ASK_ALIGN], str);
	}
	InsertMenu(askmenu[RSRC_ASK_ALIGN], hierMenu);
	if (flags.initalign >= 0)
	    curralign = flags.initalign;
	else
	    curralign = randalign(currrole, currrace);

	/* Initialize the mode popup menu */
	if (!(askmenu[RSRC_ASK_MODE] = NewMenu(RSRC_ASK_MODE, "\p")))
	    fatal("\pCannot create mode menu");
	AppendMenu(askmenu[RSRC_ASK_MODE], "\pNormal");
	AppendMenu(askmenu[RSRC_ASK_MODE], "\pExplore");
#ifdef WIZARD
	AppendMenu(askmenu[RSRC_ASK_MODE], "\pDebug");
#endif
	InsertMenu(askmenu[RSRC_ASK_MODE], hierMenu);
	currmode = 0;

	/* Set the redraw procedures */
	for (item = RSRC_ASK_DEFAULT; item <= RSRC_ASK_MODE; item++) {
	    GetDialogItem(askdialog, item, &type, &handle, &rect);
	    SetDialogItem(askdialog, item, type, (Handle)redraw, &rect);
	}

	/* Handle dialog events */
	do {
	    /* Adjust the Play button */
	    ask_enable(askdialog, RSRC_ASK_PLAY,
				GetDialogTextEditHandle(askdialog)[0]->teLength);

	    /* Adjust the race popup menu */
	    i = j = currrace;
	    do {
	    	if (validrace(currrole, j)) {
	    		EnableMenuItem(askmenu[RSRC_ASK_RACE], j+1);
	    		CheckMenuItem(askmenu[RSRC_ASK_RACE], j+1,
	    				currrace == j);
	    	} else {
	    		DisableMenuItem(askmenu[RSRC_ASK_RACE], j+1);
	    		CheckMenuItem(askmenu[RSRC_ASK_RACE], j+1, FALSE);
	    		if ((currrace == j) && !races[++currrace].noun)
	    			currrace = 0;
	    	}
	    	if (!races[++j].noun) j = 0;
	    } while (i != j);
	    if (currrace != i) {
	    	GetDialogItem(askdialog, RSRC_ASK_RACE, &type, &handle, &rect);
	    	InvalWindowRect(GetDialogWindow(askdialog), &rect);
	    }

	    /* Adjust the gender popup menu */
	    i = j = currgend;
	    do {
	    	if (validgend(currrole, currrace, j)) {
	    		EnableMenuItem(askmenu[RSRC_ASK_GEND], j+1);
	    		CheckMenuItem(askmenu[RSRC_ASK_GEND], j+1,
	    				currgend == j);
	    	} else {
	    		DisableMenuItem(askmenu[RSRC_ASK_GEND], j+1);
	    		CheckMenuItem(askmenu[RSRC_ASK_GEND], j+1, FALSE);
	    		if ((currgend == j) && (++currgend >= ROLE_GENDERS))
	    			currgend = 0;
	    	}
	    	if (++j >= ROLE_GENDERS) j = 0;
	    } while (i != j);
	    if (currgend != i) {
	    	GetDialogItem(askdialog, RSRC_ASK_GEND, &type, &handle, &rect);
	    	InvalWindowRect(GetDialogWindow(askdialog), &rect);
	    }

	    /* Adjust the alignment popup menu */
	    i = j = curralign;
	    do {
	    	if (validalign(currrole, currrace, j)) {
	    		EnableMenuItem(askmenu[RSRC_ASK_ALIGN], j+1);
	    		CheckMenuItem(askmenu[RSRC_ASK_ALIGN], j+1,
	    				curralign == j);
	    	} else {
	    		DisableMenuItem(askmenu[RSRC_ASK_ALIGN], j+1);
	    		CheckMenuItem(askmenu[RSRC_ASK_ALIGN], j+1, FALSE);
	    		if ((curralign == j) && (++curralign >= ROLE_ALIGNS))
	    			curralign = 0;
	    	}
	    	if (++j >= ROLE_ALIGNS) j = 0;
	    } while (i != j);
	    if (curralign != i) {
	    	GetDialogItem(askdialog, RSRC_ASK_ALIGN, &type, &handle, &rect);
	    	InvalWindowRect(GetDialogWindow(askdialog), &rect);
	    }

	    /* Adjust the role popup menu */
	    for (i = 0; roles[i].name.m; i++) {
	    	ask_restring((currgend && roles[i].name.f) ?
	    			roles[i].name.f : roles[i].name.m, str);
	    	SetMenuItemText(askmenu[RSRC_ASK_ROLE], i+1, str);
	    	CheckMenuItem(askmenu[RSRC_ASK_ROLE], i+1, currrole == i);
	    }

	    /* Adjust the mode popup menu */
	    CheckMenuItem(askmenu[RSRC_ASK_MODE], 1, currmode == 0);
	    CheckMenuItem(askmenu[RSRC_ASK_MODE], 2, currmode == 1);
#ifdef WIZARD
	    CheckMenuItem(askmenu[RSRC_ASK_MODE], 3, currmode == 2);
#endif

	    /* Wait for an action on an item */
	    ModalDialog(filter, &item);
	    switch (item) {
	    case RSRC_ASK_PLAY:
	    	break;
	    case RSRC_ASK_QUIT:
	    	currmode = -1;
	    	break;
	    case RSRC_ASK_ROLE:
	    case RSRC_ASK_RACE:
	    case RSRC_ASK_ALIGN:
	    case RSRC_ASK_GEND:
	    case RSRC_ASK_MODE:
	    	GetDialogItem(askdialog, item, &type, &handle, &rect);
	    	pt = *(Point *)&rect;
	    	LocalToGlobal(&pt);
	    	if (!!(i = PopUpMenuSelect(askmenu[item], pt.v, pt.h,
	    			askselect[item] + 1)))
	    		askselect[item] = LoWord(i) - 1;
	    	InvalWindowRect(GetDialogWindow(askdialog), &rect);
	    	break;
	    case RSRC_ASK_NAME:
#if 0
	    /* limit the data here to 25 chars */
	    {
	    	short beepTEDelete = 1;

	    	while ((**dRec.textH).teLength > 25)
	    	{
	    		if (beepTEDelete++ <= 3)
	    			SysBeep(3);
	    		TEKey('\b', dRec.textH);
	    	}
	    }

	    /* special case filter (that doesn't plug all the holes!) */
	    if (((**dRec.textH).teLength == 1) && (**((**dRec.textH).hText) < 32))
	    	TEKey('\b', dRec.textH);
#endif
	    	break;
	    }
	} while ((item != RSRC_ASK_PLAY) && (item != RSRC_ASK_QUIT));

	/* Process the name */
	GetDialogItem(askdialog, RSRC_ASK_NAME, &type, &handle, &rect);
	GetDialogItemText(handle, str);
	if (str[0] > PL_NSIZ-1) str[0] = PL_NSIZ-1;
	BlockMove(&str[1], plname, str[0]);
	plname[str[0]] = '\0';

	/* Destroy the dialog */
	for (i = RSRC_ASK_ROLE; i <= RSRC_ASK_MODE; i++) {
	    DeleteMenu(i);
	    DisposeMenu(askmenu[i]);
	}
	SetPort(oldport);
	DisposeDialog(askdialog);
	DisposeRoutineDescriptor(filter);
	DisposeRoutineDescriptor(redraw);

	/* Process the mode */
#ifdef WIZARD
	wizard =
#endif
	discover = 0;
	switch (currmode) {
	case 0:		/* Normal */
	    break;
	case 1:		/* Explore */
	    discover = 1;
	    break;
#ifdef WIZARD
	case 2:		/* Debug */
	    wizard = 1;
	    strcpy(plname, WIZARD);
	    break;
#endif
	default:	/* Quit */
	    ExitToShell();
	}

	/* Process the role */
	strcpy(pl_character, roles[currrole].name.m);
	flags.initrole = currrole;

	/* Process the race */
	flags.initrace = currrace;

	/* Process the gender */
	flags.female = flags.initgend = currgend;

	/* Process the alignment */
	flags.initalign = curralign;

	return;
}



/*** Menu bar routines ***/

#if !TARGET_API_MAC_CARBON
static void
alignAD(Rect *pRct, short vExempt)
{
	(*pRct).right -= (*pRct).left;		/* width */
	(*pRct).bottom -= (*pRct).top;		/* height */
	(*pRct).left = (qd.screenBits.bounds.right - (*pRct).right) / 2;
	(*pRct).top = (qd.screenBits.bounds.bottom - (*pRct).bottom - vExempt) / 2;
	(*pRct).top += vExempt;
	(*pRct).right += (*pRct).left;
	(*pRct).bottom += (*pRct).top;
}
#endif


static void
mustGetMenuAlerts()
{
	short		i;
	Rect		**hRct;

	for (i = alrt_Menu_start; i < alrt_Menu_limit; i++)
	{
		if (! (hRct = (Rect **) GetResource('ALRT', i)))	/* AlertTHndl */
		{
			for (i = 0; i < beepMenuAlertErr; i++)
				SysBeep(3);
			ExitToShell();
		}

#if !TARGET_API_MAC_CARBON
		alignAD(*hRct, GetMBarHeight());
#endif
	}
}

static void
menuError(short menuErr)
{
	short	i;

	for (i = 0; i < beepMenuAlertErr; i++)
		SysBeep(3);

	ParamText(menuErrStr[menuErr], "\p", "\p", "\p");
	(void) Alert(alrtMenuNote, (ModalFilterUPP) 0L);

	ExitToShell();
}

void
InitMenuRes()
{
	static Boolean was_inited = 0;
	short			i, j;
	menuListHandle	mlHnd;
	MenuHandle		menu;

	if (was_inited)
		return;
	was_inited = 1;

	mustGetMenuAlerts();

	for (i = listMenubar; i <= listSubmenu; i++) {
		if (! (mlHnd = (menuListHandle) GetResource('MNU#', (menuBarListID + i))))
			menuError(errGetMenuList);

		pMenuList[i] = (menuListPtr) NewPtr(GetHandleSize((Handle) mlHnd));
		*pMenuList[i] = **mlHnd;

		for (j = 0; j < pMenuList[i]->numMenus; j++)
		{
			if (! (menu = (MenuHandle) GetMenu((**mlHnd).mref[j].mresID))) {
			Str31 d;
				NumToString ((**mlHnd).mref[j].mresID, d);
				menuError(errGetMenu);
			}

			pMenuList[i]->mref[j].mhnd = menu;
#if !TARGET_API_MAC_CARBON
			* ((short *) *menu) = j + (**mlHnd).firstMenuID;
#else
			SetMenuID(menu, j + (**mlHnd).firstMenuID);
#endif
	/* consecutive IDs */

			/* expand apple menu */
			if ((i == listMenubar) && (j == menuApple)) {
				AppendResMenu(menu, 'DRVR');
			}

			InsertMenu(menu, ((i == listSubmenu) ? hierMenu : 0));
		}
	}
	DrawMenuBar();
	return;
}

void
AdjustMenus(short dimMenubar)
{
	short		newMenubar = mbarRegular;
	WindowRef win = FrontWindow();
	short		i;

	/*
	 *	if (windowprocs != mac_procs) {
	 *		return;
	 *	}
	 */
	/* determine the new menubar state */
	if (dimMenubar)
		newMenubar = mbarDim;
	else if (!win)
		newMenubar = mbarNoWindows;
	else if (GetWindowKind(win) < 0)
		newMenubar = mbarDA;
	else if (!IsWindowVisible(_mt_window))
		newMenubar = mbarNoMap;

	if (newMenubar != mbarRegular)
		;							/* we've already found its state */
#ifdef WIZARD
	else if (wizard)
	{
		newMenubar = mbarSpecial;

		if (kAdjustWizardMenu)
		{
			kAdjustWizardMenu = 0;

			SetMenuItemText(MHND_FILE, menuFilePlayMode, "\pDebug");
		}
	}
#endif

	else if (discover)
	{
		newMenubar = mbarSpecial;

		if (kAdjustWizardMenu)
		{
			kAdjustWizardMenu = 0;

			SetMenuItemText(MHND_FILE, menuFilePlayMode, "\pExplore");

			for (i = CountMenuItems(MHND_WIZ); i > menuWizardAttributes; i--)
				DeleteMenuItem(MHND_WIZ, i);
		}
	}

	/* adjust the menubar, if there's a state change */
	if (theMenubar != newMenubar)
	{
		switch(theMenubar = newMenubar)
		{
		case mbarDim:
			/* disable all menus (except the apple menu) */
			for (i = menuFile; i < NUM_MBAR; i++)
				DisableMenuItem(MBARHND(i), 0);
			break;

		case mbarNoWindows:
		case mbarDA:
		case mbarNoMap:
			/* enable the file menu, but ... */
			EnableMenuItem(MHND_FILE, 0);

			/* ... disable the window commands! */
			for (i = menuFileRedraw; i <= menuFileEnterExplore; i++)
				DisableMenuItem(MHND_FILE, i);

			/* ... and disable the rest of the menus */
			for (i = menuEdit; i < NUM_MBAR; i++)
				DisableMenuItem(MBARHND(i), 0);

			if (theMenubar == mbarDA)
				EnableMenuItem(MHND_EDIT, 0);

			break;

		case mbarRegular:
		case mbarSpecial:
			/* enable all menus ... */
			for (i = menuFile; i < NUM_MBAR; i++)
				EnableMenuItem(MBARHND(i), 0);

			/* ... except the unused Edit menu */
			DisableMenuItem(MHND_EDIT, 0);

			/* ... enable the window commands */
			for (i = menuFileRedraw; i <= menuFileEnterExplore; i++)
				EnableMenuItem(MHND_FILE, i);

			if (theMenubar == mbarRegular)
				DisableMenuItem(MHND_FILE, menuFilePlayMode);
			else
				DisableMenuItem(MHND_FILE, menuFileEnterExplore);

			break;
		}

		DrawMenuBar();
	}
}

void
DoMenuEvt(long menuEntry)
{
	short menuID = HiWord(menuEntry);
	short menuItem = LoWord(menuEntry);

	switch(menuID - ID1_MBAR)	/* all submenus are default case */
	{
	case menuApple:
		if (menuItem == menuAppleAboutBox)
			aboutNetHack();
#if !TARGET_API_MAC_CARBON
		else
		{
			unsigned char daName[32];

			GetMenuItemText(MHND_APPLE, menuItem, * (Str255 *) daName);
			(void) OpenDeskAcc(daName);
		}
#endif
		break;

	/*
	 * Those direct calls are ugly: they should be installed into cmd.c .
	 * Those AddToKeyQueue() calls are also ugly: they should be put into
	 * the 'STR#' resource.
	 */
	case menuFile:
		switch(menuItem)
		{
		case menuFileRedraw:
			AddToKeyQueue ('R' & 0x1f, 1);
			break;

		case menuFilePrevMsg:
			AddToKeyQueue ('P' & 0x1f, 1);
			break;

		case menuFileCleanup:
			(void) SanePositions();
			break;

		case menuFileEnterExplore:
			AddToKeyQueue ('X', 1);
			break;

		case menuFileSave:
			askSave();
			break;

		case menuFileQuit:
			askQuit();
			break;
		}
		break;

	case menuEdit:
#if !TARGET_API_MAC_CARBON
		(void) SystemEdit(menuItem - 1);
#endif
		break;

	default:	/* get associated string and add to key queue */
		{
			Str255	mstr;
			short	i;

			GetIndString(mstr, menuID, menuItem);
			if (mstr[0] > QUEUE_LEN)
				mstr[0] = QUEUE_LEN;

			for (i = 1; ((i <= mstr[0]) && (mstr[i] != mstrEndChar)); i++)
				AddToKeyQueue(mstr[i], false);

			/* Special processing for extended commands. The command
			   selection appears before mstrEndChar, the rest appears
			   after. The rest is used only when in Mac windows mode.
			   In any case we add a newline to get the command
			   going.
			*/
			if ( mstr[1] == '#' )
			{
				if ( windowprocs.win_init_nhwindows == mac_procs.win_init_nhwindows )
					for ( i++; ((i <= mstr[0]) && (mstr[i] != mstrEndChar)); i++)
						AddToKeyQueue(mstr[i], false);
				AddToKeyQueue('\n', false);
			}
		}
		break;
	}

	HiliteMenu(0);
}


static void
aboutNetHack() {
	if (theMenubar >= mbarRegular) {
		(void) doversion();				/* is this necessary? */
	} else {
		unsigned char aboutStr[32] = "\pSlash'EM 0.0.";

		if (PATCHLEVEL > 10) {
			aboutStr[++aboutStr[0]] = '0' + PATCHLEVEL / 10;
		}

		aboutStr[++aboutStr[0]] = '0' + PATCHLEVEL % 10;
		
#ifdef FIXLEVEL		
		if (EDITLEVEL || FIXLEVEL) {
			aboutStr[++aboutStr[0]] = 'E';
			aboutStr[++aboutStr[0]] = '0' + EDITLEVEL;
		}
		
		if (FIXLEVEL) {
			aboutStr[++aboutStr[0]] = 'F';
			aboutStr[++aboutStr[0]] = '0' + FIXLEVEL;
		}
#else
		if (EDITLEVEL) {
			aboutStr[++aboutStr[0]] = 'E';
			aboutStr[++aboutStr[0]] = '0' + EDITLEVEL;
		}
		
#endif
		aboutStr[++aboutStr[0]] = CHAR_CR;

		ParamText(aboutStr, "\pwww.slashem.org", "\p", "\p");
		(void) Alert(alrtMenuNote, (ModalFilterUPP) 0L);
		ResetAlertStage();
	}
}


static void
askSave()
{
	Boolean doSave = 1;
	Boolean doYes = 0;

	if (theMenubar < mbarRegular) {
	short	itemHit;

		ParamText("\pReally Save?", "\p", "\p", "\p");
		itemHit = Alert(alrtMenu_NY, (ModalFilterUPP) 0L);
		ResetAlertStage();

		if (itemHit != bttnMenuAlertYes) {
			doSave = 0;
		} else {
			doYes = 1;
		}
	}
	if (doSave) {
		AddToKeyQueue ('S', 1);
		if (doYes) {
			AddToKeyQueue ('y', 1);
		}
	}
}

static void
askQuit()
{
	Boolean doQuit = 1;
	Boolean doYes = 0;
	Boolean winMac;
	char *quitinput;

	if (!strcmp (windowprocs.name, "mac"))
		winMac = 1;
	else
		winMac = 0;
		
	if (theMenubar < mbarRegular) {
	short	itemHit;

		ParamText("\pReally Quit?", "\p", "\p", "\p");
		itemHit = Alert(alrtMenu_NY, (ModalFilterUPP) 0L);
		ResetAlertStage();

		if (itemHit != bttnMenuAlertYes) {
			doQuit = 0;
		} else {
			doYes = 1;
		}
	}
	if (doQuit) {
		/* MWM -- forgive me lord, an even uglier kludge to deal with differences
			in command input handling
		 */
		 if (winMac)
			quitinput = "#quit\r";
		else
			quitinput = "#q\r";
			
		/* KMH -- Ugly kludge */
		while (*quitinput)
			AddToKeyQueue(*quitinput++, 1);
		if (doYes) {
			if (winMac)
				quitinput = "y\rq\r\r\r";
			else
				quitinput = "yq\r";
			while (*quitinput)
				AddToKeyQueue(*quitinput++, 1);
		}
	}
}

