/*
 * File: main-crb.c
 * Purpose: Provide support for OS X, version 10.3 and later.
 *
 * Copyright (c) 2003 pelpel
 * Copyright (c) 2003,2004,2005 Robert Ruehlmann
 * Copyright (c) 2007,2008 pete mack
 * Copyright (c) 2008 Rowan Beentje
 *
 * Some excerpts quoted under fair use from works by Ben Harrison,
 * Keith Randall, Peter Ammon, and Ron Anderson.
 *
 * This work is free software; you can redistribute it and/or modify it
 * under the terms of either:
 *
 * a) the GNU General Public License as published by the Free Software
 *    Foundation, version 2, or
 *
 * b) the "Angband licence":
 *    This software may be copied and distributed for educational, research,
 *    and not for profit purposes provided that this copyright and statement
 *    are included in all such copies.  Other copyrights may also apply.
 */
#include "angband.h"


/*
 * Notes:
 *
 * (pelpel) Characters in the ASCII mode are clipped by their bounding
 * rects to reduce redraw artefacts that were quite annoying in certain
 * font-point combos.
 *
 *
 * (pete mack) Nearly all quickdraw removed to improve performance from
 * interleaved Quickdraw and CGContext events.  (Hence the really ugly
 * ATSU font metrics.)
 * IMPORTANT: If you need to use QuickDraw, make sure to call hibernate()
 * and SetPort() first.  This will keep the graphics state consistent.
 *
 * (pete mack) If you wish to add additional heirarchical menus, the following
 * must hold:
 *   All menus and submenus must have unique ids.
 *   All menus must have IDs greater than 0 and less than MAX_MENU_ID
 *   All submenus must have greater IDs than their parents.
 *
 *
 * Important Resources in the resource file:
 *
 *   FREF 130 = ANGBAND_CREATOR / 'APPL' (application)
 *   FREF 129 = ANGBAND_CREATOR / 'SAVE' (save file)
 *   FREF 130 = ANGBAND_CREATOR / 'TEXT' (bone file, generic text file)
 *   FREF 131 = ANGBAND_CREATOR / 'DATA' (binary image file, score file)
 *
 */

/* -------------------------------------------------------
 * PORTING YOUR VARIANT
 * ------------------------------------------------------
 * 0. Requires installation of XCode.  The framework .h files are otherwise
 * absent. New changes also require use of NIB (xml) files, which
 * are most easily edited with Interface Builder. (XCode package.)
 * This reduces the amount of hand-coded menu creation by an order of magnitude.
 *
 *
 * 1. Compiling the binary
 *
 * You might wish to disable some SET_UID features for various reasons:
 * to have user folder within the lib folder, savefile names etc.
 *
 * 2. Installation
 *
 * The "angband" binary must be arranged this way for it to work:
 *
 * lib/ <- the lib folder
 * Angband (OS X).app/
 *   Contents/
 *     MacOS/
 *       angband <- the binary you've just compiled
 *     Info.plist <- to be explained below
 *     Resources/
 *       Angband.icns
 *       Data.icns
 *       Edit.icns
 *       Save.icns
 *       8x8.png <- 8x8 tiles
 *       16x16.png <- 16x16 tiles
 *       angband.rsrc <- see below
 *
 * Graphics resources are moved out of the resource fork and become ordinary
 * PNG files.  Make sure to set its resolution to 72 dpi (<- VERY important)
 * while keeping vertical and horizontal scaling factor to 100% (<- VERY
 * important), when you convert tiles in any formats to PNG.  This means
 * that the real size of an image must shrink or grow when you change it's dpi.
 * Graphics files must be of type png, because OSX 10.3 doesn't do
 * a good job with composited images.
 *
 * Transparency now uses the alpha channel, rather than a background color.
 *
 * ---(end of OS X + gcc porting note)--------------------------------------
 *
 * Because the default font-size combination causes redraw artefact problem
 * (some characters, even in monospace fonts, have negative left bearings),
 * I [pelpel] introduced rather crude hack to clip all character drawings within
 * their bounding rects. If you don't like this, please comment out the line
 * #define CLIP_HACK
 * below. The alternative, #define OVERWRITE_HACK, is based on Julian Lighton's
 * brilliant suggestion, but it doesn't work as expected. This is because
 * DrawText can render the same character with _different_ pixel width,
 * depending on relative position of a character to the pen. Fonts do look
 * very nice on the Mac, but too nice I'd say, in case of Angband.
 *
 * The check for return values of AEProcessAppleEvent is intentionally ignored.
 *
 * Changes 2006 (pete mack)
 * - Removed conditional code. Removed non-quicktime sound.
 * - Reorganized event model to use Carbon events and dispatching.
 * - Removed old font menus & calculations; replaced with Apple font panel.
 * - Removed Quickdraw graphics; replaced with CGContext (Quartz) graphics.
 * - Removed Quickdraw text; replaced with CGFont. ATSUI is used for
 *   finding fonts, font metrics, and Glyph information for fast rendering
 *   of variable-width fonts.
 */


#ifdef MACH_O_CARBON


#include <Cocoa/Cocoa.h>
#include <Carbon/Carbon.h>
#include <QuickTime/QuickTime.h>
#include <CoreServices/CoreServices.h>
#include <CoreFoundation/CoreFoundation.h>

#include "osx/osx_tables.h"



/* Default creator signature */
#ifndef huge
#define huge size_t
#endif

/*
 * Width in pixels of white borders around the black screen.
 */
#define BORDER_WID 1


static const bool show_events = 0;

/*
 * A rather crude fix to reduce amount of redraw artifacts.
 * Some fixed width fonts (i.e. Monaco) has characters with negative
 * left bearings, so Term_wipe_mac or overwriting cannot completely
 * erase them.
 */

#define CLIP_HACK 0 /* */
static const int use_clip_hack = CLIP_HACK;

/*
 * Minimum font size
 */
#define MIN_FONT 8

/*
 * Another redraw artifact killer, based on suggestion by Julian Lighton
 */
#define OVERWRITE_HACK (0)

/* 
 * These hacks should never be enabled at the same time.
 * Clip-hack renders overwrite-hack meaningless, and
 * it will cause use of an unitialized variable. - pete mack.
 */

static const int use_overwrite_hack = (OVERWRITE_HACK && !CLIP_HACK);

/*
 * Maximum number of windows.
 */
#define MAX_TERM_DATA 8


/* graphics_modes index of current graphics mode */
static int graf_mode = 0;

/* Tile dimensions of the current graphics mode */
static int graf_height = 0;
static int graf_width = 0;

typedef struct GlyphInfo GlyphInfo;

typedef struct term_data term_data;

/*
 * Extra "term" data
 */
struct term_data
{
	term *t;
	WindowRef w;
	GlyphInfo *ginfo;
	
	Rect wr;		// Absolute bounds of window (for save/restore)
	Rect r;			// Canvas bounds of window (for mouse addressing &c) 
	CGRect bounds;  // Relative bounds of border-clipped canvas.

	int spacing;  	// Text padding (in pixels) for tiling wider than text

	Str15 title;	// Window title.

	s16b mapped;	// Active state.

	s16b rows;		// rows in picture
	s16b cols;		// columns in picture.

	char font_name[200]; // Name of font for storage.
	ATSUFontID font_id;
	float font_size;	// Scaled ATSU font size.

	s16b font_wid;
	s16b font_hgt;

	s16b tile_wid;
	s16b tile_hgt;

	s16b size_wid;	// Window size in x.
	s16b size_hgt;	// Window size in y.
};

struct GlyphInfo
{
	UInt32 refcount;
	char psname[1000];
	ATSUFontID font_id;
	CGFontRef fontRef;
	float font_size;
	ATSUStyle style;
	ATSUTextLayout layout;
	float font_wid;  // max character advance.
	s32b ascent;
	s32b descent;
	bool monospace;
	float offsets[256][3];
	float heights[256][3];
	float widths[256];
};

static GlyphInfo glyph_data[MAX_TERM_DATA+1];

static WindowRef aboutDialog;


static bool CheckEvents(int wait);
static OSStatus RevalidateGraphics(term_data *td, bool reset_tilesize);
static char *locate_lib(char *buf, size_t size);
static void graphics_aux(int op);
static void Term_wipe_mac_aux(int x, int y, int n);
inline static void term_data_color(int a);
static void install_handlers(WindowRef w);
static void graphics_tiles_nuke(void);
static void play_sound(int num);
static void redrawRecentItemsMenu();
/*
 * Available values for 'wait'
 */

#define CHECK_EVENTS_DRAIN -1
#define CHECK_EVENTS_NO_WAIT	0
#define CHECK_EVENTS_WAIT 1


/*
 * Delay handling of double-clicked savefiles
 */
bool open_when_ready = FALSE;

/*
 * Delay handling of pre-emptive "quit" event
 */
bool quit_when_ready = FALSE;

static long mac_os_version;


/* Out-of-band color identifiers */
/* True black (TERM_BLACK may be altered) */
#define COLOR_BLACK		(256)
/* No current color */
#define COLOR_INVALID	(-1)


/*
 * Keeps track of who owns the QD CGContext, and it's current state.
 * Always use this to change the active graphics port.
 * (It is a parallel structure to the Term variable.)
 */
struct ActivePort {
	WindowRef		active;
	CGContextRef	ctx;
	int		  		color;   // Current fill colorcode
	// CGColorRef		color_info[256+1];
	float color_info[256+1][3];
}; 

static struct ActivePort focus; /* initialized to 0 */


/*
 * An array of term_data's
 */
static term_data data[MAX_TERM_DATA];


/*
 * Note when "open"/"new" become valid
 */
static bool initialized = FALSE;

/*
 * A mutable array for Recent Items
 */ 
CFMutableArrayRef recentItemsArrayRef = NULL;

/*
 * Support the improved game command handling
 */
#include "textui.h"
static game_command cmd = { CMD_NULL, 0 };


static MenuRef MyGetMenuHandle_aux(int menuID, bool first)
{
	static MenuRef menuRefs[MAX_MENU_ID];

	if(menuID <= 0 || menuID >= MAX_MENU_ID) return 0;

	if(menuRefs[menuID]) return menuRefs[menuID];
	MenuRef m = GetMenuHandle(menuID);
	if(m) {
		menuRefs[menuID] = m;
		return m;
	}

	/*
	 * First heirarchical call, find and initialize all menu IDs.
	 * Subsequent misses will attempt to update the menuRefs array.
	 * This will work for any depth heirarchy, so long as child menus have
	 * higher IDs than their parents.	
	 *
	 * Invariant: all MenuRefs with ID < MenuID(tmp) have been initialized.
	 */
	for(int id = 1; id < MAX_MENU_ID; id++)
	{
		/* Recursion depth is 1 */
		if(first) m = MyGetMenuHandle_aux(id, false);
		if(!m) continue;
		menuRefs[id] = m;
		for(int i = 0; i < N_ELEMENTS(menuRefs); i++) {
			MenuRef tmp = 0;
			GetMenuItemHierarchicalMenu (m, i, &tmp);
			if(tmp) {
				menuRefs[GetMenuID(tmp)] = tmp;
			}
		}
	}
	return menuRefs[menuID];
}

/*
 * Provide a flat namespace for OS X menus and submenus. 
 * It's a nuisance doing this via heirarchical calls all the time.
 */
inline static MenuRef MyGetMenuHandle(int menuID)
{
	return MyGetMenuHandle_aux(menuID, true);
}


/*
 * Convert a pathname to a corresponding FSSpec.
 * Returns noErr on success.
 */
static OSErr path_to_spec(const char *path, FSSpec *spec)
{
	OSErr err;
	FSRef ref;

	/* Convert pathname to FSRef ... */
	err = FSPathMakeRef((byte*) path, &ref, NULL);
	if (err != noErr) return (err);

	/* ... then FSRef to FSSpec */
	err = FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, spec, NULL);
	
	/* Inform caller of success or failure */
	return (err);
}


/*
 * Convert a FSSpec to a corresponding pathname.
 * Returns noErr on success.
 */
static OSErr spec_to_path(const FSSpec *spec, char *buf, size_t size)
{
	OSErr err;
	FSRef ref;

	/* Convert FSSpec to FSRef ... */
	err = FSpMakeFSRef(spec, &ref);
	if (err != noErr) return (err);

	/* ... then FSRef to pathname */
	err = FSRefMakePath(&ref, (byte*)buf, size);

	/* Inform caller of success or failure */
	return (err);
}


/*
 * [via path_to_spec]
 * Set creator and filetype of a file specified by POSIX-style pathname.
 * Returns 0 on success, -1 in case of errors.
 */
void fsetfileinfo(cptr pathname, u32b fcreator, u32b ftype)
{
	OSErr err;
	FSSpec spec;
	FInfo info;

	/* Convert pathname to FSSpec */
	if (path_to_spec(pathname, &spec) != noErr) return;

	/* Obtain current finder info of the file */
	if (FSpGetFInfo(&spec, &info) != noErr) return;

	/* Overwrite creator and type */
	info.fdCreator = fcreator;
	info.fdType = ftype;
	err = FSpSetFInfo(&spec, &info);

	/* Done */
	return;
}


/*
 * Activate a given window, if necessary.  This should normally 
 * be called by Term_activate, when the z-term is updating it's
 * state.  It should also be called prior to any updates when
 * the window CGContext has been closed by hibernate()
 */
static void activate(WindowRef w)
{
	/* Activate */

	if(focus.active && focus.ctx) {
		CGContextSynchronize(focus.ctx);
		if(focus.active != w) {
			/* Change window context */
			QDEndCGContext(GetWindowPort(focus.active), &focus.ctx);
			focus.ctx = 0;
		}
	}
	/* Activate */
	if (w && focus.ctx == 0) {
		focus.color = COLOR_INVALID;

		term_data *td = (term_data*)GetWRefCon(w);

		/* Start queueing graphics events. */
		/* and set up the context */
		QDBeginCGContext(GetWindowPort(w), &focus.ctx);

		// Shift the origin to inside the border, and use inverted y axis.
		CGAffineTransform m;
		m = CGAffineTransformMake(BORDER_WID, 0, 0, -1,
					 			BORDER_WID, BORDER_WID+td->bounds.size.height);

		CGContextConcatCTM (focus.ctx, m); 
		CGContextClipToRect(focus.ctx, td->bounds);

		CGContextSetShouldAntialias (focus.ctx, antialias); 
		CGContextSetInterpolationQuality(focus.ctx, kCGInterpolationNone);

		// Invert the text so it's no longer mirrored in y.
		// Origin is at still at the bottom of the line, so the ascent must
		// be subtracted at display-time. (Not added, because the coordinate
		// system is also flipped.)
		m = CGAffineTransformMake(1, 0, 0, -1, 0, 0);
		CGContextSetTextMatrix(focus.ctx, m);

		// I don't know why this doesn't work.
		// CGContextSetFont(focus.ctx, td->ginfo->fontRef);
		// CGContextSetFontSize(focus.ctx, td->font_size);
		// HACK: use full postscript name.
		CGContextSelectFont(focus.ctx, td->ginfo->psname, td->font_size,
														kCGEncodingMacRoman); 

		if(td->ginfo->monospace) {
			CGContextSetCharacterSpacing(focus.ctx,
											td->tile_wid - td->ginfo->font_wid);
		}
		else {
			const ATSUAttributeTag itags[] =  { kATSUCGContextTag,
										kATSUImposeWidthTag };
			Fixed advance = (1<<16)*(td->tile_wid - td->font_wid);
			void *ivals[] = { &focus.ctx, &advance };
			const ByteCount isizes[] = {sizeof(focus.ctx), sizeof(advance) };
			ATSUSetAttributes(td->ginfo->style, 2, itags, isizes, ivals);
		}
	}

	/* Remember */
	focus.active = w;
}

