OSDN Git Service

Position the splash screen prompt in relation to the assumed size of the splash scree...
[hengbandforosx/hengbandosx.git] / src / main-cocoa.m
index 57dc7e1..7390782 100644 (file)
@@ -1,6 +1,7 @@
-/* File: main-cocoa.m */
-
-/*
+/**
+ * \file main-cocoa.m
+ * \brief OS X front end
+ *
  * Copyright (c) 2011 Peter Ammon
  *
  * This work is free software; you can redistribute it and/or modify it
  */
 
 #include "angband.h"
+/* This is not included in angband.h in Hengband. */
+#include "grafmode.h"
 
-#if defined(MACH_O_CARBON)
-
-/* Default creator signature */
-#ifndef ANGBAND_CREATOR
-# define ANGBAND_CREATOR 'Heng'
-#endif
+#if defined(MACH_O_COCOA)
 
 /* Mac headers */
-#include <Cocoa/Cocoa.h>
-#include <Carbon/Carbon.h> // For keycodes
+#include <cocoa/AppDelegate.h>
+//#include <Carbon/Carbon.h> /* For keycodes */
+/* Hack - keycodes to enable compiling in macOS 10.14 */
+#define kVK_Return 0x24
+#define kVK_Tab    0x30
+#define kVK_Delete 0x33
+#define kVK_Escape 0x35
+#define kVK_ANSI_KeypadEnter 0x4C
 
-static NSSize const AngbandScaleIdentity = {1.0, 1.0};
 static NSString * const AngbandDirectoryNameLib = @"lib";
-static NSString * const AngbandDirectoryNameBase = @"PosChengband";
+static NSString * const AngbandDirectoryNameBase = @"Hengband";
 
+static NSString * const AngbandMessageCatalog = @"Localizable";
 static NSString * const AngbandTerminalsDefaultsKey = @"Terminals";
 static NSString * const AngbandTerminalRowsDefaultsKey = @"Rows";
 static NSString * const AngbandTerminalColumnsDefaultsKey = @"Columns";
+static NSString * const AngbandTerminalVisibleDefaultsKey = @"Visible";
+static NSString * const AngbandGraphicsDefaultsKey = @"GraphicsID";
+static NSString * const AngbandBigTileDefaultsKey = @"UseBigTiles";
+static NSString * const AngbandFrameRateDefaultsKey = @"FramesPerSecond";
+static NSString * const AngbandSoundDefaultsKey = @"AllowSound";
 static NSInteger const AngbandWindowMenuItemTagBase = 1000;
 static NSInteger const AngbandCommandMenuItemTagBase = 2000;
 
-/* We can blit to a large layer or image and then scale it down during live resize, which makes resizing much faster, at the cost of some image quality during resizing */
+/* We can blit to a large layer or image and then scale it down during live
+ * resize, which makes resizing much faster, at the cost of some image quality
+ * during resizing */
 #ifndef USE_LIVE_RESIZE_CACHE
 # define USE_LIVE_RESIZE_CACHE 1
 #endif
 
 /* Global defines etc from Angband 3.5-dev - NRM */
 #define ANGBAND_TERM_MAX 8
-/**
- * Specifies what kind of thing a file is, when writing.  See file_open().
- */
-typedef enum
-{
-        FTYPE_TEXT = 1,
-        FTYPE_SAVE,
-        FTYPE_RAW,
-        FTYPE_HTML
-} file_type;
-
-static bool new_game = TRUE;
 
 #define MAX_COLORS 256
 #define MSG_MAX SOUND_MAX
 
-/**
- * Keyset mappings for various keys.
- */
-#define ARROW_DOWN    0x80
-#define ARROW_LEFT    0x81
-#define ARROW_RIGHT   0x82
-#define ARROW_UP      0x83
-
-#define KC_F1         0x84
-#define KC_F2         0x85
-#define KC_F3         0x86
-#define KC_F4         0x87
-#define KC_F5         0x88
-#define KC_F6         0x89
-#define KC_F7         0x8A
-#define KC_F8         0x8B
-#define KC_F9         0x8C
-#define KC_F10        0x8D
-#define KC_F11        0x8E
-#define KC_F12        0x8F
-#define KC_F13        0x90
-#define KC_F14        0x91
-#define KC_F15        0x92
-
-#define KC_HELP       0x93
-#define KC_HOME       0x94
-#define KC_PGUP       0x95
-#define KC_END        0x96
-#define KC_PGDOWN     0x97
-#define KC_INSERT     0x98
-#define KC_PAUSE      0x99
-#define KC_BREAK      0x9a
-#define KC_BEGIN      0x9b
-#define KC_ENTER      0x9c /* ASCII \r */
-#define KC_TAB        0x9d /* ASCII \t */
-#define KC_DELETE     0x9e
-#define KC_BACKSPACE  0x9f /* ASCII \h */
-//#define ESCAPE        0xE000
-
-//OSType _ftype;
-//OSType _fcreator;
-
 /* End Angband stuff - NRM */
 
-/* Our command-fetching function */
-//static errr cocoa_get_cmd(cmd_context context, bool wait);
-
-
 /* Application defined event numbers */
 enum
 {
     AngbandEventWakeup = 1
 };
 
-/* Redeclare some 10.7 constants and methods so we can build on 10.6 */
-enum
-{
-    Angband_NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
-    Angband_NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8
-};
-
-@interface NSWindow (AngbandLionRedeclares)
-- (void)setRestorable:(BOOL)flag;
-@end
-
 /* Delay handling of pre-emptive "quit" event */
 static BOOL quit_when_ready = FALSE;
 
-/* Whether or not we allow sounds (only relevant for the screensaver, where the user can't configure it in-game) */
-static BOOL allow_sounds = YES;
-
 /* Set to indicate the game is over and we can quit without delay */
 static Boolean game_is_finished = FALSE;
 
 /* Our frames per second (e.g. 60). A value of 0 means unthrottled. */
 static int frames_per_second;
 
-/* Function to get the default font */
-static NSFont *default_font;
+/* Force a new game or not? */
+static bool new_game = FALSE;
 
 @class AngbandView;
 