/* 
 * Temporarily give up control of the Quickdraw port.
 * Call when the window becomes inactive.
 * Call when the graphics state changes in any way, to
 * invalidate the current CGContext and force an update.
 */
static void hibernate()
{
	if(focus.ctx != 0)
		QDEndCGContext(GetWindowPort(focus.active), &focus.ctx);

	focus.ctx = 0;
}

/*
 * Display a warning message
 */
static void mac_warning(cptr warning)
{
	CFStringRef msg;
	msg = CFStringCreateWithCString(NULL, warning, kTextEncodingUS_ASCII);

	DialogRef dlg = 0;
	CreateStandardAlert(kAlertCautionAlert, msg, CFSTR(""), NULL, &dlg);

	DialogItemIndex itemIndex;
	RunStandardAlert(dlg, NULL, &itemIndex);
	// DisposeDialog(dlg); // NO!
	CFRelease(msg);
}

/*
 * Notice fully up-to-date status of the main window
 */
static void validate_main_window(void)
{
	WindowRef w;
	Rect r;

	/* Get the main window */
	w = data[0].w;

	/* Get its rectangle */
	GetPortBounds(GetWindowPort(w), &r);

	/* Validate it */
	ValidWindowRect(w, &r);
}


/*** Some generic functions ***/

/*
 * Update color_info with the current values in angband_color_table
 */
static void update_color_info(void)
{
	focus.color = COLOR_INVALID;
	focus.color_info[COLOR_BLACK][0] = 0;
	focus.color_info[COLOR_BLACK][1] = 0;
	focus.color_info[COLOR_BLACK][2] = 0;

	/* Update colors */
	for (int i = 0; i < 256; i++)
	{
		float r = angband_color_table[i][1];
		float g = angband_color_table[i][2];
		float b = angband_color_table[i][3];
		r = r == 255 ? 1 : r / 256;
		g = g == 255 ? 1 : g / 256;
		b = b == 255 ? 1 : b / 256;

		focus.color_info[i][0] = r;
		focus.color_info[i][1] = g;
		focus.color_info[i][2] = b;
	}
}

/*
 * Activate a color (0 to 256)
 * -1 is invalid, 256 is true black.
 */
inline static void term_data_color(int a)
{
	if(!focus.ctx) activate(focus.active);
	if( a == COLOR_INVALID) {
		focus.color = a;
	}
	else if (focus.color != a)  // Assumes the window exists.
	{
		focus.color = a;
		// CGContextSetFillColorWithColor(focus.ctx, focus.color_info[a]);
		CGContextSetRGBFillColor(focus.ctx, focus.color_info[a][0],
							focus.color_info[a][1], focus.color_info[a][2], 1);
	}
}

/*
 * Get font metrics 
 */
static GlyphInfo *get_glyph_info(ATSUFontID fid, float size)
{
	GlyphInfo *info;

	// One extra, so a term_data can fetch before it frees.
			
	for(info = glyph_data; info <= glyph_data+MAX_TERM_DATA; info++)
	{
		if(info->font_id == fid && info->font_size == size)
		{
			info->refcount++;
			return info;
		}
	}

	// One is always available.
	info = glyph_data;
	for(int c = 0; info->refcount != 0; info++, c++)
			assert(c <= MAX_TERM_DATA);

	info->style = 0;
	info->layout = 0;

	/* ICK */

	info->font_size = size;
	info->font_id = fid;

	OSStatus err = ATSUCreateStyle(&info->style);
	require_noerr(err, CantInitialize);

	Fixed fsize = (Fixed)(size*(1<<16));
	const ByteCount isizes[] = {sizeof(fid), sizeof(fsize) };
	void *ivals[] = { &fid, &fsize };

	const ATSUAttributeTag itags[] =  { kATSUFontTag, 
										kATSUSizeTag };

	ATSUSetAttributes(info->style, 2, itags, isizes, ivals);
	err = ATSUCreateTextLayout(&info->layout);
	require_noerr(err, CantInitialize);

	// Dummy text, required to initialize run style.
	UniChar text[1] = {'@'};
	ATSUSetTextPointerLocation(info->layout, text, 0, 1, 1);
	ATSUSetRunStyle(info->layout, info->style, 0, 1);

	ByteCount oCount = 0;
	FontNameCode oCode;
	err = ATSUGetIndFontName(fid, 6, sizeof(info->psname), info->psname,
													&oCount, &oCode, 0, 0, 0);

	require_noerr(err, CantInitialize);
	if(oCount == 0 || strlen(info->psname) == 0)
		goto CantInitialize;

	info->psname[oCount] = 0;
	// Is font mono-space?
	err = ATSUCreateTextLayout(&info->layout);
	require_noerr(err, CantInitialize);

	ATSUSetRunStyle(info->layout, info->style, 0, 0);
	info->monospace = true;
	info->font_wid = 0;
	info->ascent = 0;
	info->descent = 0;

	for(int i = 0; i < 256; i++) {
		OSStatus err;
		text[0] = i;
		Fixed start = 0, stop = 0, ascent = 0, descent = 0;
		ATSUSetTextPointerLocation(info->layout, text, 0, 1, 1);
		// SetRunStyle doesn't honor runs in layouts of size 0.
		if(i == 0) ATSUSetRunStyle(info->layout, info->style, 0, 1);
		err = ATSUGetUnjustifiedBounds(info->layout, 0, 1, &start, &stop,
														&ascent, &descent);

		if(info->ascent < ascent) info->ascent = ascent;
		if(info->descent < descent) info->descent = descent;


		info->widths[i] = (stop - start)/(1<<16);

		if(info->font_wid == 0) info->font_wid = stop - start;
		else if((info->font_wid != stop - start) && (stop - start != 0)) {
			info->monospace = false;
		}
		if(info->font_wid < stop - start) {
			info->font_wid = stop - start;
		}
	}

	/* Scale the rough font metrics to screen dimensions. */
	/* Assumes 72dpi resolution. */
	info->ascent = (info->ascent + (1<<16)-1)>>16;
	info->descent = (info->descent + (1<<16)-1)>>16;
	info->font_wid = (info->font_wid)/(1<<16);

	info->refcount++;
	return info;

CantInitialize:
	if(info->layout) ATSUDisposeTextLayout(info->layout);
	if(info->style) ATSUDisposeStyle(info->style);
	info->style = 0;
	info->layout = 0;
	return 0;
}


static void release_glyph_info(GlyphInfo *info)
{
	if(!info) return;
	assert(info->refcount > 0);

	info->refcount--;
	if(0 == info->refcount) {
		ATSUDisposeStyle(info->style);
		info->style = 0;
		CGFontRelease(info->fontRef);
		info->fontRef = 0;
	}
}

/*
 * Hack -- Apply and Verify the "font" info
 *
 * This should usually be followed by "term_data_check_size()"
 *
 * XXX XXX To force (re)initialisation of td->tile_wid and td->tile_hgt
 * you have to reset them to zero before this function is called.
 * XXX XXX This is automatic when the program starts because the term_data
 * array is WIPE'd by term_data_hack, but isn't in the other cases, i.e.
 * font, font style and size changes.
 */
static void term_data_check_font(term_data *td)
{
	GlyphInfo *info = get_glyph_info(td->font_id, td->font_size);
	if(!info) return;

	release_glyph_info(td->ginfo);
	td->ginfo = info;

	td->font_wid = (info->font_wid +.999);
	td->font_hgt = info->ascent + info->descent;
	my_strcpy(td->font_name, info->psname, sizeof(td->font_name));

	/* Set default tile size */
	if (td->tile_wid == 0) td->tile_wid = td->font_wid;
	if (td->tile_hgt == 0) td->tile_hgt = td->font_hgt;
}


/*
 * Hack -- Apply and Verify the "size" info
 */
static void term_data_check_size(term_data *td)
{
	if (td == &data[0])
	{

		/* Enforce minimal size for the main game window */
		if (td->cols < 80) td->cols = 80;
		if (td->rows < 24) td->rows = 24;

	}
	else
	{

		/* Information windows can be much smaller */
		if (td->cols < 10) td->cols = 10;
		if (td->rows < 5) td->rows = 5;
	}

	/* Enforce maximal sizes */
	if (td->cols > 255) td->cols = 255;
	if (td->rows > 255) td->rows = 255;

	/* Minimal tile size */
	bool dirty = false;
	if (td->tile_wid < td->font_wid) {
		td->tile_wid = td->font_wid;
		dirty = true;
	}
	if (td->tile_hgt < td->font_hgt) {
		td->tile_hgt = td->font_hgt;
		dirty = true;
	}
	if(dirty) graphics_tiles_nuke();

	/* Calculate full window size */
	td->size_wid = td->cols * td->tile_wid + BORDER_WID * 2;
	td->size_hgt = td->rows * td->tile_hgt + BORDER_WID;


	hibernate(); 
	BitMap tScreen;
	/* Get current screen */
	(void)GetQDGlobalsScreenBits(&tScreen);
	/* Verify the bottom */
	if (td->r.top > tScreen.bounds.bottom - td->size_hgt)
	{
		td->r.top = tScreen.bounds.bottom - td->size_hgt;
	}
	/* Verify the top */
	if (td->r.top < tScreen.bounds.top + 2*GetMBarHeight())
	{
		td->r.top = tScreen.bounds.top + 2*GetMBarHeight();
	}
	/* Verify the right */
	if (td->r.left > tScreen.bounds.right - td->size_wid)
	{
		td->r.left = tScreen.bounds.right - td->size_wid;
	}
	/* Verify the left */
	if (td->r.left < tScreen.bounds.left)
	{
		td->r.left = tScreen.bounds.left;
	}

	/* Calculate bottom right corner */
	td->r.right = td->r.left + td->size_wid;
	td->r.bottom = td->r.top + td->size_hgt;

	td->bounds = (CGRect) {{0, 0},
		{td->cols * td->tile_wid, td->rows * td->tile_hgt}};

	/* Assume no graphics, monospace */
	td->t->higher_pict = FALSE;
	td->t->always_pict = FALSE;

	
	/* Handle graphics */
	if (!td->ginfo->monospace) {
		/* Draw every character */
		td->t->always_pict = TRUE;
	}
	else if (use_graphics)
	{
		/* Use higher pict whenever possible */
		td->t->higher_pict = TRUE;
	}

}


/*
 * resize a term_data
 *
 * This should normally be followed by "term_data_redraw()"
 */
static void term_data_resize(term_data *td)
{
	// Invalidate the current CGContext.
	hibernate();
	/*
	 * Actually resize the window
	 * 
	 * ResizeWindow is the preferred API call, but it cannot
	 * be used here.
	 */
	
	SizeWindow(td->w, td->size_wid, td->size_hgt, 0);

	// Cheat a little -- can't use the active view to redraw its own border.
	CGContextRef tmpCtx;
	QDBeginCGContext(GetWindowPort(td->w), &tmpCtx);
	CGContextSetRGBStrokeColor(tmpCtx, 1, 1, 1, 1);
	CGRect wbounds = {{0, 0}, {td->bounds.size.width+2*BORDER_WID,
								td->bounds.size.height+BORDER_WID+2}};
	CGContextStrokeRect(tmpCtx, wbounds);
	QDEndCGContext(GetWindowPort(td->w), &tmpCtx);
}


/*
 * Hack -- redraw a term_data
 *
 * Note that "Term_redraw()" calls "TERM_XTRA_CLEAR"
 */
static void term_data_redraw(term_data *td)
{
	term *old = Term;
	/* Activate the term */
	Term_activate(td->t);

	/* Redraw the contents */
	Term_redraw();

	/* Flush the output */
	Term_fresh();
	Term_activate(old);
}


/*
 * Graphics support
 */

/*
 * PICT id / file name of image tiles
 */
static const char *pict_id = NULL;

/*
 * Frame
 * Wrapper for CGImages of the current tile set.
 *
 */
static struct
{
	// The reference image at the original scale.
	CGImageRef image;

 	// Numbers of rows and columns in a tileset,
	int cols;
	int rows;

	/*
	 * Tile images.
	 * These are generated once at each tile scaling.  Since
	 * there are many unused tiles, and since generating tiles takes
	 * a long time, they are generated only on demand.
	 * The tiles at the current scaling.  
	 * Foreground tiles are RGBA
	 * Background tiles are RGB only.
	 * This is detected by DrawTile--background images have the same
	 * tile-id for both foreground and background.
	 * Stride is number of columns.
	 */
	CGImageRef *tile_images;
} frame = {0};

/*
 * Rendevous for font update events.
 */
static struct 
{
	WindowRef focus; // The most recently focused window. (NOT the fontpanel.)
} fontInfo;



/* 
 * Replacement for BitMap (from QD2Qz porting guide)
 */
void DrawSubimage (CGContextRef context, CGRect dst,
						CGImageRef image, CGRect src)
{
	/* the default drawing rectangle */
	float w = (float) CGImageGetWidth(image);
	float h = (float) CGImageGetHeight(image);
	CGRect drawRect = {{0, 0}, {w, h}};
	if (!CGRectEqualToRect (src, dst)) 
	{
		float sx = CGRectGetWidth(dst) / CGRectGetWidth(src);
		float sy = CGRectGetHeight(dst) / CGRectGetHeight(src);
		float dx = CGRectGetMinX(dst) - (CGRectGetMinX(src) * sx);
		float dy = CGRectGetMinY(dst) - (CGRectGetMinY(src) * sy);
		drawRect = (CGRect) {{dx, dy}, {w*sx, h*sy}};
	}

	CGContextSaveGState (context);
	CGContextClipToRect (context, dst);
	// Don't display images upside down. (Works like SetTextMatrix)
	HIViewDrawCGImage (context, &drawRect, image); 
	// CGContextDrawImage (context, &drawRect, image);
	CGContextRestoreGState (context);
}