-/* The max number of glyphs we support */
-#define GLYPH_COUNT 256
-
-/* An AngbandContext represents a logical Term (i.e. what Angband thinks is a window). This typically maps to one NSView, but may map to more than one NSView (e.g. the Test and real screen saver view). */
-@interface AngbandContext : NSObject <NSWindowDelegate>
-{
-@public
-    
-    /* The Angband term */
-    term *terminal;
-    
-    /* Column and row cont, by default 80 x 24 */
-    size_t cols;
-    size_t rows;
-    
-    /* The size of the border between the window edge and the contents */
-    NSSize borderSize;
-    
-    /* Our array of views */
-    NSMutableArray *angbandViews;
-    
-    /* The buffered image */
-    CGLayerRef angbandLayer;
-    
-    /* The font of this context */
-    NSFont *angbandViewFont;
-    
-    /* If this context owns a window, here it is */
-    NSWindow *primaryWindow;
-    
-    /* "Glyph info": an array of the CGGlyphs and their widths corresponding to the above font. */
-    CGGlyph glyphArray[GLYPH_COUNT];
-    CGFloat glyphWidths[GLYPH_COUNT];
-    
-    /* The size of one tile */
-    NSSize tileSize;
-    
-    /* Font's descender */
-    CGFloat fontDescender;
-    
-    /* Whether we are currently in live resize, which affects how big we render our image */
-    int inLiveResize;
-    
-    /* Last time we drew, so we can throttle drawing */
-    CFAbsoluteTime lastRefreshTime;
-    
-    /* To address subpixel rendering overdraw problems, we cache all the characters and attributes we're told to draw */
-    wchar_t *charOverdrawCache;
-    int *attrOverdrawCache;
-
+/**
+ * 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 caching 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.
+ */
+@interface AngbandSoundCatalog : NSObject {
 @private
-
-    BOOL _hasSubwindowFlags;
+    /**
+     * Stores instances of NSSound keyed by path so the same sound can be
+     * used for multiple events.
+     */
+    NSMutableDictionary *soundsByPath;
+    /**
+     * Stores arrays of NSSound keyed by event number.
+     */
+    NSMutableDictionary *soundArraysByEvent;
 }
 
-@property (nonatomic, assign) BOOL hasSubwindowFlags;
-
-- (void)drawRect:(NSRect)rect inView:(NSView *)view;
-
-/* Called at initialization to set the term */
-- (void)setTerm:(term *)t;
+/**
+ * If NO, then playSound effectively becomes a do nothing operation.
+ */
+@property (getter=isEnabled) BOOL enabled;
 
-/* Called when the context is going down. */
-- (void)dispose;
+/**
+ * Set up for lazy initialization in playSound().  Set enabled to NO.
+ */
+- (id)init;
 
-/* Returns the size of the image. */
-- (NSSize)imageSize;
+/**
+ * If self.enabled is YES and the given event has one or more sounds
+ * corresponding to it in the catalog, plays one of those sounds, chosen at
+ * random.
+ */
+- (void)playSound:(int)event;
 
-/* Return the rect for a tile at given coordinates. */
-- (NSRect)rectInImageForTileAtX:(int)x Y:(int)y;
+/**
+ * Impose an arbitary limit on number of possible samples per event.
+ * Currently not declaring this as a class property for compatibility with
+ * versions of Xcode prior to 8.
+ */
++ (int)maxSamples;
 
-/* Draw the given wide character into the given tile rect. */
-- (void)drawWChar:(wchar_t)wchar inRect:(NSRect)tile;
+/**
+ * Return the shared sound catalog instance, creating it if it does not
+ * exist yet.  Currently not declaring this as a class property for
+ * compatibility with versions of Xcode prior to 8.
+ */
++ (AngbandSoundCatalog*)sharedSounds;
 
-/* Locks focus on the Angband image, and scales the CTM appropriately. */
-- (CGContextRef)lockFocus;
+/**
+ * Release any resouces associated with shared sounds.
+ */
++ (void)clearSharedSounds;
 
-/* Locks focus on the Angband image but does NOT scale the CTM. Appropriate for drawing hairlines. */
-- (CGContextRef)lockFocusUnscaled;
+@end
 
-/* Unlocks focus. */
-- (void)unlockFocus;
+@implementation AngbandSoundCatalog
 
-/* Returns the primary window for this angband context, creating it if necessary */
-- (NSWindow *)makePrimaryWindow;
+- (id)init {
+    if (self = [super init]) {
+       self->soundsByPath = nil;
+       self->soundArraysByEvent = nil;
+       self->_enabled = NO;
+    }
+    return self;
+}
 
-/* Called to add a new Angband view */
-- (void)addAngbandView:(AngbandView *)view;
+- (void)playSound:(int)event {
+    if (! self.enabled) {
+       return;
+    }
 
-/* Make the context aware that one of its views changed size */
-- (void)angbandViewDidScale:(AngbandView *)view;
+    /* Initialize when the first sound is played. */
+    if (self->soundArraysByEvent == nil) {
+       /* Build the "sound" path */
+       char sound_dir[1024];
+       path_build(sound_dir, sizeof(sound_dir), ANGBAND_DIR_XTRA, "sound");
 
-/* Handle becoming the main window */
-- (void)windowDidBecomeMain:(NSNotification *)notification;
+       /* Find and open the config file */
+       char path[1024];
+       path_build(path, sizeof(path), sound_dir, "sound.cfg");
+       FILE *fff = my_fopen(path, "r");
 
-/* Order the context's primary window frontmost */
-- (void)orderFront;
+       /* Handle errors */
+       if (!fff) {
+           NSLog(@"The sound configuration file could not be opened.");
+           return;
+       }
 
-/* Order the context's primary window out */
-- (void)orderOut;
+       self->soundsByPath = [[NSMutableDictionary alloc] init];
+       self->soundArraysByEvent = [[NSMutableDictionary alloc] init];
+       @autoreleasepool {
+           /*
+            * 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 ...]" */
+           char buffer[2048];
+           while (my_fgets(fff, buffer, sizeof(buffer)) == 0) {
+               char *msg_name;
+               char *cfg_sample_list;
+               char *search;
+               char *cur_token;
+               char *next_token;
+               int event;
 
-/* Return whether the context's primary window is ordered in or not */
-- (BOOL)isOrderedIn;
+               /* Skip anything not beginning with an alphabetic character */
+               if (!buffer[0] || !isalpha((unsigned char)buffer[0])) continue;
 
-/* Return whether the context's primary window is key */
-- (BOOL)isMainWindow;
+               /* 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;
 
-/* Invalidate the whole image */
-- (void)setNeedsDisplay:(BOOL)val;
+               /* Set the message name, and terminate at first space */
+               msg_name = buffer;
+               search[0] = '\0';
 
-/* Invalidate part of the image, with the rect expressed in base coordinates */
-- (void)setNeedsDisplayInBaseRect:(NSRect)rect;
+               /* 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;
 
-/* Display (flush) our Angband views */
-- (void)displayIfNeeded;
+               /*
+                * Advance the sample list pointer so it's at the beginning of
+                * text.
+                */
+               cfg_sample_list++;
+               if (!cfg_sample_list[0]) continue;
 
-/* Called from the view to indicate that it is starting or ending live resize */
-- (void)viewWillStartLiveResize:(AngbandView *)view;
-- (void)viewDidEndLiveResize:(AngbandView *)view;
+               /* 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;
+               }
 
-/* Class methods */
+               /*
+                * Now we find all the sample names and add them one by one
+                */
+               while (cur_token) {
+                   NSMutableArray *soundSamples =
+                       [self->soundArraysByEvent
+                            objectForKey:[NSNumber numberWithInteger:event]];
+                   if (soundSamples == nil) {
+                       soundSamples = [[NSMutableArray alloc] init];
+                       [self->soundArraysByEvent
+                            setObject:soundSamples
+                            forKey:[NSNumber numberWithInteger:event]];
+                   }
+                   int num = (int) soundSamples.count;
+
+                   /* Don't allow too many samples */
+                   if (num >= [AngbandSoundCatalog maxSamples]) break;
+
+                   NSString *token_string =
+                       [NSString stringWithUTF8String:cur_token];
+                   NSSound *sound =
+                       [self->soundsByPath objectForKey:token_string];
+
+                   if (! sound) {
+                       /*
+                        * We have to load the sound. Build the path to the
+                        * sample.
+                        */
+                       path_build(path, sizeof(path), sound_dir, cur_token);
+                       struct stat stb;
+                       if (stat(path, &stb) == 0) {
+                           /* Load the sound into memory */
+                           sound = [[NSSound alloc]
+                                        initWithContentsOfFile:[NSString stringWithUTF8String:path]
+                                        byReference:YES];
+                           if (sound) {
+                               [self->soundsByPath setObject:sound
+                                           forKey:token_string];
+                           }
+                       }
+                   }
+
+                   /* Store it if we loaded it */
+                   if (sound) {
+                       [soundSamples addObject:sound];
+                   }
+
+                   /* 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;
+                        }
+                   }
+               }
+           }
+       }
 
-/* Begins an Angband game. This is the entry point for starting off. */
-+ (void)beginGame;
+       /* Close the file */
+       my_fclose(fff);
+    }
 
-/* Ends an Angband game. */
-+ (void)endGame;
+    @autoreleasepool {
+       NSMutableArray *samples =
+           [self->soundArraysByEvent
+                objectForKey:[NSNumber numberWithInteger:event]];
 
-/* Internal method */
-- (AngbandView *)activeView;
+       if (samples == nil || samples.count == 0) {
+           return;
+       }
 
-@end
+       /* Choose a random event. */
+       int s = randint0((int) samples.count);
+       NSSound *sound = samples[s];
 
-/**
- *  Generate a mask for the subwindow flags. The mask is just a safety check to make sure that our windows show and hide as expected.
- *  This function allows for future changes to the set of flags without needed to update it here (unless the underlying types change).
- */
-u32b AngbandMaskForValidSubwindowFlags(void)
-{
-    int maxBits = 16;
-    u32b mask = 0;
+       if ([sound isPlaying])
+           [sound stop];
 
-    for( int i = 0; i < maxBits; i++ )
-    {
-        if( window_flag_desc[i] != NULL )
-        {
-            mask |= (1 << i);
-        }
+       /* Play the sound. */
+       [sound play];
     }
+}
 
-    return mask;
++ (int)maxSamples {
+    return 16;
 }
 
 /**
- *  Check for changes in the subwindow flags and update window visibility. This seems to be called for every user event, so we don't
- *  want to do any unnecessary hiding or showing of windows.
+ * For sharedSounds and clearSharedSounds.
  */
-static void AngbandUpdateWindowVisibility(void)
-{
-    // because this function is called frequently, we'll make the mask static. it doesn't change between calls, since the flags themselves are hardcoded.
-    static u32b validWindowFlagsMask = 0;
-
-    if( validWindowFlagsMask == 0 )
-    {
-        validWindowFlagsMask = AngbandMaskForValidSubwindowFlags();
-    }
-
-    // loop through all of the subwindows and see if there is a change in the flags. if so, show or hide the corresponding window.
-    // we don't care about the flags themselves; we just want to know if any are set.
-    for( int i = 1; i < ANGBAND_TERM_MAX; i++ )
-    {
-        AngbandContext *angbandContext = angband_term[i]->data;
-
-        if( angbandContext == nil )
-        {
-            continue;
-        }
-
-        BOOL termHasSubwindowFlags = ((window_flag[i] & validWindowFlagsMask) > 0);
+static __strong AngbandSoundCatalog* gSharedSounds = nil;
 
-        if( angbandContext.hasSubwindowFlags && !termHasSubwindowFlags )
-        {
-            [angbandContext->primaryWindow close];
-            angbandContext.hasSubwindowFlags = NO;
-        }
-        else if( !angbandContext.hasSubwindowFlags && termHasSubwindowFlags )
-        {
-            [angbandContext->primaryWindow orderFront: nil];
-            angbandContext.hasSubwindowFlags = YES;
-        }
++ (AngbandSoundCatalog*)sharedSounds {
+    if (gSharedSounds == nil) {
+       gSharedSounds = [[AngbandSoundCatalog alloc] init];
     }
-
-    // make the main window key so that user events go to the right spot
-    AngbandContext *mainWindow = angband_term[0]->data;
-    [mainWindow->primaryWindow makeKeyAndOrderFront: nil];
+    return gSharedSounds;;
 }
 
-/* To indicate that a grid element contains a picture, we store 0xFFFF. */
-#define NO_OVERDRAW ((wchar_t)(0xFFFF))
-
-/* Here is some support for rounding to pixels in a scaled context */
-static double push_pixel(double pixel, double scale, BOOL increase)
-{
-    double scaledPixel = pixel * scale;
-    /* Have some tolerance! */
-    double roundedPixel = round(scaledPixel);
-    if (fabs(roundedPixel - scaledPixel) <= .0001)
-    {
-        scaledPixel = roundedPixel;
-    }
-    else
-    {
-        scaledPixel = (increase ? ceil : floor)(scaledPixel);
-    }
-    return scaledPixel / scale;
++ (void)clearSharedSounds {
+    gSharedSounds = nil;
 }
 
-/* Descriptions of how to "push pixels" in a given rect to integralize. For example, PUSH_LEFT means that we round expand the left edge if set, otherwise we shrink it. */
-enum
-{
-    PUSH_LEFT = 0x1,
-    PUSH_RIGHT = 0x2,
-    PUSH_BOTTOM = 0x4,
-    PUSH_TOP = 0x8
+@end
+
+/*
+ * To handle fonts where an individual glyph's bounding box can extend into
+ * neighboring columns, Term_curs_cocoa(), Term_pict_cocoa(),
+ * Term_text_cocoa(), and Term_wipe_cocoa() merely record what needs to be
+ * done with the actual drawing happening in response to the notification to
+ * flush all rows, the TERM_XTRA_FRESH case in Term_xtra_cocoa().  Can not use
+ * the TERM_XTRA_FROSH notification (the per-row flush), since with a software
+ * cursor, there are calls to Term_pict_cocoa(), Term_text_cocoa(), or
+ * Term_wipe_cocoa() to take care of the old cursor position which are not
+ * followed by a row flush.
+ */
+enum PendingCellChangeType {
+    CELL_CHANGE_NONE = 0,
+    CELL_CHANGE_WIPE,
+    CELL_CHANGE_TEXT,
+    CELL_CHANGE_TILE
+};
+struct PendingTextChange {
+    wchar_t glyph;
+    int color;
+    /*
+     * Is YES if glyph is a character that takes up two columns (i.e.
+     * Japanese kanji); otherwise it is NO.
+     */
+    BOOL doubleWidth;
+};
+struct PendingTileChange {
+    char fgdCol, fgdRow, bckCol, bckRow;
+};
+struct PendingCellChange {
+    union { struct PendingTextChange txc; struct PendingTileChange tic; } v;
+    enum PendingCellChangeType changeType;
 };
 
-/* Return a rect whose border is in the "cracks" between tiles */
-static NSRect crack_rect(NSRect rect, NSSize scale, unsigned pushOptions)
-{
-    double rightPixel = push_pixel(NSMaxX(rect), scale.width, !! (pushOptions & PUSH_RIGHT));
-    double topPixel = push_pixel(NSMaxY(rect), scale.height, !! (pushOptions & PUSH_TOP));
-    double leftPixel = push_pixel(NSMinX(rect), scale.width, ! (pushOptions & PUSH_LEFT));
-    double bottomPixel = push_pixel(NSMinY(rect), scale.height, ! (pushOptions & PUSH_BOTTOM));
-    return NSMakeRect(leftPixel, bottomPixel, rightPixel - leftPixel, topPixel - bottomPixel);    
+@interface PendingTermChanges : NSObject {
+@private
+    int *colBounds;
+    struct PendingCellChange **changesByRow;
 }
 
-/* Returns the pixel push options (describing how we round) for the tile at a given index. Currently it's pretty uniform! */
-static unsigned push_options(unsigned x, unsigned y)
-{
-    return PUSH_TOP | PUSH_LEFT;
-}
+/**
+ * Returns YES if nCol and nRow are a feasible size for the pending changes.
+ * Otherwise, returns NO.
+ */
++ (BOOL)isValidSize:(int)nCol rows:(int)nRow;
 
-/*
- * Graphics support
+/**
+ * Initialize with zero columns and zero rows.
  */
+- (id)init;
 
-/*
- * The tile image
+/**
+ * Initialize with nCol columns and nRow rows.  No changes will be marked.
  */
-//static CGImageRef pict_image;
+- (id)initWithColumnsRows:(int)nCol rows:(int)nRow NS_DESIGNATED_INITIALIZER;
 
-/*
- * Numbers of rows and columns in a tileset,
- * calculated by the PICT/PNG loading code
+/**
+ * Clears all marked changes.
  */
-//static int pict_cols = 0;
-//static int pict_rows = 0;
+- (void)clear;
 
-/*
- * Value used to signal that we using ASCII, not graphical tiles.
- */ 
-#define GRAF_MODE_NONE 0
+/**
+ * Changes the bounds over which changes are recorded.  Has the side effect
+ * of clearing any marked changes.  Will throw an exception if nCol or nRow
+ * is negative.
+ */
+- (void)resize:(int)nCol rows:(int)nRow;
 
-/*
- * Requested graphics mode (as a grafID).
- * The current mode is stored in current_graphics_mode.
+/**
+ * Mark the cell, (iCol, iRow), as having changed text.
  */
-static int graf_mode_req = 0;
+- (void)markTextChange:(int)iCol row:(int)iRow glyph:(wchar_t)g color:(int)c
+        isDoubleWidth:(BOOL)dw;
 
-/*
- * Helper function to check the various ways that graphics can be enabled, guarding against NULL
+/**
+ * Mark the cell, (iCol, iRow), as having a changed tile.
  */
-static BOOL graphics_are_enabled(void)
-{
-    return GRAPHICS_NONE;
-}
+- (void)markTileChange:(int)iCol row:(int)iRow
+        foregroundCol:(char)fc foregroundRow:(char)fr
+        backgroundCol:(char)bc backgroundRow:(char)br;
 
-/*
- * Hack -- game in progress
+/**
+ * Mark the cells from (iCol, iRow) to (iCol + nCol - 1, iRow) as wiped.
  */
-static Boolean game_in_progress = FALSE;
+- (void)markWipeRange:(int)iCol row:(int)iRow n:(int)nCol;
 
+/**
+ * Mark the location of the cursor.  The cursor will be the standard size:
+ * one cell.
+ */
+- (void)markCursor:(int)iCol row:(int)iRow;
 
-#pragma mark Prototypes
-static void wakeup_event_loop(void);
-static void hook_plog(const char *str);
-static void hook_quit(const char * str);
-static void load_prefs(void);
-static void load_sounds(void);
-static void init_windows(void);
-static void handle_open_when_ready(void);
-//static void play_sound(int event);
-static BOOL check_events(int wait);
-//static void cocoa_file_open_hook(const char *path, file_type ftype);
-//static bool cocoa_get_file(const char *suggested_name, char *path, size_t len);
-static BOOL send_event(NSEvent *event);
-static void record_current_savefile(void);
+/**
+ * Mark the location of the cursor.  The cursor will be w cells wide and
+ * h cells tall and the given location is the position of the upper left
+ * corner.
+ */
+- (void)markBigCursor:(int)iCol row:(int)iRow
+           cellsWide:(int)w cellsHigh:(int)h;
 
-/*
- * Available values for 'wait'
+/**
+ * Return the zero-based index of the first column changed for the given
+ * zero-based row index.  If there are no changes in the row, the returned
+ * value will be the number of columns.
  */
-#define CHECK_EVENTS_DRAIN -1
-#define CHECK_EVENTS_NO_WAIT   0
-#define CHECK_EVENTS_WAIT 1
+- (int)getFirstChangedColumnInRow:(int)iRow;
 
+/**
+ * Return the zero-based index of the last column changed for the given
+ * zero-based row index.  If there are no changes in the row, the returned
+ * value will be -1.
+ */
+- (int)getLastChangedColumnInRow:(int)iRow;
 
-/* Methods for getting the appropriate NSUserDefaults */
-@interface NSUserDefaults (AngbandDefaults)
-+ (NSUserDefaults *)angbandDefaults;
-@end
+/**
+ * Return the type of change at the given cell, (iCol, iRow).
+ */
+- (enum PendingCellChangeType)getCellChangeType:(int)iCol row:(int)iRow;
 
-@implementation NSUserDefaults (AngbandDefaults)
-+ (NSUserDefaults *)angbandDefaults
-{
-    return [NSUserDefaults standardUserDefaults];
-}
-@end
+/**
+ * Return the nature of a text change at the given cell, (iCol, iRow).
+ * Will throw an exception if [obj getCellChangeType:iCol row:iRow] is
+ * neither CELL_CHANGE_TEXT nor CELL_CHANGE_WIPE.
+ */
+- (struct PendingTextChange)getCellTextChange:(int)iCol row:(int)iRow;
 
-/* Methods for pulling images out of the Angband bundle (which may be separate from the current bundle in the case of a screensaver */
-@interface NSImage (AngbandImages)
-+ (NSImage *)angbandImage:(NSString *)name;
-@end
+/**
+ * Return the nature of a tile change at the given cell, (iCol, iRow).
+ * Will throw an exception if [obj getCellChangeType:iCol row:iRow] is
+ * different than CELL_CHANGE_TILE.
+ */
+- (struct PendingTileChange)getCellTileChange:(int)iCol row:(int)iRow;
 
-/* The NSView subclass that draws our Angband image */
-@interface AngbandView : NSView
-{
-    IBOutlet AngbandContext *angbandContext;
-}
+/**
+ * Is the number of columns for recording changes.
+ */
+@property (readonly) int columnCount;
 
-- (void)setAngbandContext:(AngbandContext *)context;
-- (AngbandContext *)angbandContext;
+/**
+ * Is the number of rows for recording changes.
+ */
+@property (readonly) int rowCount;
 
-@end
+/**
+ * Will be YES if there are any pending changes to locations rendered as text.
+ * Otherwise, it will be NO.
+ */
+@property (readonly) BOOL hasTextChanges;
 
-@implementation NSImage (AngbandImages)
+/**
+ * Will be YES if there are any pending changes to locations rendered as tiles.
+ * Otherwise, it will be NO.
+ */
+@property (readonly) BOOL hasTileChanges;
 
-/* Returns an image in the resource directoy of the bundle containing the Angband view class. */
-+ (NSImage *)angbandImage:(NSString *)name
-{
-    NSBundle *bundle = [NSBundle bundleForClass:[AngbandView class]];
-    NSString *path = [bundle pathForImageResource:name];
-    NSImage *result;
-    if (path) result = [[[NSImage alloc] initByReferencingFile:path] autorelease];
-    else result = nil;
-    return result;
-}
+/**
+ * Will be YES if there are any pending wipes.  Otherwise, it will be NO.
+ */
+@property (readonly) BOOL hasWipeChanges;
 
-@end
+/**
+ * Is the zero-based index of the first row with changes.  Will be equal to
+ * the number of rows if there are no changes.
+ */
+@property (readonly) int firstChangedRow;
 
+/**
+ * Is the zero-based index of the last row with changes.  Will be equal to
+ * -1 if there are no changes.
+ */
+@property (readonly) int lastChangedRow;
 
-@implementation AngbandContext
+/**
+ * Is the zero-based index for the column with the upper left corner of the
+ * cursor.  It will be -1 if the cursor position has not been set since the
+ * changes were cleared.
+ */
+@property (readonly) int cursorColumn;
 
-@synthesize hasSubwindowFlags=_hasSubwindowFlags;
+/**
+ * Is the zero-based index for the row with the upper left corner of the
+ * cursor.  It will be -1 if the cursor position has not been set since the
+ * changes were cleared.
+ */
+@property (readonly) int cursorRow;
+
+/**
+ * Is the cursor width in number of cells.
+ */
+@property (readonly) int cursorWidth;
+
+/**
+ * Is the cursor height in number of cells.
+ */
+@property (readonly) int cursorHeight;
 
-- (NSFont *)selectionFont
+/**
+ * This is a helper for the mark* messages.
+ */
+- (void)setupForChange:(int)iCol row:(int)iRow n:(int)nCol;
+
+/**
+ * Throw an exception if the given range of column indices is invalid
+ * (including non-positive values for nCol).
+ */
+- (void)checkColumnIndices:(int)iCol n:(int)nCol;
+
+/**
+ * Throw an exception if the given row index is invalid.
+ */
+- (void)checkRowIndex:(int)iRow;
+
+@end
+
+@implementation PendingTermChanges
+
++ (BOOL)isValidSize:(int)nCol rows:(int)nRow
 {
-    return angbandViewFont;
+    if (nCol < 0 ||
+       (size_t) nCol > SIZE_MAX / sizeof(struct PendingCellChange) ||
+       nRow < 0 ||
+       (size_t) nRow > SIZE_MAX / sizeof(struct PendingCellChange*) ||
+       (size_t) nRow > SIZE_MAX / (2 * sizeof(int))) {
+       return NO;
+    }
+    return YES;
 }
 
-- (BOOL)useLiveResizeOptimization
+- (id)init
 {
-    /* If we have graphics turned off, text rendering is fast enough that we don't need to use a live resize optimization. Note here we are depending on current_graphics_mode being NULL when in text mode. */
-    return inLiveResize && graphics_are_enabled();
+    return [self initWithColumnsRows:0 rows:0];
 }
 
-- (NSSize)baseSize
+- (id)initWithColumnsRows:(int)nCol rows:(int)nRow
 {
-    /* We round the base size down. If we round it up, I believe we may end up with pixels that nobody "owns" that may accumulate garbage. In general rounding down is harmless, because any lost pixels may be sopped up by the border. */
-    return NSMakeSize(floor(cols * tileSize.width + 2 * borderSize.width), floor(rows * tileSize.height + 2 * borderSize.height));
+    if (self = [super init]) {
+       if (! [PendingTermChanges isValidSize:nCol rows:nRow]) {
+           return nil;
+       }
+       self->colBounds = malloc((size_t) 2 * sizeof(int) * nRow);
+       if (self->colBounds == 0 && nRow > 0) {
+           return nil;
+       }
+       self->changesByRow = calloc(nRow, sizeof(struct PendingCellChange*));
+       if (self->changesByRow == 0 && nRow > 0) {
+           free(self->colBounds);
+           return nil;
+       }
+       for (int i = 0; i < nRow + nRow; i += 2) {
+           self->colBounds[i] = nCol;
+           self->colBounds[i + 1] = -1;
+       }
+       self->_columnCount = nCol;
+       self->_rowCount = nRow;
+       self->_hasTextChanges = NO;
+       self->_hasTileChanges = NO;
+       self->_hasWipeChanges = NO;
+       self->_firstChangedRow = nRow;
+       self->_lastChangedRow = -1;
+       self->_cursorColumn = -1;
+       self->_cursorRow = -1;
+       self->_cursorWidth = 1;
+       self->_cursorHeight = 1;
+    }
+    return self;
 }
 
-// qsort-compatible compare function for CGSizes
-static int compare_advances(const void *ap, const void *bp)
+- (void)dealloc
 {
-    const CGSize *a = ap, *b = bp;
-    return (a->width > b->width) - (a->width < b->width);
+    if (self->changesByRow != 0) {
+       for (int i = 0; i < self.rowCount; ++i) {
+           if (self->changesByRow[i] != 0) {
+               free(self->changesByRow[i]);
+               self->changesByRow[i] = 0;
+           }
+       }
+       free(self->changesByRow);
+       self->changesByRow = 0;
+    }
+    if (self->colBounds != 0) {
+       free(self->colBounds);
+       self->colBounds = 0;
+    }
 }
 
-- (void)updateGlyphInfo
+- (void)clear
 {
-    // Update glyphArray and glyphWidths
-    NSFont *screenFont = [angbandViewFont screenFont];
+    for (int i = 0; i < self.rowCount; ++i) {
+       self->colBounds[i + i] = self.columnCount;
+       self->colBounds[i + i + 1] = -1;
+       if (self->changesByRow[i] != 0) {
+           free(self->changesByRow[i]);
+           self->changesByRow[i] = 0;
+       }
+    }
+    self->_hasTextChanges = NO;
+    self->_hasTileChanges = NO;
+    self->_hasWipeChanges = NO;
+    self->_firstChangedRow = self.rowCount;
+    self->_lastChangedRow = -1;
+    self->_cursorColumn = -1;
+    self->_cursorRow = -1;
+    self->_cursorWidth = 1;
+    self->_cursorHeight = 1;
+}
 
-    // Generate a string containing each MacRoman character
-    unsigned char latinString[GLYPH_COUNT];
-    size_t i;
-    for (i=0; i < GLYPH_COUNT; i++) latinString[i] = (unsigned char)i;
-    
-    // Turn that into unichar. Angband uses ISO Latin 1.
-    unichar unicharString[GLYPH_COUNT] = {0};
-    NSString *allCharsString = [[NSString alloc] initWithBytes:latinString length:sizeof latinString encoding:NSISOLatin1StringEncoding];
-    [allCharsString getCharacters:unicharString range:NSMakeRange(0, MIN(GLYPH_COUNT, [allCharsString length]))];
-    [allCharsString autorelease];
-    
-    // Get glyphs
-    memset(glyphArray, 0, sizeof glyphArray);
-    CTFontGetGlyphsForCharacters((CTFontRef)screenFont, unicharString, glyphArray, GLYPH_COUNT);
-    
-    // Get advances. Record the max advance.
-    CGSize advances[GLYPH_COUNT] = {};
-    CTFontGetAdvancesForGlyphs((CTFontRef)screenFont, kCTFontHorizontalOrientation, glyphArray, advances, GLYPH_COUNT);
-    for (i=0; i < GLYPH_COUNT; i++) {
-        glyphWidths[i] = advances[i].width;
+- (void)resize:(int)nCol rows:(int)nRow
+{
+    if (! [PendingTermChanges isValidSize:nCol rows:nRow]) {
+       NSException *exc = [NSException
+                              exceptionWithName:@"PendingTermChangesRowsColumns"
+                              reason:@"resize called with number of columns or rows that is negative or too large"
+                              userInfo:nil];
+       @throw exc;
     }
-    
-    // For good non-mono-font support, use the median advance. Start by sorting all advances.
-    qsort(advances, GLYPH_COUNT, sizeof *advances, compare_advances);
-    
-    // Skip over any initially empty run
-    size_t startIdx;
-    for (startIdx = 0; startIdx < GLYPH_COUNT; startIdx++)
-    {
-        if (advances[startIdx].width > 0) break;
+
+    int *cb = malloc((size_t) 2 * sizeof(int) * nRow);
+    struct PendingCellChange** cbr =
+       calloc(nRow, sizeof(struct PendingCellChange*));
+    int i;
+
+    if ((cb == 0 || cbr == 0) && nRow > 0) {
+       if (cbr != 0) {
+           free(cbr);
+       }
+       if (cb != 0) {
+           free(cb);
+       }
+
+       NSException *exc = [NSException
+                              exceptionWithName:@"OutOfMemory"
+                              reason:@"resize called for PendingTermChanges"
+                              userInfo:nil];
+       @throw exc;
     }
-    
-    // Pick the center to find the median
-    CGFloat medianAdvance = 0;
-    if (startIdx < GLYPH_COUNT)
-    { // In case we have all zero advances for some reason
-        medianAdvance = advances[(startIdx + GLYPH_COUNT)/2].width;
+
+    for (i = 0; i < nRow; ++i) {
+       cb[i + i] = nCol;
+       cb[i + i + 1] = -1;
     }
-    
-    // Record the descender
-    fontDescender = [screenFont descender];
-    
-    // Record the tile size. Note that these are typically fractional values - which seems sketchy, but we end up scaling the heck out of our view anyways, so it seems to not matter.
-    tileSize.width = medianAdvance;
-    tileSize.height = [screenFont ascender] - [screenFont descender];
+    if (self->changesByRow != 0) {
+       for (i = 0; i < self.rowCount; ++i) {
+           if (self->changesByRow[i] != 0) {
+               free(self->changesByRow[i]);
+               self->changesByRow[i] = 0;
+           }
+       }
+       free(self->changesByRow);
+    }
+    if (self->colBounds != 0) {
+       free(self->colBounds);
+    }
+
+    self->colBounds = cb;
+    self->changesByRow = cbr;
+    self->_columnCount = nCol;
+    self->_rowCount = nRow;
+    self->_hasTextChanges = NO;
+    self->_hasTileChanges = NO;
+    self->_hasWipeChanges = NO;
+    self->_firstChangedRow = self.rowCount;
+    self->_lastChangedRow = -1;
+    self->_cursorColumn = -1;
+    self->_cursorRow = -1;
+    self->_cursorWidth = 1;
+    self->_cursorHeight = 1;
 }
 
-- (void)updateImage
+- (void)markTextChange:(int)iCol row:(int)iRow glyph:(wchar_t)g color:(int)c
+        isDoubleWidth:(BOOL)dw
 {
-    NSSize size = NSMakeSize(1, 1);
-    
-    AngbandView *activeView = [self activeView];
-    if (activeView)
-    {
-        /* If we are in live resize, draw as big as the screen, so we can scale nicely to any size. If we are not in live resize, then use the bounds of the active view. */
-        NSScreen *screen;
-        if ([self useLiveResizeOptimization] && (screen = [[activeView window] screen]) != NULL)
-        {
-            size = [screen frame].size;
-        }
-        else
-        {
-            size = [activeView bounds].size;
-        }
+    [self setupForChange:iCol row:iRow n:((dw) ? 2 : 1)];
+    struct PendingCellChange *pcc = self->changesByRow[iRow] + iCol;
+    pcc->v.txc.glyph = g;
+    pcc->v.txc.color = c;
+    pcc->v.txc.doubleWidth = dw;
+    pcc->changeType = CELL_CHANGE_TEXT;
+    /*
+     * Fill in a dummy since the previous character will take up two columns.
+     */
+    if (dw) {
+       pcc[1].v.txc.glyph = 0;
+       pcc[1].v.txc.color = c;
+       pcc[1].v.txc.doubleWidth = NO;
+       pcc[1].changeType = CELL_CHANGE_TEXT;
     }
-    
-    size.width = fmax(1, ceil(size.width));
-    size.height = fmax(1, ceil(size.height));
-    
-    CGLayerRelease(angbandLayer);
-    
-    // make a bitmap context as an example for our layer
-    CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
-    CGContextRef exampleCtx = CGBitmapContextCreate(NULL, 1, 1, 8 /* bits per component */, 48 /* bytesPerRow */, cs, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host);
-    CGColorSpaceRelease(cs);
-    angbandLayer = CGLayerCreateWithContext(exampleCtx, *(CGSize *)&size, NULL);
-    CFRelease(exampleCtx);
+    self->_hasTextChanges = YES;
+}
 
-    [self lockFocus];
-    [[NSColor blackColor] set];
-    NSRectFill((NSRect){NSZeroPoint, [self baseSize]});
-    [self unlockFocus];
+- (void)markTileChange:(int)iCol row:(int)iRow
+        foregroundCol:(char)fc foregroundRow:(char)fr
+        backgroundCol:(char)bc backgroundRow:(char)br
+{
+    [self setupForChange:iCol row:iRow n:1];
+    struct PendingCellChange *pcc = self->changesByRow[iRow] + iCol;
+    pcc->v.tic.fgdCol = fc;
+    pcc->v.tic.fgdRow = fr;
+    pcc->v.tic.bckCol = bc;
+    pcc->v.tic.bckRow = br;
+    pcc->changeType = CELL_CHANGE_TILE;
+    self->_hasTileChanges = YES;
 }
 
-- (void)requestRedraw
+- (void)markWipeRange:(int)iCol row:(int)iRow n:(int)nCol
 {
-    if (! self->terminal) return;
-    
-    term *old = Term;
-    
-    /* Activate the term */
-    Term_activate(self->terminal);
-    
-    /* Redraw the contents */
-    Term_redraw();
-    
-    /* Flush the output */
-    Term_fresh();
-    
-    /* Restore the old term */
-    Term_activate(old);
+    [self setupForChange:iCol row:iRow n:nCol];
+    struct PendingCellChange *pcc = self->changesByRow[iRow] + iCol;
+    for (int i = 0; i < nCol; ++i) {
+       pcc[i].v.txc.glyph = 0;
+       pcc[i].v.txc.color = 0;
+       pcc[i].changeType = CELL_CHANGE_WIPE;
+    }
+    self->_hasWipeChanges = YES;
 }
 
-- (void)setTerm:(term *)t
+- (void)markCursor:(int)iCol row:(int)iRow
 {
-    terminal = t;
+    /* Allow negative indices to indicate an invalid cursor. */
+    [self checkColumnIndices:((iCol >= 0) ? iCol : 0) n:1];
+    [self checkRowIndex:((iRow >= 0) ? iRow : 0)];
+    self->_cursorColumn = iCol;
+    self->_cursorRow = iRow;
+    self->_cursorWidth = 1;
+    self->_cursorHeight = 1;
 }
 
-- (void)viewWillStartLiveResize:(AngbandView *)view
+- (void)markBigCursor:(int)iCol row:(int)iRow
+           cellsWide:(int)w cellsHigh:(int)h
 {
-#if USE_LIVE_RESIZE_CACHE
-    if (inLiveResize < INT_MAX) inLiveResize++;
-    else [NSException raise:NSInternalInconsistencyException format:@"inLiveResize overflow"];
-    
-    if (inLiveResize == 1 && graphics_are_enabled())
-    {
-        [self updateImage];
-        
-        [self setNeedsDisplay:YES]; //we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects
-        [self requestRedraw];
+    /* Allow negative indices to indicate an invalid cursor. */
+    [self checkColumnIndices:((iCol >= 0) ? iCol : 0) n:1];
+    [self checkRowIndex:((iRow >= 0) ? iRow : 0)];
+    if (w < 1 || h < 1) {
+       NSException *exc = [NSException
+                              exceptionWithName:@"InvalidCursorDimensions"
+                              reason:@"markBigCursor called for PendingTermChanges"
+                              userInfo:nil];
+       @throw exc;
     }
-#endif
+    self->_cursorColumn = iCol;
+    self->_cursorRow = iRow;
+    self->_cursorWidth = w;
+    self->_cursorHeight = h;
 }
 
-- (void)viewDidEndLiveResize:(AngbandView *)view
+- (void)setupForChange:(int)iCol row:(int)iRow n:(int)nCol
 {
-#if USE_LIVE_RESIZE_CACHE
-    if (inLiveResize > 0) inLiveResize--;
-    else [NSException raise:NSInternalInconsistencyException format:@"inLiveResize underflow"];
-    
-    if (inLiveResize == 0 && graphics_are_enabled())
-    {
-        [self updateImage];
-        
-        [self setNeedsDisplay:YES]; //we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects
-        [self requestRedraw];
+    [self checkColumnIndices:iCol n:nCol];
+    [self checkRowIndex:iRow];
+    if (self->changesByRow[iRow] == 0) {
+       self->changesByRow[iRow] =
+           malloc(self.columnCount * sizeof(struct PendingCellChange));
+       if (self->changesByRow[iRow] == 0 && self.columnCount > 0) {
+           NSException *exc = [NSException
+                                  exceptionWithName:@"OutOfMemory"
+                                  reason:@"setupForChange called for PendingTermChanges"
+                                  userInfo:nil];
+           @throw exc;
+       }
+       struct PendingCellChange* pcc = self->changesByRow[iRow];
+       for (int i = 0; i < self.columnCount; ++i) {
+           pcc[i].changeType = CELL_CHANGE_NONE;
+       }
+    }
+    if (self.firstChangedRow > iRow) {
+       self->_firstChangedRow = iRow;
+    }
+    if (self.lastChangedRow < iRow) {
+       self->_lastChangedRow = iRow;
+    }
+    if ([self getFirstChangedColumnInRow:iRow] > iCol) {
+       self->colBounds[iRow + iRow] = iCol;
+    }
+    if ([self getLastChangedColumnInRow:iRow] < iCol + nCol - 1) {
+       self->colBounds[iRow + iRow + 1] = iCol + nCol - 1;
     }
-#endif
 }
 
-/* If we're trying to limit ourselves to a certain number of frames per second, then compute how long it's been since we last drew, and then wait until the next frame has passed. */
-- (void)throttle
+- (int)getFirstChangedColumnInRow:(int)iRow
 {
-    if (frames_per_second > 0)
-    {
-        CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
-        CFTimeInterval timeSinceLastRefresh = now - lastRefreshTime;
-        CFTimeInterval timeUntilNextRefresh = (1. / (double)frames_per_second) - timeSinceLastRefresh;
-        
-        if (timeUntilNextRefresh > 0)
-        {
-            usleep((unsigned long)(timeUntilNextRefresh * 1000000.));
-        }
-    }
-    lastRefreshTime = CFAbsoluteTimeGetCurrent();
+    [self checkRowIndex:iRow];
+    return self->colBounds[iRow + iRow];
 }
 
-- (void)drawWChar:(wchar_t)wchar inRect:(NSRect)tile
+- (int)getLastChangedColumnInRow:(int)iRow
 {
-    CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
-    CGFloat tileOffsetY = CTFontGetAscent( (CTFontRef)[angbandViewFont screenFont] );
-    CGFloat tileOffsetX = 0.0;
-    NSFont *screenFont = [angbandViewFont screenFont];
-    UniChar unicharString[2] = {(UniChar)wchar, 0};
+    [self checkRowIndex:iRow];
+    return self->colBounds[iRow + iRow + 1];
+}
 
-    // Get glyph and advance
-    CGGlyph thisGlyphArray[1] = { 0 };
-    CGSize advances[1] = { { 0, 0 } };
-    CTFontGetGlyphsForCharacters((CTFontRef)screenFont, unicharString, thisGlyphArray, 1);
-    CGGlyph glyph = thisGlyphArray[0];
-    CTFontGetAdvancesForGlyphs((CTFontRef)screenFont, kCTFontHorizontalOrientation, thisGlyphArray, advances, 1);
-    CGSize advance = advances[0];
-    
-    /* If our font is not monospaced, our tile width is deliberately not big enough for every character. In that event, if our glyph is too wide, we need to compress it horizontally. Compute the compression ratio. 1.0 means no compression. */
-    double compressionRatio;
-    if (advance.width <= NSWidth(tile))
-    {
-        /* Our glyph fits, so we can just draw it, possibly with an offset */
-        compressionRatio = 1.0;
-        tileOffsetX = (NSWidth(tile) - advance.width)/2;
+- (enum PendingCellChangeType)getCellChangeType:(int)iCol row:(int)iRow
+{
+    [self checkColumnIndices:iCol n:1];
+    [self checkRowIndex:iRow];
+    if (iRow < self.firstChangedRow || iRow > self.lastChangedRow) {
+       return CELL_CHANGE_NONE;
     }
-    else
-    {
-        /* Our glyph doesn't fit, so we'll have to compress it */
-        compressionRatio = NSWidth(tile) / advance.width;
-        tileOffsetX = 0;
+    if (iCol < [self getFirstChangedColumnInRow:iRow] ||
+       iCol > [self getLastChangedColumnInRow:iRow]) {
+       return CELL_CHANGE_NONE;
     }
+    return self->changesByRow[iRow][iCol].changeType;
+}
 
-    
-    /* Now draw it */
-    CGAffineTransform textMatrix = CGContextGetTextMatrix(ctx);
-    CGFloat savedA = textMatrix.a;
-
-    /* Set the position */
-    textMatrix.tx = tile.origin.x + tileOffsetX;
-    textMatrix.ty = tile.origin.y + tileOffsetY;
+- (struct PendingTextChange)getCellTextChange:(int)iCol row:(int)iRow
+{
+    [self checkColumnIndices:iCol n:1];
+    [self checkRowIndex:iRow];
+    if (iRow < self.firstChangedRow || iRow > self.lastChangedRow ||
+       iCol < [self getFirstChangedColumnInRow:iRow] ||
+       iCol > [self getLastChangedColumnInRow:iRow] ||
+       (self->changesByRow[iRow][iCol].changeType != CELL_CHANGE_TEXT &&
+        self->changesByRow[iRow][iCol].changeType != CELL_CHANGE_WIPE)) {
+       NSException *exc = [NSException
+                              exceptionWithName:@"NotTextChange"
+                              reason:@"getCellTextChange called for PendingTermChanges"
+                              userInfo:nil];
+       @throw exc;
+    }
+    return self->changesByRow[iRow][iCol].v.txc;
+}
 
-    /* Maybe squish it horizontally. */
-    if (compressionRatio != 1.)
-    {
-        textMatrix.a *= compressionRatio;
+- (struct PendingTileChange)getCellTileChange:(int)iCol row:(int)iRow
+{
+    [self checkColumnIndices:iCol n:1];
+    [self checkRowIndex:iRow];
+    if (iRow < self.firstChangedRow || iRow > self.lastChangedRow ||
+       iCol < [self getFirstChangedColumnInRow:iRow] ||
+       iCol > [self getLastChangedColumnInRow:iRow] ||
+       self->changesByRow[iRow][iCol].changeType != CELL_CHANGE_TILE) {
+       NSException *exc = [NSException
+                              exceptionWithName:@"NotTileChange"
+                              reason:@"getCellTileChange called for PendingTermChanges"
+                              userInfo:nil];
+       @throw exc;
     }
+    return self->changesByRow[iRow][iCol].v.tic;
+}
 
-    textMatrix = CGAffineTransformScale( textMatrix, 1.0, -1.0 );
-    CGContextSetTextMatrix(ctx, textMatrix);
-    CGContextShowGlyphsWithAdvances(ctx, &glyph, &CGSizeZero, 1);
-    
-    /* Restore the text matrix if we messed with the compression ratio */
-    if (compressionRatio != 1.)
-    {
-        textMatrix.a = savedA;
-        CGContextSetTextMatrix(ctx, textMatrix);
+- (void)checkColumnIndices:(int)iCol n:(int)nCol
+{
+    if (iCol < 0) {
+       NSException *exc = [NSException
+                              exceptionWithName:@"InvalidColumnIndex"
+                              reason:@"negative column index"
+                              userInfo:nil];
+       @throw exc;
+    }
+    if (iCol >= self.columnCount || iCol + nCol > self.columnCount) {
+       NSException *exc = [NSException
+                              exceptionWithName:@"InvalidColumnIndex"
+                              reason:@"column index exceeds number of columns"
+                              userInfo:nil];
+       @throw exc;
+    }
+    if (nCol <= 0) {
+       NSException *exc = [NSException
+                              exceptionWithName:@"InvalidColumnIndex"
+                              reason:@"empty column range"
+                              userInfo:nil];
+       @throw exc;
     }
-
-    textMatrix = CGAffineTransformScale( textMatrix, 1.0, -1.0 );
-    CGContextSetTextMatrix(ctx, textMatrix);
 }
 
-/* Indication that we're redrawing everything, so get rid of the overdraw cache. */
-- (void)clearOverdrawCache
+- (void)checkRowIndex:(int)iRow
 {
-    memset(charOverdrawCache, 0, self->cols * self->rows * sizeof *charOverdrawCache);
-    memset(attrOverdrawCache, 0, self->cols * self->rows * sizeof *attrOverdrawCache);
+    if (iRow < 0) {
+       NSException *exc = [NSException
+                              exceptionWithName:@"InvalidRowIndex"
+                              reason:@"negative row index"
+                              userInfo:nil];
+       @throw exc;
+    }
+    if (iRow >= self.rowCount) {
+       NSException *exc = [NSException
+                              exceptionWithName:@"InvalidRowIndex"
+                              reason:@"row index exceeds number of rows"
+                              userInfo:nil];
+       @throw exc;
+    }
 }
 
-/* Lock and unlock focus on our image or layer, setting up the CTM appropriately. */
-- (CGContextRef)lockFocusUnscaled
-{
-    /* Create an NSGraphicsContext representing this CGLayer */
-    CGContextRef ctx = CGLayerGetContext(angbandLayer);
-    NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:NO];
-    [NSGraphicsContext saveGraphicsState];
-    [NSGraphicsContext setCurrentContext:context];
-    CGContextSaveGState(ctx);
-    return ctx;
-}
+@end
 
-- (void)unlockFocus
-{
-    /* Restore the graphics state */
-    CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
-    CGContextRestoreGState(ctx);
-    [NSGraphicsContext restoreGraphicsState];
-}
 
-- (NSSize)imageSize
-{
-    /* Return the size of our layer */
-    CGSize result = CGLayerGetSize(angbandLayer);
-    return NSMakeSize(result.width, result.height);
-}
+/* The max number of glyphs we support.  Currently this only affects
+ * updateGlyphInfo() for the calculation of the tile size, fontAscender,
+ * fontDescender, nColPre, and nColPost.  The rendering in drawWChar() will
+ * work for a glyph not in updateGlyphInfo()'s set, and that is used for
+ * rendering Japanese characters, though there may be clipping or clearing
+ * artifacts because it wasn't included in updateGlyphInfo()'s calculations.
+ */
+#define GLYPH_COUNT 256
 
-- (CGContextRef)lockFocus
+/* An AngbandContext represents a logical Term (i.e. what Angband thinks is
+ * a window). This typically maps to one NSView, but may map to more than one
+ * NSView (e.g. the Test and real screen saver view). */
+@interface AngbandContext : NSObject <NSWindowDelegate>
 {
-    return [self lockFocusUnscaled];
-}
+@public
 
+    /* The Angband term */
+    term *terminal;
 
-- (NSRect)rectInImageForTileAtX:(int)x Y:(int)y
-{
-    int flippedY = y;
-    return NSMakeRect(x * tileSize.width + borderSize.width, flippedY * tileSize.height + borderSize.height, tileSize.width, tileSize.height);
+@private
+    /* Is the last time we drew, so we can throttle drawing. */
+    CFAbsoluteTime lastRefreshTime;
+
+    /*
+     * Whether we are currently in live resize, which affects how big we
+     * render our image.
+     */
+    int inLiveResize;
+
+    /* Flags whether or not a fullscreen transition is in progress. */
+    BOOL inFullscreenTransition;
 }
 
-- (void)setSelectionFont:(NSFont*)font adjustTerminal: (BOOL)adjustTerminal
-{
-    /* Record the new font */
-    [font retain];
-    [angbandViewFont release];
-    angbandViewFont = font;
-    
-    /* Update our glyph info */
-    [self updateGlyphInfo];
+/* Column and row counts, by default 80 x 24 */
+@property int cols;
+@property int rows;
 
-    if( adjustTerminal )
-    {
-        // adjust terminal to fit window with new font; save the new columns and rows since they could be changed
-        NSRect contentRect = [self->primaryWindow contentRectForFrameRect: [self->primaryWindow frame]];
-        [self resizeTerminalWithContentRect: contentRect saveToDefaults: YES];
-    }
+/* The size of the border between the window edge and the contents */
+@property (readonly) NSSize borderSize;
 
-    /* Update our image */
-    [self updateImage];
-    
-    /* Clear our overdraw cache */
-    [self clearOverdrawCache];
-    
-    /* Get redrawn */
-    [self requestRedraw];
-}
+/* Our array of views */
+@property NSMutableArray *angbandViews;
 
-- (id)init
-{
-    if ((self = [super init]))
-    {
-        /* Default rows and cols */
-        self->cols = 80;
-        self->rows = 24;
+/* The buffered image */
+@property CGLayerRef angbandLayer;
 
-        /* Default border size */
-        self->borderSize = NSMakeSize(2, 2);
+/* The font of this context */
+@property NSFont *angbandViewFont;
 
-        /* Allocate overdraw cache, unscanned and collectable. */
-        self->charOverdrawCache = NSAllocateCollectable(self->cols * self->rows *sizeof *charOverdrawCache, 0);
-        self->attrOverdrawCache = NSAllocateCollectable(self->cols * self->rows *sizeof *attrOverdrawCache, 0);
-        
-        /* Allocate our array of views */
-        angbandViews = [[NSMutableArray alloc] init];
-        
-        /* Make the image. Since we have no views, it'll just be a puny 1x1 image. */
-        [self updateImage];        
-    }
-    return self;
-}
+/* The size of one tile */
+@property (readonly) NSSize tileSize;
 
-/* Destroy all the receiver's stuff. This is intended to be callable more than once. */
-- (void)dispose
-{
-    terminal = NULL;
-    
-    /* Disassociate ourselves from our angbandViews */
-    [angbandViews makeObjectsPerformSelector:@selector(setAngbandContext:) withObject:nil];
-    [angbandViews release];
-    angbandViews = nil;
-    
-    /* Destroy the layer/image */
-    CGLayerRelease(angbandLayer);
-    angbandLayer = NULL;
+/* Font's ascender and descender */
+@property (readonly) CGFloat fontAscender;
+@property (readonly) CGFloat fontDescender;
 
-    /* Font */
-    [angbandViewFont release];
-    angbandViewFont = nil;
-    
-    /* Window */
-    [primaryWindow setDelegate:nil];
-    [primaryWindow close];
-    [primaryWindow release];
-    primaryWindow = nil;
-    
-    /* Free overdraw cache (unless we're GC, in which case it was allocated collectable) */
-    if (! [NSGarbageCollector defaultCollector]) free(self->charOverdrawCache);
-    self->charOverdrawCache = NULL;
-    if (! [NSGarbageCollector defaultCollector]) free(self->attrOverdrawCache);
-    self->attrOverdrawCache = NULL;
-}
+/*
+ * These are the number of columns before or after, respectively, a text
+ * change that may need to be redrawn.
+ */
+@property (readonly) int nColPre;
+@property (readonly) int nColPost;
 
-/* Usual Cocoa fare */
-- (void)dealloc
-{
-    [self dispose];
-    [super dealloc];
-}
+/* If this context owns a window, here it is. */
+@property NSWindow *primaryWindow;
 
+/* Is the record of changes to the contents for the next update. */
+@property PendingTermChanges *changes;
 
+@property (nonatomic, assign) BOOL hasSubwindowFlags;
+@property (nonatomic, assign) BOOL windowVisibilityChecked;
 
-#pragma mark -
-#pragma mark Directories and Paths Setup
+- (void)drawRect:(NSRect)rect inView:(NSView *)view;
 
-/**
- *  Return the path for Angband's lib directory and bail if it isn't found. The lib directory should be in the bundle's resources directory, since it's copied when built.
- */
-+ (NSString *)libDirectoryPath
-{
-    NSString *bundleLibPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: AngbandDirectoryNameLib];
-    BOOL isDirectory = NO;
-    BOOL libExists = [[NSFileManager defaultManager] fileExistsAtPath: bundleLibPath isDirectory: &isDirectory];
+/* Called at initialization to set the term */
+- (void)setTerm:(term *)t;
 
-    if( !libExists || !isDirectory )
-    {
-        NSLog( @"[%@ %@]: can't find %@/ in bundle: isDirectory: %d libExists: %d", NSStringFromClass( [self class] ), NSStringFromSelector( _cmd ), AngbandDirectoryNameLib, isDirectory, libExists );
-        NSRunAlertPanel( @"Missing Resources", @"PosChengband was unable to find required resources and must quit. Please report a bug on the Angband forums.", @"Quit", nil, nil );
-        exit( 0 );
-    }
+/* Called when the context is going down. */
+- (void)dispose;
 
-    // angband requires the trailing slash for the directory path
-    return [bundleLibPath stringByAppendingString: @"/"];
-}
+/* Returns the size of the image. */
+- (NSSize)imageSize;
 
-/**
- *  Return the path for the directory where Angband should look for its standard user file tree.
- */
-+ (NSString *)angbandDocumentsPath
-{
-    // angband requires the trailing slash, so we'll just add it here; NSString won't care about it when we use the base path for other things
-    NSString *documents = [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES ) lastObject];
+/* Return the rect for a tile at given coordinates. */
+- (NSRect)rectInImageForTileAtX:(int)x Y:(int)y;
 
-#if defined(SAFE_DIRECTORY)
-    NSString *versionedDirectory = [NSString stringWithFormat: @"%@-%s", AngbandDirectoryNameBase, VERSION_STRING];
-    return [[documents stringByAppendingPathComponent: versionedDirectory] stringByAppendingString: @"/"];
-#else
-    return [[documents stringByAppendingPathComponent: AngbandDirectoryNameBase] stringByAppendingString: @"/"];
-#endif
-}
+/* Draw the given wide character into the given tile rect. */
+- (void)drawWChar:(wchar_t)wchar inRect:(NSRect)tile context:(CGContextRef)ctx;
 
-/**
- *  Give Angband the base paths that should be used for the various directories it needs. It will create any needed directories.
- */
-+ (void)prepareFilePathsAndDirectories
-{
-    char libpath[PATH_MAX + 1] = "\0";
-    char basepath[PATH_MAX + 1] = "\0";
+/* Locks focus on the Angband image, and scales the CTM appropriately. */
+- (CGContextRef)lockFocus;
 
-    [[self libDirectoryPath] getFileSystemRepresentation: libpath maxLength: sizeof(libpath)];
-    [[self angbandDocumentsPath] getFileSystemRepresentation: basepath maxLength: sizeof(basepath)];
+/* Locks focus on the Angband image but does NOT scale the CTM. Appropriate
+ * for drawing hairlines. */
+- (CGContextRef)lockFocusUnscaled;
 
-    init_file_paths( libpath, libpath, basepath );
-    create_needed_dirs();
-}
+/* Unlocks focus. */
+- (void)unlockFocus;
 
-#pragma mark -
+/* Returns the primary window for this angband context, creating it if
+ * necessary */
+- (NSWindow *)makePrimaryWindow;
 
-/* Entry point for initializing Angband */
-+ (void)beginGame
-{
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    
-    /* Hooks in some "z-util.c" hooks */
-    plog_aux = hook_plog;
-    quit_aux = hook_quit;
-    
-    // initialize file paths
-    [self prepareFilePathsAndDirectories];
+/* Called to add a new Angband view */
+- (void)addAngbandView:(AngbandView *)view;
 
-    // load preferences
-    load_prefs();
-    
-    // load sounds
-    load_sounds();
-    
-    /* Prepare the windows */
-    init_windows();
-    
-    /* Set up game event handlers */
-    //init_display();
-    
-       /* Register the sound hook */
-       //sound_hook = play_sound;
-    
-    /* Initialize */
-    init_angband();
+/* Make the context aware that one of its views changed size */
+- (void)angbandViewDidScale:(AngbandView *)view;
 
-    /* Note the "system" */
-    ANGBAND_SYS = "mac";
-    
-    /* Initialize some save file stuff */
-    player_egid = getegid();
-    
-    /* Handle "open_when_ready" */
-    //handle_open_when_ready();
-    
-    /* Handle pending events (most notably update) and flush input */
-    Term_flush();
-    
-    /*
-     * Play a game -- "new_game" is set by "new", "open" or the open document
-     * even handler as appropriate
-     */
-        
-    [pool drain];
-    
-    /* Wait for response */
-    prt("[Choose 'New' or 'Open' from the 'File' menu]", 23, 17);
-    while (!game_in_progress) (check_events(CHECK_EVENTS_WAIT)); 
+/* Handle becoming the main window */
+- (void)windowDidBecomeMain:(NSNotification *)notification;
 
-    /* Play the game */
-    play_game(new_game);
-}
+/* Return whether the context's primary window is ordered in or not */
+- (BOOL)isOrderedIn;
 
-+ (void)endGame
-{    
-    /* Hack -- Forget messages */
-    msg_flag = FALSE;
-    
-    p_ptr->playing = FALSE;
-    p_ptr->leaving = TRUE;
-    quit_when_ready = TRUE;
-}
+/* Return whether the context's primary window is key */
+- (BOOL)isMainWindow;
 
+/* Invalidate the whole image */
+- (void)setNeedsDisplay:(BOOL)val;
 
-- (IBAction)setGraphicsMode:(NSMenuItem *)sender
-{
-    /* We stashed the graphics mode ID in the menu item's tag */
-    graf_mode_req = [sender tag];
+/* Invalidate part of the image, with the rect expressed in base coordinates */
+- (void)setNeedsDisplayInBaseRect:(NSRect)rect;
 
-    /* Stash it in UserDefaults */
-    [[NSUserDefaults angbandDefaults] setInteger:graf_mode_req forKey:@"GraphicsID"];
-    [[NSUserDefaults angbandDefaults] synchronize];
-    
-    if (game_in_progress)
-    {
-        /* Hack -- Force redraw */
-        do_cmd_redraw();
-        
-        /* Wake up the event loop so it notices the change */
-        wakeup_event_loop();
-    }
-}
+/* Display (flush) our Angband views */
+- (void)displayIfNeeded;
 
-- (void)addAngbandView:(AngbandView *)view
-{
-    if (! [angbandViews containsObject:view])
-    {
-        [angbandViews addObject:view];
-        [self updateImage];
-        [self setNeedsDisplay:YES]; //we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects
-        [self requestRedraw];
-    }
-}
+/* Resize context to size of contentRect, and optionally save size to
+ * defaults */
+- (void)resizeTerminalWithContentRect: (NSRect)contentRect saveToDefaults: (BOOL)saveToDefaults;
 
-/* We have this notion of an "active" AngbandView, which is the largest - the idea being that in the screen saver, when the user hits Test in System Preferences, we don't want to keep driving the AngbandView in the background.  Our active AngbandView is the widest - that's a hack all right. Mercifully when we're just playing the game there's only one view. */
-- (AngbandView *)activeView
+/*
+ * Change the minimum size for the window associated with the context.
+ * termIdx is the index for the terminal:  pass it so this function can be
+ * used when self->terminal has not yet been set.
+ */
+- (void)setMinimumWindowSize:(int)termIdx;
+
+/* Called from the view to indicate that it is starting or ending live resize */
+- (void)viewWillStartLiveResize:(AngbandView *)view;
+- (void)viewDidEndLiveResize:(AngbandView *)view;
+- (void)saveWindowVisibleToDefaults: (BOOL)windowVisible;
+- (BOOL)windowVisibleUsingDefaults;
+
+/* Class methods */
+/**
+ * Gets the default font for all contexts.  Currently not declaring this as
+ * a class property for compatibility with versions of Xcode prior to 8.
+ */
++ (NSFont*)defaultFont;
+/**
+ * Sets the default font for all contexts.
+ */
++ (void)setDefaultFont:(NSFont*)font;
+
+/* Internal method */
+- (AngbandView *)activeView;
+
+@end
+
+/**
+ * Generate a mask for the subwindow flags. The mask is just a safety check to
+ * make sure that our windows show and hide as expected.  This function allows
+ * for future changes to the set of flags without needed to update it here
+ * (unless the underlying types change).
+ */
+u32b AngbandMaskForValidSubwindowFlags(void)
 {
-    if ([angbandViews count] == 1)
-        return [angbandViews objectAtIndex:0];
-    
-    AngbandView *result = nil;
-    float maxWidth = 0;
-    for (AngbandView *angbandView in angbandViews)
+    int windowFlagBits = sizeof(*(window_flag)) * CHAR_BIT;
+    int maxBits = MIN( 16, windowFlagBits );
+    u32b mask = 0;
+
+    for( int i = 0; i < maxBits; i++ )
     {
-        float width = [angbandView frame].size.width;
-        if (width > maxWidth)
+        if( window_flag_desc[i] != NULL )
         {
-            maxWidth = width;
-            result = angbandView;
+            mask |= (1 << i);
         }
     }
-    return result;
+
+    return mask;
 }
 
-- (void)angbandViewDidScale:(AngbandView *)view
+/**
+ * Check for changes in the subwindow flags and update window visibility.
+ * This seems to be called for every user event, so we don't
+ * want to do any unnecessary hiding or showing of windows.
+ */
+static void AngbandUpdateWindowVisibility(void)
 {
-    /* If we're live-resizing with graphics, we're using the live resize optimization, so don't update the image. Otherwise do it. */
-    if (! (inLiveResize && graphics_are_enabled()) && view == [self activeView])
+    /* Because this function is called frequently, we'll make the mask static.
+        * It doesn't change between calls, as the flags themselves are hardcoded */
+    static u32b validWindowFlagsMask = 0;
+
+    if( validWindowFlagsMask == 0 )
     {
-        [self updateImage];
-        
-        [self setNeedsDisplay:YES]; //we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects
-        [self requestRedraw];
+        validWindowFlagsMask = AngbandMaskForValidSubwindowFlags();
     }
-}
 
+    /* Loop through all of the subwindows and see if there is a change in the
+        * flags. If so, show or hide the corresponding window. We don't care about
+        * the flags themselves; we just want to know if any are set. */
+    for( int i = 1; i < ANGBAND_TERM_MAX; i++ )
+    {
+        AngbandContext *angbandContext =
+           (__bridge AngbandContext*) (angband_term[i]->data);
 
-- (void)removeAngbandView:(AngbandView *)view
-{
-    if ([angbandViews containsObject:view])
-    {
-        [angbandViews removeObject:view];
-        [self updateImage];
-        [self setNeedsDisplay:YES]; //we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects
-        if ([angbandViews count]) [self requestRedraw];
-    }
-}
-
-
-static NSMenuItem *superitem(NSMenuItem *self)
-{
-    NSMenu *supermenu = [[self menu] supermenu];
-    int index = [supermenu indexOfItemWithSubmenu:[self menu]];
-    if (index == -1) return nil;
-    else return [supermenu itemAtIndex:index];
-}
+        if( angbandContext == nil )
+        {
+            continue;
+        }
 
+        /* This horrible mess of flags is so that we can try to maintain some
+                * user visibility preference. This should allow the user a window and
+                * have it stay closed between application launches. However, this
+                * means that when a subwindow is turned on, it will no longer appear
+                * automatically. Angband has no concept of user control over window
+                * visibility, other than the subwindow flags. */
+        if( !angbandContext.windowVisibilityChecked )
+        {
+            if( [angbandContext windowVisibleUsingDefaults] )
+            {
+                [angbandContext.primaryWindow orderFront: nil];
+                angbandContext.windowVisibilityChecked = YES;
+            }
+            else
+            {
+                [angbandContext.primaryWindow close];
+                angbandContext.windowVisibilityChecked = NO;
+            }
+        }
+        else
+        {
+            BOOL termHasSubwindowFlags = ((window_flag[i] & validWindowFlagsMask) > 0);
 
-- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
-{
-    int tag = [menuItem tag];
-    SEL sel = [menuItem action];
-    if (sel == @selector(setGraphicsMode:))
-    {
-        [menuItem setState: (tag == graf_mode_req)];
-        return YES;
-    }
-    else
-    {
-        return YES;
+            if( angbandContext.hasSubwindowFlags && !termHasSubwindowFlags )
+            {
+                [angbandContext.primaryWindow close];
+                angbandContext.hasSubwindowFlags = NO;
+                [angbandContext saveWindowVisibleToDefaults: NO];
+            }
+            else if( !angbandContext.hasSubwindowFlags && termHasSubwindowFlags )
+            {
+                [angbandContext.primaryWindow orderFront: nil];
+                angbandContext.hasSubwindowFlags = YES;
+                [angbandContext saveWindowVisibleToDefaults: YES];
+            }
+        }
     }
-}
-
-- (NSWindow *)makePrimaryWindow
-{
-    if (! primaryWindow)
-    {
-        // this has to be done after the font is set, which it already is in term_init_cocoa()
-        CGFloat width = self->cols * tileSize.width + borderSize.width * 2.0;
-        CGFloat height = self->rows * tileSize.height + borderSize.height * 2.0;
-        NSRect contentRect = NSMakeRect( 0.0, 0.0, width, height );
 
-        NSUInteger styleMask = NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask;
+    /* Make the main window key so that user events go to the right spot */
+    AngbandContext *mainWindow =
+       (__bridge AngbandContext*) (angband_term[0]->data);
+    [mainWindow.primaryWindow makeKeyAndOrderFront: nil];
+}
 
-        // make every window other than the main window closable
-        if( angband_term[0]->data != self )
-        {
-            styleMask |= NSClosableWindowMask;
-        }
 
-        primaryWindow = [[NSWindow alloc] initWithContentRect:contentRect styleMask: styleMask backing:NSBackingStoreBuffered defer:YES];
+/**
+ * ------------------------------------------------------------------------
+ * Graphics support
+ * ------------------------------------------------------------------------ */
 
-        /* Not to be released when closed */
-        [primaryWindow setReleasedWhenClosed:NO];
-        [primaryWindow setExcludedFromWindowsMenu: YES]; // we're using custom window menu handling
+/**
+ * The tile image
+ */
+static CGImageRef pict_image;
 
-        /* Make the view */
-        AngbandView *angbandView = [[AngbandView alloc] initWithFrame:contentRect];
-        [angbandView setAngbandContext:self];
-        [angbandViews addObject:angbandView];
-        [primaryWindow setContentView:angbandView];
-        [angbandView release];
+/**
+ * Numbers of rows and columns in a tileset,
+ * calculated by the PICT/PNG loading code
+ */
+static int pict_cols = 0;
+static int pict_rows = 0;
 
-        /* We are its delegate */
-        [primaryWindow setDelegate:self];
+/**
+ * Requested graphics mode (as a grafID).
+ * The current mode is stored in current_graphics_mode.
+ */
+static int graf_mode_req = 0;
 
-        /* Update our image, since this is probably the first angband view we've gotten. */
-        [self updateImage];
-    }
-    return primaryWindow;
+/**
+ * Helper function to check the various ways that graphics can be enabled,
+ * guarding against NULL
+ */
+static BOOL graphics_are_enabled(void)
+{
+    return current_graphics_mode
+       && current_graphics_mode->grafID != GRAPHICS_NONE;
 }
 
+/**
+ * Hack -- game in progress
+ */
+static Boolean game_in_progress = FALSE;
 
 
-#pragma mark View/Window Passthrough
+#pragma mark Prototypes
+static void wakeup_event_loop(void);
+static void hook_plog(const char *str);
+static void hook_quit(const char * str);
+static NSString* get_lib_directory(void);
+static NSString* get_doc_directory(void);
+static NSString* AngbandCorrectedDirectoryPath(NSString *originalPath);
+static void prepare_paths_and_directories(void);
+static void handle_open_when_ready(void);
+static void play_sound(int event);
+static BOOL check_events(int wait);
+static BOOL send_event(NSEvent *event);
+static void record_current_savefile(void);
+#ifdef JP
+static wchar_t convert_two_byte_eucjp_to_utf16_native(const char *cp);
+#endif
 
-/* This is what our views call to get us to draw to the window */
-- (void)drawRect:(NSRect)rect inView:(NSView *)view
-{
-    /* Take this opportunity to throttle so we don't flush faster than desird. */
-    BOOL viewInLiveResize = [view inLiveResize];
-    if (! viewInLiveResize) [self throttle];
+/**
+ * Available values for 'wait'
+ */
+#define CHECK_EVENTS_DRAIN -1
+#define CHECK_EVENTS_NO_WAIT   0
+#define CHECK_EVENTS_WAIT 1
 
-    /* With a GLayer, use CGContextDrawLayerInRect */
-    CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
-    NSRect bounds = [view bounds];
-    if (viewInLiveResize) CGContextSetInterpolationQuality(context, kCGInterpolationLow);
-    CGContextSetBlendMode(context, kCGBlendModeCopy);
-    CGContextDrawLayerInRect(context, *(CGRect *)&bounds, angbandLayer);
-    if (viewInLiveResize) CGContextSetInterpolationQuality(context, kCGInterpolationDefault);
-}
 
+/**
+ * Note when "open"/"new" become valid
+ */
+static bool initialized = FALSE;
 
-- (void)orderFront
-{
-    [[[angbandViews lastObject] window] makeKeyAndOrderFront:self];
-}
+/* Methods for getting the appropriate NSUserDefaults */
+@interface NSUserDefaults (AngbandDefaults)
++ (NSUserDefaults *)angbandDefaults;
+@end
 
-- (BOOL)isOrderedIn
+@implementation NSUserDefaults (AngbandDefaults)
++ (NSUserDefaults *)angbandDefaults
 {
-    return [[[angbandViews lastObject] window] isVisible];
+    return [NSUserDefaults standardUserDefaults];
 }
+@end
 
-- (BOOL)isMainWindow
-{
-    return [[[angbandViews lastObject] window] isMainWindow];
-}
+/* Methods for pulling images out of the Angband bundle (which may be separate
+ * from the current bundle in the case of a screensaver */
+@interface NSImage (AngbandImages)
++ (NSImage *)angbandImage:(NSString *)name;
+@end
 
-- (void)orderOut
+/* The NSView subclass that draws our Angband image */
+@interface AngbandView : NSView
 {
-    [[[angbandViews lastObject] window] orderOut:self];
+    AngbandContext *angbandContext;
 }
 
-- (void)setNeedsDisplay:(BOOL)val
-{
-    for (NSView *angbandView in angbandViews)
-    {
-        [angbandView setNeedsDisplay:val];
-    }
-}
+- (void)setAngbandContext:(AngbandContext *)context;
+- (AngbandContext *)angbandContext;
 
-- (void)setNeedsDisplayInBaseRect:(NSRect)rect
-{
-    for (NSView *angbandView in angbandViews)
-    {
-        [angbandView setNeedsDisplayInRect: rect];
-    }
-}
+@end
 
-- (void)displayIfNeeded
-{
-    [[self activeView] displayIfNeeded];
-}
+@implementation NSImage (AngbandImages)
 
-- (void)resizeOverdrawCache
+/* Returns an image in the resource directoy of the bundle containing the
+ * Angband view class. */
++ (NSImage *)angbandImage:(NSString *)name
 {
-    /* Free overdraw cache (unless we're GC, in which case it was allocated collectable) */
-    if (! [NSGarbageCollector defaultCollector]) free(self->charOverdrawCache);
-    self->charOverdrawCache = NULL;
-    if (! [NSGarbageCollector defaultCollector]) free(self->attrOverdrawCache);
-    self->attrOverdrawCache = NULL;
-
-    /* Allocate overdraw cache, unscanned and collectable. */
-    self->charOverdrawCache = NSAllocateCollectable(self->cols * self->rows *sizeof *charOverdrawCache, 0);
-    self->attrOverdrawCache = NSAllocateCollectable(self->cols * self->rows *sizeof *attrOverdrawCache, 0);
+    NSBundle *bundle = [NSBundle bundleForClass:[AngbandView class]];
+    NSString *path = [bundle pathForImageResource:name];
+    return (path) ? [[NSImage alloc] initByReferencingFile:path] : nil;
 }
 
-- (void)resizeTerminalWithContentRect: (NSRect)contentRect saveToDefaults: (BOOL)saveToDefaults
-{
-    CGFloat newRows = floor( (contentRect.size.height - (borderSize.height * 2.0)) / tileSize.height );
-    CGFloat newColumns = ceil( (contentRect.size.width - (borderSize.width * 2.0)) / tileSize.width );
-
-    self->cols = newColumns;
-    self->rows = newRows;
-    [self resizeOverdrawCache];
-
-    if( saveToDefaults )
-    {
-        int termIndex = 0;
-
-        for( termIndex = 0; termIndex < ANGBAND_TERM_MAX; termIndex++ )
-        {
-            if( angband_term[termIndex] == self->terminal )
-            {
-                break;
-            }
-        }
-
-        NSArray *terminals = [[NSUserDefaults standardUserDefaults] valueForKey: AngbandTerminalsDefaultsKey];
-
-        if( termIndex < (int)[terminals count] )
-        {
-            NSMutableDictionary *mutableTerm = [[NSMutableDictionary alloc] initWithDictionary: [terminals objectAtIndex: termIndex]];
-            [mutableTerm setValue: @(self->cols) forKey: AngbandTerminalColumnsDefaultsKey];
-            [mutableTerm setValue: @(self->rows) forKey: AngbandTerminalRowsDefaultsKey];
-
-            NSMutableArray *mutableTerminals = [[NSMutableArray alloc] initWithArray: terminals];
-            [mutableTerminals replaceObjectAtIndex: termIndex withObject: mutableTerm];
-
-            [[NSUserDefaults standardUserDefaults] setValue: mutableTerminals forKey: AngbandTerminalsDefaultsKey];
-            [mutableTerminals release];
-            [mutableTerm release];
-        }
-    }
+@end
 
-    term *old = Term;
-    Term_activate( self->terminal );
-    Term_resize( (int)newColumns, (int)newRows);
-    Term_redraw();
-    Term_activate( old );
-}
 
-#pragma mark -
-#pragma mark NSWindowDelegate Methods
+@implementation AngbandContext
 
-//- (void)windowWillStartLiveResize: (NSNotification *)notification
-//{
-//}
+@synthesize hasSubwindowFlags=_hasSubwindowFlags;
+@synthesize windowVisibilityChecked=_windowVisibilityChecked;
 
-- (void)windowDidEndLiveResize: (NSNotification *)notification
+- (BOOL)useLiveResizeOptimization
 {
-    NSWindow *window = [notification object];
-    NSRect contentRect = [window contentRectForFrameRect: [window frame]];
-    [self resizeTerminalWithContentRect: contentRect saveToDefaults: YES];
+    /* If we have graphics turned off, text rendering is fast enough that we
+        * don't need to use a live resize optimization. */
+    return self->inLiveResize && graphics_are_enabled();
 }
 
-//- (NSSize)windowWillResize: (NSWindow *)sender toSize: (NSSize)frameSize
-//{
-//}
-
-- (void)windowDidEnterFullScreen: (NSNotification *)notification
+- (NSSize)baseSize
 {
-    NSWindow *window = [notification object];
-    NSRect contentRect = [window contentRectForFrameRect: [window frame]];
-    [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
+    /* We round the base size down. If we round it up, I believe we may end up
+        * with pixels that nobody "owns" that may accumulate garbage. In general
+        * rounding down is harmless, because any lost pixels may be sopped up by
+        * the border. */
+    return NSMakeSize(
+       floor(self.cols * self.tileSize.width + 2 * self.borderSize.width),
+       floor(self.rows * self.tileSize.height + 2 * self.borderSize.height));
 }
 
-- (void)windowDidExitFullScreen: (NSNotification *)notification
+/* qsort-compatible compare function for CGSizes */
+static int compare_advances(const void *ap, const void *bp)
 {
-    NSWindow *window = [notification object];
-    NSRect contentRect = [window contentRectForFrameRect: [window frame]];
-    [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
+    const CGSize *a = ap, *b = bp;
+    return (a->width > b->width) - (a->width < b->width);
 }
 
-- (void)windowDidBecomeMain:(NSNotification *)notification
+/**
+ * Precompute certain metrics (tileSize, fontAscender, fontDescender, nColPre,
+ * and nColPost) for the current font.
+ */
+- (void)updateGlyphInfo
 {
-    NSWindow *window = [notification object];
+    NSFont *screenFont = [self.angbandViewFont screenFont];
 
-    if( window != self->primaryWindow )
-    {
-        return;
+    /* Generate a string containing each MacRoman character */
+    /*
+     * Here and below, dynamically allocate working arrays rather than put them
+     * on the stack in case limited stack space is an issue.
+     */
+    unsigned char *latinString = malloc(GLYPH_COUNT);
+    if (latinString == 0) {
+       NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
+                                       reason:@"latinString in updateGlyphInfo"
+                                       userInfo:nil];
+       @throw exc;
     }
+    size_t i;
+    for (i=0; i < GLYPH_COUNT; i++) latinString[i] = (unsigned char)i;
 
-    int termIndex = 0;
-
-    for( termIndex = 0; termIndex < ANGBAND_TERM_MAX; termIndex++ )
-    {
-        if( angband_term[termIndex] == self->terminal )
-        {
-            break;
-        }
+    /* Turn that into unichar. Angband uses ISO Latin 1. */
+    NSString *allCharsString = [[NSString alloc] initWithBytes:latinString length:sizeof latinString encoding:NSISOLatin1StringEncoding];
+    unichar *unicharString = malloc(GLYPH_COUNT * sizeof(unichar));
+    if (unicharString == 0) {
+       free(latinString);
+       NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
+                                       reason:@"unicharString in updateGlyphInfo"
+                                       userInfo:nil];
+       @throw exc;
     }
-
-    NSMenuItem *item = [[[NSApplication sharedApplication] windowsMenu] itemWithTag: AngbandWindowMenuItemTagBase + termIndex];
-    [item setState: NSOnState];
-
-    if( [[NSFontPanel sharedFontPanel] isVisible] )
-    {
-        [[NSFontPanel sharedFontPanel] setPanelFont: [self selectionFont] isMultiple: NO];
+    unicharString[0] = 0;
+    [allCharsString getCharacters:unicharString range:NSMakeRange(0, MIN(GLYPH_COUNT, [allCharsString length]))];
+    allCharsString = nil;
+    free(latinString);
+
+    /* Get glyphs */
+    CGGlyph *glyphArray = calloc(GLYPH_COUNT, sizeof(CGGlyph));
+    if (glyphArray == 0) {
+       free(unicharString);
+       NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
+                                       reason:@"glyphArray in updateGlyphInfo"
+                                       userInfo:nil];
+       @throw exc;
+    }
+    CTFontGetGlyphsForCharacters((CTFontRef)screenFont, unicharString,
+                                glyphArray, GLYPH_COUNT);
+    free(unicharString);
+
+    /* Get advances. Record the max advance. */
+    CGSize *advances = malloc(GLYPH_COUNT * sizeof(CGSize));
+    if (advances == 0) {
+       free(glyphArray);
+       NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
+                                       reason:@"advances in updateGlyphInfo"
+                                       userInfo:nil];
+       @throw exc;
+    }
+    CTFontGetAdvancesForGlyphs(
+       (CTFontRef)screenFont, kCTFontHorizontalOrientation, glyphArray,
+       advances, GLYPH_COUNT);
+    CGFloat *glyphWidths = malloc(GLYPH_COUNT * sizeof(CGFloat));
+    if (glyphWidths == 0) {
+       free(glyphArray);
+       free(advances);
+       NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
+                                       reason:@"glyphWidths in updateGlyphInfo"
+                                       userInfo:nil];
+       @throw exc;
+    }
+    for (i=0; i < GLYPH_COUNT; i++) {
+        glyphWidths[i] = advances[i].width;
     }
-}
 
-- (void)windowDidResignMain: (NSNotification *)notification
-{
-    NSWindow *window = [notification object];
+    /* For good non-mono-font support, use the median advance. Start by sorting
+        * all advances. */
+    qsort(advances, GLYPH_COUNT, sizeof *advances, compare_advances);
 
-    if( window != self->primaryWindow )
+    /* Skip over any initially empty run */
+    size_t startIdx;
+    for (startIdx = 0; startIdx < GLYPH_COUNT; startIdx++)
     {
-        return;
+        if (advances[startIdx].width > 0) break;
     }
 
-    int termIndex = 0;
-
-    for( termIndex = 0; termIndex < ANGBAND_TERM_MAX; termIndex++ )
+    /* Pick the center to find the median */
+    CGFloat medianAdvance = 0;
+    if (startIdx < GLYPH_COUNT)
     {
-        if( angband_term[termIndex] == self->terminal )
-        {
-            break;
-        }
+               /* In case we have all zero advances for some reason */
+        medianAdvance = advances[(startIdx + GLYPH_COUNT)/2].width;
     }
 
-    NSMenuItem *item = [[[NSApplication sharedApplication] windowsMenu] itemWithTag: AngbandWindowMenuItemTagBase + termIndex];
-    [item setState: NSOffState];
-}
+    free(advances);
 
-@end
+    /*
+     * Record the ascender and descender.  Some fonts, for instance DIN
+     * Condensed and Rockwell in 10.14, the ascent on '@' exceeds that
+     * reported by [screenFont ascender].  Get the overall bounding box
+     * for the glyphs and use that instead of the ascender and descender
+     * values if the bounding box result extends farther from the baseline.
+     */
+    CGRect bounds = CTFontGetBoundingRectsForGlyphs(
+       (CTFontRef) screenFont, kCTFontHorizontalOrientation, glyphArray,
+       NULL, GLYPH_COUNT);
+    self->_fontAscender = [screenFont ascender];
+    if (self->_fontAscender < bounds.origin.y + bounds.size.height) {
+       self->_fontAscender = bounds.origin.y + bounds.size.height;
+    }
+    self->_fontDescender = [screenFont descender];
+    if (self->_fontDescender > bounds.origin.y) {
+       self->_fontDescender = bounds.origin.y;
+    }
 
+    /*
+     * Record the tile size.  Round both values up to have tile boundaries
+     * match pixel boundaries.
+     */
+    self->_tileSize.width = ceil(medianAdvance);
+    self->_tileSize.height = ceil(self.fontAscender - self.fontDescender);
 
-@implementation AngbandView
-
-- (BOOL)isOpaque
-{
-    return YES;
-}
+    /*
+     * Determine whether neighboring columns need to be redrawn when a
+     * character changes.
+     */
+    CGRect *boxes = malloc(GLYPH_COUNT * sizeof(CGRect));
+    if (boxes == 0) {
+       free(glyphWidths);
+       free(glyphArray);
+       NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
+                                       reason:@"boxes in updateGlyphInfo"
+                                       userInfo:nil];
+       @throw exc;
+    }
+    CGFloat beyond_right = 0.;
+    CGFloat beyond_left = 0.;
+    CTFontGetBoundingRectsForGlyphs(
+       (CTFontRef)screenFont,
+       kCTFontHorizontalOrientation,
+       glyphArray,
+       boxes,
+       GLYPH_COUNT);
+    for (i = 0; i < GLYPH_COUNT; i++) {
+       /* Account for the compression and offset used by drawWChar(). */
+       CGFloat compression, offset;
+       CGFloat v;
+
+       if (glyphWidths[i] <= self.tileSize.width) {
+           compression = 1.;
+           offset = 0.5 * (self.tileSize.width - glyphWidths[i]);
+       } else {
+           compression = self.tileSize.width / glyphWidths[i];
+           offset = 0.;
+       }
+       v = (offset + boxes[i].origin.x) * compression;
+       if (beyond_left > v) {
+           beyond_left = v;
+       }
+       v = (offset + boxes[i].origin.x + boxes[i].size.width) * compression;
+       if (beyond_right < v) {
+           beyond_right = v;
+       }
+    }
+    free(boxes);
+    self->_nColPre = ceil(-beyond_left / self.tileSize.width);
+    if (beyond_right > self.tileSize.width) {
+       self->_nColPost =
+           ceil((beyond_right - self.tileSize.width) / self.tileSize.width);
+    } else {
+       self->_nColPost = 0;
+    }
 
-- (BOOL)isFlipped
-{
-    return YES;
+    free(glyphWidths);
+    free(glyphArray);
 }
 
-- (void)drawRect:(NSRect)rect
+- (void)updateImage
 {
-    if (! angbandContext)
+    NSSize size = NSMakeSize(1, 1);
+    
+    AngbandView *activeView = [self activeView];
+    if (activeView)
     {
-        /* Draw bright orange, 'cause this ain't right */
-        [[NSColor orangeColor] set];
-        NSRectFill([self bounds]);
+        /* If we are in live resize, draw as big as the screen, so we can scale
+                * nicely to any size. If we are not in live resize, then use the
+                * bounds of the active view. */
+        NSScreen *screen;
+        if ([self useLiveResizeOptimization] && (screen = [[activeView window] screen]) != NULL)
+        {
+            size = [screen frame].size;
+        }
+        else
+        {
+            size = [activeView bounds].size;
+        }
     }
-    else
-    {
-        /* Tell the Angband context to draw into us */
-        [angbandContext drawRect:rect inView:self];
+
+    CGLayerRelease(self.angbandLayer);
+    
+    /* Use the highest monitor scale factor on the system to work out what
+     * scale to draw at - not the recommended method, but works where we
+     * can't easily get the monitor the current draw is occurring on. */
+    float angbandLayerScale = 1.0;
+    if ([[NSScreen mainScreen] respondsToSelector:@selector(backingScaleFactor)]) {
+        for (NSScreen *screen in [NSScreen screens]) {
+            angbandLayerScale = fmax(angbandLayerScale, [screen backingScaleFactor]);
+        }
     }
-}
 
-- (void)setAngbandContext:(AngbandContext *)context
-{
-    angbandContext = context;
-}
+    /* Make a bitmap context as an example for our layer */
+    CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
+    CGContextRef exampleCtx = CGBitmapContextCreate(NULL, 1, 1, 8 /* bits per component */, 48 /* bytesPerRow */, cs, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host);
+    CGColorSpaceRelease(cs);
 
-- (AngbandContext *)angbandContext
-{
-    return angbandContext;
-}
+    /* Create the layer at the appropriate size */
+    size.width = fmax(1, ceil(size.width * angbandLayerScale));
+    size.height = fmax(1, ceil(size.height * angbandLayerScale));
+    self.angbandLayer =
+       CGLayerCreateWithContext(exampleCtx, *(CGSize *)&size, NULL);
 
-- (void)setFrameSize:(NSSize)size
-{
-    BOOL changed = ! NSEqualSizes(size, [self frame].size);
-    [super setFrameSize:size];
-    if (changed) [angbandContext angbandViewDidScale:self];
+    CFRelease(exampleCtx);
+
+    /* Set the new context of the layer to draw at the correct scale */
+    CGContextRef ctx = CGLayerGetContext(self.angbandLayer);
+    CGContextScaleCTM(ctx, angbandLayerScale, angbandLayerScale);
+
+    [self lockFocus];
+    [[NSColor blackColor] set];
+    NSRectFill((NSRect){NSZeroPoint, [self baseSize]});
+    [self unlockFocus];
 }
 
-- (void)viewWillStartLiveResize
+- (void)requestRedraw
 {
-    [angbandContext viewWillStartLiveResize:self];
+    if (! self->terminal) return;
+    
+    term *old = Term;
+    
+    /* Activate the term */
+    Term_activate(self->terminal);
+    
+    /* Redraw the contents */
+    Term_redraw();
+    
+    /* Flush the output */
+    Term_fresh();
+    
+    /* Restore the old term */
+    Term_activate(old);
 }
 
-- (void)viewDidEndLiveResize
+- (void)setTerm:(term *)t
 {
-    [angbandContext viewDidEndLiveResize:self];
+    self->terminal = t;
 }
 
-@end
-
-/*
- * Delay handling of double-clicked savefiles
- */
-Boolean open_when_ready = FALSE;
-
-
-
-/*** Some generic functions ***/
-
-/* Sets an Angband color at a given index */
-static void set_color_for_index(int idx)
+- (void)viewWillStartLiveResize:(AngbandView *)view
 {
-    u16b rv, gv, bv;
-    
-    /* Extract the R,G,B data */
-    rv = angband_color_table[idx][1];
-    gv = angband_color_table[idx][2];
-    bv = angband_color_table[idx][3];
+#if USE_LIVE_RESIZE_CACHE
+    if (self->inLiveResize < INT_MAX) self->inLiveResize++;
+    else [NSException raise:NSInternalInconsistencyException format:@"inLiveResize overflow"];
     
-    CGContextSetRGBFillColor([[NSGraphicsContext currentContext] graphicsPort], rv/255., gv/255., bv/255., 1.);
+    if (self->inLiveResize == 1 && graphics_are_enabled())
+    {
+        [self updateImage];
+        
+        [self setNeedsDisplay:YES]; /* We'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */
+        [self requestRedraw];
+    }
+#endif
 }
 
-/* Remember the current character in UserDefaults so we can select it by default next time. */
-static void record_current_savefile(void)
+- (void)viewDidEndLiveResize:(AngbandView *)view
 {
-    NSString *savefileString = [[NSString stringWithCString:savefile encoding:NSMacOSRomanStringEncoding] lastPathComponent];
-    if (savefileString)
+#if USE_LIVE_RESIZE_CACHE
+    if (self->inLiveResize > 0) self->inLiveResize--;
+    else [NSException raise:NSInternalInconsistencyException format:@"inLiveResize underflow"];
+    
+    if (self->inLiveResize == 0 && graphics_are_enabled())
     {
-        NSUserDefaults *angbandDefs = [NSUserDefaults angbandDefaults];
-        [angbandDefs setObject:savefileString forKey:@"SaveFile"];
-        [angbandDefs synchronize];        
+        [self updateImage];
+        
+        [self setNeedsDisplay:YES]; /* We'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */
+        [self requestRedraw];
     }
+#endif
 }
 
-
-/*** Support for the "z-term.c" package ***/
-
-
-/*
- * Initialize a new Term
- *
- */
-static void Term_init_cocoa(term *t)
+/**
+ * If we're trying to limit ourselves to a certain number of frames per second,
+ * then compute how long it's been since we last drew, and then wait until the
+ * next frame has passed. */
+- (void)throttle
 {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    AngbandContext *context = [[AngbandContext alloc] init];
-    
-    /* Give the term a hard retain on context (for GC) */
-    t->data = (void *)CFRetain(context);
-    [context release];
-    
-    /* Handle graphics */
-    t->higher_pict = !! use_graphics;
-    t->always_pict = FALSE;
-    
-    NSDisableScreenUpdates();
-    
-    /* Figure out the frame autosave name based on the index of this term */
-    NSString *autosaveName = nil;
-    int termIdx;
-    for (termIdx = 0; termIdx < ANGBAND_TERM_MAX; termIdx++)
+    if (frames_per_second > 0)
     {
-        if (angband_term[termIdx] == t)
+        CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
+        CFTimeInterval timeSinceLastRefresh = now - self->lastRefreshTime;
+        CFTimeInterval timeUntilNextRefresh = (1. / (double)frames_per_second) - timeSinceLastRefresh;
+        
+        if (timeUntilNextRefresh > 0)
         {
-            autosaveName = [NSString stringWithFormat:@"AngbandTerm-%d", termIdx];
-            break;
+            usleep((unsigned long)(timeUntilNextRefresh * 1000000.));
         }
     }
-    
-    /* Set its font. */
-    NSString *fontName = [[NSUserDefaults angbandDefaults] 
-        stringForKey:[NSString stringWithFormat:@"FontName-%d", termIdx]];
-    if (! fontName) fontName = [default_font fontName];
-    float fontSize = [[NSUserDefaults angbandDefaults] 
-        floatForKey:[NSString stringWithFormat:@"FontSize-%d", termIdx]];
-    if (! fontSize) fontSize = [default_font pointSize];
-    [context setSelectionFont:[NSFont fontWithName:fontName size:fontSize] adjustTerminal: NO];
-
-    NSArray *terminalDefaults = [[NSUserDefaults standardUserDefaults] valueForKey: AngbandTerminalsDefaultsKey];
-    NSInteger rows = 24;
-    NSInteger columns = 80;
-
-    if( termIdx < (int)[terminalDefaults count] )
-    {
-        NSDictionary *term = [terminalDefaults objectAtIndex: termIdx];
-        rows = [[term valueForKey: AngbandTerminalRowsDefaultsKey] integerValue];
-        columns = [[term valueForKey: AngbandTerminalColumnsDefaultsKey] integerValue];
-    }
+    self->lastRefreshTime = CFAbsoluteTimeGetCurrent();
+}
 
-    context->cols = columns;
-    context->rows = rows;
-    [context resizeOverdrawCache];
+- (void)drawWChar:(wchar_t)wchar inRect:(NSRect)tile context:(CGContextRef)ctx
+{
+    CGFloat tileOffsetY = self.fontAscender;
+    CGFloat tileOffsetX = 0.0;
+    NSFont *screenFont = [self.angbandViewFont screenFont];
+    UniChar unicharString[2] = {(UniChar)wchar, 0};
 
-    /* Get the window */
-    NSWindow *window = [context makePrimaryWindow];
+    /* Get glyph and advance */
+    CGGlyph thisGlyphArray[1] = { 0 };
+    CGSize advances[1] = { { 0, 0 } };
+    CTFontGetGlyphsForCharacters((CTFontRef)screenFont, unicharString, thisGlyphArray, 1);
+    CGGlyph glyph = thisGlyphArray[0];
+    CTFontGetAdvancesForGlyphs((CTFontRef)screenFont, kCTFontHorizontalOrientation, thisGlyphArray, advances, 1);
+    CGSize advance = advances[0];
     
-    /* Set its title and, for auxiliary terms, tentative size */
-    if (termIdx == 0)
+    /* If our font is not monospaced, our tile width is deliberately not big
+        * enough for every character. In that event, if our glyph is too wide, we
+        * need to compress it horizontally. Compute the compression ratio.
+        * 1.0 means no compression. */
+    double compressionRatio;
+    if (advance.width <= NSWidth(tile))
     {
-        [window setTitle:@"PosChengband"];
+        /* Our glyph fits, so we can just draw it, possibly with an offset */
+        compressionRatio = 1.0;
+        tileOffsetX = (NSWidth(tile) - advance.width)/2;
     }
     else
     {
-        [window setTitle:[NSString stringWithFormat:@"Term %d", termIdx]];
+        /* Our glyph doesn't fit, so we'll have to compress it */
+        compressionRatio = NSWidth(tile) / advance.width;
+        tileOffsetX = 0;
     }
+
     
-    
-    /* If this is the first term, and we support full screen (Mac OS X Lion or later), then allow it to go full screen (sweet). Allow other terms to be FullScreenAuxilliary, so they can at least show up. Unfortunately in Lion they don't get brought to the full screen space; but they would only make sense on multiple displays anyways so it's not a big loss. */
-    if ([window respondsToSelector:@selector(toggleFullScreen:)])
+    /* Now draw it */
+    CGAffineTransform textMatrix = CGContextGetTextMatrix(ctx);
+    CGFloat savedA = textMatrix.a;
+
+    /* Set the position */
+    textMatrix.tx = tile.origin.x + tileOffsetX;
+    textMatrix.ty = tile.origin.y + tileOffsetY;
+
+    /* Maybe squish it horizontally. */
+    if (compressionRatio != 1.)
     {
-        NSWindowCollectionBehavior behavior = [window collectionBehavior];
-        behavior |= (termIdx == 0 ? Angband_NSWindowCollectionBehaviorFullScreenPrimary : Angband_NSWindowCollectionBehaviorFullScreenAuxiliary);
-        [window setCollectionBehavior:behavior];
+        textMatrix.a *= compressionRatio;
     }
+
+    textMatrix = CGAffineTransformScale( textMatrix, 1.0, -1.0 );
+    CGContextSetTextMatrix(ctx, textMatrix);
+    CGContextShowGlyphsAtPositions(ctx, &glyph, &CGPointZero, 1);
     
-    /* No Resume support yet, though it would not be hard to add */
-    if ([window respondsToSelector:@selector(setRestorable:)])
+    /* Restore the text matrix if we messed with the compression ratio */
+    if (compressionRatio != 1.)
     {
-        [window setRestorable:NO];
+        textMatrix.a = savedA;
+        CGContextSetTextMatrix(ctx, textMatrix);
     }
-    
-    /* Position the window, either through autosave or cascading it */
-    [window center];
-    
-    /* Cascade it */
-    static NSPoint lastPoint = {0, 0};
-    lastPoint = [window cascadeTopLeftFromPoint:lastPoint];
-    
-    /* And maybe that's all for naught */
-    if (autosaveName) [window setFrameAutosaveName:autosaveName];
-    
-    /* Tell it about its term. Do this after we've sized it so that the sizing doesn't trigger redrawing and such. */
-    [context setTerm:t];
-    
-    /* Only order front if it's the first term. Other terms will be ordered front from update_term_visibility(). This is to work around a problem where Angband aggressively tells us to initialize terms that don't do anything! */
-    if (t == angband_term[0]) [context orderFront];
-    
-    NSEnableScreenUpdates();
-    
-    /* Set "mapped" flag */
-    t->mapped_flag = true;
-    [pool drain];
+
+    textMatrix = CGAffineTransformScale( textMatrix, 1.0, -1.0 );
+    CGContextSetTextMatrix(ctx, textMatrix);
 }
 
+/* Lock and unlock focus on our image or layer, setting up the CTM
+ * appropriately. */
+- (CGContextRef)lockFocusUnscaled
+{
+    /* Create an NSGraphicsContext representing this CGLayer */
+    CGContextRef ctx = CGLayerGetContext(self.angbandLayer);
+    NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:NO];
+    [NSGraphicsContext saveGraphicsState];
+    [NSGraphicsContext setCurrentContext:context];
+    CGContextSaveGState(ctx);
+    return ctx;
+}
 
+- (void)unlockFocus
+{
+    /* Restore the graphics state */
+    CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
+    CGContextRestoreGState(ctx);
+    [NSGraphicsContext restoreGraphicsState];
+}
 
-/*
- * Nuke an old Term
- */
-static void Term_nuke_cocoa(term *t)
+- (NSSize)imageSize
 {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    
-    AngbandContext *context = t->data;
-    if (context)
-    {
-        /* Tell the context to get rid of its windows, etc. */
-        [context dispose];
-        
-        /* Balance our CFRetain from when we created it */
-        CFRelease(context);
-        
-        /* Done with it */
-        t->data = NULL;
-    }
-    
-    [pool drain];
+    /* Return the size of our layer */
+    CGSize result = CGLayerGetSize(self.angbandLayer);
+    return NSMakeSize(result.width, result.height);
 }
-#if 0
-/* Returns the CGImageRef corresponding to an image with the given name in the resource directory, transferring ownership to the caller */
-static CGImageRef create_angband_image(NSString *name)
+
+- (CGContextRef)lockFocus
 {
-    CGImageRef decodedImage = NULL, result = NULL;
-    
-    /* Get the path to the image */
-    NSBundle *bundle = [NSBundle bundleForClass:[AngbandView class]];
-    NSString *path = [bundle pathForImageResource:name];
-    
-    /* Try using ImageIO to load it */
-    if (path)
-    {
-        NSURL *url = [[NSURL alloc] initFileURLWithPath:path isDirectory:NO];
-        if (url)
-        {
-            NSDictionary *options = [[NSDictionary alloc] initWithObjectsAndKeys:(id)kCFBooleanTrue, kCGImageSourceShouldCache, nil];
-            CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, (CFDictionaryRef)options);
-            if (source)
-            {
-                /* We really want the largest image, but in practice there's only going to be one */
-                decodedImage = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)options);
-                CFRelease(source);
-            }
-            [options release];
-            [url release];
-        }
-    }
-    
-    /* Draw the sucker to defeat ImageIO's weird desire to cache and decode on demand. Our images aren't that big! */
-    if (decodedImage)
+    return [self lockFocusUnscaled];
+}
+
+
+- (NSRect)rectInImageForTileAtX:(int)x Y:(int)y
+{
+    int flippedY = y;
+    return NSMakeRect(x * self.tileSize.width + self.borderSize.width,
+                     flippedY * self.tileSize.height + self.borderSize.height,
+                     self.tileSize.width, self.tileSize.height);
+}
+
+- (void)setSelectionFont:(NSFont*)font adjustTerminal: (BOOL)adjustTerminal
+{
+    /* Record the new font */
+    self.angbandViewFont = font;
+
+    /* Update our glyph info */
+    [self updateGlyphInfo];
+
+    if( adjustTerminal )
     {
-        size_t width = CGImageGetWidth(decodedImage), height = CGImageGetHeight(decodedImage);
-        
-        /* Compute our own bitmap info */
-        CGBitmapInfo imageBitmapInfo = CGImageGetBitmapInfo(decodedImage);
-        CGBitmapInfo contextBitmapInfo = kCGBitmapByteOrderDefault;
-        
-        switch (imageBitmapInfo & kCGBitmapAlphaInfoMask) {
-            case kCGImageAlphaNone:
-            case kCGImageAlphaNoneSkipLast:
-            case kCGImageAlphaNoneSkipFirst:
-                /* No alpha */
-                contextBitmapInfo |= kCGImageAlphaNone;
-                break;
-            default:
-                /* Some alpha, use premultiplied last which is most efficient. */
-                contextBitmapInfo |= kCGImageAlphaPremultipliedLast;
-                break;
+        /* Adjust terminal to fit window with new font; save the new columns
+                * and rows since they could be changed */
+        NSRect contentRect =
+           [self.primaryWindow
+                contentRectForFrameRect: [self.primaryWindow frame]];
+
+       [self setMinimumWindowSize:[self terminalIndex]];
+       NSSize size = self.primaryWindow.contentMinSize;
+       BOOL windowNeedsResizing = NO;
+       if (contentRect.size.width < size.width) {
+           contentRect.size.width = size.width;
+           windowNeedsResizing = YES;
+       }
+       if (contentRect.size.height < size.height) {
+           contentRect.size.height = size.height;
+           windowNeedsResizing = YES;
+       }
+       if (windowNeedsResizing) {
+           size.width = contentRect.size.width;
+           size.height = contentRect.size.height;
+           [self.primaryWindow setContentSize:size];
+       }
+        [self resizeTerminalWithContentRect: contentRect saveToDefaults: YES];
+    }
+
+    /* Update our image */
+    [self updateImage];
+}
+
+- (id)init
+{
+    if ((self = [super init]))
+    {
+        /* Default rows and cols */
+        self->_cols = 80;
+        self->_rows = 24;
+
+        /* Default border size */
+        self->_borderSize = NSMakeSize(2, 2);
+
+        /* Allocate our array of views */
+        self->_angbandViews = [[NSMutableArray alloc] init];
+
+       self->_nColPre = 0;
+       self->_nColPost = 0;
+
+       self->_changes =
+           [[PendingTermChanges alloc] initWithColumnsRows:self->_cols
+                                       rows:self->_rows];
+       self->lastRefreshTime = CFAbsoluteTimeGetCurrent();
+       self->inLiveResize = 0;
+       self->inFullscreenTransition = NO;
+
+        /* Make the image. Since we have no views, it'll just be a puny 1x1 image. */
+        [self updateImage];
+
+        self->_windowVisibilityChecked = NO;
+    }
+    return self;
+}
+
+/**
+ * Destroy all the receiver's stuff. This is intended to be callable more than
+ * once.
+ */
+- (void)dispose
+{
+    self->terminal = NULL;
+
+    /* Disassociate ourselves from our angbandViews */
+    [self.angbandViews makeObjectsPerformSelector:@selector(setAngbandContext:) withObject:nil];
+    self.angbandViews = nil;
+
+    /* Destroy the layer/image */
+    CGLayerRelease(self.angbandLayer);
+    self.angbandLayer = NULL;
+
+    /* Font */
+    self.angbandViewFont = nil;
+
+    /* Window */
+    [self.primaryWindow setDelegate:nil];
+    [self.primaryWindow close];
+    self.primaryWindow = nil;
+
+    /* Pending changes */
+    self.changes = nil;
+}
+
+/* Usual Cocoa fare */
+- (void)dealloc
+{
+    [self dispose];
+}
+
+#if 0
+/* From the Linux mbstowcs(3) man page:
+ *   If dest is NULL, n is ignored, and the conversion  proceeds  as  above,
+ *   except  that  the converted wide characters are not written out to mem‐
+ *   ory, and that no length limit exists.
+ */
+static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n)
+{
+    int i;
+    int count = 0;
+
+    /* Unicode code point to UTF-8
+     *  0x0000-0x007f:   0xxxxxxx
+     *  0x0080-0x07ff:   110xxxxx 10xxxxxx
+     *  0x0800-0xffff:   1110xxxx 10xxxxxx 10xxxxxx
+     * 0x10000-0x1fffff: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+     * Note that UTF-16 limits Unicode to 0x10ffff. This code is not
+     * endian-agnostic.
+     */
+    for (i = 0; i < n || dest == NULL; i++) {
+        if ((src[i] & 0x80) == 0) {
+            if (dest != NULL) dest[count] = src[i];
+            if (src[i] == 0) break;
+        } else if ((src[i] & 0xe0) == 0xc0) {
+            if (dest != NULL) dest[count] = 
+                            (((unsigned char)src[i] & 0x1f) << 6)| 
+                            ((unsigned char)src[i+1] & 0x3f);
+            i++;
+        } else if ((src[i] & 0xf0) == 0xe0) {
+            if (dest != NULL) dest[count] = 
+                            (((unsigned char)src[i] & 0x0f) << 12) | 
+                            (((unsigned char)src[i+1] & 0x3f) << 6) |
+                            ((unsigned char)src[i+2] & 0x3f);
+            i += 2;
+        } else if ((src[i] & 0xf8) == 0xf0) {
+            if (dest != NULL) dest[count] = 
+                            (((unsigned char)src[i] & 0x0f) << 18) | 
+                            (((unsigned char)src[i+1] & 0x3f) << 12) |
+                            (((unsigned char)src[i+2] & 0x3f) << 6) |
+                            ((unsigned char)src[i+3] & 0x3f);
+            i += 3;
+        } else {
+            /* Found an invalid multibyte sequence */
+            return (size_t)-1;
         }
-        
-        CGContextRef ctx = CGBitmapContextCreate(NULL, width, height, CGImageGetBitsPerComponent(decodedImage), CGImageGetBytesPerRow(decodedImage), CGImageGetColorSpace(decodedImage), contextBitmapInfo);
-        CGContextSetBlendMode(ctx, kCGBlendModeCopy);
-        CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), decodedImage);
-        result = CGBitmapContextCreateImage(ctx);
-        
-        /* Done with these things */
-        CFRelease(ctx);
-        CGImageRelease(decodedImage);
+        count++;
     }
-    return result;
+    return count;
 }
 #endif
-/*
- * React to changes
+
+- (void)addAngbandView:(AngbandView *)view
+{
+    if (! [self.angbandViews containsObject:view])
+    {
+        [self.angbandViews addObject:view];
+        [self updateImage];
+        [self setNeedsDisplay:YES]; /* We'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */
+        [self requestRedraw];
+    }
+}
+
+/**
+ * For defaultFont and setDefaultFont.
  */
-static errr Term_xtra_cocoa_react(void)
+static __strong NSFont* gDefaultFont = nil;
+
++ (NSFont*)defaultFont
 {
-    /* Don't actually switch graphics until the game is running */
-    if (!initialized || !game_in_progress) return (-1);
+    return gDefaultFont;
+}
 
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    //AngbandContext *angbandContext = Term->data;
-#if 0    
-    /* Handle graphics */
-    int expected_graf_mode = (current_graphics_mode ? current_graphics_mode->grafID : GRAF_MODE_NONE);
-    if (graf_mode_req != expected_graf_mode)
-    {
-        graphics_mode *new_mode;
-               if (graf_mode_req != GRAF_MODE_NONE) {
-                       new_mode = get_graphics_mode(graf_mode_req);
-               } else {
-                       new_mode = NULL;
-        }
-        
-        /* Get rid of the old image. CGImageRelease is NULL-safe. */
-        CGImageRelease(pict_image);
-        pict_image = NULL;
-        
-        /* Try creating the image if we want one */
-        if (new_mode != NULL)
-        {
-            NSString *img_name = [NSString stringWithCString:new_mode->file 
-                                                encoding:NSMacOSRomanStringEncoding];
-            pict_image = create_angband_image(img_name);
++ (void)setDefaultFont:(NSFont*)font
+{
+    gDefaultFont = font;
+}
 
-            /* If we failed to create the image, set the new desired mode to NULL */
-            if (! pict_image)
-                new_mode = NULL;
-        }
-        
-        /* Record what we did */
-        use_graphics = (new_mode != NULL);
-        use_transparency = (new_mode != NULL);
-        ANGBAND_GRAF = (new_mode ? new_mode->pref : NULL);
-        current_graphics_mode = new_mode;
-        
-        /* Enable or disable higher picts. Note: this should be done for all terms. */
-        angbandContext->terminal->higher_pict = !! use_graphics;
-        
-        if (pict_image && current_graphics_mode)
-        {
-            /* Compute the row and column count via the image height and width. */
-            pict_rows = (int)(CGImageGetHeight(pict_image) / current_graphics_mode->cell_height);
-            pict_cols = (int)(CGImageGetWidth(pict_image) / current_graphics_mode->cell_width);
-        }
-        else
+/**
+ * We have this notion of an "active" AngbandView, which is the largest - the
+ * idea being that in the screen saver, when the user hits Test in System
+ * Preferences, we don't want to keep driving the AngbandView in the
+ * background.  Our active AngbandView is the widest - that's a hack all right.
+ * Mercifully when we're just playing the game there's only one view.
+ */
+- (AngbandView *)activeView
+{
+    if ([self.angbandViews count] == 1)
+        return [self.angbandViews objectAtIndex:0];
+
+    AngbandView *result = nil;
+    float maxWidth = 0;
+    for (AngbandView *angbandView in self.angbandViews)
+    {
+        float width = [angbandView frame].size.width;
+        if (width > maxWidth)
         {
-            pict_rows = 0;
-            pict_cols = 0;
+            maxWidth = width;
+            result = angbandView;
         }
+    }
+    return result;
+}
+
+- (void)angbandViewDidScale:(AngbandView *)view
+{
+    /* If we're live-resizing with graphics, we're using the live resize
+        * optimization, so don't update the image. Otherwise do it. */
+    if (! (self->inLiveResize && graphics_are_enabled()) && view == [self activeView])
+    {
+        [self updateImage];
         
-        /* Reset visuals */
-        if (initialized && game_in_progress)
+        [self setNeedsDisplay:YES]; /*we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */
+        [self requestRedraw];
+    }
+}
+
+
+- (void)removeAngbandView:(AngbandView *)view
+{
+    if ([self.angbandViews containsObject:view])
+    {
+        [self.angbandViews removeObject:view];
+        [self updateImage];
+        [self setNeedsDisplay:YES]; /* We'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */
+        if ([self.angbandViews count]) [self requestRedraw];
+    }
+}
+
+
+- (NSWindow *)makePrimaryWindow
+{
+    if (! self.primaryWindow)
+    {
+        /* This has to be done after the font is set, which it already is in
+                * term_init_cocoa() */
+        NSSize sz = self.baseSize;
+        NSRect contentRect = NSMakeRect( 0.0, 0.0, sz.width, sz.height );
+
+        NSUInteger styleMask = NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask;
+
+        /* Make every window other than the main window closable */
+        if ((__bridge AngbandContext*) (angband_term[0]->data) != self)
         {
-            reset_visuals(TRUE);
+            styleMask |= NSClosableWindowMask;
         }
+
+        self.primaryWindow = [[NSWindow alloc] initWithContentRect:contentRect styleMask: styleMask backing:NSBackingStoreBuffered defer:YES];
+
+        /* Not to be released when closed */
+        [self.primaryWindow setReleasedWhenClosed:NO];
+        [self.primaryWindow setExcludedFromWindowsMenu: YES]; /* we're using custom window menu handling */
+
+        /* Make the view */
+        AngbandView *angbandView = [[AngbandView alloc] initWithFrame:contentRect];
+        [angbandView setAngbandContext:self];
+        [self.angbandViews addObject:angbandView];
+        [self.primaryWindow setContentView:angbandView];
+
+        /* We are its delegate */
+        [self.primaryWindow setDelegate:self];
+
+        /* Update our image, since this is probably the first angband view
+                * we've gotten. */
+        [self updateImage];
     }
-#endif    
-    [pool drain];
-    
-    /* Success */
-    return (0);
+    return self.primaryWindow;
 }
 
 
-/*
- * Do a "special thing"
+
+#pragma mark View/Window Passthrough
+
+/**
+ * This is what our views call to get us to draw to the window
  */
-static errr Term_xtra_cocoa(int n, int v)
+- (void)drawRect:(NSRect)rect inView:(NSView *)view
 {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    AngbandContext* angbandContext = Term->data;
-    
-    errr result = 0;
-    
-    /* Analyze */
-    switch (n)
+    /* Take this opportunity to throttle so we don't flush faster than desired.
+        */
+    BOOL viewInLiveResize = [view inLiveResize];
+    if (! viewInLiveResize) [self throttle];
+
+    /* With a GLayer, use CGContextDrawLayerInRect */
+    CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
+    NSRect bounds = [view bounds];
+    if (viewInLiveResize) CGContextSetInterpolationQuality(context, kCGInterpolationLow);
+    CGContextSetBlendMode(context, kCGBlendModeCopy);
+    CGContextDrawLayerInRect(context, *(CGRect *)&bounds, self.angbandLayer);
+    if (viewInLiveResize) CGContextSetInterpolationQuality(context, kCGInterpolationDefault);
+}
+
+- (BOOL)isOrderedIn
+{
+    return [[[self.angbandViews lastObject] window] isVisible];
+}
+
+- (BOOL)isMainWindow
+{
+    return [[[self.angbandViews lastObject] window] isMainWindow];
+}
+
+- (void)setNeedsDisplay:(BOOL)val
+{
+    for (NSView *angbandView in self.angbandViews)
     {
-            /* Make a noise */
-        case TERM_XTRA_NOISE:
-        {
-            /* Make a noise */
-            NSBeep();
-            
-            /* Success */
-            break;
-        }
-            
-            /* Process random events */
-        case TERM_XTRA_BORED:
-        {
-            // show or hide cocoa windows based on the subwindow flags set by the user
-            AngbandUpdateWindowVisibility();
+        [angbandView setNeedsDisplay:val];
+    }
+}
 
-            /* Process an event */
-            (void)check_events(CHECK_EVENTS_NO_WAIT);
-            
-            /* Success */
-            break;
-        }
-            
-            /* Process pending events */
-        case TERM_XTRA_EVENT:
-        {
-            /* Process an event */
-            (void)check_events(v);
-            
-            /* Success */
-            break;
-        }
-            
-            /* Flush all pending events (if any) */
-        case TERM_XTRA_FLUSH:
-        {
-            /* Hack -- flush all events */
-            while (check_events(CHECK_EVENTS_DRAIN)) /* loop */;
-            
-            /* Success */
-            break;
-        }
-            
-            /* Hack -- Change the "soft level" */
-        case TERM_XTRA_LEVEL:
-        {
-            /* Here we could activate (if requested), but I don't think Angband should be telling us our window order (the user should decide that), so do nothing. */            
-            break;
-        }
-            
-            /* Clear the screen */
-        case TERM_XTRA_CLEAR:
-        {        
-            [angbandContext lockFocus];
-            [[NSColor blackColor] set];
-            NSRect imageRect = {NSZeroPoint, [angbandContext imageSize]};            
-            NSRectFillUsingOperation(imageRect, NSCompositeCopy);
-            [angbandContext unlockFocus];
-            [angbandContext clearOverdrawCache];
-            [angbandContext setNeedsDisplay:YES];
-            /* Success */
-            break;
-        }
-            
-            /* React to changes */
-        case TERM_XTRA_REACT:
-        {
-            /* React to changes */
-            return (Term_xtra_cocoa_react());
-        }
-            
-            /* Delay (milliseconds) */
-        case TERM_XTRA_DELAY:
-        {
-            /* If needed */
-            if (v > 0)
-            {
-                
-                double seconds = v / 1000.;
-                NSDate* date = [NSDate dateWithTimeIntervalSinceNow:seconds];
-                do
-                {
-                    NSEvent* event;
-                    do
-                    {
-                        event = [NSApp nextEventMatchingMask:-1 untilDate:date inMode:NSDefaultRunLoopMode dequeue:YES];
-                        if (event) send_event(event);
-                    } while (event);
-                } while ([date timeIntervalSinceNow] >= 0);
-                
-            }
-            
-            /* Success */
-            break;
-        }
-            
-        case TERM_XTRA_FRESH:
+- (void)setNeedsDisplayInBaseRect:(NSRect)rect
+{
+    for (NSView *angbandView in self.angbandViews)
+    {
+        [angbandView setNeedsDisplayInRect: rect];
+    }
+}
+
+- (void)displayIfNeeded
+{
+    [[self activeView] displayIfNeeded];
+}
+
+- (int)terminalIndex
+{
+       int termIndex = 0;
+
+       for( termIndex = 0; termIndex < ANGBAND_TERM_MAX; termIndex++ )
+       {
+               if( angband_term[termIndex] == self->terminal )
+               {
+                       break;
+               }
+       }
+
+       return termIndex;
+}
+
+- (void)resizeTerminalWithContentRect: (NSRect)contentRect saveToDefaults: (BOOL)saveToDefaults
+{
+    CGFloat newRows = floor(
+       (contentRect.size.height - (self.borderSize.height * 2.0)) /
+       self.tileSize.height);
+    CGFloat newColumns = ceil(
+       (contentRect.size.width - (self.borderSize.width * 2.0)) /
+       self.tileSize.width);
+
+    if (newRows < 1 || newColumns < 1) return;
+    self->_cols = newColumns;
+    self->_rows = newRows;
+    [self.changes resize:self.cols rows:self.rows];
+
+    if( saveToDefaults )
+    {
+        int termIndex = [self terminalIndex];
+        NSArray *terminals = [[NSUserDefaults standardUserDefaults] valueForKey: AngbandTerminalsDefaultsKey];
+
+        if( termIndex < (int)[terminals count] )
         {
-            /* No-op -- see #1669 
-             * [angbandContext displayIfNeeded]; */
-            break;
+            NSMutableDictionary *mutableTerm = [[NSMutableDictionary alloc] initWithDictionary: [terminals objectAtIndex: termIndex]];
+            [mutableTerm setValue: [NSNumber numberWithInteger: self.cols]
+                        forKey: AngbandTerminalColumnsDefaultsKey];
+            [mutableTerm setValue: [NSNumber numberWithInteger: self.rows]
+                        forKey: AngbandTerminalRowsDefaultsKey];
+
+            NSMutableArray *mutableTerminals = [[NSMutableArray alloc] initWithArray: terminals];
+            [mutableTerminals replaceObjectAtIndex: termIndex withObject: mutableTerm];
+
+            [[NSUserDefaults standardUserDefaults] setValue: mutableTerminals forKey: AngbandTerminalsDefaultsKey];
         }
-            
-        default:
-            /* Oops */
-            result = 1;
-            break;
     }
-    
-    [pool drain];
-    
-    /* Oops */
-    return result;
+
+    term *old = Term;
+    Term_activate( self->terminal );
+    Term_resize( self.cols, self.rows );
+    Term_redraw();
+    Term_activate( old );
+}
+
+- (void)setMinimumWindowSize:(int)termIdx
+{
+    NSSize minsize;
+
+    if (termIdx == 0) {
+       minsize.width = 80;
+       minsize.height = 24;
+    } else {
+       minsize.width = 1;
+       minsize.height = 1;
+    }
+    minsize.width =
+       minsize.width * self.tileSize.width + self.borderSize.width * 2.0;
+    minsize.height =
+        minsize.height * self.tileSize.height + self.borderSize.height * 2.0;
+    [[self makePrimaryWindow] setContentMinSize:minsize];
+}
+
+- (void)saveWindowVisibleToDefaults: (BOOL)windowVisible
+{
+       int termIndex = [self terminalIndex];
+       BOOL safeVisibility = (termIndex == 0) ? YES : windowVisible; /* Ensure main term doesn't go away because of these defaults */
+       NSArray *terminals = [[NSUserDefaults standardUserDefaults] valueForKey: AngbandTerminalsDefaultsKey];
+
+       if( termIndex < (int)[terminals count] )
+       {
+               NSMutableDictionary *mutableTerm = [[NSMutableDictionary alloc] initWithDictionary: [terminals objectAtIndex: termIndex]];
+               [mutableTerm setValue: [NSNumber numberWithBool: safeVisibility] forKey: AngbandTerminalVisibleDefaultsKey];
+
+               NSMutableArray *mutableTerminals = [[NSMutableArray alloc] initWithArray: terminals];
+               [mutableTerminals replaceObjectAtIndex: termIndex withObject: mutableTerm];
+
+               [[NSUserDefaults standardUserDefaults] setValue: mutableTerminals forKey: AngbandTerminalsDefaultsKey];
+       }
+}
+
+- (BOOL)windowVisibleUsingDefaults
+{
+       int termIndex = [self terminalIndex];
+
+       if( termIndex == 0 )
+       {
+               return YES;
+       }
+
+       NSArray *terminals = [[NSUserDefaults standardUserDefaults] valueForKey: AngbandTerminalsDefaultsKey];
+       BOOL visible = NO;
+
+       if( termIndex < (int)[terminals count] )
+       {
+               NSDictionary *term = [terminals objectAtIndex: termIndex];
+               NSNumber *visibleValue = [term valueForKey: AngbandTerminalVisibleDefaultsKey];
+
+               if( visibleValue != nil )
+               {
+                       visible = [visibleValue boolValue];
+               }
+       }
+
+       return visible;
+}
+
+#pragma mark -
+#pragma mark NSWindowDelegate Methods
+
+/*- (void)windowWillStartLiveResize: (NSNotification *)notification
+{ 
+}*/ 
+
+- (void)windowDidEndLiveResize: (NSNotification *)notification
+{
+    NSWindow *window = [notification object];
+    NSRect contentRect = [window contentRectForFrameRect: [window frame]];
+    [self resizeTerminalWithContentRect: contentRect saveToDefaults: !(self->inFullscreenTransition)];
+}
+
+/*- (NSSize)windowWillResize: (NSWindow *)sender toSize: (NSSize)frameSize
+{
+} */
+
+- (void)windowWillEnterFullScreen: (NSNotification *)notification
+{
+    self->inFullscreenTransition = YES;
+}
+
+- (void)windowDidEnterFullScreen: (NSNotification *)notification
+{
+    NSWindow *window = [notification object];
+    NSRect contentRect = [window contentRectForFrameRect: [window frame]];
+    self->inFullscreenTransition = NO;
+    [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
+}
+
+- (void)windowWillExitFullScreen: (NSNotification *)notification
+{
+    self->inFullscreenTransition = YES;
+}
+
+- (void)windowDidExitFullScreen: (NSNotification *)notification
+{
+    NSWindow *window = [notification object];
+    NSRect contentRect = [window contentRectForFrameRect: [window frame]];
+    self->inFullscreenTransition = NO;
+    [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
+}
+
+- (void)windowDidBecomeMain:(NSNotification *)notification
+{
+    NSWindow *window = [notification object];
+
+    if( window != self.primaryWindow )
+    {
+        return;
+    }
+
+    int termIndex = [self terminalIndex];
+    NSMenuItem *item = [[[NSApplication sharedApplication] windowsMenu] itemWithTag: AngbandWindowMenuItemTagBase + termIndex];
+    [item setState: NSOnState];
+
+    if( [[NSFontPanel sharedFontPanel] isVisible] )
+    {
+        [[NSFontPanel sharedFontPanel] setPanelFont:self.angbandViewFont
+                                      isMultiple: NO];
+    }
+}
+
+- (void)windowDidResignMain: (NSNotification *)notification
+{
+    NSWindow *window = [notification object];
+
+    if( window != self.primaryWindow )
+    {
+        return;
+    }
+
+    int termIndex = [self terminalIndex];
+    NSMenuItem *item = [[[NSApplication sharedApplication] windowsMenu] itemWithTag: AngbandWindowMenuItemTagBase + termIndex];
+    [item setState: NSOffState];
+}
+
+- (void)windowWillClose: (NSNotification *)notification
+{
+       [self saveWindowVisibleToDefaults: NO];
+}
+
+@end
+
+
+@implementation AngbandView
+
+- (BOOL)isOpaque
+{
+    return YES;
+}
+
+- (BOOL)isFlipped
+{
+    return YES;
+}
+
+- (void)drawRect:(NSRect)rect
+{
+    if (! angbandContext)
+    {
+        /* Draw bright orange, 'cause this ain't right */
+        [[NSColor orangeColor] set];
+        NSRectFill([self bounds]);
+    }
+    else
+    {
+        /* Tell the Angband context to draw into us */
+        [angbandContext drawRect:rect inView:self];
+    }
 }
 
-static errr Term_curs_cocoa(int x, int y)
+- (void)setAngbandContext:(AngbandContext *)context
 {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    AngbandContext *angbandContext = Term->data;
-    
-    /* Get the tile */
-    NSRect rect = [angbandContext rectInImageForTileAtX:x Y:y];
-    
-    /* We'll need to redisplay in that rect */
-    NSRect redisplayRect = rect;
+    angbandContext = context;
+}
 
-    /* Go to the pixel boundaries corresponding to this tile */
-    rect = crack_rect(rect, AngbandScaleIdentity, push_options(x, y));
-    
-    /* Lock focus and draw it */
-    [angbandContext lockFocus];
-    [[NSColor yellowColor] set];
-    NSFrameRectWithWidth(rect, 1);
-    [angbandContext unlockFocus];
-    
-    /* Invalidate that rect */
-    [angbandContext setNeedsDisplayInBaseRect:redisplayRect];
-    
-    /* Success */
-    [pool drain];
-    return 0;
+- (AngbandContext *)angbandContext
+{
+    return angbandContext;
 }
 
-/*
- * Low level graphics (Assumes valid input)
- *
- * Erase "n" characters starting at (x,y)
- */
-static errr Term_wipe_cocoa(int x, int y, int n)
+- (void)setFrameSize:(NSSize)size
 {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    AngbandContext *angbandContext = Term->data;
-    
-    /* clear our overdraw cache for subpixel rendering */
-    [angbandContext clearOverdrawCache];
-    
-    /* Erase the block of characters */
-    NSRect rect = [angbandContext rectInImageForTileAtX:x Y:y];
-    
-    /* Maybe there's more than one */
-    if (n > 1) rect = NSUnionRect(rect, [angbandContext rectInImageForTileAtX:x + n-1 Y:y]);
-    
-    /* Lock focus and clear */
-    [angbandContext lockFocus];
-    [[NSColor blackColor] set];
-    NSRectFill(rect);
-    [angbandContext unlockFocus];    
-    [angbandContext setNeedsDisplayInBaseRect:rect];
-    
-    [pool drain];
-    
-    /* Success */
-    return (0);
+    BOOL changed = ! NSEqualSizes(size, [self frame].size);
+    [super setFrameSize:size];
+    if (changed) [angbandContext angbandViewDidScale:self];
 }
-#if 0
-static void draw_image_tile(CGImageRef image, NSRect srcRect, NSRect dstRect, NSCompositingOperation op)
+
+- (void)viewWillStartLiveResize
 {
-    /* When we use high-quality resampling to draw a tile, pixels from outside the tile may bleed in, causing graphics artifacts. Work around that. */
-    CGImageRef subimage = CGImageCreateWithImageInRect(image, *(CGRect *)&srcRect);
-    NSGraphicsContext *context = [NSGraphicsContext currentContext];
-    [context setCompositingOperation:op];
-    CGContextDrawImage([context graphicsPort], *(CGRect *)&dstRect, subimage);
-    CGImageRelease(subimage);
+    [angbandContext viewWillStartLiveResize:self];
 }
 
-static errr Term_pict_cocoa(int x, int y, int n, const int *ap,
-                            const wchar_t *cp, const int *tap,
-                            const wchar_t *tcp)
+- (void)viewDidEndLiveResize
 {
-    
-    /* Paranoia: Bail if we don't have a current graphics mode */
-    if (! current_graphics_mode) return -1;
-    
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    AngbandContext* angbandContext = Term->data;
+    [angbandContext viewDidEndLiveResize:self];
+}
+
+@end
+
+/**
+ * Delay handling of double-clicked savefiles
+ */
+Boolean open_when_ready = FALSE;
+
 
-    /* Indicate that we have a picture here (and hence this should not be overdrawn by Term_text_cocoa) */
-    angbandContext->charOverdrawCache[y * angbandContext->cols + x] = NO_OVERDRAW;
-    
-    /* Lock focus */
-    [angbandContext lockFocus];
-    
-    NSRect destinationRect = [angbandContext rectInImageForTileAtX:x Y:y];
 
-    /* Expand the rect to every touching pixel to figure out what to redisplay */
-    NSRect redisplayRect = crack_rect(destinationRect, AngbandScaleIdentity, PUSH_RIGHT | PUSH_TOP | PUSH_BOTTOM | PUSH_LEFT);
+/**
+ * ------------------------------------------------------------------------
+ * Some generic functions
+ * ------------------------------------------------------------------------ */
+
+/**
+ * Sets an Angband color at a given index
+ */
+static void set_color_for_index(int idx)
+{
+    u16b rv, gv, bv;
     
-    /* Expand our destinationRect */
-    destinationRect = crack_rect(destinationRect, AngbandScaleIdentity, push_options(x, y));
+    /* Extract the R,G,B data */
+    rv = angband_color_table[idx][1];
+    gv = angband_color_table[idx][2];
+    bv = angband_color_table[idx][3];
     
-    /* Scan the input */
-    int i;
-    int graf_width = current_graphics_mode->cell_width;
-    int graf_height = current_graphics_mode->cell_height;
+    CGContextSetRGBFillColor([[NSGraphicsContext currentContext] graphicsPort], rv/255., gv/255., bv/255., 1.);
+}
 
-    for (i = 0; i < n; i++)
+/**
+ * Remember the current character in UserDefaults so we can select it by
+ * default next time.
+ */
+static void record_current_savefile(void)
+{
+    NSString *savefileString = [[NSString stringWithCString:savefile encoding:NSMacOSRomanStringEncoding] lastPathComponent];
+    if (savefileString)
     {
-        
-        int a = *ap++;
-        wchar_t c = *cp++;
-        
-        int ta = *tap++;
-        wchar_t tc = *tcp++;
-        
-        
-        /* Graphics -- if Available and Needed */
-        if (use_graphics && (a & 0x80) && (c & 0x80))
-        {
-            int col, row;
-            int t_col, t_row;
-            
-
-            /* Primary Row and Col */
-            row = ((byte)a & 0x7F) % pict_rows;
-            col = ((byte)c & 0x7F) % pict_cols;
-            
-            NSRect sourceRect;
-            sourceRect.origin.x = col * graf_width;
-            sourceRect.origin.y = row * graf_height;
-            sourceRect.size.width = graf_width;
-            sourceRect.size.height = graf_height;
-            
-            /* Terrain Row and Col */
-            t_row = ((byte)ta & 0x7F) % pict_rows;
-            t_col = ((byte)tc & 0x7F) % pict_cols;
-            
-            NSRect terrainRect;
-            terrainRect.origin.x = t_col * graf_width;
-            terrainRect.origin.y = t_row * graf_height;
-            terrainRect.size.width = graf_width;
-            terrainRect.size.height = graf_height;
-            
-            /* Transparency effect. We really want to check current_graphics_mode->alphablend, but as of this writing that's never set, so we do something lame.  */
-            //if (current_graphics_mode->alphablend)
-            if (graf_width > 8 || graf_height > 8)
-            {
-                draw_image_tile(pict_image, terrainRect, destinationRect, NSCompositeCopy);
-                draw_image_tile(pict_image, sourceRect, destinationRect, NSCompositeSourceOver); 
-            }
-            else
-            {
-                draw_image_tile(pict_image, sourceRect, destinationRect, NSCompositeCopy);
-            }
-        }        
+        NSUserDefaults *angbandDefs = [NSUserDefaults angbandDefaults];
+        [angbandDefs setObject:savefileString forKey:@"SaveFile"];
     }
-    
-    [angbandContext unlockFocus];
-    [angbandContext setNeedsDisplayInBaseRect:redisplayRect];
-    
-    [pool drain];
-    
-    /* Success */
-    return (0);
 }
+
+
+#ifdef JP
+/**
+ * Convert a two-byte EUC-JP encoded character (both *cp and (*cp + 1) are in
+ * the range, 0xA1-0xFE, or *cp is 0x8E) to a utf16 value in the native byte
+ * ordering.
+ */
+static wchar_t convert_two_byte_eucjp_to_utf16_native(const char *cp)
+{
+    NSString* str = [[NSString alloc] initWithBytes:cp length:2
+                                     encoding:NSJapaneseEUCStringEncoding];
+    wchar_t result = [str characterAtIndex:0];
+    str = nil;
+    return result;
+}
+#endif /* JP */
+
+
+/**
+ * ------------------------------------------------------------------------
+ * Support for the "z-term.c" package
+ * ------------------------------------------------------------------------ */
+
+
+/**
+ * Initialize a new Term
+ */
+static void Term_init_cocoa(term *t)
+{
+    @autoreleasepool {
+       AngbandContext *context = [[AngbandContext alloc] init];
+
+       /* Give the term ownership of the context */
+       t->data = (void *)CFBridgingRetain(context);
+
+       /* Handle graphics */
+       t->higher_pict = !! use_graphics;
+       t->always_pict = FALSE;
+
+       NSDisableScreenUpdates();
+
+       /*
+        * Figure out the frame autosave name based on the index of this term
+        */
+       NSString *autosaveName = nil;
+       int termIdx;
+       for (termIdx = 0; termIdx < ANGBAND_TERM_MAX; termIdx++)
+       {
+           if (angband_term[termIdx] == t)
+           {
+               autosaveName =
+                   [NSString stringWithFormat:@"AngbandTerm-%d", termIdx];
+               break;
+           }
+       }
+
+       /* Set its font. */
+       NSString *fontName =
+           [[NSUserDefaults angbandDefaults]
+               stringForKey:[NSString stringWithFormat:@"FontName-%d", termIdx]];
+       if (! fontName) fontName = [[AngbandContext defaultFont] fontName];
+
+       /*
+        * Use a smaller default font for the other windows, but only if the
+        * font hasn't been explicitly set.
+        */
+       float fontSize =
+           (termIdx > 0) ? 10.0 : [[AngbandContext defaultFont] pointSize];
+       NSNumber *fontSizeNumber =
+           [[NSUserDefaults angbandDefaults]
+               valueForKey: [NSString stringWithFormat: @"FontSize-%d", termIdx]];
+
+       if( fontSizeNumber != nil )
+       {
+           fontSize = [fontSizeNumber floatValue];
+       }
+
+       [context setSelectionFont:[NSFont fontWithName:fontName size:fontSize]
+                adjustTerminal: NO];
+
+       NSArray *terminalDefaults =
+           [[NSUserDefaults standardUserDefaults]
+               valueForKey: AngbandTerminalsDefaultsKey];
+       NSInteger rows = 24;
+       NSInteger columns = 80;
+
+       if( termIdx < (int)[terminalDefaults count] )
+       {
+           NSDictionary *term = [terminalDefaults objectAtIndex: termIdx];
+           NSInteger defaultRows =
+               [[term valueForKey: AngbandTerminalRowsDefaultsKey]
+                   integerValue];
+           NSInteger defaultColumns =
+               [[term valueForKey: AngbandTerminalColumnsDefaultsKey]
+                   integerValue];
+
+           if (defaultRows > 0) rows = defaultRows;
+           if (defaultColumns > 0) columns = defaultColumns;
+       }
+
+       context.cols = columns;
+       context.rows = rows;
+       [context.changes resize:columns rows:rows];
+
+       /* Get the window */
+       NSWindow *window = [context makePrimaryWindow];
+
+       /* Set its title and, for auxiliary terms, tentative size */
+       NSString *title =
+           [NSString stringWithCString:angband_term_name[termIdx]
+#ifdef JP
+                     encoding:NSJapaneseEUCStringEncoding
+#else
+                     encoding:NSMacOSRomanStringEncoding
 #endif
-/*
- * Low level graphics.  Assumes valid input.
- *
- * Draw several ("n") chars, with an attr, at a given location.
+           ];
+       [window setTitle:title];
+       [context setMinimumWindowSize:termIdx];
+
+       /*
+        * If this is the first term, and we support full screen (Mac OS X Lion
+        * or later), then allow it to go full screen (sweet). Allow other
+        * terms to be FullScreenAuxilliary, so they can at least show up.
+        * Unfortunately in Lion they don't get brought to the full screen
+        * space; but they would only make sense on multiple displays anyways
+        * so it's not a big loss.
+        */
+       if ([window respondsToSelector:@selector(toggleFullScreen:)])
+       {
+           NSWindowCollectionBehavior behavior = [window collectionBehavior];
+           behavior |=
+               (termIdx == 0 ?
+                NSWindowCollectionBehaviorFullScreenPrimary :
+                NSWindowCollectionBehaviorFullScreenAuxiliary);
+           [window setCollectionBehavior:behavior];
+       }
+
+       /* No Resume support yet, though it would not be hard to add */
+       if ([window respondsToSelector:@selector(setRestorable:)])
+       {
+           [window setRestorable:NO];
+       }
+
+       /* default window placement */ {
+           static NSRect overallBoundingRect;
+
+           if( termIdx == 0 )
+           {
+               /*
+                * This is a bit of a trick to allow us to display multiple
+                * windows in the "standard default" window position in OS X:
+                * the upper center of the screen.  The term sizes set in
+                * AngbandAppDelegate's loadPrefs() are based on a 5-wide by
+                * 3-high grid, with the main term being 4/5 wide by 2/3 high
+                * (hence the scaling to find what the containing rect would
+                * be).
+                */
+               NSRect originalMainTermFrame = [window frame];
+               NSRect scaledFrame = originalMainTermFrame;
+               scaledFrame.size.width *= 5.0 / 4.0;
+               scaledFrame.size.height *= 3.0 / 2.0;
+               scaledFrame.size.width += 1.0; /* spacing between window columns */
+               scaledFrame.size.height += 1.0; /* spacing between window rows */
+               [window setFrame: scaledFrame  display: NO];
+               [window center];
+               overallBoundingRect = [window frame];
+               [window setFrame: originalMainTermFrame display: NO];
+           }
+
+           static NSRect mainTermBaseRect;
+           NSRect windowFrame = [window frame];
+
+           if( termIdx == 0 )
+           {
+               /*
+                * The height and width adjustments were determined
+                * experimentally, so that the rest of the windows line up
+                * nicely without overlapping.
+                */
+               windowFrame.size.width += 7.0;
+               windowFrame.size.height += 9.0;
+               windowFrame.origin.x = NSMinX( overallBoundingRect );
+               windowFrame.origin.y =
+                   NSMaxY( overallBoundingRect ) - NSHeight( windowFrame );
+               mainTermBaseRect = windowFrame;
+           }
+           else if( termIdx == 1 )
+           {
+               windowFrame.origin.x = NSMinX( mainTermBaseRect );
+               windowFrame.origin.y =
+                   NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0;
+           }
+           else if( termIdx == 2 )
+           {
+               windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0;
+               windowFrame.origin.y =
+                   NSMaxY( mainTermBaseRect ) - NSHeight( windowFrame );
+           }
+           else if( termIdx == 3 )
+           {
+               windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0;
+               windowFrame.origin.y =
+                   NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0;
+           }
+           else if( termIdx == 4 )
+           {
+               windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0;
+               windowFrame.origin.y = NSMinY( mainTermBaseRect );
+           }
+           else if( termIdx == 5 )
+           {
+               windowFrame.origin.x =
+                   NSMinX( mainTermBaseRect ) + NSWidth( windowFrame ) + 1.0;
+               windowFrame.origin.y =
+                   NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0;
+           }
+
+           [window setFrame: windowFrame display: NO];
+       }
+
+       /* Override the default frame above if the user has adjusted windows in
+        * the past */
+       if (autosaveName) [window setFrameAutosaveName:autosaveName];
+
+       /*
+        * Tell it about its term. Do this after we've sized it so that the
+        * sizing doesn't trigger redrawing and such.
+        */
+       [context setTerm:t];
+
+       /*
+        * Only order front if it's the first term. Other terms will be ordered
+        * front from AngbandUpdateWindowVisibility(). This is to work around a
+        * problem where Angband aggressively tells us to initialize terms that
+        * don't do anything!
+        */
+       if (t == angband_term[0])
+           [context.primaryWindow makeKeyAndOrderFront: nil];
+
+       NSEnableScreenUpdates();
+
+       /* Set "mapped" flag */
+       t->mapped_flag = true;
+    }
+}
+
+
+
+/**
+ * Nuke an old Term
  */
-static errr Term_text_cocoa(int x, int y, int n, byte_hack a, cptr cp)
+static void Term_nuke_cocoa(term *t)
 {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    @autoreleasepool {
+        AngbandContext *context = (__bridge AngbandContext*) (t->data);
+       if (context)
+       {
+           /* Tell the context to get rid of its windows, etc. */
+           [context dispose];
 
-    /* Subpixel rendering looks really nice!  Unfortunately, drawing a string like this:
-     .@
-     causes subpixels to extend slightly into the region 'owned' by the period.  This means that when the user presses right,
-     those subpixels 'owned' by the period above do not get redrawn by Angband, so we leave little blue and red subpixel turds
-     all over the screen.  Turning off subpixel rendering fixes this, as does increasing the font advance by a pixel, but that is
-     ugly.  Our hack solution is to remember all of the characters we draw as well as their locations and colors (in charOverdrawCache),
-     and then re-blit the previous and next character (if any).
-     */
-    
-    NSRect redisplayRect = NSZeroRect;
-    AngbandContext* angbandContext = Term->data;
-    
-    /* record our data in our cache */
-    int start = y * angbandContext->cols + x;
-    int location;
-    for (location = 0; location < n; location++) {
-        angbandContext->charOverdrawCache[start + location] = cp[location];
-        angbandContext->attrOverdrawCache[start + location] = a;
+           /* Balance our CFBridgingRetain from when we created it */
+           CFRelease(t->data);
+
+           /* Done with it */
+           t->data = NULL;
+       }
     }
-    
-    /* Focus on our layer */
-    [angbandContext lockFocus];
+}
 
-    /* Starting pixel */
-    NSRect charRect = [angbandContext rectInImageForTileAtX:x Y:y];
-    
-    const CGFloat tileWidth = angbandContext->tileSize.width;
+/**
+ * Returns the CGImageRef corresponding to an image with the given name in the
+ * resource directory, transferring ownership to the caller
+ */
+static CGImageRef create_angband_image(NSString *path)
+{
+    CGImageRef decodedImage = NULL, result = NULL;
     
-    /* erase behind us */
-    unsigned leftPushOptions = push_options(x, y);
-    unsigned rightPushOptions = push_options(x + n - 1, y);
-    leftPushOptions &= ~ PUSH_RIGHT;
-    rightPushOptions &= ~ PUSH_LEFT;
-#if 0    
-    switch (a / MAX_COLORS) {
-    case BG_BLACK:
-           [[NSColor blackColor] set];
-           break;
-    case BG_SAME:
-           set_color_for_index(a % MAX_COLORS);
-           break;
-    case BG_DARK:
-           set_color_for_index(TERM_SHADE);
-           break;
+    /* Try using ImageIO to load the image */
+    if (path)
+    {
+        NSURL *url = [[NSURL alloc] initFileURLWithPath:path isDirectory:NO];
+        if (url)
+        {
+            NSDictionary *options = [[NSDictionary alloc] initWithObjectsAndKeys:(id)kCFBooleanTrue, kCGImageSourceShouldCache, nil];
+            CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, (CFDictionaryRef)options);
+            if (source)
+            {
+                /* We really want the largest image, but in practice there's
+                                * only going to be one */
+                decodedImage = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)options);
+                CFRelease(source);
+            }
+        }
     }
-#endif    
-    NSRect rectToClear = charRect;
-    rectToClear.size.width = tileWidth * n;
-    NSRectFill(crack_rect(rectToClear, AngbandScaleIdentity, leftPushOptions | rightPushOptions));
     
-    NSFont *selectionFont = [[angbandContext selectionFont] screenFont];
-    [selectionFont set];
-    
-    /* Handle overdraws */
-    const int overdraws[2] = {x-1, x+n}; //left, right
-    int i;
-    for (i=0; i < 2; i++) {
-        int overdrawX = overdraws[i];
+    /* Draw the sucker to defeat ImageIO's weird desire to cache and decode on
+        * demand. Our images aren't that big! */
+    if (decodedImage)
+    {
+        size_t width = CGImageGetWidth(decodedImage), height = CGImageGetHeight(decodedImage);
         
-        // Nothing to overdraw if we're at an edge
-        if (overdrawX >= 0 && (size_t)overdrawX < angbandContext->cols)
-        {
-            wchar_t previouslyDrawnVal = angbandContext->charOverdrawCache[y * angbandContext->cols + overdrawX];
-           //int previouslyDrawnAttr = angbandContext->attrOverdrawCache[y * angbandContext->cols + overdrawX];
-            // Don't overdraw if it's not text
-            if (previouslyDrawnVal != NO_OVERDRAW)
-            {
-                NSRect overdrawRect = [angbandContext rectInImageForTileAtX:overdrawX Y:y];
-                NSRect expandedRect = crack_rect(overdrawRect, AngbandScaleIdentity, push_options(overdrawX, y));
-#if 0                
-                // Make sure we redisplay it
-               switch (previouslyDrawnAttr / MAX_COLORS) {
-               case BG_BLACK:
-                   [[NSColor blackColor] set];
-                   break;
-               case BG_SAME:
-                   set_color_for_index(previouslyDrawnAttr % MAX_COLORS);
-                   break;
-               case BG_DARK:
-                   set_color_for_index(TERM_SHADE);
-                   break;
+        /* Compute our own bitmap info */
+        CGBitmapInfo imageBitmapInfo = CGImageGetBitmapInfo(decodedImage);
+        CGBitmapInfo contextBitmapInfo = kCGBitmapByteOrderDefault;
+        
+        switch (imageBitmapInfo & kCGBitmapAlphaInfoMask) {
+            case kCGImageAlphaNone:
+            case kCGImageAlphaNoneSkipLast:
+            case kCGImageAlphaNoneSkipFirst:
+                /* No alpha */
+                contextBitmapInfo |= kCGImageAlphaNone;
+                break;
+            default:
+                /* Some alpha, use premultiplied last which is most efficient. */
+                contextBitmapInfo |= kCGImageAlphaPremultipliedLast;
+                break;
+        }
+
+        /* Draw the source image flipped, since the view is flipped */
+        CGContextRef ctx = CGBitmapContextCreate(NULL, width, height, CGImageGetBitsPerComponent(decodedImage), CGImageGetBytesPerRow(decodedImage), CGImageGetColorSpace(decodedImage), contextBitmapInfo);
+       if (ctx) {
+           CGContextSetBlendMode(ctx, kCGBlendModeCopy);
+           CGContextTranslateCTM(ctx, 0.0, height);
+           CGContextScaleCTM(ctx, 1.0, -1.0);
+           CGContextDrawImage(
+               ctx, CGRectMake(0, 0, width, height), decodedImage);
+           result = CGBitmapContextCreateImage(ctx);
+           CFRelease(ctx);
+       }
+
+        CGImageRelease(decodedImage);
+    }
+    return result;
+}
+
+/**
+ * React to changes
+ */
+static errr Term_xtra_cocoa_react(void)
+{
+    /* Don't actually switch graphics until the game is running */
+    if (!initialized || !game_in_progress) return (-1);
+
+    @autoreleasepool {
+       AngbandContext *angbandContext =
+           (__bridge AngbandContext*) (Term->data);
+
+       /* Handle graphics */
+       int expected_graf_mode = (current_graphics_mode) ?
+           current_graphics_mode->grafID : GRAPHICS_NONE;
+       if (graf_mode_req != expected_graf_mode)
+       {
+           graphics_mode *new_mode;
+           if (graf_mode_req != GRAPHICS_NONE) {
+               new_mode = get_graphics_mode(graf_mode_req);
+           } else {
+               new_mode = NULL;
+           }
+
+           /* Get rid of the old image. CGImageRelease is NULL-safe. */
+           CGImageRelease(pict_image);
+           pict_image = NULL;
+
+           /* Try creating the image if we want one */
+           if (new_mode != NULL)
+           {
+               NSString *img_path =
+                   [NSString stringWithFormat:@"%s/%s", new_mode->path, new_mode->file];
+               pict_image = create_angband_image(img_path);
+
+               /* If we failed to create the image, revert to ASCII. */
+               if (! pict_image) {
+                   new_mode = NULL;
+                   if (use_bigtile) {
+                       arg_bigtile = FALSE;
+                   }
+                   [[NSUserDefaults angbandDefaults]
+                       setInteger:GRAPHICS_NONE
+                       forKey:AngbandGraphicsDefaultsKey];
+
+                   NSString *msg = NSLocalizedStringWithDefaultValue(
+                       @"Error.TileSetLoadFailed",
+                       AngbandMessageCatalog,
+                       [NSBundle mainBundle],
+                       @"Failed to Load Tile Set",
+                       @"Alert text for failed tile set load");
+                   NSString *info = NSLocalizedStringWithDefaultValue(
+                       @"Error.TileSetRevertToASCII",
+                       AngbandMessageCatalog,
+                       [NSBundle mainBundle],
+                       @"Could not load the tile set.  Switched back to ASCII.",
+                       @"Alert informative message for failed tile set load");
+                   NSAlert *alert = [[NSAlert alloc] init];
+
+                   alert.messageText = msg;
+                   alert.informativeText = info;
+                   [alert runModal];
                }
-#endif
-                NSRectFill(expandedRect);
-                redisplayRect = NSUnionRect(redisplayRect, expandedRect);
-                
-                // Redraw text if we have any
-                if (previouslyDrawnVal != 0)
-                {
-                    byte color = angbandContext->attrOverdrawCache[y * angbandContext->cols + overdrawX]; 
-                    
-                    set_color_for_index(color);
-                    [angbandContext drawWChar:previouslyDrawnVal inRect:overdrawRect];
-                }
-            }
-        }
-    }
-    
-    /* Set the color */
-    set_color_for_index(a % MAX_COLORS);
-    
-    /* Draw each */
-    NSRect rectToDraw = charRect;
-    for (i=0; i < n; i++) {
-        [angbandContext drawWChar:cp[i] inRect:rectToDraw];
-        rectToDraw.origin.x += tileWidth;
+           }
+
+           /* Record what we did */
+           use_graphics = new_mode ? new_mode->grafID : 0;
+           ANGBAND_GRAF = (new_mode ? new_mode->graf : "ascii");
+           current_graphics_mode = new_mode;
+
+           /*
+            * Enable or disable higher picts. Note: this should be done for
+            * all terms.
+            */
+           angbandContext->terminal->higher_pict = !! use_graphics;
+
+           if (pict_image && current_graphics_mode)
+           {
+               /*
+                * Compute the row and column count via the image height and
+                * width.
+                */
+               pict_rows = (int)(CGImageGetHeight(pict_image) /
+                                 current_graphics_mode->cell_height);
+               pict_cols = (int)(CGImageGetWidth(pict_image) /
+                                 current_graphics_mode->cell_width);
+           }
+           else
+           {
+               pict_rows = 0;
+               pict_cols = 0;
+           }
+
+           /* Reset visuals */
+           if (arg_bigtile == use_bigtile)
+           {
+               reset_visuals();
+           }
+       }
+
+       if (arg_bigtile != use_bigtile) {
+           /* Reset visuals */
+           reset_visuals();
+
+           Term_activate(angband_term[0]);
+           Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
+       }
     }
 
-    
-    // Invalidate what we just drew
-    NSRect drawnRect = charRect;
-    drawnRect.size.width = tileWidth * n;
-    redisplayRect = NSUnionRect(redisplayRect, drawnRect);
-    
-    [angbandContext unlockFocus];    
-    [angbandContext setNeedsDisplayInBaseRect:redisplayRect];
-    
-    [pool drain];
-    
     /* Success */
     return (0);
 }
-#if 0
-/* From the Linux mbstowcs(3) man page:
- *   If dest is NULL, n is ignored, and the conversion  proceeds  as  above,
- *   except  that  the converted wide characters are not written out to mem‐
- *   ory, and that no length limit exists.
+
+
+/**
+ * Draws one tile as a helper function for Term_xtra_cocoa_fresh().
  */
-static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n)
+static void draw_image_tile(
+    NSGraphicsContext* nsContext,
+    CGContextRef cgContext,
+    CGImageRef image,
+    NSRect srcRect,
+    NSRect dstRect,
+    NSCompositingOperation op)
 {
-    int i;
-    int count = 0;
+    /* Flip the source rect since the source image is flipped */
+    CGAffineTransform flip = CGAffineTransformIdentity;
+    flip = CGAffineTransformTranslate(flip, 0.0, CGImageGetHeight(image));
+    flip = CGAffineTransformScale(flip, 1.0, -1.0);
+    CGRect flippedSourceRect =
+       CGRectApplyAffineTransform(NSRectToCGRect(srcRect), flip);
 
-    /* Unicode code point to UTF-8
-     *  0x0000-0x007f:   0xxxxxxx
-     *  0x0080-0x07ff:   110xxxxx 10xxxxxx
-     *  0x0800-0xffff:   1110xxxx 10xxxxxx 10xxxxxx
-     * 0x10000-0x1fffff: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
-     * Note that UTF-16 limits Unicode to 0x10ffff. This code is not
-     * endian-agnostic.
+    /*
+     * When we use high-quality resampling to draw a tile, pixels from outside
+     * the tile may bleed in, causing graphics artifacts. Work around that.
      */
-    for (i = 0; i < n || dest == NULL; i++) {
-        if ((src[i] & 0x80) == 0) {
-            if (dest != NULL) dest[count] = src[i];
-            if (src[i] == 0) break;
-        } else if ((src[i] & 0xe0) == 0xc0) {
-            if (dest != NULL) dest[count] = 
-                            (((unsigned char)src[i] & 0x1f) << 6)| 
-                            ((unsigned char)src[i+1] & 0x3f);
-            i++;
-        } else if ((src[i] & 0xf0) == 0xe0) {
-            if (dest != NULL) dest[count] = 
-                            (((unsigned char)src[i] & 0x0f) << 12) | 
-                            (((unsigned char)src[i+1] & 0x3f) << 6) |
-                            ((unsigned char)src[i+2] & 0x3f);
-            i += 2;
-        } else if ((src[i] & 0xf8) == 0xf0) {
-            if (dest != NULL) dest[count] = 
-                            (((unsigned char)src[i] & 0x0f) << 18) | 
-                            (((unsigned char)src[i+1] & 0x3f) << 12) |
-                            (((unsigned char)src[i+2] & 0x3f) << 6) |
-                            ((unsigned char)src[i+3] & 0x3f);
-            i += 3;
-        } else {
-            /* Found an invalid multibyte sequence */
-            return (size_t)-1;
-        }
-        count++;
-    }
-    return count;
+    CGImageRef subimage =
+       CGImageCreateWithImageInRect(image, flippedSourceRect);
+    [nsContext setCompositingOperation:op];
+    CGContextDrawImage(cgContext, NSRectToCGRect(dstRect), subimage);
+    CGImageRelease(subimage);
 }
-#endif
-/* Post a nonsense event so that our event loop wakes up */
-static void wakeup_event_loop(void)
+
+
+/**
+ * This is a helper function for Term_xtra_cocoa_fresh():  look before a block
+ * of text on a row to see if the bounds for rendering and clipping need to be
+ * extended.
+ */
+static void query_before_text(
+    PendingTermChanges *tc, int iy, int npre, int* pclip, int* prend)
 {
-    /* Big hack - send a nonsense event to make us update */
-    NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:NULL subtype:AngbandEventWakeup data1:0 data2:0];
-    [NSApp postEvent:event atStart:NO];
+    int start = *prend;
+    int i = start - 1;
+
+    while (1) {
+       if (i < 0 || i < start - npre) {
+           break;
+       }
+       enum PendingCellChangeType ctype = [tc getCellChangeType:i row:iy];
+
+       if (ctype == CELL_CHANGE_TILE) {
+           /*
+            * The cell has been rendered with a tile.  Do not want to modify
+            * its contents so the clipping and rendering region can not be
+            * extended.
+            */
+           break;
+       } else if (ctype == CELL_CHANGE_NONE) {
+           /*
+            * It has not changed (or using big tile mode and it is within
+            * a changed tile but is not the left cell for that tile) so
+            * inquire what it is.
+            */
+           TERM_COLOR a[2];
+           char c[2];
+
+           Term_what(i, iy, a + 1, c + 1);
+           if (use_graphics && (a[1] & 0x80) && (c[1] & 0x80)) {
+               /*
+                * It is an unchanged location rendered with a tile.  Do not
+                * want to modify its contents so the clipping and rendering
+                * region can not be extended.
+                */
+               break;
+           }
+           if (use_bigtile && i > 0) {
+               Term_what(i - 1, iy, a, c);
+               if (use_graphics && (a[0] & 0x80) && (c[0] & 0x80)) {
+                   /*
+                    * It is the right cell of a location rendered with a tile.
+                    * Do not want to modify its contents so the clipping and
+                    * rendering region can not be exteded.
+                    */
+                   break;
+               }
+           }
+           /*
+            * It is unchanged text.  A character from the changed region
+            * may have extended into it so render it to clear that.
+            */
+#ifdef JP
+           /* Check to see if it is the second part of a kanji character. */
+           if (i > 0) {
+               Term_what(i - 1, iy, a, c);
+               if (iskanji(c)) {
+                   [tc markTextChange:i-1 row:iy
+                       glyph:convert_two_byte_eucjp_to_utf16_native(c)
+                       color:a[0] isDoubleWidth:YES];
+                   *pclip = i - 1;
+                   *prend = i - 1;
+                   --i;
+               } else {
+                   [tc markTextChange:i row:iy
+                       glyph:c[1] color:a[1] isDoubleWidth:NO];
+                   *pclip = i;
+                   *prend = i;
+               }
+           } else {
+               [tc markTextChange:i row:iy
+                   glyph:c[1] color:a[1] isDoubleWidth:NO];
+               *pclip = i;
+               *prend = i;
+           }
+#else
+           [tc markTextChange:i row:iy
+               glyph:c[1] color:a[1] isDoubleWidth:NO];
+           *pclip = i;
+           *prend = i;
+#endif
+           --i;
+       } else {
+           /*
+            * The cell has been wiped or had changed text rendered.  Do
+            * not need to render.  Can extend the clipping rectangle into it.
+            */
+           *pclip = i;
+           --i;
+       }
+    }
 }
 
 
-/*
- * Create and initialize window number "i"
+/**
+ * This is a helper function for Term_xtra_cocoa_fresh():  look after a block
+ * of text on a row to see if the bounds for rendering and clipping need to be
+ * extended.
  */
-static term *term_data_link(int i)
+static void query_after_text(
+    PendingTermChanges *tc, int iy, int npost, int* pclip, int* prend)
 {
-    NSArray *terminalDefaults = [[NSUserDefaults standardUserDefaults] valueForKey: AngbandTerminalsDefaultsKey];
-    NSInteger rows = 24;
-    NSInteger columns = 80;
+    int end = *prend;
+    int i = end + 1;
+    int ncol = tc.columnCount;
 
-    if( i < (int)[terminalDefaults count] )
-    {
-        NSDictionary *term = [terminalDefaults objectAtIndex: i];
-        rows = [[term valueForKey: AngbandTerminalRowsDefaultsKey] integerValue];
-        columns = [[term valueForKey: AngbandTerminalColumnsDefaultsKey] integerValue];
-    }
+    while (1) {
+       if (i >= ncol) {
+           break;
+       }
 
-    /* Allocate */
-    term *newterm = ZNEW(term);
+       enum PendingCellChangeType ctype = [tc getCellChangeType:i row:iy];
 
-    /* Initialize the term */
-    term_init(newterm, columns, rows, 256 /* keypresses, for some reason? */);
-    
-    /* Differentiate between BS/^h, Tab/^i, etc. */
-    //newterm->complex_input = TRUE;
+       /*
+        * Be willing to consolidate this block with the one after it.  This
+        * logic should be sufficient to avoid redraws of the region between
+        * changed blocks of text if angbandContext.nColPre is zero or one.
+        * For larger values of nColPre, would need to do something more to
+        * avoid extra redraws.
+        */
+       if (i > end + npost && ctype != CELL_CHANGE_TEXT &&
+           ctype != CELL_CHANGE_WIPE) {
+           break;
+       }
 
-    /* Use a "software" cursor */
-    newterm->soft_cursor = TRUE;
-    
-    /* Erase with "white space" */
-    newterm->attr_blank = TERM_WHITE;
-    newterm->char_blank = ' ';
-    
-    /* Prepare the init/nuke hooks */
-    newterm->init_hook = Term_init_cocoa;
-    newterm->nuke_hook = Term_nuke_cocoa;
-    
-    /* Prepare the function hooks */
-    newterm->xtra_hook = Term_xtra_cocoa;
-    newterm->wipe_hook = Term_wipe_cocoa;
-    newterm->curs_hook = Term_curs_cocoa;
-    newterm->text_hook = Term_text_cocoa;
-    //newterm->pict_hook = Term_pict_cocoa;
-    //newterm->mbcs_hook = Term_mbcs_cocoa;
-    
-    /* Global pointer */
-    angband_term[i] = newterm;
-    
-    return newterm;
+       if (ctype == CELL_CHANGE_TILE) {
+           /*
+            * The cell has been rendered with a tile.  Do not want to modify
+            * its contents so the clipping and rendering region can not be
+            * extended.
+            */
+           break;
+       } else if (ctype == CELL_CHANGE_NONE) {
+           /* It has not changed so inquire what it is. */
+           TERM_COLOR a[2];
+           char c[2];
+
+           Term_what(i, iy, a, c);
+           if (use_graphics && (a[0] & 0x80) && (c[0] & 0x80)) {
+               /*
+                * It is an unchanged location rendered with a tile.  Do not
+                * want to modify its contents so the clipping and rendering
+                * region can not be extended.
+                */
+               break;
+           }
+           /*
+            * It is unchanged text.  A character from the changed region
+            * may have extended into it so render it to clear that.
+            */
+#ifdef JP
+           /* Check to see if it is the first part of a kanji character. */
+           if (i < ncol - 1) {
+               Term_what(i + 1, iy, a + 1, c + 1);
+               if (iskanji(c)) {
+                   [tc markTextChange:i row:iy
+                       glyph:convert_two_byte_eucjp_to_utf16_native(c)
+                       color:a[0] isDoubleWidth:YES];
+                   *pclip = i + 1;
+                   *prend = i + 1;
+                   ++i;
+               } else {
+                   [tc markTextChange:i row:iy
+                       glyph:c[0] color:a[0] isDoubleWidth:NO];
+                   *pclip = i;
+                   *prend = i;
+               }
+           } else {
+               [tc markTextChange:i row:iy
+                   glyph:c[0] color:a[0] isDoubleWidth:NO];
+               *pclip = i;
+               *prend = i;
+           }
+#else
+           [tc markTextChange:i row:iy
+               glyph:c[0] color:a[0] isDoubleWidth:NO];
+           *pclip = i;
+           *prend = i;
+#endif
+           ++i;
+       } else {
+           /*
+            * Have come to another region of changed text or another region
+            * to wipe.  Combine the regions to minimize redraws.
+            */
+           *pclip = i;
+           *prend = i;
+           end = i;
+           ++i;
+       }
+    }
 }
 
-/*
- * Load preferences from preferences file for current host+current user+
- * current application.
+
+/**
+ * Draw the pending changes saved in angbandContext->changes.
  */
-static void load_prefs()
+static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext)
 {
-    NSUserDefaults *defs = [NSUserDefaults angbandDefaults];
-    
-    /* Make some default defaults */
-    NSMutableArray *defaultTerms = [[NSMutableArray alloc] init];
-    NSDictionary *standardTerm = @{
-                                   AngbandTerminalRowsDefaultsKey : @24,
-                                   AngbandTerminalColumnsDefaultsKey : @80,
-                                   };
-    
-    for( NSUInteger i = 0; i < 9; i++ )
-    {
-        [defaultTerms addObject: standardTerm];
+    int graf_width, graf_height, alphablend;
+
+    if (angbandContext.changes.hasTileChanges) {
+       CGImageAlphaInfo ainfo = CGImageGetAlphaInfo(pict_image);
+
+       graf_width = current_graphics_mode->cell_width;
+       graf_height = current_graphics_mode->cell_height;
+       /*
+        * As of this writing, a value of zero for
+        * current_graphics_mode->alphablend can mean either that the tile set
+        * doesn't have an alpha channel or it does but it only takes on values
+        * of 0 or 255.  For main-cocoa.m's purposes, the latter is rendered
+        * using the same procedure as if alphablend was nonzero.  The former
+        * is handled differently, but alphablend doesn't distinguish it from
+        * the latter.  So ignore alphablend and directly test whether an
+        * alpha channel is present.
+        */
+       alphablend = (ainfo & (kCGImageAlphaPremultipliedFirst |
+                              kCGImageAlphaPremultipliedLast)) ? 1 : 0;
+    } else {
+       graf_width = 0;
+       graf_height = 0;
+       alphablend = 0;
     }
 
-    NSDictionary *defaults = [[NSDictionary alloc] initWithObjectsAndKeys:
-                              @"Menlo", @"FontName",
-                              [NSNumber numberWithFloat:13.f], @"FontSize",
-                              [NSNumber numberWithInt:60], @"FramesPerSecond",
-                              [NSNumber numberWithBool:YES], @"AllowSound",
-                              [NSNumber numberWithInt:GRAPHICS_NONE], @"GraphicsID",
-                              defaultTerms, AngbandTerminalsDefaultsKey,
-                              nil];
-    [defs registerDefaults:defaults];
-    [defaults release];
-    [defaultTerms release];
-    
-    /* preferred graphics mode */
-    graf_mode_req = [defs integerForKey:@"GraphicsID"];
-    
-    /* use sounds */
-    allow_sounds = [defs boolForKey:@"AllowSound"];
-    
-    /* fps */
-    frames_per_second = [[NSUserDefaults angbandDefaults] integerForKey:@"FramesPerSecond"];
-    
-    /* font */
-    default_font = [[NSFont fontWithName:[defs valueForKey:@"FontName-0"] size:[defs floatForKey:@"FontSize-0"]] retain];
-    if (! default_font) default_font = [[NSFont fontWithName:@"Menlo" size:13.] retain];
-}
+    CGContextRef ctx = [angbandContext lockFocus];
 
-/* Arbitary limit on number of possible samples per event */
-#define MAX_SAMPLES            8
+    if (angbandContext.changes.hasTextChanges ||
+       angbandContext.changes.hasWipeChanges) {
+       NSFont *selectionFont = [angbandContext.angbandViewFont screenFont];
+       [selectionFont set];
+    }
 
-/* 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;
+    for (int iy = angbandContext.changes.firstChangedRow;
+        iy <= angbandContext.changes.lastChangedRow;
+        ++iy) {
+       /* Skip untouched rows. */
+       if ([angbandContext.changes getFirstChangedColumnInRow:iy] >
+           [angbandContext.changes getLastChangedColumnInRow:iy]) {
+           continue;
+       }
+       int ix = [angbandContext.changes getFirstChangedColumnInRow:iy];
+       int ixmax = [angbandContext.changes getLastChangedColumnInRow:iy];
 
-/* Array of event sound structs */
-//static sound_sample_list samples[MSG_MAX];
+       while (1) {
+           int jx;
 
+           if (ix > ixmax) {
+               break;
+           }
 
-/*
- * 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)
-{
-#if 0
-       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)
-       {
-               NSLog(@"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];
-    
-    /* Use a dictionary to unique sounds, so we can share NSSounds across multiple events */
-    NSMutableDictionary *sound_dict = [NSMutableDictionary dictionary];
-    
-       /*
-        * 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
+           switch ([angbandContext.changes getCellChangeType:ix row:iy]) {
+           case CELL_CHANGE_NONE:
+               ++ix;
+               break;
+
+           case CELL_CHANGE_TILE:
                {
-                       next_token = NULL;
+                   /*
+                    * Because changes are made to the compositing mode, save
+                    * the incoming value.
+                    */
+                   NSGraphicsContext *nsContext =
+                       [NSGraphicsContext currentContext];
+                   NSCompositingOperation op = nsContext.compositingOperation;
+                   int step = (use_bigtile) ? 2 : 1;
+
+                   jx = ix;
+                   while (jx <= ixmax &&
+                          [angbandContext.changes getCellChangeType:jx row:iy]
+                          == CELL_CHANGE_TILE) {
+                       NSRect destinationRect =
+                           [angbandContext rectInImageForTileAtX:jx Y:iy];
+                       struct PendingTileChange tileIndices =
+                           [angbandContext.changes
+                                          getCellTileChange:jx row:iy];
+                       NSRect sourceRect, terrainRect;
+
+                       destinationRect.size.width *= step;
+                       sourceRect.origin.x = graf_width * tileIndices.fgdCol;
+                       sourceRect.origin.y = graf_height * tileIndices.fgdRow;
+                       sourceRect.size.width = graf_width;
+                       sourceRect.size.height = graf_height;
+                       terrainRect.origin.x = graf_width * tileIndices.bckCol;
+                       terrainRect.origin.y = graf_height *
+                           tileIndices.bckRow;
+                       terrainRect.size.width = graf_width;
+                       terrainRect.size.height = graf_height;
+                       if (alphablend) {
+                           draw_image_tile(
+                               nsContext,
+                               ctx,
+                               pict_image,
+                               terrainRect,
+                               destinationRect,
+                               NSCompositeCopy);
+                           /*
+                            * Skip drawing the foreground if it is the same
+                            * as the background.
+                            */
+                           if (sourceRect.origin.x != terrainRect.origin.x ||
+                               sourceRect.origin.y != terrainRect.origin.y) {
+                               draw_image_tile(
+                                   nsContext,
+                                   ctx,
+                                   pict_image,
+                                   sourceRect,
+                                   destinationRect,
+                                   NSCompositeSourceOver);
+                           }
+                       } else {
+                           draw_image_tile(
+                               nsContext,
+                               ctx,
+                               pict_image,
+                               sourceRect,
+                               destinationRect,
+                               NSCompositeCopy);
+                       }
+                       jx += step;
+                   }
+
+                   [nsContext setCompositingOperation:op];
+
+                   NSRect rect =
+                       [angbandContext rectInImageForTileAtX:ix Y:iy];
+                   rect.size.width =
+                       angbandContext.tileSize.width * (jx - ix);
+                   [angbandContext setNeedsDisplayInBaseRect:rect];
                }
-        
+               ix = jx;
+               break;
+
+           case CELL_CHANGE_WIPE:
+           case CELL_CHANGE_TEXT:
                /*
-                * Now we find all the sample names and add them one by one
+                * For a wiped region, treat it as if it had text (the only
+                * loss if it was not is some extra work rendering
+                * neighboring unchanged text).
                 */
-               while (cur_token)
+               jx = ix + 1;
+               while (1) {
+                   if (jx >= angbandContext.cols) {
+                       break;
+                   }
+                   enum PendingCellChangeType ctype =
+                       [angbandContext.changes getCellChangeType:jx row:iy];
+                   if (ctype != CELL_CHANGE_TEXT &&
+                       ctype != CELL_CHANGE_WIPE) {
+                       break;
+                   }
+                   ++jx;
+               }
                {
-                       int num = samples[event].num;
-            
-                       /* Don't allow too many samples */
-                       if (num >= MAX_SAMPLES) break;
-            
-            NSString *token_string = [NSString stringWithUTF8String:cur_token];
-            NSSound *sound = [sound_dict objectForKey:token_string];
-            
-            if (! sound)
-            {
-                /* We have to load the sound. 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 */
-                    sound = [[[NSSound alloc] initWithContentsOfFile:[NSString stringWithUTF8String:path] byReference:YES] autorelease];
-                    if (sound) [sound_dict setObject:sound forKey:token_string];
-                }
-            }
-            
-            /* Store it if we loaded it */
-            if (sound)
-            {
-                samples[event].sound[num] = [sound retain];
-                
-                /* 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;
-                               }
+                   int isclip = ix;
+                   int ieclip = jx - 1;
+                   int isrend = ix;
+                   int ierend = jx - 1;
+                   int set_color = 1;
+                   TERM_COLOR alast = 0;
+                   NSRect r;
+                   int k;
+
+                   query_before_text(
+                       angbandContext.changes,
+                       iy,
+                       angbandContext.nColPre,
+                       &isclip,
+                       &isrend);
+                   query_after_text(
+                       angbandContext.changes,
+                       iy,
+                       angbandContext.nColPost,
+                       &ieclip,
+                       &ierend
+                   );
+                   ix = ierend + 1;
+
+                   /* Save the state since the clipping will be modified. */
+                   CGContextSaveGState(ctx);
+
+                   /* Clear the area where rendering will be done. */
+                   r = [angbandContext rectInImageForTileAtX:isrend Y:iy];
+                   r.size.width = angbandContext.tileSize.width *
+                       (ierend - isrend + 1);
+                   [[NSColor blackColor] set];
+                   NSRectFill(r);
+
+                   /*
+                    * Clear the current path so it does not affect clipping.
+                    * Then set the clipping rectangle.  Using
+                    * CGContextSetTextDrawingMode() to include clipping does
+                    * not appear to be necessary on 10.14 and is actually
+                    * detrimental:  when displaying more than one character,
+                    * only the first is visible.
+                    */
+                   CGContextBeginPath(ctx);
+                   r = [angbandContext rectInImageForTileAtX:isclip Y:iy];
+                   r.size.width = angbandContext.tileSize.width *
+                       (ieclip - isclip + 1);
+                   CGContextClipToRect(ctx, r);
+
+                   /* Render. */
+                   k = isrend;
+                   while (k <= ierend) {
+                       if ([angbandContext.changes getCellChangeType:k row:iy]
+                           == CELL_CHANGE_WIPE) {
+                           /* Skip over since no rendering is necessary. */
+                           ++k;
+                           continue;
+                       }
+
+                       struct PendingTextChange textChange =
+                           [angbandContext.changes getCellTextChange:k
+                                          row:iy];
+                       int anew = textChange.color % MAX_COLORS;
+                       if (set_color || alast != anew) {
+                           set_color = 0;
+                           alast = anew;
+                           set_color_for_index(anew);
+                       }
+
+                       NSRect rectToDraw =
+                           [angbandContext rectInImageForTileAtX:k Y:iy];
+                       if (textChange.doubleWidth) {
+                           rectToDraw.size.width *= 2.0;
+                           [angbandContext drawWChar:textChange.glyph
+                                           inRect:rectToDraw context:ctx];
+                           k += 2;
+                       } else {
+                           [angbandContext drawWChar:textChange.glyph
+                                           inRect:rectToDraw context:ctx];
+                           ++k;
                        }
+                   }
+
+                   /*
+                    * Inform the context that the area in the clipping
+                    * rectangle needs to be redisplayed.
+                    */
+                   [angbandContext setNeedsDisplayInBaseRect:r];
+
+                   CGContextRestoreGState(ctx);
                }
+               break;
+           }
        }
-    
-       /* Release the autorelease pool */
-       [autorelease_pool release];
-    
-       /* Close the file */
-       file_close(fff);
-#endif
+    }
+
+    if (angbandContext.changes.cursorColumn >= 0 &&
+       angbandContext.changes.cursorRow >= 0) {
+       NSRect rect = [angbandContext
+                         rectInImageForTileAtX:angbandContext.changes.cursorColumn
+                         Y:angbandContext.changes.cursorRow];
+
+       rect.size.width *= angbandContext.changes.cursorWidth;
+       rect.size.height *= angbandContext.changes.cursorHeight;
+       [[NSColor yellowColor] set];
+       NSFrameRectWithWidth(rect, 1);
+       /* Invalidate that rect */
+       [angbandContext setNeedsDisplayInBaseRect:rect];
+    }
+
+    [angbandContext unlockFocus];
 }
-#if 0
-/*
- * Play sound effects asynchronously.  Select a sound from any available
- * for the required event, and bridge to Cocoa to play it.
+
+
+/**
+ * Do a "special thing"
  */
-static void play_sound(int event)
-{    
-    /* Maybe block it */
-    if (! allow_sounds) return;
-    
-       /* Paranoia */
-       if (event < 0 || event >= MSG_MAX) return;
-    
-    /* Load sounds just-in-time (once) */
-    static BOOL loaded = NO;
-    if (! loaded)
-    {
-        loaded = YES;
-        load_sounds();
+static errr Term_xtra_cocoa(int n, int v)
+{
+    errr result = 0;
+    @autoreleasepool {
+       AngbandContext* angbandContext =
+           (__bridge AngbandContext*) (Term->data);
+
+       /* Analyze */
+       switch (n) {
+           /* Make a noise */
+        case TERM_XTRA_NOISE:
+           NSBeep();
+           break;
+
+           /*  Make a sound */
+        case TERM_XTRA_SOUND:
+           play_sound(v);
+           break;
+
+           /* Process random events */
+        case TERM_XTRA_BORED:
+           /*
+            * Show or hide cocoa windows based on the subwindow flags set by
+            * the user.
+            */
+           AngbandUpdateWindowVisibility();
+           /* Process an event */
+           (void)check_events(CHECK_EVENTS_NO_WAIT);
+           break;
+
+           /* Process pending events */
+        case TERM_XTRA_EVENT:
+           /* Process an event */
+           (void)check_events(v);
+           break;
+
+           /* Flush all pending events (if any) */
+        case TERM_XTRA_FLUSH:
+           /* Hack -- flush all events */
+           while (check_events(CHECK_EVENTS_DRAIN)) /* loop */;
+
+           break;
+
+           /* Hack -- Change the "soft level" */
+        case TERM_XTRA_LEVEL:
+           /*
+            * Here we could activate (if requested), but I don't think
+            * Angband should be telling us our window order (the user
+            * should decide that), so do nothing.
+            */
+           break;
+
+           /* Clear the screen */
+        case TERM_XTRA_CLEAR:
+           {
+               [angbandContext lockFocus];
+               [[NSColor blackColor] set];
+               NSRect imageRect = {NSZeroPoint, [angbandContext imageSize]};
+               NSRectFillUsingOperation(imageRect, NSCompositeCopy);
+               [angbandContext unlockFocus];
+               [angbandContext setNeedsDisplay:YES];
+               /* Success */
+               break;
+           }
+
+           /* React to changes */
+        case TERM_XTRA_REACT:
+           result = Term_xtra_cocoa_react();
+           break;
+
+           /* Delay (milliseconds) */
+        case TERM_XTRA_DELAY:
+           /* If needed */
+           if (v > 0) {
+               double seconds = v / 1000.;
+               NSDate* date = [NSDate dateWithTimeIntervalSinceNow:seconds];
+               do {
+                   NSEvent* event;
+                   do {
+                       event = [NSApp nextEventMatchingMask:-1
+                                      untilDate:date
+                                      inMode:NSDefaultRunLoopMode
+                                      dequeue:YES];
+                       if (event) send_event(event);
+                   } while (event);
+               } while ([date timeIntervalSinceNow] >= 0);
+           }
+           break;
+
+           /* Draw the pending changes. */
+        case TERM_XTRA_FRESH:
+           Term_xtra_cocoa_fresh(angbandContext);
+           [angbandContext.changes clear];
+            break;
+
+        default:
+            /* Oops */
+            result = 1;
+            break;
+       }
     }
-    
-    /* 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 drain];
+
+    return result;
 }
-#endif
-/*
- * 
+
+static errr Term_curs_cocoa(TERM_LEN x, TERM_LEN y)
+{
+    AngbandContext *angbandContext = (__bridge AngbandContext*) (Term->data);
+
+    [angbandContext.changes markCursor:x row:y];
+
+    /* Success */
+    return 0;
+}
+
+/**
+ * Draw a cursor that's two tiles wide.  For Japanese, that's used when
+ * the cursor points at a kanji character, irregardless of whether operating
+ * in big tile mode.
  */
-static void init_windows(void)
+static errr Term_bigcurs_cocoa(TERM_LEN x, TERM_LEN y)
 {
-    /* Create the main window */
-    term *primary = term_data_link(0);
-    
-    /* Prepare to create any additional windows */
-    int i;
-    for (i=1; i < ANGBAND_TERM_MAX; i++) {
-        term_data_link(i);
-    }
-    
-    /* Activate the primary term */
-    Term_activate(primary);
+    AngbandContext *angbandContext = (__bridge AngbandContext*) (Term->data);
+
+    [angbandContext.changes markBigCursor:x row:y cellsWide:2 cellsHigh:1];
+
+    /* Success */
+    return 0;
 }
 
-#if 0
-/*
- *    Run the event loop and return a gameplay status to init_angband
+/**
+ * Low level graphics (Assumes valid input)
+ *
+ * Erase "n" characters starting at (x,y)
  */
-static errr get_cmd_init(void)
-{     
-    if (cmd.command == CMD_NULL)
-    {
-        /* Prompt the user */ 
-        prt("[Choose 'New' or 'Open' from the 'File' menu]", 23, 17);
-        Term_fresh();
-        
-        while (cmd.command == CMD_NULL) {
-            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-            NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES];
-            if (event) [NSApp sendEvent:event];
-            [pool drain];        
-        }
-    }
-    
-    /* Push the command to the game. */
-    cmd_insert_s(&cmd);
-    
-    return 0; 
-} 
+static errr Term_wipe_cocoa(TERM_LEN x, TERM_LEN y, int n)
+{
+    AngbandContext *angbandContext = (__bridge AngbandContext*) (Term->data);
 
+    [angbandContext.changes markWipeRange:x row:y n:n];
+
+    /* Success */
+    return 0;
+}
 
-/* Return a command */
-static errr cocoa_get_cmd(cmd_context context, bool wait)
+static errr Term_pict_cocoa(TERM_LEN x, TERM_LEN y, int n,
+                           TERM_COLOR *ap, concptr cp,
+                           const TERM_COLOR *tap, concptr tcp)
 {
-    if (context == CMD_INIT) 
-        return get_cmd_init();
-    else 
-        return textui_get_cmd(context, wait);
+    /* Paranoia: Bail if we don't have a current graphics mode */
+    if (! current_graphics_mode) return -1;
+
+    AngbandContext* angbandContext = (__bridge AngbandContext*) (Term->data);
+    int step = (use_bigtile) ? 2 : 1;
+
+    /*
+     * In bigtile mode, it is sufficient that the bounds for the modified
+     * region only encompass the left cell for the region affected by the
+     * tile and that only that cell has to have the details of the changes.
+     */
+    for (int i = x; i < x + n * step; i += step) {
+       TERM_COLOR a = *ap++;
+       char c = *cp++;
+       TERM_COLOR ta = *tap++;
+       char tc = *tcp++;
+
+       if (use_graphics && (a & 0x80) && (c & 0x80)) {
+           [angbandContext.changes markTileChange:i row:y
+                          foregroundCol:((byte)c & 0x7F) % pict_cols
+                          foregroundRow:((byte)a & 0x7F) % pict_rows
+                          backgroundCol:((byte)tc & 0x7F) % pict_cols
+                          backgroundRow:((byte)ta & 0x7F) % pict_rows];
+       }
+    }
+
+    /* Success */
+    return (0);
 }
+
+/**
+ * Low level graphics.  Assumes valid input.
+ *
+ * Draw several ("n") chars, with an attr, at a given location.
+ */
+static errr Term_text_cocoa(
+    TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr cp)
+{
+    AngbandContext* angbandContext = (__bridge AngbandContext*) (Term->data);
+    int i = 0;
+
+    while (i < n) {
+#ifdef JP
+       if (iskanji(*cp)) {
+           if (i == n - 1) {
+               /*
+                * The second byte of the character is past the end.  Ignore
+                * the character.
+                */
+               break;
+           } else {
+               [angbandContext.changes markTextChange:i+x row:y
+                              glyph:convert_two_byte_eucjp_to_utf16_native(cp)
+                              color:a isDoubleWidth:YES];
+               cp += 2;
+               i += 2;
+           }
+       } else {
+           [angbandContext.changes markTextChange:i+x row:y
+                          glyph:*cp color:a isDoubleWidth:NO];
+           ++cp;
+           ++i;
+       }
+#else
+       [angbandContext.changes markTextChange:i+x row:y
+                      glyph:*cp color:a isDoubleWidth:NO];
+       ++cp;
+       ++i;
 #endif
-/* Return the directory into which we put data (save and config) */
-static NSString *get_data_directory(void)
+    }
+
+    /* Success */
+    return 0;
+}
+
+/**
+ * Post a nonsense event so that our event loop wakes up
+ */
+static void wakeup_event_loop(void)
 {
-    return [@"~/Documents/PosChengband/" stringByExpandingTildeInPath];
+    /* Big hack - send a nonsense event to make us update */
+    NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:NULL subtype:AngbandEventWakeup data1:0 data2:0];
+    [NSApp postEvent:event atStart:NO];
 }
 
-/*
+
+/**
  * Handle the "open_when_ready" flag
  */
 static void handle_open_when_ready(void)
@@ -2570,12 +3575,12 @@ static void handle_open_when_ready(void)
         game_in_progress = TRUE;
         
         /* Wait for a keypress */
-        pause_line(23);
+        pause_line(Term->hgt - 1);
     }
 }
 
 
-/*
+/**
  * Handle quit_when_ready, by Peter Ammon,
  * slightly modified to check inkey_flag.
  */
@@ -2583,28 +3588,31 @@ static void quit_calmly(void)
 {
     /* Quit immediately if game's not started */
     if (!game_in_progress || !character_generated) quit(NULL);
-    
+
     /* Save the game and Quit (if it's safe) */
     if (inkey_flag)
     {
-        /* Hack -- Forget messages */
+        /* Hack -- Forget messages and term */
         msg_flag = FALSE;
-        
+               Term->mapped_flag = FALSE;
+
         /* Save the game */
         do_cmd_save_game(FALSE);
         record_current_savefile();
-        
-        
+
         /* Quit */
         quit(NULL);
     }
-    
+
     /* Wait until inkey_flag is set */
 }
 
 
 
-/* returns YES if we contain an AngbandView (and hence should direct our events to Angband) */
+/**
+ * Returns YES if we contain an AngbandView (and hence should direct our events
+ * to Angband)
+ */
 static BOOL contains_angband_view(NSView *view)
 {
     if ([view isKindOfClass:[AngbandView class]]) return YES;
@@ -2614,17 +3622,85 @@ static BOOL contains_angband_view(NSView *view)
     return NO;
 }
 
-/* Encodes an NSEvent Angband-style, or forwards it along.  Returns YES if the event was sent to Angband, NO if Cocoa (or nothing) handled it */
+
+/**
+ * Queue mouse presses if they occur in the map section of the main window.
+ */
+static void AngbandHandleEventMouseDown( NSEvent *event )
+{
+#if 0
+       AngbandContext *angbandContext = [[[event window] contentView] angbandContext];
+       AngbandContext *mainAngbandContext =
+           (__bridge AngbandContext*) (angband_term[0]->data);
+
+       if (mainAngbandContext.primaryWindow &&
+           [[event window] windowNumber] ==
+           [mainAngbandContext.primaryWindow windowNumber])
+       {
+               int cols, rows, x, y;
+               Term_get_size(&cols, &rows);
+               NSSize tileSize = angbandContext.tileSize;
+               NSSize border = angbandContext.borderSize;
+               NSPoint windowPoint = [event locationInWindow];
+
+               /* Adjust for border; add border height because window origin is at
+                * bottom */
+               windowPoint = NSMakePoint( windowPoint.x - border.width, windowPoint.y + border.height );
+
+               NSPoint p = [[[event window] contentView] convertPoint: windowPoint fromView: nil];
+               x = floor( p.x / tileSize.width );
+               y = floor( p.y / tileSize.height );
+
+               /* Being safe about this, since xcode doesn't seem to like the
+                * bool_hack stuff */
+               BOOL displayingMapInterface = ((int)inkey_flag != 0);
+
+               /* Sidebar plus border == thirteen characters; top row is reserved. */
+               /* Coordinates run from (0,0) to (cols-1, rows-1). */
+               BOOL mouseInMapSection = (x > 13 && x <= cols - 1 && y > 0  && y <= rows - 2);
+
+               /* If we are displaying a menu, allow clicks anywhere; if we are
+                * displaying the main game interface, only allow clicks in the map
+                * section */
+               if (!displayingMapInterface || (displayingMapInterface && mouseInMapSection))
+               {
+                       /* [event buttonNumber] will return 0 for left click,
+                        * 1 for right click, but this is safer */
+                       int button = ([event type] == NSLeftMouseDown) ? 1 : 2;
+
+#ifdef KC_MOD_ALT
+                       NSUInteger eventModifiers = [event modifierFlags];
+                       byte angbandModifiers = 0;
+                       angbandModifiers |= (eventModifiers & NSShiftKeyMask) ? KC_MOD_SHIFT : 0;
+                       angbandModifiers |= (eventModifiers & NSControlKeyMask) ? KC_MOD_CONTROL : 0;
+                       angbandModifiers |= (eventModifiers & NSAlternateKeyMask) ? KC_MOD_ALT : 0;
+                       button |= (angbandModifiers & 0x0F) << 4; /* encode modifiers in the button number (see Term_mousepress()) */
+#endif
+
+                       Term_mousepress(x, y, button);
+               }
+       }
+#endif
+
+       /* Pass click through to permit focus change, resize, etc. */
+       [NSApp sendEvent:event];
+}
+
+
+
+/**
+ * Encodes an NSEvent Angband-style, or forwards it along.  Returns YES if the
+ * event was sent to Angband, NO if Cocoa (or nothing) handled it */
 static BOOL send_event(NSEvent *event)
 {
-        
+
     /* If the receiving window is not an Angband window, then do nothing */
     if (! contains_angband_view([[event window] contentView]))
     {
         [NSApp sendEvent:event];
         return NO;
     }
-    
+
     /* Analyze the event */
     switch ([event type])
     {
@@ -2646,65 +3722,40 @@ static BOOL send_event(NSEvent *event)
             
             
             /* Extract some modifiers */
-            //int mc = !! (modifiers & NSControlKeyMask);
-            //int ms = !! (modifiers & NSShiftKeyMask);
-            //int mo = !! (modifiers & NSAlternateKeyMask);
-            //int mx = !! (modifiers & NSCommandKeyMask);
+            int mc = !! (modifiers & NSControlKeyMask);
+            int ms = !! (modifiers & NSShiftKeyMask);
+            int mo = !! (modifiers & NSAlternateKeyMask);
             int kp = !! (modifiers & NSNumericPadKeyMask);
             
             
             /* Get the Angband char corresponding to this unichar */
             unichar c = [[event characters] characterAtIndex:0];
             char ch;
-            switch (c) {
-                /* Note that NSNumericPadKeyMask is set if any of the arrow
-                 * keys are pressed. We don't want KC_MOD_KEYPAD set for
-                 * those. See #1662 for more details. */
-                case NSUpArrowFunctionKey: ch = ARROW_UP; kp = 0; break;
-                case NSDownArrowFunctionKey: ch = ARROW_DOWN; kp = 0; break;
-                case NSLeftArrowFunctionKey: ch = ARROW_LEFT; kp = 0; break;
-                case NSRightArrowFunctionKey: ch = ARROW_RIGHT; kp = 0; break;
-                case NSF1FunctionKey: ch = KC_F1; break;
-                case NSF2FunctionKey: ch = KC_F2; break;
-                case NSF3FunctionKey: ch = KC_F3; break;
-                case NSF4FunctionKey: ch = KC_F4; break;
-                case NSF5FunctionKey: ch = KC_F5; break;
-                case NSF6FunctionKey: ch = KC_F6; break;
-                case NSF7FunctionKey: ch = KC_F7; break;
-                case NSF8FunctionKey: ch = KC_F8; break;
-                case NSF9FunctionKey: ch = KC_F9; break;
-                case NSF10FunctionKey: ch = KC_F10; break;
-                case NSF11FunctionKey: ch = KC_F11; break;
-                case NSF12FunctionKey: ch = KC_F12; break;
-                case NSF13FunctionKey: ch = KC_F13; break;
-                case NSF14FunctionKey: ch = KC_F14; break;
-                case NSF15FunctionKey: ch = KC_F15; break;
-                case NSHelpFunctionKey: ch = KC_HELP; break;
-                case NSHomeFunctionKey: ch = KC_HOME; break;
-                case NSPageUpFunctionKey: ch = KC_PGUP; break;
-                case NSPageDownFunctionKey: ch = KC_PGDOWN; break;
-                case NSBeginFunctionKey: ch = KC_BEGIN; break;
-                case NSEndFunctionKey: ch = KC_END; break;
-                case NSInsertFunctionKey: ch = KC_INSERT; break;
-                case NSDeleteFunctionKey: ch = KC_DELETE; break;
-                case NSPauseFunctionKey: ch = KC_PAUSE; break;
-                case NSBreakFunctionKey: ch = KC_BREAK; break;
-                    
-                default:
-                    if (c <= 0x7F)
-                        ch = (char)c;
-                    else
-                        ch = '\0';
-                    break;
+           /*
+            * Have anything from the numeric keypad generate a macro
+            * trigger so that shift or control modifiers can be passed.
+            */
+           if (c <= 0x7F && !kp)
+           {
+               ch = (char) c;
+           }
+           else {
+               /*
+                * The rest of Hengband uses Angband 2.7's or so key handling:
+                * so for the rest do something like the encoding that
+                * main-win.c does:  send a macro trigger with the Unicode
+                * value encoded into printable ASCII characters.
+                */
+               ch = '\0';
             }
             
             /* override special keys */
             switch([event keyCode]) {
-                case kVK_Return: ch = KC_ENTER; break;
-                case kVK_Escape: ch = ESCAPE; break;
-                case kVK_Tab: ch = KC_TAB; break;
-                case kVK_Delete: ch = KC_BACKSPACE; break;
-                case kVK_ANSI_KeypadEnter: ch = KC_ENTER; kp = TRUE; break;
+                case kVK_Return: ch = '\r'; break;
+                case kVK_Escape: ch = 27; break;
+                case kVK_Tab: ch = '\t'; break;
+                case kVK_Delete: ch = '\b'; break;
+               case kVK_ANSI_KeypadEnter: ch = '\r'; kp = TRUE; break;
             }
 
             /* Hide the mouse pointer */
@@ -2713,68 +3764,44 @@ static BOOL send_event(NSEvent *event)
             /* Enqueue it */
             if (ch != '\0')
             {
-                
-                /* Enqueue the keypress */
-#ifdef KC_MOD_ALT
-                byte mods = 0;
-                if (mo) mods |= KC_MOD_ALT;
-                if (mx) mods |= KC_MOD_META;
-                if (mc && MODS_INCLUDE_CONTROL(ch)) mods |= KC_MOD_CONTROL;
-                if (ms && MODS_INCLUDE_SHIFT(ch)) mods |= KC_MOD_SHIFT;
-                if (kp) mods |= KC_MOD_KEYPAD;
-                Term_keypress(ch, mods);
-#else
                 Term_keypress(ch);
-#endif
             }
+           else
+           {
+               /*
+                * Could use the hexsym global but some characters overlap with
+                * those used to indicate modifiers.
+                */
+               const char encoded[16] = {
+                   '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
+                   'c', 'd', 'e', 'f'
+               };
+
+               /* Begin the macro trigger. */
+               Term_keypress(31);
+
+               /* Send the modifiers. */
+               if (mc) Term_keypress('C');
+               if (ms) Term_keypress('S');
+               if (mo) Term_keypress('O');
+               if (kp) Term_keypress('K');
+
+               do {
+                   Term_keypress(encoded[c & 0xF]);
+                   c >>= 4;
+               } while (c > 0);
+
+               /* End the macro trigger. */
+               Term_keypress(13);
+           }
             
             break;
         }
             
         case NSLeftMouseDown:
-        {
-            /* Queue mouse presses if they occur in the map section
-             * of the main window.
-             */
-#if 0
-            AngbandContext *angbandContext =
-                [[[event window] contentView] angbandContext];
-            AngbandContext *mainAngbandContext =
-                angband_term[0]->data;
-
-            if (mainAngbandContext->primaryWindow &&
-                [[event window] windowNumber] ==
-                [mainAngbandContext->primaryWindow windowNumber])
-            {
-                int cols, rows, x, y;
-                Term_get_size(&cols, &rows);
-
-                /* Term_mousepress() expects the origin (0,0) at the upper
-                 * left, while locationInWindow puts the origin at the lower
-                 * left.
-                 */
-                NSPoint p = [event locationInWindow];
-                NSSize tileSize = angbandContext->tileSize;
-                x = p.x/(tileSize.width * AngbandScaleIdentity.width);
-                y = rows - p.y/(tileSize.height * AngbandScaleIdentity.height);
-                    
-                /* Sidebar plus border == thirteen characters;
-                 * top row is reserved, and bottom row may have mouse buttons.
-                 * Coordinates run from (0,0) to (cols-1, rows-1).
-                 */
-                if ((x > 13 && x <= cols - 1 &&
-                     y > 0  && y <= rows - 2) ||
-                    (OPT(mouse_buttons) && y == rows - 1 && 
-                     x >= COL_MAP && x < COL_MAP + button_get_length()))
-                {
-                    Term_mousepress(x, y, 1);
-                }
-            }
-#endif
-            /* Pass click through to permit focus change, resize, etc. */
-            [NSApp sendEvent:event];
+               case NSRightMouseDown:
+                       AngbandHandleEventMouseDown(event);
             break;
-        }
 
         case NSApplicationDefined:
         {
@@ -2788,131 +3815,212 @@ static BOOL send_event(NSEvent *event)
         default:
             [NSApp sendEvent:event];
             return YES;
-            break;
     }
     return YES;
 }
 
-/*
+/**
  * Check for Events, return TRUE if we process any
  */
 static BOOL check_events(int wait)
-{ 
-    
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    
-    /* Handles the quit_when_ready flag */
-    if (quit_when_ready) quit_calmly();
-    
-    NSDate* endDate;
-    if (wait == CHECK_EVENTS_WAIT) endDate = [NSDate distantFuture];
-    else endDate = [NSDate distantPast];
-    
-    NSEvent* event;
-    for (;;) {
-        if (quit_when_ready)
-        {
-            /* send escape events until we quit */
-            Term_keypress(0x1B);
-            [pool drain];
-            return false;
-        }
-        else {
-            event = [NSApp nextEventMatchingMask:-1 untilDate:endDate inMode:NSDefaultRunLoopMode dequeue:YES];
-            if (! event)
-            {
-                [pool drain];
-                return FALSE;
-            }
-            if (send_event(event)) break;
-        }
+{
+    BOOL result = YES;
+
+    @autoreleasepool {
+       /* Handles the quit_when_ready flag */
+       if (quit_when_ready) quit_calmly();
+
+       NSDate* endDate;
+       if (wait == CHECK_EVENTS_WAIT) endDate = [NSDate distantFuture];
+       else endDate = [NSDate distantPast];
+
+       NSEvent* event;
+       for (;;) {
+           if (quit_when_ready)
+           {
+               /* send escape events until we quit */
+               Term_keypress(0x1B);
+               result = NO;
+               break;
+           }
+           else {
+               event = [NSApp nextEventMatchingMask:-1 untilDate:endDate
+                              inMode:NSDefaultRunLoopMode dequeue:YES];
+               if (! event) {
+                   result = NO;
+                   break;
+               }
+               if (send_event(event)) break;
+           }
+       }
     }
-    
-    [pool drain];
-    
-    /* Something happened */
-    return YES;
-    
+
+    return result;
 }
 
-/*
+/**
  * Hook to tell the user something important
  */
 static void hook_plog(const char * str)
 {
     if (str)
     {
-        NSString *string = [NSString stringWithCString:str encoding:NSMacOSRomanStringEncoding];
-        NSRunAlertPanel(@"Danger Will Robinson", @"%@", @"OK", nil, nil, string);
+       NSString *msg = NSLocalizedStringWithDefaultValue(
+           @"Warning", AngbandMessageCatalog, [NSBundle mainBundle],
+           @"Warning", @"Alert text for generic warning");
+        NSString *info = [NSString stringWithCString:str
+#ifdef JP
+                                  encoding:NSJapaneseEUCStringEncoding
+#else
+                                  encoding:NSMacOSRomanStringEncoding
+#endif
+       ];
+       NSAlert *alert = [[NSAlert alloc] init];
+
+       alert.messageText = msg;
+       alert.informativeText = info;
+       [alert runModal];
     }
 }
 
 
-/*
+/**
  * Hook to tell the user something, and then quit
  */
 static void hook_quit(const char * str)
 {
+    for (int i = ANGBAND_TERM_MAX - 1; i >= 0; --i) {
+       if (angband_term[i]) {
+           term_nuke(angband_term[i]);
+       }
+    }
+    [AngbandSoundCatalog clearSharedSounds];
+    [AngbandContext setDefaultFont:nil];
     plog(str);
     exit(0);
 }
-#if 0
-/* Set HFS file type and creator codes on a path */
-static void cocoa_file_open_hook(const char *path, file_type ftype)
-{
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    NSString *pathString = [NSString stringWithUTF8String:path];
-    if (pathString)
-    {   
-        u32b mac_type = 'TEXT';
-        if (ftype == FTYPE_RAW)
-            mac_type = 'DATA';
-        else if (ftype == FTYPE_SAVE)
-            mac_type = 'SAVE';
-        
-        NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedLong:mac_type], NSFileHFSTypeCode, [NSNumber numberWithUnsignedLong:ANGBAND_CREATOR], NSFileHFSCreatorCode, nil];
-        [[NSFileManager defaultManager] setAttributes:attrs ofItemAtPath:pathString error:NULL];
-    }
-    [pool drain];
-}
 
-/* A platform-native file save dialogue box, e.g. for saving character dumps */
-static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
+/**
+ * Return the path for Angband's lib directory and bail if it isn't found. The
+ * lib directory should be in the bundle's resources directory, since it's
+ * copied when built.
+ */
+static NSString* get_lib_directory(void)
 {
-    NSSavePanel *panel = [NSSavePanel savePanel];
-    NSString *directory = [NSString stringWithCString:ANGBAND_DIR_USER encoding:NSASCIIStringEncoding];
-    NSString *filename = [NSString stringWithCString:suggested_name encoding:NSASCIIStringEncoding];
+    NSString *bundleLibPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: AngbandDirectoryNameLib];
+    BOOL isDirectory = NO;
+    BOOL libExists = [[NSFileManager defaultManager] fileExistsAtPath: bundleLibPath isDirectory: &isDirectory];
+
+    if( !libExists || !isDirectory )
+    {
+       NSLog( @"Hengband: can't find %@/ in bundle: isDirectory: %d libExists: %d", AngbandDirectoryNameLib, isDirectory, libExists );
+
+       NSString *msg = NSLocalizedStringWithDefaultValue(
+           @"Error.MissingResources",
+           AngbandMessageCatalog,
+           [NSBundle mainBundle],
+           @"Missing Resources",
+           @"Alert text for missing resources");
+       NSString *info = NSLocalizedStringWithDefaultValue(
+           @"Error.MissingAngbandLib",
+           AngbandMessageCatalog,
+           [NSBundle mainBundle],
+           @"Hengband was unable to find required resources and must quit. Please report a bug on the Angband forums.",
+           @"Alert informative message for missing Angband lib/ folder");
+       NSString *quit_label = NSLocalizedStringWithDefaultValue(
+           @"Label.Quit", AngbandMessageCatalog, [NSBundle mainBundle],
+           @"Quit", @"Quit");
+       NSAlert *alert = [[NSAlert alloc] init];
 
-    if ([panel runModalForDirectory:directory file:filename] == NSOKButton) {
-        const char *p = [[[panel URL] path] UTF8String];
-        my_strcpy(path, p, len);
-        return TRUE;
+       /*
+        * Note that NSCriticalAlertStyle was deprecated in 10.10.  The
+        * replacement is NSAlertStyleCritical.
+        */
+       alert.alertStyle = NSCriticalAlertStyle;
+       alert.messageText = msg;
+       alert.informativeText = info;
+       [alert addButtonWithTitle:quit_label];
+       [alert runModal];
+       exit( 0 );
     }
 
-    return FALSE;
+    return bundleLibPath;
 }
+
+/**
+ * Return the path for the directory where Angband should look for its standard
+ * user file tree.
+ */
+static NSString* get_doc_directory(void)
+{
+       NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
+
+#if defined(SAFE_DIRECTORY)
+       NSString *versionedDirectory = [NSString stringWithFormat: @"%@-%s", AngbandDirectoryNameBase, VERSION_STRING];
+       return [documents stringByAppendingPathComponent: versionedDirectory];
+#else
+       return [documents stringByAppendingPathComponent: AngbandDirectoryNameBase];
 #endif
-/*** Main program ***/
+}
+
+/**
+ * Adjust directory paths as needed to correct for any differences needed by
+ * Angband.  init_file_paths() currently requires that all paths provided have
+ * a trailing slash and all other platforms honor this.
+ *
+ * \param originalPath The directory path to adjust.
+ * \return A path suitable for Angband or nil if an error occurred.
+ */
+static NSString* AngbandCorrectedDirectoryPath(NSString *originalPath)
+{
+       if ([originalPath length] == 0) {
+               return nil;
+       }
+
+       if (![originalPath hasSuffix: @"/"]) {
+               return [originalPath stringByAppendingString: @"/"];
+       }
 
-@interface AngbandAppDelegate : NSObject {
-    IBOutlet NSMenu *terminalsMenu;
-    NSMenu *_commandMenu;
-    NSDictionary *_commandMenuTagMap;
+       return originalPath;
 }
 
-@property (nonatomic, retain) IBOutlet NSMenu *commandMenu;
-@property (nonatomic, retain) NSDictionary *commandMenuTagMap;
-
-- (IBAction)newGame:sender;
-- (IBAction)editFont:sender;
-- (IBAction)openGame:sender;
+/**
+ * Give Angband the base paths that should be used for the various directories
+ * it needs. It will create any needed directories.
+ */
+static void prepare_paths_and_directories(void)
+{
+       char libpath[PATH_MAX + 1] = "\0";
+       NSString *libDirectoryPath =
+           AngbandCorrectedDirectoryPath(get_lib_directory());
+       [libDirectoryPath getFileSystemRepresentation: libpath maxLength: sizeof(libpath)];
+
+       char basepath[PATH_MAX + 1] = "\0";
+       NSString *angbandDocumentsPath =
+           AngbandCorrectedDirectoryPath(get_doc_directory());
+       [angbandDocumentsPath getFileSystemRepresentation: basepath maxLength: sizeof(basepath)];
+
+       init_file_paths(libpath, basepath);
+       create_needed_dirs();
+}
 
-- (IBAction)selectWindow: (id)sender;
+/**
+ * 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)
+{
+    [[AngbandSoundCatalog sharedSounds] playSound:event];
+}
 
-@end
+/**
+ * ------------------------------------------------------------------------
+ * Main program
+ * ------------------------------------------------------------------------ */
 
 @implementation AngbandAppDelegate
 
+@synthesize graphicsMenu=_graphicsMenu;
 @synthesize commandMenu=_commandMenu;
 @synthesize commandMenuTagMap=_commandMenuTagMap;
 
@@ -2926,105 +4034,126 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
 - (IBAction)editFont:sender
 {
     NSFontPanel *panel = [NSFontPanel sharedFontPanel];
-    NSFont *termFont = default_font;
+    NSFont *termFont = [AngbandContext defaultFont];
 
     int i;
     for (i=0; i < ANGBAND_TERM_MAX; i++) {
-        if ([(id)angband_term[i]->data isMainWindow]) {
-            termFont = [(id)angband_term[i]->data selectionFont];
+       AngbandContext *context =
+           (__bridge AngbandContext*) (angband_term[i]->data);
+        if ([context isMainWindow]) {
+            termFont = [context angbandViewFont];
             break;
         }
     }
-    
+
     [panel setPanelFont:termFont isMultiple:NO];
     [panel orderFront:self];
 }
 
+/**
+ * Implement NSObject's changeFont() method to receive a notification about the
+ * changed font.  Note that, as of 10.14, changeFont() is deprecated in
+ * NSObject - it will be removed at some point and the application delegate
+ * will have to be declared as implementing the NSFontChanging protocol.
+ */
 - (void)changeFont:(id)sender
 {
     int mainTerm;
     for (mainTerm=0; mainTerm < ANGBAND_TERM_MAX; mainTerm++) {
-        if ([(id)angband_term[mainTerm]->data isMainWindow]) {
+       AngbandContext *context =
+           (__bridge AngbandContext*) (angband_term[mainTerm]->data);
+        if ([context isMainWindow]) {
             break;
         }
     }
 
     /* Bug #1709: Only change font for angband windows */
     if (mainTerm == ANGBAND_TERM_MAX) return;
-    
-    NSFont *oldFont = default_font;
+
+    NSFont *oldFont = [AngbandContext defaultFont];
     NSFont *newFont = [sender convertFont:oldFont];
-    if (! newFont) return; //paranoia
-    
+    if (! newFont) return; /*paranoia */
+
     /* Store as the default font if we changed the first term */
     if (mainTerm == 0) {
-        [newFont retain];
-        [default_font release];
-        default_font = newFont;
+       [AngbandContext setDefaultFont:newFont];
     }
-    
+
     /* Record it in the preferences */
     NSUserDefaults *defs = [NSUserDefaults angbandDefaults];
     [defs setValue:[newFont fontName] 
         forKey:[NSString stringWithFormat:@"FontName-%d", mainTerm]];
     [defs setFloat:[newFont pointSize]
         forKey:[NSString stringWithFormat:@"FontSize-%d", mainTerm]];
-    [defs synchronize];
-    
+
     NSDisableScreenUpdates();
-    
+
     /* Update window */
-    AngbandContext *angbandContext = angband_term[mainTerm]->data;
+    AngbandContext *angbandContext =
+       (__bridge AngbandContext*) (angband_term[mainTerm]->data);
     [(id)angbandContext setSelectionFont:newFont adjustTerminal: YES];
-    
+
     NSEnableScreenUpdates();
+
+    if (mainTerm == 0 && game_in_progress) {
+       /* Mimics the logic in setGraphicsMode(). */
+       do_cmd_redraw();
+       wakeup_event_loop();
+    } else {
+       [(id)angbandContext requestRedraw];
+    }
 }
 
 - (IBAction)openGame:sender
 {
-    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
-    BOOL selectedSomething = NO;
-    int panelResult;
-    NSString* startingDirectory;
-    
-    /* Get where we think the save files are */
-    startingDirectory = [get_data_directory() stringByAppendingPathComponent:@"/save/"];
-    
-    /* Get what we think the default save file name is. Deafult to the empty string. */
-    NSString *savefileName = [[NSUserDefaults angbandDefaults] stringForKey:@"SaveFile"];
-    if (! savefileName) savefileName = @"";
-    
-    /* Set up an open panel */
-    NSOpenPanel* panel = [NSOpenPanel openPanel];
-    [panel setCanChooseFiles:YES];
-    [panel setCanChooseDirectories:NO];
-    [panel setResolvesAliases:YES];
-    [panel setAllowsMultipleSelection:YES];
-    [panel setTreatsFilePackagesAsDirectories:YES];
-    
-    /* Run it */
-    panelResult = [panel runModalForDirectory:startingDirectory file:savefileName types:nil];
-    if (panelResult == NSOKButton)
-    {
-        NSArray* filenames = [panel filenames];
-        if ([filenames count] > 0)
-        {
-            selectedSomething = [[filenames objectAtIndex:0] getFileSystemRepresentation:savefile maxLength:sizeof savefile];
-        }
-    }
-    
-    if (selectedSomething)
-    {
-        
-        /* Remember this so we can select it by default next time */
-        record_current_savefile();
-        
-        /* Game is in progress */
-        game_in_progress = TRUE;
-        new_game = FALSE;
+    @autoreleasepool {
+       BOOL selectedSomething = NO;
+       int panelResult;
+
+       /* Get where we think the save files are */
+       NSURL *startingDirectoryURL =
+           [NSURL fileURLWithPath:[NSString stringWithCString:ANGBAND_DIR_SAVE encoding:NSASCIIStringEncoding]
+                  isDirectory:YES];
+
+       /* Set up an open panel */
+       NSOpenPanel* panel = [NSOpenPanel openPanel];
+       [panel setCanChooseFiles:YES];
+       [panel setCanChooseDirectories:NO];
+       [panel setResolvesAliases:YES];
+       [panel setAllowsMultipleSelection:NO];
+       [panel setTreatsFilePackagesAsDirectories:YES];
+       [panel setDirectoryURL:startingDirectoryURL];
+
+       /* Run it */
+       panelResult = [panel runModal];
+       if (panelResult == NSOKButton)
+       {
+           NSArray* fileURLs = [panel URLs];
+           if ([fileURLs count] > 0 && [[fileURLs objectAtIndex:0] isFileURL])
+           {
+               NSURL* savefileURL = (NSURL *)[fileURLs objectAtIndex:0];
+               /*
+                * The path property doesn't do the right thing except for
+                * URLs with the file scheme. We had
+                * getFileSystemRepresentation here before, but that wasn't
+                * introduced until OS X 10.9.
+                */
+               selectedSomething = [[savefileURL path]
+                                       getCString:savefile
+                                       maxLength:sizeof savefile
+                                       encoding:NSMacOSRomanStringEncoding];
+           }
+       }
+
+       if (selectedSomething)
+       {
+           /* Remember this so we can select it by default next time */
+           record_current_savefile();
+
+           /* Game is in progress */
+           game_in_progress = TRUE;
+       }
     }
-    
-    [pool drain];
 }
 
 - (IBAction)saveGame:sender
@@ -3035,10 +4164,277 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
     /* Save the game */
     do_cmd_save_game(FALSE);
     
-    /* Record the current save file so we can select it by default next time. It's a little sketchy that this only happens when we save through the menu; ideally game-triggered saves would trigger it too. */
+    /* Record the current save file so we can select it by default next time.
+        * It's a little sketchy that this only happens when we save through the
+        * menu; ideally game-triggered saves would trigger it too. */
     record_current_savefile();
 }
 
+/**
+ * Create and initialize Angband terminal number "termIndex".
+ */
+- (void)linkTermData:(int)termIndex
+{
+    NSArray *terminalDefaults = [[NSUserDefaults standardUserDefaults]
+                                   valueForKey: AngbandTerminalsDefaultsKey];
+    NSInteger rows = 24;
+    NSInteger columns = 80;
+
+    if (termIndex < (int)[terminalDefaults count]) {
+        NSDictionary *term = [terminalDefaults objectAtIndex:termIndex];
+        rows = [[term valueForKey: AngbandTerminalRowsDefaultsKey]
+                  integerValue];
+        columns = [[term valueForKey: AngbandTerminalColumnsDefaultsKey]
+                     integerValue];
+    }
+
+    /* Allocate */
+    term *newterm = ZNEW(term);
+
+    /* Initialize the term */
+    term_init(newterm, columns, rows, 256 /* keypresses, for some reason? */);
+
+    /* Differentiate between BS/^h, Tab/^i, etc. */
+    /* newterm->complex_input = TRUE; */
+
+    /* Use a "software" cursor */
+    newterm->soft_cursor = TRUE;
+
+    /* Disable the per-row flush notifications since they are not used. */
+    newterm->never_frosh = TRUE;
+
+    /* Erase with "white space" */
+    newterm->attr_blank = TERM_WHITE;
+    newterm->char_blank = ' ';
+
+    /* Prepare the init/nuke hooks */
+    newterm->init_hook = Term_init_cocoa;
+    newterm->nuke_hook = Term_nuke_cocoa;
+
+    /* Prepare the function hooks */
+    newterm->xtra_hook = Term_xtra_cocoa;
+    newterm->wipe_hook = Term_wipe_cocoa;
+    newterm->curs_hook = Term_curs_cocoa;
+    newterm->bigcurs_hook = Term_bigcurs_cocoa;
+    newterm->text_hook = Term_text_cocoa;
+    newterm->pict_hook = Term_pict_cocoa;
+    /* newterm->mbcs_hook = Term_mbcs_cocoa; */
+
+    /* Global pointer */
+    angband_term[termIndex] = newterm;
+}
+
+/**
+ * Allocate the primary Angband terminal and activate it.  Allocate the other
+ * Angband terminals.
+ */
+- (void)initWindows {
+    for (int i = 0; i < ANGBAND_TERM_MAX; i++) {
+       [self linkTermData:i];
+    }
+
+    Term_activate(angband_term[0]);
+}
+
+/**
+ * Load preferences from preferences file for current host+current user+
+ * current application.
+ */
+- (void)loadPrefs
+{
+    NSUserDefaults *defs = [NSUserDefaults angbandDefaults];
+
+    /* Make some default defaults */
+    NSMutableArray *defaultTerms = [[NSMutableArray alloc] init];
+
+    /*
+     * The following default rows/cols were determined experimentally by first
+     * finding the ideal window/font size combinations. But because of awful
+     * temporal coupling in Term_init_cocoa(), it's impossible to set up the
+     * defaults there, so we do it this way.
+     */
+    for (NSUInteger i = 0; i < ANGBAND_TERM_MAX; i++) {
+       int columns, rows;
+       BOOL visible = YES;
+
+       switch (i) {
+       case 0:
+           columns = 129;
+           rows = 32;
+           break;
+       case 1:
+           columns = 84;
+           rows = 20;
+           break;
+       case 2:
+           columns = 42;
+           rows = 24;
+           break;
+       case 3:
+           columns = 42;
+           rows = 20;
+           break;
+       case 4:
+           columns = 42;
+           rows = 16;
+           break;
+       case 5:
+           columns = 84;
+           rows = 20;
+           break;
+       default:
+           columns = 80;
+           rows = 24;
+           visible = NO;
+           break;
+       }
+
+       NSDictionary *standardTerm =
+           [NSDictionary dictionaryWithObjectsAndKeys:
+                         [NSNumber numberWithInt: rows], AngbandTerminalRowsDefaultsKey,
+                         [NSNumber numberWithInt: columns], AngbandTerminalColumnsDefaultsKey,
+                         [NSNumber numberWithBool: visible], AngbandTerminalVisibleDefaultsKey,
+                         nil];
+        [defaultTerms addObject: standardTerm];
+    }
+
+    NSDictionary *defaults = [[NSDictionary alloc] initWithObjectsAndKeys:
+#ifdef JP
+                              @"Osaka", @"FontName",
+#else
+                              @"Menlo", @"FontName",
+#endif
+                              [NSNumber numberWithFloat:13.f], @"FontSize",
+                              [NSNumber numberWithInt:60], AngbandFrameRateDefaultsKey,
+                              [NSNumber numberWithBool:YES], AngbandSoundDefaultsKey,
+                              [NSNumber numberWithInt:GRAPHICS_NONE], AngbandGraphicsDefaultsKey,
+                              [NSNumber numberWithBool:YES], AngbandBigTileDefaultsKey,
+                              defaultTerms, AngbandTerminalsDefaultsKey,
+                              nil];
+    [defs registerDefaults:defaults];
+
+    /* Preferred graphics mode */
+    graf_mode_req = [defs integerForKey:AngbandGraphicsDefaultsKey];
+    if (graf_mode_req != GRAPHICS_NONE &&
+       get_graphics_mode(graf_mode_req)->grafID != GRAPHICS_NONE &&
+       [defs boolForKey:AngbandBigTileDefaultsKey] == YES) {
+       use_bigtile = TRUE;
+       arg_bigtile = TRUE;
+    } else {
+       use_bigtile = FALSE;
+       arg_bigtile = FALSE;
+    }
+
+    /* Use sounds; set the Angband global */
+    if ([defs boolForKey:AngbandSoundDefaultsKey] == YES) {
+       use_sound = TRUE;
+       [AngbandSoundCatalog sharedSounds].enabled = YES;
+    } else {
+       use_sound = FALSE;
+       [AngbandSoundCatalog sharedSounds].enabled = NO;
+    }
+
+    /* fps */
+    frames_per_second = [defs integerForKey:AngbandFrameRateDefaultsKey];
+
+    /* Font */
+    [AngbandContext
+       setDefaultFont:[NSFont fontWithName:[defs valueForKey:@"FontName-0"]
+                              size:[defs floatForKey:@"FontSize-0"]]];
+    if (! [AngbandContext defaultFont])
+       [AngbandContext
+           setDefaultFont:[NSFont fontWithName:@"Menlo" size:13.]];
+}
+
+/**
+ * Entry point for initializing Angband
+ */
+- (void)beginGame
+{
+    @autoreleasepool {
+       /* Hooks in some "z-util.c" hooks */
+       plog_aux = hook_plog;
+       quit_aux = hook_quit;
+
+       /* Initialize file paths */
+       prepare_paths_and_directories();
+
+       /* Note the "system" */
+       ANGBAND_SYS = "coc";
+
+       /* Load possible graphics modes */
+       init_graphics_modes();
+
+       /* Load preferences */
+       [self loadPrefs];
+
+       /* Prepare the windows */
+       [self initWindows];
+
+       /* Set up game event handlers */
+       /* init_display(); */
+
+       /* Register the sound hook */
+       /* sound_hook = play_sound; */
+
+       /* Initialise game */
+       init_angband();
+
+       /* Initialize some save file stuff */
+       player_egid = getegid();
+
+       /* We are now initialized */
+       initialized = TRUE;
+
+       /* Handle "open_when_ready" */
+       handle_open_when_ready();
+
+       /* Handle pending events (most notably update) and flush input */
+       Term_flush();
+
+       /*
+        * Prompt the user; assume the splash screen is 80 x 23 and position
+        * relative to that rather than center based on the full size of the
+        * window.
+        */
+       int message_row = 23;
+       Term_erase(0, message_row, 255);
+       put_str(
+#ifdef JP
+           "['ファイル' メニューから '新規' または '開く' を選択します]",
+           message_row, (80 - 59) / 2
+#else
+           "[Choose 'New' or 'Open' from the 'File' menu]",
+           message_row, (80 - 45) / 2
+#endif
+       );
+       Term_fresh();
+    }
+
+    while (!game_in_progress) {
+       @autoreleasepool {
+           NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES];
+           if (event) [NSApp sendEvent:event];
+       }
+    }
+
+    /*
+     * Play a game -- "new_game" is set by "new", "open" or the open document
+     * even handler as appropriate
+     */
+    Term_fresh();
+    play_game(new_game);
+
+    quit(NULL);
+}
+
+/**
+ * Implement NSObject's validateMenuItem() method to override enabling or
+ * disabling a menu item.  Note that, as of 10.14, validateMenuItem() is
+ * deprecated in NSObject - it will be removed at some point and  the
+ * application delegate will have to be declared as implementing the
+ * NSMenuItemValidation protocol.
+ */
 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
 {
     SEL sel = [menuItem action];
@@ -3048,13 +4444,20 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
     {
         if( tag == AngbandWindowMenuItemTagBase )
         {
-            // the main window should always be available and visible
+            /* The main window should always be available and visible */
             return YES;
         }
         else
         {
+           /*
+            * Another window is only usable after Term_init_cocoa() has
+            * been called for it.  For Angband if window_flag[i] is nonzero
+            * then that has happened for window i.  For Hengband, that is
+            * not the case so also test angband_term[i]->data.
+            */
             NSInteger subwindowNumber = tag - AngbandWindowMenuItemTagBase;
-            return (window_flag[subwindowNumber] > 0);
+            return (angband_term[subwindowNumber]->data != 0
+                   && window_flag[subwindowNumber] > 0);
         }
 
         return NO;
@@ -3072,22 +4475,35 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
     {
         return ! game_in_progress;
     }
-    else if (sel == @selector(setRefreshRate:) && [superitem(menuItem) tag] == 150)
+    else if (sel == @selector(setRefreshRate:) &&
+            [[menuItem parentItem] tag] == 150)
     {
-        NSInteger fps = [[NSUserDefaults standardUserDefaults] integerForKey: @"FramesPerSecond"];
+        NSInteger fps = [[NSUserDefaults standardUserDefaults] integerForKey:AngbandFrameRateDefaultsKey];
         [menuItem setState: ([menuItem tag] == fps)];
         return YES;
     }
     else if( sel == @selector(setGraphicsMode:) )
     {
-        NSInteger requestedGraphicsMode = [[NSUserDefaults standardUserDefaults] integerForKey: @"GraphicsID"];
+        NSInteger requestedGraphicsMode = [[NSUserDefaults standardUserDefaults] integerForKey:AngbandGraphicsDefaultsKey];
         [menuItem setState: (tag == requestedGraphicsMode)];
         return YES;
     }
-    else if( sel == @selector(sendAngbandCommand:) )
+    else if( sel == @selector(toggleSound:) )
+    {
+       BOOL is_on = [[NSUserDefaults standardUserDefaults]
+                        boolForKey:AngbandSoundDefaultsKey];
+
+       [menuItem setState: ((is_on) ? NSOnState : NSOffState)];
+       return YES;
+    }
+    else if( sel == @selector(sendAngbandCommand:) ||
+            sel == @selector(saveGame:) )
     {
-        // we only want to be able to send commands during an active game
-        return !!game_in_progress;
+        /*
+         * we only want to be able to send commands during an active game
+         * after the birth screens
+         */
+        return !!game_in_progress && character_generated;
     }
     else return YES;
 }
@@ -3096,51 +4512,149 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
 - (IBAction)setRefreshRate:(NSMenuItem *)menuItem
 {
     frames_per_second = [menuItem tag];
-    [[NSUserDefaults angbandDefaults] setInteger:frames_per_second forKey:@"FramesPerSecond"];
+    [[NSUserDefaults angbandDefaults] setInteger:frames_per_second forKey:AngbandFrameRateDefaultsKey];
+}
+
+- (void)setGraphicsMode:(NSMenuItem *)sender
+{
+    /* We stashed the graphics mode ID in the menu item's tag */
+    graf_mode_req = [sender tag];
+
+    /* Stash it in UserDefaults */
+    [[NSUserDefaults angbandDefaults] setInteger:graf_mode_req forKey:AngbandGraphicsDefaultsKey];
+
+    if (graf_mode_req == GRAPHICS_NONE ||
+       get_graphics_mode(graf_mode_req) == GRAPHICS_NONE) {
+       if (use_bigtile) {
+           arg_bigtile = FALSE;
+       }
+    } else if ([[NSUserDefaults angbandDefaults] boolForKey:AngbandBigTileDefaultsKey] == YES &&
+              ! use_bigtile) {
+       arg_bigtile = TRUE;
+    }
+
+    if (game_in_progress)
+    {
+       if (arg_bigtile != use_bigtile) {
+           Term_activate(angband_term[0]);
+           Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
+       }
+
+        /* Hack -- Force redraw */
+        do_cmd_redraw();
+
+        /* Wake up the event loop so it notices the change */
+        wakeup_event_loop();
+    }
 }
 
-- (IBAction)selectWindow: (id)sender
+- (void)selectWindow: (id)sender
 {
-    NSInteger subwindowNumber = [(NSMenuItem *)sender tag] - AngbandWindowMenuItemTagBase;
-    AngbandContext *context = angband_term[subwindowNumber]->data;
-    [context->primaryWindow makeKeyAndOrderFront: self];
+    NSInteger subwindowNumber =
+       [(NSMenuItem *)sender tag] - AngbandWindowMenuItemTagBase;
+    AngbandContext *context =
+       (__bridge AngbandContext*) (angband_term[subwindowNumber]->data);
+    [context.primaryWindow makeKeyAndOrderFront: self];
+    [context saveWindowVisibleToDefaults: YES];
 }
 
-- (void)prepareWindowsMenu
+- (IBAction) toggleSound: (NSMenuItem *) sender
+{
+    BOOL is_on = (sender.state == NSOnState);
+
+    /* Toggle the state and update the Angband global and preferences. */
+    if (is_on) {
+       sender.state = NSOffState;
+       use_sound = FALSE;
+       [AngbandSoundCatalog sharedSounds].enabled = NO;
+    } else {
+       sender.state = NSOnState;
+       use_sound = TRUE;
+       [AngbandSoundCatalog sharedSounds].enabled = YES;
+    }
+    [[NSUserDefaults angbandDefaults] setBool:(! is_on)
+                                     forKey:AngbandSoundDefaultsKey];
+}
+
+- (IBAction)toggleWideTiles:(NSMenuItem *) sender
 {
-    // get the window menu with default items and add a separator and item for the main window
-    NSMenu *windowsMenu = [[NSApplication sharedApplication] windowsMenu];
-    [windowsMenu addItem: [NSMenuItem separatorItem]];
+    BOOL is_on = (sender.state == NSOnState);
+
+    /* Toggle the state and update the Angband globals and preferences. */
+    sender.state = (is_on) ? NSOffState : NSOnState;
+    [[NSUserDefaults angbandDefaults] setBool:(! is_on)
+                                     forKey:AngbandBigTileDefaultsKey];
+    if (graphics_are_enabled()) {
+       arg_bigtile = (is_on) ? FALSE : TRUE;
+       /* Mimics the logic in setGraphicsMode(). */
+       if (game_in_progress && arg_bigtile != use_bigtile) {
+           Term_activate(angband_term[0]);
+           Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
+           do_cmd_redraw();
+           wakeup_event_loop();
+       }
+    }
+}
 
-    NSMenuItem *angbandItem = [[NSMenuItem alloc] initWithTitle: @"PosChengband" action: @selector(selectWindow:) keyEquivalent: @"0"];
-    [angbandItem setTarget: self];
-    [angbandItem setTag: AngbandWindowMenuItemTagBase];
-    [windowsMenu addItem: angbandItem];
-    [angbandItem release];
+- (void)prepareWindowsMenu
+{
+    @autoreleasepool {
+       /*
+        * Get the window menu with default items and add a separator and
+        * item for the main window.
+        */
+       NSMenu *windowsMenu = [[NSApplication sharedApplication] windowsMenu];
+       [windowsMenu addItem: [NSMenuItem separatorItem]];
 
-    // add items for the additional term windows
-    for( NSInteger i = 1; i < ANGBAND_TERM_MAX; i++ )
-    {
-        NSString *title = [NSString stringWithFormat: @"Term %ld", (long)i];
-        NSMenuItem *windowItem = [[NSMenuItem alloc] initWithTitle: title action: @selector(selectWindow:) keyEquivalent: @""];
-        [windowItem setTarget: self];
-        [windowItem setTag: AngbandWindowMenuItemTagBase + i];
-        [windowsMenu addItem: windowItem];
-        [windowItem release];
+       NSString *title1 = [NSString stringWithCString:angband_term_name[0]
+#ifdef JP
+                                    encoding:NSJapaneseEUCStringEncoding
+#else
+                                    encoding:NSMacOSRomanStringEncoding
+#endif
+       ];
+       NSMenuItem *angbandItem = [[NSMenuItem alloc] initWithTitle:title1 action: @selector(selectWindow:) keyEquivalent: @"0"];
+       [angbandItem setTarget: self];
+       [angbandItem setTag: AngbandWindowMenuItemTagBase];
+       [windowsMenu addItem: angbandItem];
+
+       /* Add items for the additional term windows */
+       for( NSInteger i = 1; i < ANGBAND_TERM_MAX; i++ )
+       {
+           NSString *title = [NSString stringWithCString:angband_term_name[i]
+#ifdef JP
+                                       encoding:NSJapaneseEUCStringEncoding
+#else
+                                       encoding:NSMacOSRomanStringEncoding
+#endif
+           ];
+           NSString *keyEquivalent =
+               [NSString stringWithFormat: @"%ld", (long)i];
+           NSMenuItem *windowItem =
+               [[NSMenuItem alloc] initWithTitle: title
+                                   action: @selector(selectWindow:)
+                                   keyEquivalent: keyEquivalent];
+           [windowItem setTarget: self];
+           [windowItem setTag: AngbandWindowMenuItemTagBase + i];
+           [windowsMenu addItem: windowItem];
+       }
     }
 }
 
 /**
- *  Send a command to Angband via a menu item. This places the appropriate key down events into the queue
- *  so that it seems like the user pressed them (instead of trying to use the term directly).
+ *  Send a command to Angband via a menu item. This places the appropriate key
+ * down events into the queue so that it seems like the user pressed them
+ * (instead of trying to use the term directly).
  */
 - (void)sendAngbandCommand: (id)sender
 {
     NSMenuItem *menuItem = (NSMenuItem *)sender;
-    NSString *command = [self.commandMenuTagMap objectForKey: @([menuItem tag])];
-    NSInteger windowNumber = [((AngbandContext *)angband_term[0]->data)->primaryWindow windowNumber];
+    NSString *command = [self.commandMenuTagMap objectForKey: [NSNumber numberWithInteger: [menuItem tag]]];
+    AngbandContext* context =
+       (__bridge AngbandContext*) (angband_term[0]->data);
+    NSInteger windowNumber = [context.primaryWindow windowNumber];
 
-    // send a \ to bypass keymaps
+    /* Send a \ to bypass keymaps */
     NSEvent *escape = [NSEvent keyEventWithType: NSKeyDown
                                        location: NSZeroPoint
                                   modifierFlags: 0
@@ -3153,7 +4667,7 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
                                         keyCode: 0];
     [[NSApplication sharedApplication] postEvent: escape atStart: NO];
 
-    // send the actual command (from the original command set)
+    /* Send the actual command (from the original command set) */
     NSEvent *keyDown = [NSEvent keyEventWithType: NSKeyDown
                                         location: NSZeroPoint
                                    modifierFlags: 0
@@ -3172,39 +4686,49 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
  */
 - (void)prepareCommandMenu
 {
-    NSString *commandMenuPath = [[NSBundle mainBundle] pathForResource: @"CommandMenu" ofType: @"plist"];
-    NSArray *commandMenuItems = [[NSArray alloc] initWithContentsOfFile: commandMenuPath];
-    NSMutableDictionary *angbandCommands = [[NSMutableDictionary alloc] init];
-    NSInteger tagOffset = 0;
-
-    for( NSDictionary *item in commandMenuItems )
-    {
-        BOOL useShiftModifier = [[item valueForKey: @"ShiftModifier"] boolValue];
-        BOOL useOptionModifier = [[item valueForKey: @"OptionModifier"] boolValue];
-        NSUInteger keyModifiers = NSCommandKeyMask;
-        keyModifiers |= (useShiftModifier) ? NSShiftKeyMask : 0;
-        keyModifiers |= (useOptionModifier) ? NSAlternateKeyMask : 0;
-
-        NSString *title = [item valueForKey: @"Title"];
-        NSString *key = [item valueForKey: @"KeyEquivalent"];
-        NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle: title action: @selector(sendAngbandCommand:) keyEquivalent: key];
-        [menuItem setTarget: self];
-        [menuItem setKeyEquivalentModifierMask: keyModifiers];
-        [menuItem setTag: AngbandCommandMenuItemTagBase + tagOffset];
-        [self.commandMenu addItem: menuItem];
-        [menuItem release];
+    @autoreleasepool {
+       NSString *commandMenuPath =
+           [[NSBundle mainBundle] pathForResource: @"CommandMenu"
+                                  ofType: @"plist"];
+       NSArray *commandMenuItems =
+           [[NSArray alloc] initWithContentsOfFile: commandMenuPath];
+       NSMutableDictionary *angbandCommands =
+           [[NSMutableDictionary alloc] init];
+       NSString *tblname = @"CommandMenu";
+       NSInteger tagOffset = 0;
+
+       for( NSDictionary *item in commandMenuItems )
+       {
+           BOOL useShiftModifier =
+               [[item valueForKey: @"ShiftModifier"] boolValue];
+           BOOL useOptionModifier =
+               [[item valueForKey: @"OptionModifier"] boolValue];
+           NSUInteger keyModifiers = NSCommandKeyMask;
+           keyModifiers |= (useShiftModifier) ? NSShiftKeyMask : 0;
+           keyModifiers |= (useOptionModifier) ? NSAlternateKeyMask : 0;
+
+           NSString *lookup = [item valueForKey: @"Title"];
+           NSString *title = NSLocalizedStringWithDefaultValue(
+               lookup, tblname, [NSBundle mainBundle], lookup, @"");
+           NSString *key = [item valueForKey: @"KeyEquivalent"];
+           NSMenuItem *menuItem =
+               [[NSMenuItem alloc] initWithTitle: title
+                                   action: @selector(sendAngbandCommand:)
+                                   keyEquivalent: key];
+           [menuItem setTarget: self];
+           [menuItem setKeyEquivalentModifierMask: keyModifiers];
+           [menuItem setTag: AngbandCommandMenuItemTagBase + tagOffset];
+           [self.commandMenu addItem: menuItem];
+
+           NSString *angbandCommand = [item valueForKey: @"AngbandCommand"];
+           [angbandCommands setObject: angbandCommand
+                            forKey: [NSNumber numberWithInteger: [menuItem tag]]];
+           tagOffset++;
+       }
 
-        NSString *angbandCommand = [item valueForKey: @"AngbandCommand"];
-        [angbandCommands setObject: angbandCommand forKey: @([menuItem tag])];
-        tagOffset++;
+       self.commandMenuTagMap = [[NSDictionary alloc]
+                                    initWithDictionary: angbandCommands];
     }
-
-    [commandMenuItems release];
-
-    NSDictionary *safeCommands = [[NSDictionary alloc] initWithDictionary: angbandCommands];
-    self.commandMenuTagMap = safeCommands;
-    [safeCommands release];
-    [angbandCommands release];
 }
 
 - (void)awakeFromNib
@@ -3217,9 +4741,10 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
 
 - (void)applicationDidFinishLaunching:sender
 {
-    [AngbandContext beginGame];
+    [self beginGame];
     
-    //once beginGame finished, the game is over - that's how Angband works, and we should quit
+    /* Once beginGame finished, the game is over - that's how Angband works,
+        * and we should quit */
     game_is_finished = TRUE;
     [NSApp terminate:self];
 }
@@ -3237,24 +4762,32 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
     }
     else
     {
-      //cmd_insert(CMD_QUIT);
-        /* Post an escape event so that we can return from our get-key-event function */
+        /* Stop playing */
+        /* player->upkeep->playing = FALSE; */
+
+        /* Post an escape event so that we can return from our get-key-event
+                * function */
         wakeup_event_loop();
         quit_when_ready = true;
-        // must return Cancel, not Later, because we need to get out of the run loop and back to Angband's loop
+        /* Must return Cancel, not Later, because we need to get out of the
+                * run loop and back to Angband's loop */
         return NSTerminateCancel;
     }
 }
 
-/* Dynamically build the Graphics menu */
+/**
+ * Dynamically build the Graphics menu
+ */
 - (void)menuNeedsUpdate:(NSMenu *)menu {
     
     /* Only the graphics menu is dynamic */
-    //if (! [[menu title] isEqualToString:@"Graphics"])
+    if (! [menu isEqual:self.graphicsMenu])
         return;
-#if 0    
-    /* If it's non-empty, then we've already built it. Currently graphics modes won't change once created; if they ever can we can remove this check.
-       Note that the check mark does change, but that's handled in validateMenuItem: instead of menuNeedsUpdate: */
+    
+    /* If it's non-empty, then we've already built it. Currently graphics modes
+        * won't change once created; if they ever can we can remove this check.
+     * Note that the check mark does change, but that's handled in
+        * validateMenuItem: instead of menuNeedsUpdate: */
     if ([menu numberOfItems] > 0)
         return;
     
@@ -3262,54 +4795,80 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
     SEL action = @selector(setGraphicsMode:);
     
     /* Add an initial Classic ASCII menu item */
-    NSMenuItem *item = [menu addItemWithTitle:@"Classic ASCII" action:action keyEquivalent:@""];
-    [item setTag:GRAPHICS_NONE];
+    NSString *tblname = @"GraphicsMenu";
+    NSString *key = @"Classic ASCII";
+    NSString *title = NSLocalizedStringWithDefaultValue(
+       key, tblname, [NSBundle mainBundle], key, @"");
+    NSMenuItem *classicItem = [menu addItemWithTitle:title action:action keyEquivalent:@""];
+    [classicItem setTag:GRAPHICS_NONE];
     
     /* Walk through the list of graphics modes */
-    NSInteger i;
-    for (i=0; graphics_modes[i].pNext; i++)
-    {
-        const graphics_mode *graf = &graphics_modes[i];
-        
-        /* Make the title. NSMenuItem throws on a nil title, so ensure it's not nil. */
-        NSString *title = [[NSString alloc] initWithUTF8String:graf->menuname];
-        if (! title) title = [@"(Unknown)" copy];
-        
-        /* Make the item */
-        NSMenuItem *item = [menu addItemWithTitle:title action:action keyEquivalent:@""];
-        [item setTag:graf->grafID];
+    if (graphics_modes) {
+       NSInteger i;
+
+       for (i=0; graphics_modes[i].pNext; i++)
+       {
+           const graphics_mode *graf = &graphics_modes[i];
+
+           if (graf->grafID == GRAPHICS_NONE) {
+               continue;
+           }
+           /* Make the title. NSMenuItem throws on a nil title, so ensure it's
+                  * not nil. */
+           key = [[NSString alloc] initWithUTF8String:graf->menuname];
+           title = NSLocalizedStringWithDefaultValue(
+               key, tblname, [NSBundle mainBundle], key, @"");
+
+           /* Make the item */
+           NSMenuItem *item = [menu addItemWithTitle:title action:action keyEquivalent:@""];
+           [item setTag:graf->grafID];
+       }
     }
-#endif
 }
 
-/* Delegate method that gets called if we're asked to open a file. */
-- (BOOL)application:(NSApplication *)sender openFiles:(NSArray *)filenames
+/**
+ * Delegate method that gets called if we're asked to open a file.
+ */
+- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
 {
     /* Can't open a file once we've started */
-    if (game_in_progress) return NO;
-    
+    if (game_in_progress) {
+       [[NSApplication sharedApplication]
+           replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
+       return;
+    }
+
     /* We can only open one file. Use the last one. */
     NSString *file = [filenames lastObject];
-    if (! file) return NO;
-    
+    if (! file) {
+       [[NSApplication sharedApplication]
+           replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
+       return;
+    }
+
     /* Put it in savefile */
-    if (! [file getFileSystemRepresentation:savefile maxLength:sizeof savefile]) return NO;
-    
-    /* Success, remember to load it */
-    //cmd.command = CMD_LOADFILE;
-    
-    /* Wake us up in case this arrives while we're sitting at the Welcome screen! */
+    if (! [file getFileSystemRepresentation:savefile maxLength:sizeof savefile]) {
+       [[NSApplication sharedApplication]
+           replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
+       return;
+    }
+
+    game_in_progress = TRUE;
+
+    /* Wake us up in case this arrives while we're sitting at the Welcome
+        * screen! */
     wakeup_event_loop();
-    
-    return YES;
+
+    [[NSApplication sharedApplication]
+       replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
 }
 
 @end
 
 int main(int argc, char* argv[])
 {
-    NSApplicationMain(argc, (void*)argv);    
+    NSApplicationMain(argc, (void*)argv);
     return (0);
 }
 
-#endif /* MACINTOSH || MACH_O_CARBON */
+#endif /* MACINTOSH || MACH_O_COCOA */