/*
 * Copy an image with tiles of size src into a new one with
 * tiles of size dst.  Interpolation will not cross tile borders.
 */
static CGImageRef GetTileImage(int row, int col, bool has_alpha) 
{
	// Cache hit.
	assert(col < frame.cols && row < frame.rows);
	if(frame.tile_images[row*frame.cols + col] != 0) {
		return frame.tile_images[row*frame.cols+col];
	}

	term_data *td = &data[0];

	size_t tile_wid = td->tile_wid *(1+use_bigtile);
	size_t nbytes = (td->tile_hgt * tile_wid) * 4;
	void *data = calloc(1, nbytes);

	CGContextRef map;
	map = CGBitmapContextCreate(data, tile_wid, td->tile_hgt,
								8,
								nbytes/td->tile_hgt,
								CGColorSpaceCreateDeviceRGB(),
								kCGImageAlphaPremultipliedLast);

	CGAffineTransform m = {1, 0, 0, -1, 0, td->tile_hgt};
	CGContextConcatCTM(map, m); 

	// Attempt to avoid interpolation across cell boundaries by clipping.
	// It may be that we need a clip image first.
	CGRect src_r = {{ graf_width*col, graf_height*row },
						{ graf_width, graf_height }};
	CGRect dst_r = {{ 0, 0 }, { tile_wid, td->tile_hgt }};
	DrawSubimage(map, dst_r, frame.image, src_r);

	CGDataProviderRef prov;
	prov = CGDataProviderCreateWithData (NULL, data, nbytes, NULL);

	CGImageAlphaInfo alphaInfo = kCGImageAlphaPremultipliedLast;
	size_t pixelBits = 4 * 8;
	if(!has_alpha) {
		alphaInfo = kCGImageAlphaNone;
		pixelBits = 3 * 8;
	}

	CGImageRef timg = CGImageCreate(CGBitmapContextGetWidth(map),
									CGBitmapContextGetHeight(map),
									CGBitmapContextGetBitsPerComponent(map),
									CGBitmapContextGetBitsPerPixel(map),
									CGBitmapContextGetBytesPerRow(map),
									CGBitmapContextGetColorSpace(map),
									alphaInfo,
									prov, NULL, false,
									kCGRenderingIntentDefault);

	CGDataProviderRelease(prov);
	CGContextRelease(map);
	// free(data); // Duplicate free?

	frame.tile_images[row*frame.cols+col] = timg;
	return timg; 
}

static void DrawTile(int x, int y, byte a, byte c, byte ta, byte tc)
{
	term_data *td = (term_data*) Term->data;

	int tile_wid = (1+use_bigtile)*td->tile_wid;
	CGRect dst_r = {{x*td->tile_wid, y*td->tile_hgt}, {tile_wid, td->tile_hgt}};
	CGRect src_r = {{0, 0}, {tile_wid, td->tile_hgt}};

	tc = (tc&0x7f) % frame.cols;
	ta = (ta&0x7f) % frame.rows;
	c = (c&0x7f) % frame.cols;
	a = (a&0x7f) % frame.rows;
	
	/* Requires alpha-masked foreground images */
	/* Draw terrain.  No alpha or rescaling, should just be a bitblt. */
	if (!use_transparency) tc = c, ta = a;
	CGImageRef image = GetTileImage(ta, tc, false);
	DrawSubimage(focus.ctx, dst_r, image, src_r);

	/* Draw the foreground, if it is distinct from the background */
	/* Use alpha. Rare, so it shouldn't take much time. */
	if(use_transparency && (tc != c || ta != a)) {
		// This doesn't preserve alpha. Don't know how to fix.
		image = GetTileImage(a, c, true);
		DrawSubimage(focus.ctx, dst_r, image, src_r);
		// Use the original source instead. This is SLOW!
		//src_r = (CGRect) {{ graf_width*c, graf_height*a },
		//							{ graf_width, graf_height }};
		// DrawSubimage(focus.ctx, dst_r, frame.image, src_r);
	}
}

static void ShowTextAt(int x, int y, int color, int n, const char *text )
{
	term_data *td = (term_data*) Term->data;
	GlyphInfo *info = td->ginfo;
	/* Overwite the text, unless it's being called recursively. */
	if(use_overwrite_hack && color != COLOR_BLACK) {
		Term_wipe_mac_aux(x, y, n); 
	}

	int c = *(unsigned char*) text;

	int xp = x * td->tile_wid + (td->tile_wid - (info->widths[c] + td->spacing))/2;
	// Only round once.
	int yp = y * td->tile_hgt + info->ascent + (td->tile_hgt - td->font_hgt)/2;

 	CGRect r;
	if(use_graphics || !use_overwrite_hack) {
		r = (CGRect) {{x*td->tile_wid, y*td->tile_hgt},
											{n*td->tile_wid, td->tile_hgt}};
		term_data_color(COLOR_BLACK);
		CGContextFillRect(focus.ctx, r);
	}

	if(use_clip_hack) {
		CGContextSaveGState (focus.ctx);
		CGContextClipToRect(focus.ctx, r);
	}

	term_data_color(color);
	/* Monospace; use preset text spacing when tiling is wider than text */
	if(n == 1 || info->monospace) {
		CGContextShowTextAtPoint ( focus.ctx, xp, yp, text, n ); 
		if(use_clip_hack)
			CGContextRestoreGState(focus.ctx);
		return;
	}

	
	GlyphInfo *gi = td->ginfo;

	UniChar utext[n];
	for(int i = 0; i < n; i++) utext[i] = text[i];
	ATSUSetTextPointerLocation(gi->layout, utext, 0, n, n);
	ATSUSetRunStyle(gi->layout, info->style, 0, n);
	ATSUDrawText(info->layout, 0, n, xp*(1<<16), yp*(1<<16));

	if(use_clip_hack)
		CGContextRestoreGState (focus.ctx);

	return;
}


/*
 * Init the graphics "frame"
 */
static errr graphics_init(void)
{
	OSErr err;

	/* Get the tile resources */
	char path[1024];
	locate_lib(path, sizeof(path));
	char *tail = path + strlen(path);
	FSSpec pict_spec;
	snprintf(tail, path+1024-tail, "xtra/graf/%s.png", pict_id);
	if(noErr != path_to_spec(path, &pict_spec))
		return -1;

 	/* Attempt to create a CGImage from FSSpec using Quicktime importer */
	GraphicsImportComponent gi;
	if( (err = GetGraphicsImporterForFile(&pict_spec, &gi)) )
		return err;

	Rect r;
	/* Retrieve the rect of the image */
	err = GraphicsImportGetNaturalBounds(gi, &r);

	/* Calculate and set numbers of rows and columns */
	frame.rows = (r.bottom-r.top) / graf_height;
	frame.cols = (r.right-r.left) / graf_width;

	frame.tile_images = calloc(frame.rows*frame.cols, sizeof(CGImageRef));
	if(!frame.tile_images) {
		return -1; // E_NO_MEM
	}

	CGImageRef tile_img;
	err = GraphicsImportCreateCGImage (gi, &tile_img, 0);
	CloseComponent(gi);

	if (err != noErr) {
		free(frame.tile_images);
		frame.tile_images = 0;
		return (err);
	}

	frame.image = tile_img;

	/* Success */
	return (noErr);
}

static void graphics_tiles_nuke(void)
{
	if(frame.tile_images) {
		for(int i = frame.rows*frame.cols; --i >= 0; ) {
			if(frame.tile_images[i]) CGImageRelease(frame.tile_images[i]);
			frame.tile_images[i] = 0;
		}
	}
}

/*
 * Nuke the graphics "frame" and contents.
 */
static errr graphics_nuke(void)
{
	/* Dispose image */
	if (frame.image)
	{
		/* Dispose of the tile image */
		CGImageRelease(frame.image);
		frame.image = NULL;
	}
	if(frame.tile_images) {
		graphics_tiles_nuke();
		free(frame.tile_images);
		frame.tile_images = 0;
	}

	/* Flush events */
	if (initialized) FlushEventQueue(GetMainEventQueue());

	/* Success */
	return (0);
}


/* Arbitary limit on number of possible samples per event */
#define MAX_SAMPLES			8

/* Struct representing all data for a set of event samples */
typedef struct
{
	int num;		/* Number of available samples for this event */
	NSSound *sound[MAX_SAMPLES];
} sound_sample_list;

/* Array of event sound structs */
static sound_sample_list samples[MSG_MAX];

/*
 * Load sound effects based on sound.cfg within the xtra/sound directory;
 * bridge to Cocoa to use NSSound for simple loading and playback, avoiding
 * I/O latency by cacheing all sounds at the start.  Inherits full sound
 * format support from Quicktime base/plugins.
 * pelpel favoured a plist-based parser for the future but .cfg support
 * improves cross-platform compatibility.
 */
static void load_sounds(void)
{
	char path[2048];
	char buffer[2048];
	ang_file *fff;

	/* Build the "sound" path */
	path_build(path, sizeof(path), ANGBAND_DIR_XTRA, "sound");
	ANGBAND_DIR_XTRA_SOUND = string_make(path);

	/* Find and open the config file */
	path_build(path, sizeof(path), ANGBAND_DIR_XTRA_SOUND, "sound.cfg");
	fff = file_open(path, MODE_READ, -1);

	/* Handle errors */
	if (!fff)
	{
		mac_warning("The sound configuration file could not be opened.");
		return;
	}
	
	/* Instantiate an autorelease pool for use by NSSound */
	NSAutoreleasePool *autorelease_pool;
	autorelease_pool = [[NSAutoreleasePool alloc] init];

	/*
	 * This loop may take a while depending on the count and size of samples
	 * to load.
	 */

	/* Parse the file */
	/* Lines are always of the form "name = sample [sample ...]" */
	while (file_getl(fff, buffer, sizeof(buffer)))
	{
		char *msg_name;
		char *cfg_sample_list;
		char *search;
		char *cur_token;
		char *next_token;
		int event;

		/* Skip anything not beginning with an alphabetic character */
		if (!buffer[0] || !isalpha((unsigned char)buffer[0])) continue;

		/* Split the line into two: message name, and the rest */
		search = strchr(buffer, ' ');
        cfg_sample_list = strchr(search + 1, ' ');
		if (!search) continue;
        if (!cfg_sample_list) continue;

		/* Set the message name, and terminate at first space */
		msg_name = buffer;
		search[0] = '\0';

		/* Make sure this is a valid event name */
		for (event = MSG_MAX - 1; event >= 0; event--)
		{
			if (strcmp(msg_name, angband_sound_name[event]) == 0)
			    break;
		}
        if (event < 0) continue;

		/* Advance the sample list pointer so it's at the beginning of text */
		cfg_sample_list++;
		if (!cfg_sample_list[0]) continue;

		/* Terminate the current token */
		cur_token = cfg_sample_list;
		search = strchr(cur_token, ' ');
		if (search)
		{
			search[0] = '\0';
			next_token = search + 1;
		}
		else
		{
			next_token = NULL;
		}

        /*
         * Now we find all the sample names and add them one by one
         */
        while (cur_token)
        {
            int num = samples[event].num;

			/* Don't allow too many samples */
			if (num >= MAX_SAMPLES) break;

			/* Build the path to the sample */
			path_build(path, sizeof(path), ANGBAND_DIR_XTRA_SOUND, cur_token);
			if (file_exists(path)) {
				
				/* Load the sound into memory */
				samples[event].sound[num] = [[NSSound alloc] initWithContentsOfFile:[NSString stringWithUTF8String:path] byReference:NO];
				if (samples[event].sound[num] != nil) {
				
					/* Imcrement the sample count */
					samples[event].num++;
				}
			}

			/* Figure out next token */
			cur_token = next_token;
			if (next_token)
			{
				/* Try to find a space */
				search = strchr(cur_token, ' ');

				/* If we can find one, terminate, and set new "next" */
				if (search)
				{
					search[0] = '\0';
					next_token = search + 1;
				}
				else
				{
					/* Otherwise prevent infinite looping */
					next_token = NULL;
				}
			}
		}
	}

	/* Release the autorelease pool */
	[autorelease_pool release];

	/* Close the file */
	file_close(fff);

	/* Register the sound hook */
	sound_hook = play_sound;
}



/*
 * Play sound effects asynchronously.  Select a sound from any available
 * for the required event, and bridge to Cocoa to play it.
 */

static void play_sound(int event)
{
	/* Paranoia */
	if (event < 0 || event >= MSG_MAX) return;

	/* Check there are samples for this event */
	if (!samples[event].num) return;

	/* Instantiate an autorelease pool for use by NSSound */
	NSAutoreleasePool *autorelease_pool;
	autorelease_pool = [[NSAutoreleasePool alloc] init];

	/* Choose a random event */
	int s = randint0(samples[event].num);
	
	/* Stop the sound if it's currently playing */
	if ([samples[event].sound[s] isPlaying])
		[samples[event].sound[s] stop];

	/* Play the sound */
	[samples[event].sound[s] play];

	/* Release the autorelease pool */
	[autorelease_pool release];
}



/*** Support for the "z-term.c" package ***/

/*
 * Initialize a new Term
 *
 * Note also the "window type" called "noGrowDocProc", which might be more
 * appropriate for the main "screen" window.
 *
 * Note the use of "srcCopy" mode for optimized screen writes.
 */
static void Term_init_mac(term *t)
{
	term_data *td = (term_data*)(t->data);
	WindowAttributes wattrs;
	OSStatus err;

	wattrs = kWindowCloseBoxAttribute | kWindowCollapseBoxAttribute
						| kWindowResizableAttribute;

	/* Make the window  */
	err = CreateNewWindow(
			kDocumentWindowClass,
			wattrs,
			&td->r,
			&td->w);

	Rect tmpR;
	GetWindowBounds((WindowRef)td->w, kWindowTitleBarRgn, &tmpR);
	int trueTop = td->r.top - (tmpR.bottom-tmpR.top);

	/* Enforce a minimum y position to avoid windows positioned vertically off screen */
	if (trueTop < GetMBarHeight()) trueTop = GetMBarHeight();
	
	MoveWindowStructure((WindowRef)td->w, td->r.left, trueTop);
	

	install_handlers(td->w);


	/* Fatal error */
	if (err != noErr) ExitToShell();

	/* Set refcon */
	SetWRefCon(td->w, (long)td);

	/* Set window title */
	SetWTitle(td->w, td->title);

	InstallStandardEventHandler(GetWindowEventTarget(td->w));

	/* Apply and Verify */
	term_data_check_font(td);
	term_data_check_size(td);
	term_data_resize(td);

	/* assert(td->mapped) */
	if (td->mapped)
	{
		WindowRef old_win = focus.active;

		ShowWindow(td->w);

		activate(td->w);
		term_data_color(COLOR_BLACK);
		CGContextFillRect(focus.ctx, td->bounds);
		activate(old_win);
	}

	/* Hack -- set "mapped" flag */
	t->mapped_flag = td->mapped;
}

/*
 * Nuke an old Term
 */
static void Term_nuke_mac(term *t)
{
#pragma unused(t)
}

/*
 * Unused
 */
static errr Term_user_mac(int c)
{
#pragma unused(c)
	return (0);
}


/*
 * React to changes
 */
static errr Term_xtra_mac_react(void)
{
	/* Update colors */
	update_color_info();


	/* Success */
	return (0);
}


/*
 * Do a "special thing"
 */
static errr Term_xtra_mac(int n, int v)
{
	term_data *td = (term_data*)(Term->data);

	/* Analyze */
	switch (n)
	{
		/* Make a noise */
		case TERM_XTRA_NOISE:
		{
			/* Make a noise */
			SysBeep(1);

			/* Success */
			return (0);
		}

		/* Process random events */
		case TERM_XTRA_BORED:
		{
			return (0);
		}

		/* Process pending events */
		case TERM_XTRA_EVENT:
		{
			/* Process an event */
			(void)CheckEvents(v);

			/* Success */
			return (0);
		}

		/* Flush all pending input events (if any) */
		case TERM_XTRA_FLUSH:
		{
			FlushEventsMatchingListFromQueue(GetMainEventQueue(),
				N_ELEMENTS(input_event_types), input_event_types);

			/* Success */
			return (0);
		}

		/* Hack -- Change the "soft level" */
		case TERM_XTRA_LEVEL:
		{
			/* Activate if requested */
			if (v) activate(td->w);

			/* Success */
			return (0);
		}

		/* Clear the screen */
		case TERM_XTRA_CLEAR:
		{
			if(!focus.ctx) activate(td->w);
			term_data_color(COLOR_BLACK);
			CGContextFillRect(focus.ctx, td->bounds);
			CGContextSynchronize(focus.ctx);

			return (0);
		}

		/* React to changes */
		case TERM_XTRA_REACT:
		{
			/* React to changes */
			return (Term_xtra_mac_react());
		}

		/* Delay (milliseconds) */
		case TERM_XTRA_DELAY:
		{
			/*
			 * WaitNextEvent relinquishes CPU as well as
			 * induces a screen refresh on OS X
			 */

			/* If needed */
			if (v > 0)
			{
				EventRecord tmp;
				UInt32 ticks;

				/* Convert millisecs to ticks */
				ticks = (v * 60L) / 1000;

				/*
				 * Hack? - Put the programme into sleep.
				 * No events match ~everyEvent, so nothing
				 * should be lost in Angband's event queue.
				 * Even if ticks are 0, it's worth calling for
				 * the above mentioned reasons.
				 */
				WaitNextEvent((EventMask)~everyEvent, &tmp, ticks, nil);
			}

			/* Success */
			return (0);
		}
		case TERM_XTRA_FRESH:
		{
			assert(focus.active == td->w);
			if(focus.ctx)
				CGContextSynchronize(focus.ctx);
			return (0);
		}
	}

	/* Oops */
	return (1);
}

/*
 * Low level graphics (Assumes valid input).
 * Draw a "cursor" at (x,y), using a "yellow box".
 * We are allowed to use "Term_what()" to determine
 * the current screen contents (for inverting, etc).
 */
static errr Term_curs_mac(int x, int y)
{
	if(!focus.active) activate(focus.active);

	term_data *td = (term_data*)(Term->data);

	CGContextSaveGState(focus.ctx);

	// Temporarily set stroke color to yellow
	int a = TERM_YELLOW;
	CGContextSetRGBStrokeColor(focus.ctx, focus.color_info[a][0],
							focus.color_info[a][1], focus.color_info[a][2], 1);

	/* Frame the grid, staying within the boundary. */
	int tile_wid = td->tile_wid;
	if(use_bigtile) {
		byte a;
		char c;
		Term_what(x+1,y, &a, &c);
		if(c == (char) 0xff) tile_wid *= 2;
	}

	CGRect r = {{x * td->tile_wid + .5, y * td->tile_hgt + .5 },
							{ tile_wid - 1, td->tile_hgt - 1}};

	CGContextStrokeRectWithWidth(focus.ctx, r, 1.0);

	CGContextRestoreGState(focus.ctx);

	/* Success */
	return (0);
}

/*
 * Low level graphics helper (Assumes valid input)
 *
 * Based on suggestion by Julian Lighton
 *
 * Overwrite "n" old characters starting at	(x,y)
 * with the same ones in the background colour
 */
static void Term_wipe_mac_aux(int x, int y, int n)
{
	/* Use old screen image kept inside the term package */
	const char *cp = &(Term->old->c[y][x]);

	/* And write it in the background color */
	ShowTextAt(x, y, COLOR_BLACK, n, cp);
}


/*
 * Low level graphics (Assumes valid input)
 *
 * Erase "n" characters starting at (x,y)
 */
static errr Term_wipe_mac(int x, int y, int n)
{

	/*
	 * Hack - overstrike the leftmost character with
	 * the background colour. This doesn't interfere with
	 * the graphics modes, because they set always_pict.
	 */
	if(use_overwrite_hack)
		Term_wipe_mac_aux(x, y, n);

	term_data_color(COLOR_BLACK);

	term_data *td = (term_data*)(Term->data);
	int tile_wid = (1+use_bigtile)*td->tile_wid;
 	CGRect r = {{ x*tile_wid, y*td->tile_hgt },
											{ n*tile_wid, -td->tile_hgt }};
	CGContextFillRect(focus.ctx, r);

	/* Success */
	return (0);
}


/*
 * Low level graphics.  Assumes valid input.
 *
 * Draw several ("n") chars, with an attr, at a given location.
 */
static errr Term_text_mac(int x, int y, int n, byte a, const char *cp)
{
	if(!focus.ctx) activate(focus.active);

	/* Draw the string */
	ShowTextAt(x, y, a, n, cp);

	/* Success */
	return (0);
}

static errr Term_pict_mac(int x, int y, int n, const byte *ap, const char *cp,
			  const byte *tap, const char *tcp)
{
	if(!focus.ctx) activate(focus.active);

	/* Scan the input */
	for (int i = 0; i < n; i++)
	{
		byte a = *ap++;
		char c = *cp++;
		byte ta = *tap++;
		char tc = *tcp++;

		/* Hack -- a filler for double-width tile */
		if(use_bigtile && (a == 255)) continue;

		// TODO: background should be overridden with neutral color
		// if unavailable.
		/* Graphics -- if Available and Needed */
		if (use_graphics && (a & 0x80) && (c & 0x80))
		{
			DrawTile(x+i, y, a, c, ta, tc);
		}
		/*
		 * Deal with these cases:
		 * (1) the player changed tile width / height, or
		 * (2) fake fixed-width for proportional font
		 */
		else
		{
			ShowTextAt(x+i, y, a, 1, &c);
		}
	}

	/* Success */
	return (0);
}


/*
 * Create and initialize window number "i"
 */
static void term_data_link(int i)
{
	term *old = Term;

	term_data *td = &data[i];

	/* Only once */
	if (td->t) return;

	/* Require mapped */
	if (!td->mapped) return;

	/* Allocate */
	td->t = ZNEW(term);

	/* Initialize the term */
	term_init(td->t, td->cols, td->rows, td == &data[0] ? 100 : 1);

	/* Use a "software" cursor */
	td->t->soft_cursor = TRUE;

	/*
	 * We have an "icky" lower right corner, since
	 * the window resize control is placed there
	 */
	td->t->icky_corner = TRUE;

	/* Erase with "white space" */
	td->t->attr_blank = TERM_WHITE;
	td->t->char_blank = ' ';

	/* Prepare the init/nuke hooks */
	td->t->init_hook = Term_init_mac;
	td->t->nuke_hook = Term_nuke_mac;

	/* Prepare the function hooks */
	td->t->user_hook = Term_user_mac;
	td->t->xtra_hook = Term_xtra_mac;
	td->t->wipe_hook = Term_wipe_mac;
	td->t->curs_hook = Term_curs_mac;
	td->t->bigcurs_hook = Term_curs_mac;
	td->t->text_hook = Term_text_mac;
	td->t->pict_hook = Term_pict_mac;


	td->t->never_bored = TRUE;
	td->t->never_frosh = TRUE;


	/* Link the local structure */
	td->t->data = (void *)(td);

	/* Activate it */
	Term_activate(td->t);
	/* Global pointer */
	angband_term[i] = td->t;

	/* Activate old */
	Term_activate(old);
}

/*
 * (Carbon, Bundle)
 * Return a POSIX pathname of the lib directory, or NULL if it can't be
 * located.  Caller must supply a buffer along with its size in bytes,
 * where returned pathname will be stored.
 */
static char *locate_lib(char *buf, size_t size)
{
	CFURLRef main_url;
	bool success;

	/* Obtain the URL of the main bundle */
	main_url = CFBundleCopyBundleURL(CFBundleGetMainBundle());

	/* Oops */
	if (!main_url) return (NULL);

	/* Get the URL in the file system's native string representation */
	success = CFURLGetFileSystemRepresentation(main_url, TRUE, (byte*)buf, size);

	/* Free the url */
	CFRelease(main_url);

	/* Oops */
	if (!success) return (NULL);	

	/* Append "/Contents/Resources/lib/" */
	my_strcat(buf, "/Contents/Resources/lib/", size);

	return (buf);
}


/*
 * Using Core Foundation's Preferences services -- pelpel
 *
 * Requires OS 8.6 or greater with CarbonLib 1.1 or greater. Or OS X,
 * of course.
 *
 * Without this, we can support older versions of OS 8 as well
 * (with CarbonLib 1.0.4).
 *
 * Frequent allocation/deallocation of small chunks of data is
 * far from my liking, but since this is only called at the
 * beginning and the end of a session, I hope this hardly matters.
 */


/*
 * Store "value" as the value for preferences item name
 * pointed by key
 */
static void save_preference(const char *key, type_union value)
{
	CFStringRef cf_key;
	CFPropertyListRef cf_value = 0;

	/* allocate and initialise the key */
	cf_key = CFStringCreateWithCString(NULL, key, kTextEncodingUS_ASCII);

	/* allocate and initialise the value */
	if(value.t == T_INT)
		cf_value = CFNumberCreate(NULL, kCFNumberIntType, &value.u.i);

	else if(value.t == T_STRING)
		cf_value = CFStringCreateWithCString(NULL, value.u.s, kTextEncodingUS_ASCII);

	else quit(format("Unrecognized save type %d\n", value.t));


	if ((cf_key != NULL) && (cf_value != NULL))
	{
		/* Store the key-value pair in the applications preferences */
		CFPreferencesSetAppValue(
			cf_key,
			cf_value,
			kCFPreferencesCurrentApplication);
	}

	/*
	 * Free CF data - the reverse order is a vain attempt to
	 * minimise memory fragmentation.
	 */
	if (cf_value) CFRelease(cf_value);
	if (cf_key) CFRelease(cf_key);
}


/*
 * Load preference value for key, returns TRUE if it succeeds with
 * vptr updated appropriately, FALSE otherwise.
 */
static bool load_preference(const char *key, type_union *vptr, size_t maxlen )
{
	CFStringRef cf_key;
	CFPropertyListRef cf_value;

	/* allocate and initialise the key */
	cf_key = CFStringCreateWithCString(NULL, key, kTextEncodingUS_ASCII);

	/* Oops */
	if (cf_key == NULL) return (FALSE);

	/* Retrieve value for the key */
	cf_value = CFPreferencesCopyAppValue(
		cf_key,
		kCFPreferencesCurrentApplication);

	/* Value not found */
	if (cf_value == NULL)
	{
		CFRelease(cf_key);
		return (FALSE);
	}

	/* Convert the value to appropriate type */
	if(vptr->t == T_INT)
		CFNumberGetValue( cf_value, kCFNumberIntType, &vptr->u.i);
	else if(vptr->t == T_STRING) {
		CFRange range = { 0, 200};
		(void) CFStringGetBytes (cf_value, range, kCGEncodingMacRoman, 0, 0, (UInt8*)vptr->u.s, maxlen, 0);
	}

	/* Free CF data */
	CFRelease(cf_value);
	CFRelease(cf_key);

	/* Success */
	return (TRUE);
}

/* Convenience wrappers for commonly used type short */
static void save_pref_short(const char *key, short value)
{
	type_union u = i2u(value);
	save_preference(key, u);
}
static bool load_pref_short(const char *key, short *vptr)
{
	bool ret;
	type_union u = { T_INT };
	ret = load_preference(key, &u, 0);
	if( ret == TRUE ) *vptr = u.u.i;
	return ret;
}

/*
 * Save preferences to preferences file for current host+current user+
 * current application.
 */
static void cf_save_prefs()
{
	/* Version stamp */
	save_pref_short("version.major", VERSION_MAJOR);
	save_pref_short("version.minor", VERSION_MINOR);
	save_pref_short("version.patch", VERSION_PATCH);
	save_pref_short("version.extra", VERSION_EXTRA);

	/* Gfx settings */
	/* double-width tiles */
	save_pref_short("arg.use_bigtile", use_bigtile);

	/* graphics mode */
	save_pref_short("graf_mode", graf_mode);

	/* text antialiasing */
	save_pref_short("arg.use_antialiasing", antialias);


	/* Windows */
	for (int i = 0; i < MAX_TERM_DATA; i++)
	{
		term_data *td = &data[i];

		save_pref_short(format("term%d.mapped", i), td->mapped);

		save_pref_short(format("term%d.tile_wid", i), td->tile_wid);
		save_pref_short(format("term%d.tile_hgt", i), td->tile_hgt);

		save_pref_short(format("term%d.cols", i), td->cols);
		save_pref_short(format("term%d.rows", i), td->rows);
		save_pref_short(format("term%d.left", i), td->r.left);
		save_pref_short(format("term%d.top", i), td->r.top);

		/* Integer font sizes only */
		save_preference(format("term%d.font_size", i), i2u((int)td->font_size));
		save_preference(format("term%d.font_name", i), s2u(td->font_name));
	}

	/*
	 * Save the recent items array - directly.
	 */
	CFPreferencesSetAppValue(
		CFSTR("recent_items"),
		recentItemsArrayRef,
		kCFPreferencesCurrentApplication);
	
	/*
	 * Make sure preferences are persistent
	 */
	CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
}


/*
 * Load preferences from preferences file for current host+current user+
 * current application.
 */
static void cf_load_prefs()
{
	short pref_major, pref_minor, pref_patch, pref_extra;
	short valid;

	/* Assume nothing is wrong, yet */
	bool ok = TRUE;

	/* Load version information */
	ok &= load_pref_short("version.major", &pref_major);
	ok &= load_pref_short("version.minor", &pref_minor);
	ok &= load_pref_short("version.patch", &pref_patch);
	ok &= load_pref_short("version.extra", &pref_extra);

	/* Any of the above failed */
	if (!ok)
	{
#if 0
		/* This may be the first run */
		mac_warning("Preferences are not found.");
#endif /* 0 */

		/* Ignore the rest */
		return;
	}


	/* Check version */
	if ((pref_major != VERSION_MAJOR) ||
		(pref_minor != VERSION_MINOR) ||
		(pref_patch != VERSION_PATCH) ||
		(pref_extra != VERSION_EXTRA))
	{
		/* Version 3.0.8 rewrote the preference file format - don't attempt to load previous versions. */
		if ((pref_major < 3) ||
			((pref_major == 3) && (pref_minor == 0) && (pref_patch < 8)))
		{
			
			/* Message */
			mac_warning(
				format("Ignoring %d.%d.%d.%d preferences.",
					pref_major, pref_minor, pref_patch, pref_extra));
	
			/* Ignore */
			return;
		}
		else
		{
			FSRef fsRef;
			char prefpath[1024];
			CFStringRef bundleid = (CFStringRef)CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), kCFBundleIdentifierKey);
			CFIndex bufferlength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(bundleid), kCFStringEncodingASCII) + 1;
			char bundlename[bufferlength];
			
			CFStringGetCString(bundleid, bundlename, bufferlength, kCFStringEncodingASCII);
			FSFindFolder(kOnAppropriateDisk, kPreferencesFolderType, kDontCreateFolder, &fsRef);
			FSRefMakePath(&fsRef, (UInt8 *)prefpath, 1024);
			mac_warning(format("Preference file has changed.  If you have display problems, delete %s/%s.plist and restart.", prefpath, bundlename));
			CFRelease(bundleid);
		}
	}


	/* HACK - Check for broken preferences */
	load_pref_short("term0.mapped", &valid);

	/* Ignore broken preferences */
	if (!valid)
	{
		// mac_warning("Ignoring broken preferences.");

		/* Ignore */
		return;
	}

	/* Gfx settings */
	short pref_tmp;

	/* double-width tiles */
	if (load_pref_short("arg.use_bigtile", &pref_tmp))
		use_bigtile = pref_tmp;

	/* anti-aliasing */
	if(load_pref_short("arg.use_antialiasing", &pref_tmp))
		antialias = pref_tmp;

	if(load_pref_short("graf_mode", &pref_tmp))
		graf_mode = pref_tmp;


	/* Windows */
	for (int i = 0; i < MAX_TERM_DATA; i++)
	{
		term_data *td = &data[i];

		load_pref_short(format("term%d.mapped", i), &td->mapped);
		CheckMenuItem(MyGetMenuHandle(kWindowMenu), kAngbandTerm+i, td->mapped);

		load_pref_short(format("term%d.tile_wid", i), &td->tile_wid);
		load_pref_short(format("term%d.tile_hgt", i), &td->tile_hgt);

		load_pref_short(format("term%d.cols", i), &td->cols);
		load_pref_short(format("term%d.rows", i), &td->rows);
		load_pref_short(format("term%d.left", i), &td->r.left);
		load_pref_short(format("term%d.top", i), &td->r.top);

		type_union u = {T_INT};
		if(load_preference(format("term%d.font_size", i), &u, sizeof(int)))
			td->font_size = (float) u.u.i;
		u = s2u(td->font_name);
		if(load_preference(format("term%d.font_name", i), &u, sizeof(td->font_name))) {
			ATSUFontID fid = 0;
			ATSUFindFontFromName(td->font_name, strlen(td->font_name),
								kFontPostscriptName, kFontMacintoshPlatform,
								kFontNoScriptCode, kFontNoLanguageCode, &fid);
			if(fid) td->font_id = fid;
			/* Use the default */
			else my_strcpy(td->font_name, "Monaco", sizeof(td->font_name));
		}
	}
	
	/*
	 * Load the recent items array, if present, directly
	 */
	CFArrayRef recentItemsLoaded = (CFArrayRef)CFPreferencesCopyAppValue(
		CFSTR("recent_items"),
		kCFPreferencesCurrentApplication);
	if (recentItemsLoaded != NULL)
	{
		CFRelease(recentItemsArrayRef);
		recentItemsArrayRef = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, recentItemsLoaded);
		CFRelease(recentItemsLoaded);
		redrawRecentItemsMenu();
	}
}


/*
 * Hack -- default data for a window
 */
static void term_data_hack(term_data *td)
{
	/* Default to Monaco font */
	ATSUFontID fid = 0;

	ATSUFindFontFromName("Monaco", strlen("Monaco"), kFontPostscriptName,
							kFontMacintoshPlatform, kFontNoScriptCode,
							kFontNoLanguageCode, &fid);


	if(!fid)
		quit("Failed to find font 'Monaco'");

	/* Wipe it */
	WIPE(td, term_data);

	/* Start hidden */
	td->mapped = FALSE;

	/* Default font */
	td->font_id = fid;
	my_strcpy(td->font_name, "Monaco", sizeof(td->font_name));

	/* Default font size - was 12 */
	td->font_size = 14;

	/* Default size */
	td->rows = 24;
	td->cols = 80;

	/* Default position */
	td->r.left = 10;
	td->r.top = 40;
}


/*
 * Read the preference file, Create the windows.
 *
 * We attempt to use "FindFolder()" to track down the preference file.
 */
static void init_windows(void)
{
	term_data *td;
	/*** Default values ***/

	/* Initialize (backwards) */
	for (int b = 0, i = MAX_TERM_DATA; i-- > 0; )
	{
		/* Obtain */
		td = &data[i];

		/* Defaults */
		term_data_hack(td);

		/* Obtain title */
		cptr s = angband_term_name[i];

		/* Get length */
		int n = strlen(s);

		/* Maximal length */
		if (n > 15) n = 15;

		/* Copy the title */
		strncpy((char*)(td->title) + 1, s, n);

		/* Save the length */
		td->title[0] = n;

		/* Tile the windows */
		td->r.left += (b * 30);
		td->r.top += (b * 30);

		/* Tile */
		b++;
	}

	/*** Load preferences ***/

	cf_load_prefs();

	/*** Instantiate ***/

	/* Main window */
	td = &data[0];

	/* Start visible */
	td->mapped = TRUE;

	/* Link (backwards, for stacking order) */
	for (int i = MAX_TERM_DATA; i-- > 0; )
	{
		term_data_link(i);
	}

	/* Main window */
	td = &data[0];

	/* Main window */
	Term_activate(td->t);
}


/* Set up the contents of the about dialog */
static void init_aboutdialogcontent()
{
	HIViewRef aboutDialogViewRef;
	OSStatus err;
	
	/* Set the application name from the constants set in defines.h */
	char *applicationName = format("%s %s", VERSION_NAME, VERSION_STRING);
	CFStringRef cfstr_applicationName = CFStringCreateWithBytes(NULL, (byte *)applicationName,
										strlen(applicationName), kCFStringEncodingASCII, false);
	HIViewFindByID(HIViewGetRoot(aboutDialog), aboutDialogName, &aboutDialogViewRef);
	SetControlData(aboutDialogViewRef, kControlEntireControl, kControlStaticTextCFStringTag, sizeof(cfstr_applicationName), &cfstr_applicationName);
	CFRelease(cfstr_applicationName);
	
	/* Set the application copyright as set up in variable.c */
	HIViewFindByID(HIViewGetRoot(aboutDialog), aboutDialogCopyright, &aboutDialogViewRef);
	CFStringRef cfstr_applicationCopyright = CFStringCreateWithBytes(NULL, (byte *)copyright,
										strlen(copyright), kCFStringEncodingASCII, false);
	SetControlData(aboutDialogViewRef, kControlEntireControl, kControlStaticTextCFStringTag, sizeof(cfstr_applicationCopyright), &cfstr_applicationCopyright);
	CFRelease(cfstr_applicationCopyright);

	/* Use a small font for the copyright text */
	TXNObject txnObject = HITextViewGetTXNObject(aboutDialogViewRef);
	TXNTypeAttributes typeAttr[1];
	typeAttr[0].tag = kTXNQDFontSizeAttribute;
	typeAttr[0].size = kTXNFontSizeAttributeSize;
	typeAttr[0].data.dataValue = FloatToFixed(10);
	err = TXNSetTypeAttributes(txnObject, 1, typeAttr, kTXNStartOffset, kTXNEndOffset);

	/* Get the application icon and draw it */
	ProcessSerialNumber psn = { 0, kCurrentProcess };
	ControlRef iconControl;
	FSRef ref;
	FSSpec appSpec;
	IconRef iconRef;
	ControlButtonContentInfo cInfo;

	err = GetProcessBundleLocation(&psn, &ref);
	if(err == noErr)
		err = FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, &appSpec, NULL);
	if( err == noErr )
		err = GetIconRefFromFile((const FSSpec *)&appSpec, &iconRef, nil);
	if(err == noErr)
		err = GetControlByID(aboutDialog, &aboutDialogIcon, &iconControl);
	if( err == noErr )
	{
		cInfo.contentType = kControlContentIconRef;
		cInfo.u.iconRef = iconRef;
		err = SetControlData(iconControl, 0, kControlIconContentTag, sizeof(cInfo), (Ptr)&cInfo);
	}
}


/*
 * Save preferences
 */
static void save_pref_file(void)
{
	cf_save_prefs();
}


/*
 * Prepare savefile dialogue and set the variable
 * savefile accordingly. Returns true if it succeeds, false (or
 * aborts) otherwise. If all is false, only allow files whose type
 * is 'SAVE'.
 * Originally written by Peter Ammon
 */
static bool select_savefile(bool all)
{
	OSErr err;
	FSSpec theFolderSpec;
	FSSpec savedGameSpec;
	NavDialogOptions dialogOptions;
	NavReplyRecord reply;
	/* Used only when 'all' is true */
	NavTypeList types = {'A271', 1, 1, {'SAVE'}};
	NavTypeListHandle myTypeList;
	AEDesc defaultLocation;

	/* Look for the "Angband/save/" sub-folder */
	char path[1024];
	path_build(path, sizeof(path), ANGBAND_DIR_USER, "save");
	err = path_to_spec(path, &theFolderSpec);

	if (err != noErr) quit_fmt("Unable to find the savefile folder! (Error %d)", err);

	/* Get default Navigator dialog options */
	err = NavGetDefaultDialogOptions(&dialogOptions);

	/* Clear preview option */
	dialogOptions.dialogOptionFlags &= ~kNavAllowPreviews;

	/* Disable multiple file selection */
	dialogOptions.dialogOptionFlags &= ~kNavAllowMultipleFiles;

	/* Make descriptor for default location */
	err = AECreateDesc(typeFSS, &theFolderSpec, sizeof(FSSpec),
		&defaultLocation);

	/* Oops */
	if (err != noErr) quit("Unable to allocate descriptor");

	/* We are indifferent to signature and file types */
	if (all)
	{
		myTypeList = (NavTypeListHandle)nil;
	}

	/* Set up type handle */
	else
	{
		err = PtrToHand(&types, (Handle *)&myTypeList, sizeof(NavTypeList));

		/* Oops */
		if (err != noErr) quit("Error in PtrToHand. Try enlarging heap");

	}

	/* Call NavGetFile() with the types list */
	err = NavChooseFile(&defaultLocation, &reply, &dialogOptions, NULL,
		NULL, NULL, myTypeList, NULL);

	/* Free type list */
	if (!all) DisposeHandle((Handle)myTypeList);

	/* Error */
	if (err != noErr)
	{
		/* Nothing */
	}

	/* Invalid response -- allow the user to cancel */
	else if (!reply.validRecord)
	{
		/* Hack -- Fake error */
		err = -1;
	}

	/* Retrieve FSSpec from the reply */
	else
	{
		AEKeyword theKeyword;
		DescType actualType;
		Size actualSize;

		/* Get a pointer to selected file */
		(void)AEGetNthPtr(&reply.selection, 1, typeFSS, &theKeyword,
			&actualType, &savedGameSpec, sizeof(FSSpec), &actualSize);

		/* Dispose NavReplyRecord, resources and descriptors */
		(void)NavDisposeReply(&reply);
	}

	/* Dispose location info */
	AEDisposeDesc(&defaultLocation);

	/* Error */
	if (err != noErr) return (FALSE);

	/* Convert FSSpec to pathname and store it in variable savefile */
	(void)spec_to_path(&savedGameSpec, savefile, sizeof(savefile));

	/* Success */
	return (TRUE);
}

/*
 * Initialize the menus
 *
 * Fixed top level menus are now loaded all at once by GetNewMBar().
 * Although this simplifies the function a bit, we have to make sure
 * that resources have all the expected entries defined XXX XXX
 */
static void init_menubar(void)
{
	/* Boilerplate nib stuff */
	IBNibRef nib;
	OSStatus err;
	if((err = CreateNibReference(CFSTR("main"), &nib)))
		quit("Cannot find the main nib bundle!");

	if((err = SetMenuBarFromNib(nib, CFSTR("MenuBar"))))
		quit("Cannot prepare menu bar!");

	/* Load the about dialog and set its contents */
	(void) CreateWindowFromNib(nib, CFSTR("DLOG:about"), &aboutDialog);
	init_aboutdialogcontent();

	DisposeNibReference(nib);

	MenuRef m = MyGetMenuHandle(kStyleMenu);
	for(int i = 1; i <= CountMenuItems(m); i++) {
		// Invalid entry
		SetMenuItemRefCon(m, i, -1);
	}
	for(int i = 0; i < N_ELEMENTS(graphics_modes); i++) {
		SetMenuItemRefCon(m, graphics_modes[i].menuItem, i);
	}

	for(int j = kTileWidMenu; j <= kTileHgtMenu; j++) {
		m = MyGetMenuHandle(j);
		for(int i = MIN_FONT; i <= 32; i++) {
			char buf[15];
			/* Tile size */
			strnfmt((char*)buf, 15, "%d", i);
			CFStringRef cfstr = CFStringCreateWithBytes ( NULL, (byte*) buf,
									strlen(buf), kCFStringEncodingASCII, false);
			AppendMenuItemTextWithCFString(m, cfstr, 0, j, NULL);
			SetMenuItemRefCon(m, i-MIN_FONT+1, i);
			CFRelease(cfstr);
		}
	}
}

// Install the handlers from the Commands table.
static void install_handlers(WindowRef w)
{
	EventHandlerRef prevRef;
	for(int i = 0; i < N_ELEMENTS(event_defs) ; i++) {
		const CommandDef *def = &event_defs[i];

		/* Install window handlers only for kWINDOW events */
		if((!w) == (def->targetID == kWINDOW))
			continue;

		EventHandlerUPP evtUPP = NewEventHandlerUPP( def->handler );
		const EventTypeSpec eventSpec = { def->evtClass, def->evtType };

		EventTargetRef target = GetApplicationEventTarget();
		if(def->targetID == kWINDOW)
			target = GetWindowEventTarget(w);
		else if(def->targetID)
			target = GetMenuEventTarget(MyGetMenuHandle(def->targetID));
		
		OSStatus err = InstallEventHandler(target, evtUPP, 1, &eventSpec,
													def->userData, &prevRef);
		if(err == eventHandlerAlreadyInstalledErr) {
			err = AddEventTypesToHandler(prevRef, 1, &eventSpec);
		}
	}
}

static int funcGTE(int a, int b) { return a >= b; }
static int funcConst(int a, int c) {return c; }

/* This initializes all the menus with values that change unpredictably.
 * This function is called on every menu draw and therefore should be kept
 * light and fast; menus that change rarely are done at the time of change
 */
static void validate_menus(void)
{
	WindowRef w = GetFrontWindowOfClass(kDocumentWindowClass, true);
	term_data *td;
	if(!w || !initialized) return;
	td = (term_data*) GetWRefCon(w);
	if(!td) return;

	struct {
		int menu;				/* Radio-style Menu ID to validate */
		int cur;				/* Value in use (Compare to RefCon) */
		int limit;				/* Constraint value */
		int (*cmp) (int, int);	/* Filter function */
	} funcs [] = {
		{ kTileWidMenu, td->tile_wid, td->font_wid, funcGTE },
		{ kTileHgtMenu, td->tile_hgt, td->font_hgt, funcGTE },
		{ kStyleMenu, graf_mode, 1, funcConst }
	};

	MenuHandle m;

	if(cmd.command != CMD_NULL) {
		EnableAllMenuItems(MyGetMenuHandle(kSpecialMenu));
		EnableAllMenuItems(MyGetMenuHandle(kStyleMenu));
	}

	for(int i = 0; i < N_ELEMENTS(funcs); i++) {
		m = MyGetMenuHandle(funcs[i].menu);
		int n = CountMenuItems(m);
		for(int j = 1; j <= n; j++) {
			UInt32 value;
			GetMenuItemRefCon(m, j, &value);
			CheckMenuItem(m, j, funcs[i].cur == value);
			if(funcs[i].cmp(value, funcs[i].limit)) {
				EnableMenuItem(m, j);
			}
			else
			{
				DisableMenuItem(m, j);
			}
		}
	}

	if(cmd.command != CMD_NULL && character_generated)
	{
		EnableMenuItem(MyGetMenuHandle(kFileMenu), kSave);
		EnableMenuItem(MyGetMenuHandle(kSpecialMenu), kSound);
	}
	else
	{
		DisableMenuItem(MyGetMenuHandle(kFileMenu), kSave);
		DisableMenuItem(MyGetMenuHandle(kSpecialMenu), kSound);
	}
	
	/* Keep the sound menu up-to-date */
	CheckMenuItem(MyGetMenuHandle(kSpecialMenu), kSound, OPT(use_sound));

	for(int i = 0; i < N_ELEMENTS(toggle_defs); i++) {
		m = MyGetMenuHandle(toggle_defs[i].menuID);
		CheckMenuItem(m, toggle_defs[i].menuItem, *(toggle_defs[i].var));
	}
}

static OSStatus ValidateMenuCommand(EventHandlerCallRef inCallRef,
									EventRef inEvent, void *inUserData )
{
	validate_menus();
	return noErr;
}

/* Populate the recent items menu */
static void redrawRecentItemsMenu()
{
	DeleteMenuItems(MyGetMenuHandle(kOpenRecentMenu), 1, CountMenuItems(MyGetMenuHandle(kOpenRecentMenu)));
	if (!recentItemsArrayRef || CFArrayGetCount(recentItemsArrayRef) == 0)
	{
		AppendMenuItemTextWithCFString(MyGetMenuHandle(kOpenRecentMenu), CFSTR("No recent items"), kMenuItemAttrDisabled, kOpenRecentMenu, NULL);
	}
	else
	{
		for (int i = 0; i < CFArrayGetCount(recentItemsArrayRef); i++)
		{
			OSErr err;
			FSRef recentFileRef;
			CFDataRef recentFileData;
			Boolean updateAlias = FALSE;
			MenuItemIndex item;

			recentFileData = CFArrayGetValueAtIndex(recentItemsArrayRef, i);
			if (CFDataGetTypeID() != CFGetTypeID(recentFileData)) continue;
			AliasHandle recentFileAlias = (AliasHandle)NewHandle(CFDataGetLength(recentFileData));
			CFDataGetBytes(recentFileData, CFRangeMake(0, CFDataGetLength(recentFileData)), (UInt8 *) *recentFileAlias);
			
			err = FSResolveAlias(NULL, recentFileAlias, &recentFileRef, &updateAlias);
			if (err != noErr) continue;
			
			HFSUniStr255 recentFileName;
			err = FSGetCatalogInfo(&recentFileRef, kFSCatInfoNone, NULL, &recentFileName, NULL, NULL);
			if (err != noErr) continue;
			
			CFStringRef cfstr = CFStringCreateWithCharacters(kCFAllocatorDefault, recentFileName.unicode, recentFileName.length);
			AppendMenuItemTextWithCFString(MyGetMenuHandle(kOpenRecentMenu), cfstr, 0, i, &item);

			// Add a shortcut key -- command-alt-number
			if (i < 9)
			{
				SetMenuItemCommandKey(MyGetMenuHandle(kOpenRecentMenu), item, false, 0x31+i);
				SetMenuItemModifiers(MyGetMenuHandle(kOpenRecentMenu), item, kMenuOptionModifier);
			}

			CFRelease(cfstr);
		}
		AppendMenuItemTextWithCFString(MyGetMenuHandle(kOpenRecentMenu), CFSTR("-"), kMenuItemAttrSeparator, -1, NULL);
		AppendMenuItemTextWithCFString(MyGetMenuHandle(kOpenRecentMenu), CFSTR("Clear menu"), 0, -1, NULL);
	}
}

/* Add a savefile to the recent items list, or update its existing status */
static void updateRecentItems(char *savefile)
{
	OSErr err;
	FSRef recentFileRef;
	AliasHandle recentFileAlias;
	CFDataRef newRecentFileData;
	CFDataRef recentFileData;

	/* Convert the save path to an FSRef, then an Alias, and convert to data ready for storage */
	err = FSPathMakeRef((byte *)savefile, &recentFileRef, NULL);
	if (err != noErr) return;
	err = FSNewAlias(NULL, &recentFileRef, &recentFileAlias);
	if (err != noErr) return;
	newRecentFileData = CFDataCreate(kCFAllocatorDefault, (UInt8 *) *recentFileAlias, GetHandleSize((Handle)recentFileAlias));
	
	/* Loop through the recent items array, and delete any matches */
	for (int i = CFArrayGetCount(recentItemsArrayRef) - 1; i >= 0; i--)
	{
		Boolean updateAlias = FALSE;
		char recentFilePath[1024];

		/* Retrieve the recent item, resolve the alias, and extract a path */
		recentFileData = CFArrayGetValueAtIndex(recentItemsArrayRef, i);
		if (CFDataGetTypeID() != CFGetTypeID(recentFileData)) continue;
		AliasHandle recentFileAlias = (AliasHandle)NewHandle(CFDataGetLength(recentFileData));
		CFDataGetBytes(recentFileData, CFRangeMake(0, CFDataGetLength(recentFileData)), (UInt8 *) *recentFileAlias);
		
		/* If resolving an alias fails, don't delete it - the array is size-limited
		 * anyway, and this allows network shares or removeable drives to come back later */
		err = FSResolveAlias(NULL, recentFileAlias, &recentFileRef, &updateAlias);
		if (err != noErr) continue;
		err = FSRefMakePath(&recentFileRef, (byte *)recentFilePath, 1024);
		if (err != noErr) continue;

		/* Remove the item from the array if the paths match */
		if (strcmp(recentFilePath, savefile) == 0)
		{
			CFArrayRemoveValueAtIndex(recentItemsArrayRef, i);
			continue;
		}

		/* If performing a file search via the alias updated it, save changes */
		if (updateAlias)
		{
			recentFileData = CFDataCreate(kCFAllocatorDefault, (UInt8 *) *recentFileAlias, GetHandleSize((Handle)recentFileAlias));
			CFArraySetValueAtIndex(recentItemsArrayRef, i, newRecentFileData);
		}
	}
	
	/* Insert the encoded alias at the start of the recent items array */
	CFArrayInsertValueAtIndex(recentItemsArrayRef, 0, newRecentFileData);
	
	/* Limit to ten items */
	if (CFArrayGetCount(recentItemsArrayRef) > 10)
		CFArrayRemoveValueAtIndex(recentItemsArrayRef, 10);

	/* Redraw the menu */
	redrawRecentItemsMenu();
}

/* Handle a selection in the recent items menu */
static OSStatus OpenRecentCommand(EventHandlerCallRef inCallRef,
							EventRef inEvent, void *inUserData )
{
	HICommand command;
	command.commandID = 0;
	GetEventParameter( inEvent, kEventParamDirectObject, typeHICommand,
							NULL, sizeof(command), NULL, &command);
	
	/* If the 'Clear menu' command was selected, flush the recent items array */
	if (command.commandID == -1) {
		CFArrayRemoveAllValues(recentItemsArrayRef);
	
	/* Otherwise locate the correct filepath and open it. */
	} else {
		if (cmd.command != CMD_NULL || command.commandID < 0 || command.commandID >= CFArrayGetCount(recentItemsArrayRef))
			return eventNotHandledErr;

		OSErr err;
		FSRef recentFileRef;
		AliasHandle recentFileAlias;
		CFDataRef recentFileData;
		Boolean updateAlias = FALSE;

		recentFileData = CFArrayGetValueAtIndex(recentItemsArrayRef, command.commandID);
		if (CFDataGetTypeID() != CFGetTypeID(recentFileData)) return eventNotHandledErr;
		recentFileAlias = (AliasHandle)NewHandle(CFDataGetLength(recentFileData));
		CFDataGetBytes(recentFileData, CFRangeMake(0, CFDataGetLength(recentFileData)), (UInt8 *) *recentFileAlias);
		err = FSResolveAlias(NULL, recentFileAlias, &recentFileRef, &updateAlias);
		if (err != noErr) return eventNotHandledErr;
		err = FSRefMakePath(&recentFileRef, (byte *)savefile, 1024);
		if (err != noErr) return eventNotHandledErr;
		
		cmd.command = CMD_LOADFILE;
	}

	/* Redraw the menu */
	redrawRecentItemsMenu();

	return noErr;
}

static OSStatus AngbandGame(EventHandlerCallRef inCallRef,
							EventRef inEvent, void *inUserData )
{
	/* Only enabled options are Fonts, Open/New/Import and Quit. */
	DisableAllMenuItems(MyGetMenuHandle(kTileWidMenu));
	DisableAllMenuItems(MyGetMenuHandle(kTileHgtMenu));

	SetFontInfoForSelection(kFontSelectionATSUIType, 0, 0, 0);

	for(int i = kNew; i <= kImport; i++)
		EnableMenuItem(MyGetMenuHandle(kFileMenu), i);

	/* Validate graphics, after bootstrapped opening of terminals */
	for(int i = 0; i < N_ELEMENTS(data); i++) {
		if(data[i].mapped)
			RevalidateGraphics(&data[i], FALSE);
	}

	/* Flush the prompt */
	Term_fresh();
	Term_flush();

	play_game();
	quit(0);
	// Not reached
	return noErr;
}

/*
 *  "New" / "Open" /  "Import"
 */
static OSStatus openGame(int op)
{
	/* If a game is in progress, do not proceed */
	if (cmd.command != CMD_NULL) return noErr;

	/* Let the player to choose savefile */
	if (op != kNew && 0 == select_savefile(op == kImport))
	{
		// Failed to open
		return noErr;
	}

	/* Disable the file-handling options in the file menu */
	for(int i = kNew; i <= kImport; i++)
		DisableMenuItem(MyGetMenuHandle(kFileMenu), i);

	/* Wait for a keypress */
	pause_line(Term->hgt - 1);

	/* Set the game status */
	if (op == kNew)
		cmd.command = CMD_NEWGAME;
	else
		cmd.command = CMD_LOADFILE;
		
	return noErr;
}

/*
 *	Run the event loop and return a gameplay status to init_angband
 */
static errr get_cmd_init()
{ 
	EventTargetRef target = GetEventDispatcherTarget();
	OSStatus err;
	EventRef event;
	
	/* Prompt the user */ 
	prt("[Choose 'New', 'Open' or 'Import' from the 'File' menu]", 23, 11);
	Term_fresh();
	
	while (cmd.command == CMD_NULL) {
		err = ReceiveNextEvent(0, 0, kEventDurationForever, true, &event);
		if(err == noErr) {
			SendEventToEventTarget (event, target);
			ReleaseEvent(event);
		}
	}

	/* Push the command to the game. */
	cmd_insert_s(&cmd);
		
	/* A game is starting - update status and tracking as appropriate. */ 
	term_data *td0 = &data[0];
	ChangeWindowAttributes(td0->w, kWindowNoAttributes, kWindowCloseBoxAttribute);
	DisableMenuItem(MyGetMenuHandle(kFileMenu), kClose);

	/* Disable the file-handling options in the file menu.
	 * This has to be done separately for new/open due to messages/prompts
	 * which may delay open while the menus are still accessibile. */
	for(int i = kNew; i <= kImport; i++)
		DisableMenuItem(MyGetMenuHandle(kFileMenu), i);
			
	/* If supplied with a savefile, update the Recent Items list */
	if (savefile[0])
	{
		updateRecentItems(savefile);
		redrawRecentItemsMenu();
	}
	
	return 0; 
} 

static errr crb_get_cmd(cmd_context context, bool wait)
{
	if (context == CMD_INIT) 
		return get_cmd_init();
	else 
		return textui_get_cmd(context, wait);
}

static OSStatus QuitCommand(EventHandlerCallRef inCallRef,
							EventRef inEvent, void *inUserData )
{
	if (cmd.command == CMD_NULL && !character_generated)
		quit(0);	
	else Term_key_push(KTRL('x'));
	return noErr;
}

static OSStatus CommandCommand(EventHandlerCallRef inCallRef,
							EventRef inEvent, void *inUserData )
{
	HICommand command;
	command.commandID = 0;
	GetEventParameter( inEvent, kEventParamDirectObject, typeHICommand,
							NULL, sizeof(command), NULL, &command);
	UInt32 attrib;
	GetEventParameter( inEvent, kEventParamKeyModifiers, typeUInt32,
							NULL, sizeof(attrib), NULL, &attrib);
	switch(command.commandID) {
	default:
		return eventNotHandledErr;
	case 'save':
		if(cmd.command != CMD_NULL && character_generated)
			Term_key_push(KTRL('S'));
		break;
	case 'open':
		openGame(command.menu.menuItemIndex);
		break;
	case 'font':
	  {
		if(!FPIsFontPanelVisible()) {
			WindowRef w = FrontWindow();
			if (w) {
				term_data *td = (term_data*) GetWRefCon(w);
				SetFontInfoForSelection(kFontSelectionATSUIType, 1,
										&td->ginfo->style, NULL);
			}
		}
		CFStringRef tags[] = {CFSTR("Show Fonts"), CFSTR("Hide Fonts")};
		FPShowHideFontPanel(); 
		SetMenuItemTextWithCFString(command.menu.menuRef, kFonts,
											tags[FPIsFontPanelVisible()] );
		break;
	  }
	}

	return noErr;
}


static OSStatus CloseCommand(EventHandlerCallRef inCallRef,
							EventRef inEvent, void *inUserData )
{
	WindowRef w = 0;
	term_data *td;
	GetEventParameter(inEvent, kEventParamDirectObject,
							typeWindowRef, NULL, sizeof(w), NULL, &w);

	td = (term_data*) GetWRefCon(w);

	if(cmd.command == CMD_NULL && !character_generated && td == &data[0])
		quit(0);

	hibernate();

	/* Track the go-away box */
	if (td && td != &data[0])
	{
		/* Not Mapped */
		td->mapped = FALSE;

		/* Not Mapped */
		td->t->mapped_flag = FALSE;

		/* Hide the window */
		TransitionWindow(td->w,
						kWindowZoomTransitionEffect,
						kWindowHideTransitionAction,
						NULL);

		/* Update the menu status */
		CheckMenuItem(MyGetMenuHandle(kWindowMenu), kAngbandTerm+(td - &data[0]), FALSE);
	}
	return noErr;
}



static OSStatus ResizeCommand(EventHandlerCallRef inCallRef,
							EventRef inEvent, void *inUserData )
{
	int x, y;
	WindowRef w = 0;
	unsigned flags;

	term_data *td;
	term *old = Term;
	int err ;

	GetEventParameter(inEvent, kEventParamDirectObject,
							typeWindowRef, NULL, sizeof(w), NULL, &w);
	err = GetEventParameter(inEvent, kEventParamAttributes,
							typeUInt32, NULL, sizeof(flags), NULL, &flags);

	td = (term_data*) GetWRefCon(w);

	/* Oops */
	if (!td) return noErr;


	/* Obtain geometry of resized window */

	Rect tmpR;
	GetWindowBounds((WindowRef)td->w, kWindowContentRgn, &tmpR);
	td->r = tmpR;
	if(td->r.top < 40) td->r.top = 40;

	/* Extract the new ClipRect size in pixels */
	y = tmpR.bottom - tmpR.top - BORDER_WID;
	x = tmpR.right - tmpR.left - BORDER_WID * 2;

	/* Ignore drag effects, other than for moving the mouse origin */
	if(td->rows * td->tile_hgt == y && td->cols * td->tile_wid == x)
		return noErr;

	/* Extract a "close" approximation */
	td->rows = y / td->tile_hgt;
	td->cols = x / td->tile_wid;


	/* Apply and Verify */
	term_data_check_size(td);

	/* Activate */
	Term_activate(td->t);
	/* Resize the term to correspond to new count. */
	Term_resize(td->cols, td->rows);
	Term_activate(old);

	// Close the old (different size) CGContext
	hibernate();
	/* Resize and Redraw */
	term_data_resize(td);

	// Since we don't know what view needs to be updated, recalculate
	// and redraw them all. (term_data_redraw() is not sufficient)
	Term_key_push(KTRL('R'));
	
	return eventNotHandledErr;
}

static OSStatus GraphicsCommand(EventHandlerCallRef inCallRef,
							EventRef inEvent, void *inUserData )
{
	HICommand command;
	command.menu.menuRef = 0;
	GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand,
							NULL, sizeof(HICommand), NULL, &command);

	// Check for valid input
	// assert(kStyleMenu == GetMenuID(command.menu.menuRef));
	if (command.commandID != 'graf' ||
			kStyleMenu != GetMenuID(command.menu.menuRef))
		return eventNotHandledErr;

	// Index in graphics_modes[]
	UInt32 op;
	GetMenuItemRefCon(command.menu.menuRef, command.menu.menuItemIndex, &op);

	if(graf_mode != op)
		graphics_aux(op);

	return noErr;
}

static void graphics_aux(int op)
{
	graf_mode = op;
	use_transparency = graphics_modes[op].trans;
	pict_id = graphics_modes[op].file;
	graf_width = graf_height = graphics_modes[op].size;
	use_graphics = (op != 0);
	graf_mode = op;
	ANGBAND_GRAF = graphics_modes[op].name;
	arg_graphics = op;

	graphics_nuke();

	/* load tiles and setup GWorlds if tiles are requested */
	if (use_graphics && (graphics_init() != 0))
	{
		/* Oops */
		plog("Cannot initialize graphics!");

		/* reset graphics flags */
		use_graphics = 0;
		graf_mode = 0;
		ANGBAND_GRAF = 0;

		/* reset transparency mode */
		use_transparency = false;
	}
	/* Reset visuals, without updating the screen */
	if (initialized && cmd.command != CMD_NULL)
	{
		reset_visuals(TRUE);
	}
	RevalidateGraphics(&data[0], FALSE);
	Term_key_push(KTRL('R'));
}

static OSStatus TileSizeCommand(EventHandlerCallRef inCallRef,
							EventRef inEvent, void *inUserData )
{
	HICommand cmd;
	GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand,
							NULL, sizeof(cmd), NULL, &cmd);
	UInt32 newSize = 0;
	GetMenuItemRefCon(cmd.menu.menuRef, cmd.menu.menuItemIndex, &newSize);
	term_data *td = (term_data*) Term->data;
	if(!td) return noErr;

	if(GetMenuID(cmd.menu.menuRef) == kTileWidMenu) {
		if(td->font_wid > newSize || newSize == td->tile_wid) return noErr;
		else td->tile_wid = newSize;
	}
	else if(GetMenuID(cmd.menu.menuRef) == kTileHgtMenu) {
		if(td->font_hgt > newSize || newSize == td->tile_hgt) return noErr;
		else td->tile_hgt = newSize;
	}
	else {
		return eventNotHandledErr;
	}

	RevalidateGraphics(td, FALSE);

	return noErr;
}

static OSStatus RestoreCommand(EventHandlerCallRef inCallRef,
							EventRef inEvent, void *inUserData)
{
	WindowRef w = 0;
	GetEventParameter(inEvent, kEventParamDirectObject, typeWindowRef,
							NULL, sizeof(w), NULL, &w);
	term_data *td = (term_data*) GetWRefCon(w);
	
	if(!td) return eventNotHandledErr;

	/* Mapped */
	td->mapped = TRUE;

	int i = td - &data[0];

	/* Link */
	term_data_link(i);

	/* Mapped (?) */
	td->t->mapped_flag = TRUE;

	/* Bring to the front */
	SelectWindow(td->w);

	/* Update menu states */
	if (td == &data[0] && cmd.command != CMD_NULL)
		DisableMenuItem(MyGetMenuHandle(kFileMenu), kClose);
	else
		EnableMenuItem(MyGetMenuHandle(kFileMenu), kClose);
	
	return noErr;
}

static OSStatus TerminalCommand(EventHandlerCallRef inCallRef,
							EventRef inEvent, void *inUserData )
{
	HICommand command;
	GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand,
							NULL, sizeof(HICommand), NULL, &command);

	/* Offset of Angband term in Window menu. */
	int i = command.menu.menuItemIndex - kAngbandTerm;

	/* Check legality of choice */
	if ((i < 0) || (i >= MAX_TERM_DATA)) return eventNotHandledErr;

	/* Obtain the window */
	term_data *td = &data[i];

	/* Mapped */
	td->mapped = TRUE;

	/* Link */
	term_data_link(i);

	/* Mapped (?) */
	td->t->mapped_flag = TRUE;

	/* Show the window */
	TransitionWindow(td->w,
				kWindowZoomTransitionEffect,
				kWindowShowTransitionAction,
				NULL);

	/* Update the menu status */
	CheckMenuItem(MyGetMenuHandle(kWindowMenu), kAngbandTerm+i, TRUE);

	term_data_check_font(td);
	term_data_check_size(td);
	term_data_resize(td);
	Term_resize(td->cols, td->rows);
	term_data_redraw(td);

	/* Bring to the front */
	SelectWindow(td->w);

	return noErr;
}

static OSStatus RevalidateGraphics(term_data *td, bool reset_tilesize)
{
	if (!td) return noErr;

	/*
	 * Reset the tilesize on graphics changes; term_data_check_font recalculates
	 * this after it's been reset.  However, only reset the tilesize for default
	 * and font events (not startup - 'Play' 'Band' - or manual changes)
	*/
	if (reset_tilesize)
	{
		td->tile_wid = td->tile_hgt = 0;
	}

	/* Clear the graphics tile cache. */
	graphics_tiles_nuke();

	/* Sanity check for rows, columns, tilesize. */
	term_data_check_font(td);
	term_data_check_size(td);
	
	/* Window size changes */
	term_data_resize(td);
	term_data_redraw(td);

	return noErr;
}

static OSStatus UpdateCommand(EventHandlerCallRef inCallRef,
								EventRef inEvent, void *inUserData)
{
	WindowRef w = 0;
	term_data *td;
	
	GetEventParameter(inEvent, kEventParamDirectObject,
							typeWindowRef, NULL, sizeof(w), NULL, &w);
	
	if(!w) return noErr;

	/* Clear window's update region and clip drawings with it */
	BeginUpdate(w);
	td = (term_data*) GetWRefCon(w);
	if (td) term_data_redraw(td);
	EndUpdate(w);

	return noErr;
}

/* Handle toggling sound via the menu option */
static OSStatus SoundCommand(EventHandlerCallRef inCallRef,
							EventRef inEvent, void *inUserData )
{
	HICommand command;
	GetEventParameter( inEvent, kEventParamDirectObject, typeHICommand,
							NULL, sizeof(command), NULL, &command);
	if (command.menu.menuItemIndex == kSound)
	{
		OPT(use_sound) = !OPT(use_sound);
	}
	return noErr;
}


static OSStatus ToggleCommand(EventHandlerCallRef inCallRef,
								EventRef inEvent, void *inUserData )
{
	HICommand command;
	GetEventParameter( inEvent, kEventParamDirectObject, typeHICommand,
							NULL, sizeof(command), NULL, &command);

	MenuItemIndex index = command.menu.menuItemIndex;
	int menuID = GetMenuID(command.menu.menuRef);
	for(int i = N_ELEMENTS(toggle_defs);  --i >= 0;)
	{
		if(index == toggle_defs[i].menuItem && menuID == toggle_defs[i].menuID)
		{
			*toggle_defs[i].var = !(*toggle_defs[i].var);
			if(toggle_defs[i].refresh == true) {
				RevalidateGraphics(&data[0], FALSE);
				// Force redraw.
				Term_key_push(KTRL('R'));
			}
			return noErr;
		}
	}
	return eventNotHandledErr;
}


static void FontChanged(UInt32 fontID, float size)
{
	if(size < MIN_FONT) return;

	ATSUStyle fontStyle;

	// Font size must be 8 or more.
	if( 8 > size || fontID == 0)
		return;

	term_data *td = (term_data*) GetWRefCon(fontInfo.focus);
	if(!td) return; // paranoia

	// No change.
	if(td->font_id == fontID && td->font_size == size)
		return ;

	const ATSUAttributeTag tags[] = {kATSUFontTag, kATSUSizeTag};


	Fixed fsize = (Fixed)(size*(1<<16));
	const ByteCount sizes[] = {sizeof(fontID), sizeof(fsize)};
	void * values[] = { &fontID, &fsize };

	ATSUCreateStyle(&fontStyle);
	ATSUSetAttributes(fontStyle, 2, tags, sizes, values);

	// Reject italics &c
	const ATSUAttributeTag badtags[] = {kATSUQDItalicTag, 
										kATSUQDUnderlineTag,
										kATSUQDCondensedTag };

	for(int i = 0; i < N_ELEMENTS(badtags); i++)
	{
		bool ital = false;
		ByteCount ssize = sizeof(ital);
		ATSUGetAttribute(fontStyle, badtags[i], ssize, &ital, 0);
		if(ital) {
			ATSUDisposeStyle(fontStyle);
			return;
		}
	}

	ATSUDisposeStyle(fontStyle);

	td->font_id = fontID;
	td->font_size = size;
	RevalidateGraphics(td, TRUE);
}

/*
 * Bookkeeping for font-related events.
 */
static OSStatus FontCommand(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
{
	int class = GetEventClass(inEvent);
	int type = GetEventKind(inEvent);
	if(class == 'font' && type == kEventFontSelection) {
		UInt32 fid = 0;
		Fixed size = 0;
		(void) GetEventParameter (inEvent, kEventParamATSUFontSize,
							typeATSUSize, NULL, sizeof (size), NULL, &size);

		(void) GetEventParameter (inEvent, kEventParamATSUFontID,
							typeATSUFontID, NULL, sizeof(fid), NULL, &fid);

		if(size > 32*(1<<16)) size = 32*(1<<16);
		float fsize = 1.0*size/(1<<16);
		FontChanged(fid, fsize);
		return noErr;
	}
	else if(class == 'appl' && type == kEventAppActiveWindowChanged)
	{
		WindowRef w = 0;
		GetEventParameter(inEvent, kEventParamCurrentWindow, typeWindowRef,
							NULL, sizeof(w), NULL,  &w);
		if(!GetWRefCon(w)) { //  Window is Font Panel.
			w = 0;
			GetEventParameter(inEvent, kEventParamPreviousWindow,
								typeWindowRef, NULL, sizeof(w), NULL,  &w);
		}
		if(w) fontInfo.focus = w; 
		return noErr;
	}
	else if(class == 'font' && type == kEventFontPanelClosed) {
		SetMenuItemTextWithCFString(GetMenuHandle(kStyleMenu), kFonts, CFSTR("Show Fonts"));
		return noErr;
	}

	return eventNotHandledErr;
}

static OSStatus MouseCommand ( EventHandlerCallRef inCallRef,
	EventRef inEvent, void *inUserData )
{
	WindowRef w = 0;
	GetEventParameter(inEvent, kEventParamWindowRef, typeWindowRef, 
						NULL, sizeof(w), NULL, &w);

	/* Relevant "term_data" */
	term_data *td = (term_data *) GetWRefCon(w);
	if(&data[0] != td)
		return eventNotHandledErr;

	short button = 0;
	GetEventParameter(inEvent, kEventParamMouseButton, typeMouseButton,
						NULL, sizeof(button), NULL, &button);
	UInt32 modifiers = 0;
	GetEventParameter(inEvent, kEventParamKeyModifiers, typeUInt32, NULL,
						sizeof(modifiers), NULL, &modifiers);
	HIPoint p = {0, 0};
	GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint, NULL,
						sizeof(p),  NULL, &p);
	if(button == -1)
		button = 1;

	if(button == 1 && modifiers & cmdKey) 
		button = 2;
	else if(button == 1 && modifiers & shiftKey)
		button = 3;

	// X coordinate relative to left side of window exclusive of border.
	p.x -= (BORDER_WID+td->r.left);
	// Y coordinate relative to top of window content region.
	p.y -= td->r.top;

	Term_mousepress(p.x/td->tile_wid, p.y/td->tile_hgt, button);

	return noErr;
}

static OSStatus KeyboardCommand ( EventHandlerCallRef inCallRef,
	EventRef inEvent, void *inUserData )
{

	EventRecord event;
	ConvertEventRefToEventRecord(inEvent, &event);

	/* Don't handle keyboard events in open/save dialogs, to prevent a 10.4 keyboard interaction bug */
	UInt32 windowClass;
	GetWindowClass(GetUserFocusWindow(), &windowClass);
	if (windowClass == kMovableModalWindowClass) return eventNotHandledErr;

	/* Extract some modifiers */
	int mc = (event.modifiers & controlKey) ? TRUE : FALSE;
	int ms = (event.modifiers & shiftKey) ? TRUE : FALSE;
	int mo = (event.modifiers & optionKey) ? TRUE : FALSE;
	int mx = (event.modifiers & cmdKey) ? TRUE : FALSE;

	/* Keypress: (only "valid" if ck < 96) */
	int ch = (event.message & charCodeMask) & 255;

	/* Keycode: see table above */
	int ck = ((event.message & keyCodeMask) >> 8) & 255;

	/* Command + "normal key" -> menu action */
	if (mx && (ck < 64))
	{
		return eventNotHandledErr;
	}

	/* Hide the mouse pointer */
	hibernate();
	ObscureCursor();

	/* Normal key -> simple keypress */
	if ((ck < 64) || (ck == 93))
	{
		/* Enqueue the keypress */
		Term_keypress(ch);
	}

	/* Keypad keys -> trigger plus simple keypress */
	else if (!mc && !ms && !mo && !mx && (ck < 96))
	{
		/* Hack -- "enter" is confused */
		if (ck == 76) ch = '\n';

		/* Begin special trigger */
		Term_keypress(31);

		/* Send the "keypad" modifier */
		Term_keypress('K');

		/* Terminate the trigger */
		Term_keypress(13);

		/* Send the "ascii" keypress */
		Term_keypress(ch);
	}

	/* Bizarre key -> encoded keypress */
	else if (ck <= 127)
	{
		/* Begin special trigger */
		Term_keypress(31);

		/* Send some modifier keys */
		if (mc) Term_keypress('C');
		if (ms) Term_keypress('S');
		if (mo) Term_keypress('O');
		if (mx) Term_keypress('X');

		/* Downshift and encode the keycode */
		Term_keypress(I2D((ck - 64) / 10));
		Term_keypress(I2D((ck - 64) % 10));

		/* Terminate the trigger */
		Term_keypress(13);
	}
	return noErr;
}

/* About angband... */
static OSStatus AboutCommand(EventHandlerCallRef inCallRef, EventRef inEvent,
	void *inUserData )
{
	HICommand command;
	command.commandID = 0;
	GetEventParameter( inEvent, kEventParamDirectObject, typeHICommand,
							NULL, sizeof(command), NULL, &command);

	if(command.commandID != 'abou')
		return eventNotHandledErr;

	/* Move it to the middle of the screen */
	RepositionWindow(aboutDialog,  NULL, kWindowCenterOnMainScreen);

	/* Show the dialog */
	TransitionWindow(aboutDialog,
		kWindowZoomTransitionEffect,
		kWindowShowTransitionAction,
		NULL);

	/* wait for user input */
	for(;;) {
		EventTargetRef target = GetEventDispatcherTarget();
		EventRef event;
		OSStatus err = ReceiveNextEvent(0, 0, kEventDurationForever, true, &event);
		EventClass evc = GetEventClass(event);
		EventType evt = GetEventKind(event);
		if(err == noErr) {
			SendEventToEventTarget (event, target);
			ReleaseEvent(event);
		}
		if(evc == 'keyb' || (evc == 'mous' && evt == kEventMouseDown))
			break;
	}

	/* Hide the dialogue */
	TransitionWindow(aboutDialog,
		kWindowZoomTransitionEffect,
		kWindowHideTransitionAction,
		NULL);

	return noErr;
}

static OSStatus ResumeCommand (EventHandlerCallRef inCallRef,
								EventRef inEvent, void *inUserData )
{
	term_data *td;

	hibernate();
	Cursor tempCursor;
	SetCursor(GetQDGlobalsArrow(&tempCursor));

	/* Redraw all visible terms */
	for (int i = 0; i < MAX_TERM_DATA; i++ )
	{
		/* Obtain */
		td = &data[i];

		/* Redraw if mapped */
		if (td->mapped)
			term_data_redraw(td);
	}

	return noErr;
}

static OSErr AEH_Open(const AppleEvent *theAppleEvent, AppleEvent* reply,
	SInt32 handlerRefCon)
{
	FSSpec myFSS;
	AEDescList docList;
	long fileindex;
	long filecount;
	OSErr err;
	FInfo myFileInfo;

	/* If a game is in progress, do not proceed */
	if (cmd.command != CMD_NULL) return noErr;
	
	/* Put the direct parameter (a descriptor list) into a docList */
	err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList, &docList);
	if (err) return err;
	
	err = AECountItems(&docList, &filecount);
	if (err) return err;

	/* Only open one file, but check for the first valid file in the list */
	for (fileindex = 1; fileindex <= filecount; fileindex++)
	{
		err = AEGetNthPtr(&docList, fileindex, typeFSS, NULL, NULL, &myFSS, sizeof(myFSS), NULL);
		if (err) continue;
		
		err = FSpGetFInfo(&myFSS, &myFileInfo);
		if (err) continue;
		
		if (myFileInfo.fdType == 'SAVE')
		{
			
			/* Extract the filename and delay the open */
			(void)spec_to_path(&myFSS, savefile, sizeof(savefile));
			cmd.command = CMD_LOADFILE;

			break;
		}
	}

	/* Dispose */
	err = AEDisposeDesc(&docList);

	/* Success */
	return noErr;
}


/*
 * Handle quit_when_ready, by Peter Ammon,
 * slightly modified to check inkey_flag.
 */
static void quit_calmly(void)
{
	/* Quit immediately if game's not started */
	if (cmd.command == CMD_NULL || !character_generated) quit(NULL);

	/* Save the game and Quit (if it's safe) */
	if (inkey_flag)
	{
		/* Hack -- Forget messages */
		msg_flag = FALSE;

		/* Save the game */
#ifndef ZANG_AUTO_SAVE
		do_cmd_save_game();
#else
		do_cmd_save_game(FALSE);
#endif /* !ZANG_AUTO_SAVE */

		/* Quit */
		quit(NULL);
	}

	/* Wait until inkey_flag is set */
}


/*
 * Macintosh modifiers (event.modifier & ccc):
 *   cmdKey, optionKey, shiftKey, alphaLock, controlKey
 *
 *
 * Macintosh Keycodes (0-63 normal, 64-95 keypad, 96-127 extra):
 *
 * Return:36
 * Delete:51
 *
 * Period:65
 * Star:67
 * Plus:69
 * Clear:71
 * Slash:75
 * Enter:76
 * Minus:78
 * Equal:81
 * 0-7:82-89
 * 8-9:91-92
 *
 * backslash/vertical bar (Japanese keyboard):93
 *
 * F5: 96
 * F6: 97
 * F7: 98
 * F3:99
 * F8:100
 * F10:101
 * F11:103
 * F13:105
 * F14:107
 * F9:109
 * F12:111
 * F15:113
 * Help:114
 * Home:115
 * PgUp:116
 * Del:117
 * F4: 118
 * End:119
 * F2:120
 * PgDn:121
 * F1:122
 * Lt:123
 * Rt:124
 * Dn:125
 * Up:126
 */


/*
 * optimize non-blocking calls to "CheckEvents()"
 * idea from "maarten hazewinkel <mmhazewi@cs.ruu.nl>"
 *
 * was: 6. the value of one (~ 60 fps) seems to work better with the borg,
 * and so should be for other cpu-intensive features like the autoroller.
 */
#define event_ticks 1


/*
 * check for events, return true if we process any
 */
static bool CheckEvents(int wait)
{
	static EventTimeout lastticks;
	EventTimeout curticks;
	/* access the clock */
	curticks = TickCount();
	EventTimeout sleep_ticks;
	EventRef event;
	OSStatus err;

	/* hack -- allow efficient checking for non-pending events */
	if ((wait == CHECK_EVENTS_NO_WAIT) &&
		(curticks < lastticks + event_ticks)) return (false);

	/* timestamp last check */
	lastticks = curticks;

	/* handles the quit_when_ready flag */
	if (quit_when_ready) quit_calmly();

	/* blocking call to waitnextevent - should use max_int xxx xxx */
	if (wait == CHECK_EVENTS_WAIT) sleep_ticks = kEventDurationForever;

	/* non-blocking */
	else sleep_ticks = 0l;

	EventTargetRef target = GetEventDispatcherTarget();
	/* get an event (or null)  */
	do {
		err = ReceiveNextEvent(0, 0, wait ? kEventDurationForever : 0, true, &event);
		if(show_events) {
			EventClass evc = GetEventClass(event);
			EventType evt = GetEventKind(event);
			if(evc != 'mous' || evt != kEventMouseDragged )
				printf("%d (%4s) %d\n", (int)evc, (char*)&evc, (int)evt);
		}
		if(err == noErr) {
			err = SendEventToEventTarget (event, target);
			ReleaseEvent(event);
		}
		else if(err != eventNotHandledErr && sleep_ticks >= 0) {
			curticks = TickCount();
			sleep_ticks -= curticks - lastticks;
			lastticks = curticks;
			if(sleep_ticks <= 0) return false;
		}
	} while(err != eventNotHandledErr && err); // DurationForever is -1
	return true;
}


/*** Some Hooks for various routines ***/



/*
 * Hook to tell the user something important
 */
static void hook_plog(cptr str)
{
	/* Warning message */
	mac_warning(str);
}


/*
 * Hook to tell the user something, and then quit
 */
static void hook_quit(cptr str)
{
	/* Warning if needed */
	if (str) mac_warning(str);

	/* Update the Recent Items list - inserts newly created characters */
	if (savefile[0])
		updateRecentItems(savefile);

	/* Dispose of graphic tiles */
	if(frame.image)
		graphics_nuke();

	/* Write a preference file */
	if (initialized) save_pref_file();

	/* All done */
	ExitToShell();
}



/*** Main program ***/


/*
 * Initialize and verify file and dir paths
 *
 */
static void init_paths(void)
{
	char path[1024];

	/* Default to the "lib" folder with the application */
	if (locate_lib(path, sizeof(path)) == NULL)
	{
		quit("unable to find 'lib' dir");
	}
	/* Create directories for the users files */
	create_user_dirs();

	/* Prepare the paths */
	init_file_paths(path);

	/* Build the filename */
	path_build(path, sizeof(path), ANGBAND_DIR_FILE, "news.txt");

	/* Attempt to open and close that file */
	if (!file_exists(path))
	{
		/* Warning */
		plog_fmt("Unable to open the '%s' file.", path);
		quit("The Angband 'lib' folder is probably missing or misplaced.");
	}
}

/*
 * Macintosh Main loop
 */
int main(void)
{
	/* Initialise the cursor and turn it into an "arrow" */
	InitCursor();

	/* 
	 * Remember Mac OS version, in case we have to cope with version-specific
	 * problems
	 */
	(void)Gestalt(gestaltSystemVersion, &mac_os_version);

	/* Initiliases Cocoa */
	NSApplicationLoad();
	
	/* Hooks in some "z-util.c" hooks */
	plog_aux = hook_plog;
	quit_aux = hook_quit;

	/* Initialize colors */
	update_color_info();

	/* Show the "watch" cursor */
	SetCursor(*(GetCursor(watchCursor)));

	/* Prepare the menubar */
	init_menubar();

	/* Ensure that the recent items array is always an array and start with an empty menu */
	recentItemsArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
	redrawRecentItemsMenu();

	/* Initialize */
	init_paths();

	/* Prepare the windows */
	init_windows();

	/* Install the 'Apple Event' handler hook (ignore error codes) */
	(void)AEInstallEventHandler(
		kCoreEventClass,
		kAEOpenDocuments,
		NewAEEventHandlerUPP(AEH_Open),
		0L,
		FALSE);

	/* Install menu and application handlers */
	install_handlers(0);

	/* Reset the cursor */
	Cursor tempCursor;
	SetCursor(GetQDGlobalsArrow(&tempCursor));


	/* Quicktime -- Load sound effect resources */

	load_sounds();

	/* Note the "system" */
	ANGBAND_SYS = "mac";

	/* Validate the contents of the main window */
	validate_main_window();

	/* Flush input commands from the event queue */
	FlushEventsMatchingListFromQueue(GetMainEventQueue(),
		N_ELEMENTS(input_event_types), input_event_types);

	/* Set command hook */ 
	cmd_get_hook = crb_get_cmd; 

	/* Set up the display handlers and things. */
	init_display();

	if(graf_mode) graphics_aux(graf_mode);

	/* We are now initialized */
	initialized = TRUE;

	validate_menus();

	/* Start playing! */
	EventRef newGameEvent = nil;
	CreateEvent ( nil, 'Play', 'Band', GetCurrentEventTime(),
									kEventAttributeNone, &newGameEvent ); 
	PostEventToQueue(GetMainEventQueue(), newGameEvent, kEventPriorityLow);

	RunApplicationEventLoop();

	/* Quit */
	quit(NULL);

	/* Since it's an int function */
	return (0);
}

#endif /* MACH_O_CARBON */
