OSDN Git Service

Made beginGame() an instance method of AngbandAppDelegate rather than a class method...
[hengbandforosx/hengbandosx.git] / src / main-cocoa.m
index 57dc7e1..d6ea506 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
 {
@@ -129,9 +84,6 @@ enum
 /* 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;
 
@@ -143,10 +95,256 @@ static NSFont *default_font;
 
 @class AngbandView;
 
-/* The max number of glyphs we support */
+/*
+ * 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_PICT
+};
+struct PendingCellChange {
+    /*
+     * For text rendering, stores the character as a wchar_t; for tile
+     * rendering, stores the column in the tile set for the source tile.
+     */
+    union { wchar_t w; char c; } c;
+    /*
+     * For text rendering, stores the color; for tile rendering, stores the
+     * row in the tile set for the source tile.
+     */
+    TERM_COLOR a;
+    /*
+     * For text rendering, is one if wc is a character that takes up two
+     * columns (i.e. Japanese kanji); otherwise it is zero.  For tile
+     * rendering, Stores the column in the tile set for the terrain tile. */
+    char tcol;
+    /*
+     * For tile rendering, stores the row in the tile set for the
+     * terrain tile.
+     */
+    TERM_COLOR trow;
+    enum PendingCellChangeType change_type;
+};
+
+struct PendingRowChange
+{
+    /*
+     * These are the first and last columns, inclusive, that have been
+     * modified.  xmin is greater than xmax if no changes have been made.
+     */
+    int xmin, xmax;
+    /*
+     * This points to storage for a number of elements equal to the number
+     * of columns (implicitly gotten from the enclosing AngbandContext).
+     */
+    struct PendingCellChange* cell_changes;
+};
+
+static struct PendingRowChange* create_row_change(int ncol)
+{
+    struct PendingRowChange* prc =
+       (struct PendingRowChange*) malloc(sizeof(struct PendingRowChange));
+    struct PendingCellChange* pcc = (struct PendingCellChange*)
+       malloc(ncol * sizeof(struct PendingCellChange));
+    int i;
+
+    if (prc == 0 || pcc == 0) {
+       if (pcc != 0) {
+           free(pcc);
+       }
+       if (prc != 0) {
+           free(prc);
+       }
+       return 0;
+    }
+
+    prc->xmin = ncol;
+    prc->xmax = -1;
+    prc->cell_changes = pcc;
+    for (i = 0; i < ncol; ++i) {
+       pcc[i].change_type = CELL_CHANGE_NONE;
+    }
+    return prc;
+}
+
+
+static void destroy_row_change(struct PendingRowChange* prc)
+{
+    if (prc != 0) {
+       if (prc->cell_changes != 0) {
+           free(prc->cell_changes);
+       }
+       free(prc);
+    }
+}
+
+
+struct PendingChanges
+{
+    /* Hold the number of rows specified at creation. */
+    int nrow;
+    /*
+     * Hold the position set for the software cursor.  Use negative indices
+     * to indicate that the cursor is not displayed.
+     */
+    int xcurs, ycurs;
+    /* Is nonzero if the cursor should be drawn at double the tile width. */
+    int bigcurs;
+    /* Record whether the changes include any text, picts, or wipes. */
+    int has_text, has_pict, has_wipe;
+    /*
+     * These are the first and last rows, inclusive, that have been
+     * modified.  ymin is greater than ymax if no changes have been made.
+     */
+    int ymin, ymax;
+    /*
+     * This is an array of pointers to the changes.  The number of elements
+     * is the number of rows.  An element will be a NULL pointer if no
+     * modifications have been made to the row.
+     */
+    struct PendingRowChange** rows;
+};
+
+
+static struct PendingChanges* create_pending_changes(int ncol, int nrow)
+{
+    struct PendingChanges* pc =
+       (struct PendingChanges*) malloc(sizeof(struct PendingChanges));
+    struct PendingRowChange** pprc = (struct PendingRowChange**)
+       malloc(nrow * sizeof(struct PendingRowChange*));
+    int i;
+
+    if (pc == 0 || pprc == 0) {
+       if (pprc != 0) {
+           free(pprc);
+       }
+       if (pc != 0) {
+           free(pc);
+       }
+       return 0;
+    }
+
+    pc->nrow = nrow;
+    pc->xcurs = -1;
+    pc->ycurs = -1;
+    pc->bigcurs = 0;
+    pc->has_text = 0;
+    pc->has_pict = 0;
+    pc->has_wipe = 0;
+    pc->ymin = nrow;
+    pc->ymax = -1;
+    pc->rows = pprc;
+    for (i = 0; i < nrow; ++i) {
+       pprc[i] = 0;
+    }
+    return pc;
+}
+
+
+static void destroy_pending_changes(struct PendingChanges* pc)
+{
+    if (pc != 0) {
+       if (pc->rows != 0) {
+           int i;
+
+           for (i = 0; i < pc->nrow; ++i) {
+               if (pc->rows[i] != 0) {
+                   destroy_row_change(pc->rows[i]);
+               }
+           }
+           free(pc->rows);
+       }
+       free(pc);
+    }
+}
+
+
+static void clear_pending_changes(struct PendingChanges* pc)
+{
+    pc->xcurs = -1;
+    pc->ycurs = -1;
+    pc->bigcurs = 0;
+    pc->has_text = 0;
+    pc->has_pict = 0;
+    pc->has_wipe = 0;
+    pc->ymin = pc->nrow;
+    pc->ymax = -1;
+    if (pc->rows != 0) {
+       int i;
+
+       for (i = 0; i < pc->nrow; ++i) {
+           if (pc->rows[i] != 0) {
+               destroy_row_change(pc->rows[i]);
+               pc->rows[i] = 0;
+           }
+       }
+    }
+}
+
+
+/* Return zero if successful; otherwise return a nonzero value. */
+static int resize_pending_changes(struct PendingChanges* pc, int nrow)
+{
+    struct PendingRowChange** pprc;
+    int i;
+
+    if (pc == 0) {
+       return 1;
+    }
+
+    pprc = (struct PendingRowChange**)
+       malloc(nrow * sizeof(struct PendingRowChange*));
+    if (pprc == 0) {
+       return 1;
+    }
+    for (i = 0; i < nrow; ++i) {
+       pprc[i] = 0;
+    }
+
+    if (pc->rows != 0) {
+       for (i = 0; i < pc->nrow; ++i) {
+           if (pc->rows[i] != 0) {
+               destroy_row_change(pc->rows[i]);
+           }
+       }
+       free(pc->rows);
+    }
+    pc->nrow = nrow;
+    pc->xcurs = -1;
+    pc->ycurs = -1;
+    pc->bigcurs = 0;
+    pc->has_text = 0;
+    pc->has_pict = 0;
+    pc->has_wipe = 0;
+    pc->ymin = nrow;
+    pc->ymax = -1;
+    pc->rows = pprc;
+    return 0;
+}
+
+
+/* The max number of glyphs we support.  Currently this only affects
+ * updateGlyphInfo() for the calculation of the tile size, fontAscender,
+ * fontDescender, ncol_pre, and ncol_post (the glyphArray and glyphWidths
+ * members of AngbandContext are only used in updateGlyphInfo()).  The
+ * rendering in drawWChar will work for glyphs not in updateGlyphInfo()'s
+ * set, and that is used for rendering Japanese characters.
+ */
 #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). */
+/* 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
@@ -166,39 +364,49 @@ static NSFont *default_font;
     
     /* 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. */
+    /* "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;
+    /* Font's ascender and descender */
+    CGFloat fontAscender, fontDescender;
     
-    /* Whether we are currently in live resize, which affects how big we render our image */
+    /* 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;
+
+    struct PendingChanges* changes;
+    /*
+     * These are the number of columns before or after, respectively, a text
+     * change that may need to be redrawn.
+     */
+    int ncol_pre, ncol_post;
+
+    /* Flags whether or not a fullscreen transition is in progress. */
+    BOOL in_fullscreen_transition;
 
 @private
 
     BOOL _hasSubwindowFlags;
+    BOOL _windowVisibilityChecked;
 }
 
 @property (nonatomic, assign) BOOL hasSubwindowFlags;
+@property (nonatomic, assign) BOOL windowVisibilityChecked;
 
 - (void)drawRect:(NSRect)rect inView:(NSView *)view;
 
@@ -215,18 +423,20 @@ static NSFont *default_font;
 - (NSRect)rectInImageForTileAtX:(int)x Y:(int)y;
 
 /* Draw the given wide character into the given tile rect. */
-- (void)drawWChar:(wchar_t)wchar inRect:(NSRect)tile;
+- (void)drawWChar:(wchar_t)wchar inRect:(NSRect)tile context:(CGContextRef)ctx;
 
 /* Locks focus on the Angband image, and scales the CTM appropriately. */
 - (CGContextRef)lockFocus;
 
-/* Locks focus on the Angband image but does NOT scale the CTM. Appropriate for drawing hairlines. */
+/* Locks focus on the Angband image but does NOT scale the CTM. Appropriate
+ * for drawing hairlines. */
 - (CGContextRef)lockFocusUnscaled;
 
 /* Unlocks focus. */
 - (void)unlockFocus;
 
-/* Returns the primary window for this angband context, creating it if necessary */
+/* Returns the primary window for this angband context, creating it if
+ * necessary */
 - (NSWindow *)makePrimaryWindow;
 
 /* Called to add a new Angband view */
@@ -238,12 +448,6 @@ static NSFont *default_font;
 /* Handle becoming the main window */
 - (void)windowDidBecomeMain:(NSNotification *)notification;
 
-/* Order the context's primary window frontmost */
-- (void)orderFront;
-
-/* Order the context's primary window out */
-- (void)orderOut;
-
 /* Return whether the context's primary window is ordered in or not */
 - (BOOL)isOrderedIn;
 
@@ -259,17 +463,22 @@ static NSFont *default_font;
 /* Display (flush) our Angband views */
 - (void)displayIfNeeded;
 
+/* Resize context to size of contentRect, and optionally save size to
+ * defaults */
+- (void)resizeTerminalWithContentRect: (NSRect)contentRect saveToDefaults: (BOOL)saveToDefaults;
+
+/*
+ * 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;
-
-/* Class methods */
-
-/* Begins an Angband game. This is the entry point for starting off. */
-+ (void)beginGame;
-
-/* Ends an Angband game. */
-+ (void)endGame;
+- (void)saveWindowVisibleToDefaults: (BOOL)windowVisible;
+- (BOOL)windowVisibleUsingDefaults;
 
 /* Internal method */
 - (AngbandView *)activeView;
@@ -277,12 +486,15 @@ static NSFont *default_font;
 @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).
+ * 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;
+    int windowFlagBits = sizeof(*(window_flag)) * CHAR_BIT;
+    int maxBits = MIN( 16, windowFlagBits );
     u32b mask = 0;
 
     for( int i = 0; i < maxBits; i++ )
@@ -297,12 +509,14 @@ u32b AngbandMaskForValidSubwindowFlags(void)
 }
 
 /**
- *  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.
+ * 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)
 {
-    // because this function is called frequently, we'll make the mask static. it doesn't change between calls, since the flags themselves are hardcoded.
+    /* 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 )
@@ -310,8 +524,9 @@ static void AngbandUpdateWindowVisibility(void)
         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.
+    /* 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;
@@ -321,106 +536,83 @@ static void AngbandUpdateWindowVisibility(void)
             continue;
         }
 
-        BOOL termHasSubwindowFlags = ((window_flag[i] & validWindowFlagsMask) > 0);
-
-        if( angbandContext.hasSubwindowFlags && !termHasSubwindowFlags )
+        /* 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 )
         {
-            [angbandContext->primaryWindow close];
-            angbandContext.hasSubwindowFlags = NO;
+            if( [angbandContext windowVisibleUsingDefaults] )
+            {
+                [angbandContext->primaryWindow orderFront: nil];
+                angbandContext.windowVisibilityChecked = YES;
+            }
+            else
+            {
+                [angbandContext->primaryWindow close];
+                angbandContext.windowVisibilityChecked = NO;
+            }
         }
-        else if( !angbandContext.hasSubwindowFlags && termHasSubwindowFlags )
+        else
         {
-            [angbandContext->primaryWindow orderFront: nil];
-            angbandContext.hasSubwindowFlags = YES;
+            BOOL termHasSubwindowFlags = ((window_flag[i] & validWindowFlagsMask) > 0);
+
+            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];
+            }
         }
     }
 
-    // make the main window key so that user events go to the right spot
+    /* Make the main window key so that user events go to the right spot */
     AngbandContext *mainWindow = angband_term[0]->data;
     [mainWindow->primaryWindow makeKeyAndOrderFront: nil];
 }
 
-/* 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;
-}
-
-/* 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
-};
-
-/* 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);    
-}
-
-/* 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;
-}
-
-/*
+/**
+ * ------------------------------------------------------------------------
  * Graphics support
- */
+ * ------------------------------------------------------------------------ */
 
-/*
+/**
  * The tile image
  */
-//static CGImageRef pict_image;
+static CGImageRef pict_image;
 
-/*
+/**
  * 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;
-
-/*
- * Value used to signal that we using ASCII, not graphical tiles.
- */ 
-#define GRAF_MODE_NONE 0
+static int pict_cols = 0;
+static int pict_rows = 0;
 
-/*
+/**
  * Requested graphics mode (as a grafID).
  * The current mode is stored in current_graphics_mode.
  */
 static int graf_mode_req = 0;
 
-/*
- * Helper function to check the various ways that graphics can be enabled, guarding against NULL
+/**
+ * Helper function to check the various ways that graphics can be enabled,
+ * guarding against NULL
  */
 static BOOL graphics_are_enabled(void)
 {
-    return GRAPHICS_NONE;
+    return current_graphics_mode
+       && current_graphics_mode->grafID != GRAPHICS_NONE;
 }
 
-/*
+/**
  * Hack -- game in progress
  */
 static Boolean game_in_progress = FALSE;
@@ -430,18 +622,23 @@ static Boolean game_in_progress = FALSE;
 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 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 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);
+#ifdef JP
+static wchar_t convert_two_byte_eucjp_to_utf16_native(const char *cp);
+#endif
 
-/*
+/**
  * Available values for 'wait'
  */
 #define CHECK_EVENTS_DRAIN -1
@@ -449,6 +646,11 @@ static void record_current_savefile(void);
 #define CHECK_EVENTS_WAIT 1
 
 
+/**
+ * Note when "open"/"new" become valid
+ */
+static bool initialized = FALSE;
+
 /* Methods for getting the appropriate NSUserDefaults */
 @interface NSUserDefaults (AngbandDefaults)
 + (NSUserDefaults *)angbandDefaults;
@@ -461,7 +663,8 @@ static void record_current_savefile(void);
 }
 @end
 
-/* Methods for pulling images out of the Angband bundle (which may be separate from the current bundle in the case of a screensaver */
+/* 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
@@ -469,7 +672,7 @@ static void record_current_savefile(void);
 /* The NSView subclass that draws our Angband image */
 @interface AngbandView : NSView
 {
-    IBOutlet AngbandContext *angbandContext;
+    AngbandContext *angbandContext;
 }
 
 - (void)setAngbandContext:(AngbandContext *)context;
@@ -479,7 +682,8 @@ static void record_current_savefile(void);
 
 @implementation NSImage (AngbandImages)
 
-/* Returns an image in the resource directoy of the bundle containing the Angband view class. */
+/* 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]];
@@ -496,6 +700,7 @@ static void record_current_savefile(void);
 @implementation AngbandContext
 
 @synthesize hasSubwindowFlags=_hasSubwindowFlags;
+@synthesize windowVisibilityChecked=_windowVisibilityChecked;
 
 - (NSFont *)selectionFont
 {
@@ -504,17 +709,21 @@ static void record_current_savefile(void);
 
 - (BOOL)useLiveResizeOptimization
 {
-    /* 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. */
+    /* If we have graphics turned off, text rendering is fast enough that we
+        * don't need to use a live resize optimization. */
     return inLiveResize && graphics_are_enabled();
 }
 
 - (NSSize)baseSize
 {
-    /* 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. */
+    /* 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));
 }
 
-// qsort-compatible compare function for CGSizes
+/* qsort-compatible compare function for CGSizes */
 static int compare_advances(const void *ap, const void *bp)
 {
     const CGSize *a = ap, *b = bp;
@@ -523,54 +732,114 @@ static int compare_advances(const void *ap, const void *bp)
 
 - (void)updateGlyphInfo
 {
-    // Update glyphArray and glyphWidths
+    /* Update glyphArray and glyphWidths */
     NSFont *screenFont = [angbandViewFont screenFont];
 
-    // Generate a string containing each MacRoman character
+    /* 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.
+    /* 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
+    /* Get glyphs */
     memset(glyphArray, 0, sizeof glyphArray);
     CTFontGetGlyphsForCharacters((CTFontRef)screenFont, unicharString, glyphArray, GLYPH_COUNT);
     
-    // Get advances. Record the max advance.
+    /* 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;
     }
     
-    // For good non-mono-font support, use the median advance. Start by sorting all advances.
+    /* 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
+    /* Skip over any initially empty run */
     size_t startIdx;
     for (startIdx = 0; startIdx < GLYPH_COUNT; startIdx++)
     {
         if (advances[startIdx].width > 0) break;
     }
     
-    // Pick the center to find the median
+    /* Pick the center to find the median */
     CGFloat medianAdvance = 0;
     if (startIdx < GLYPH_COUNT)
-    { // In case we have all zero advances for some reason
+    {
+               /* In case we have all zero advances for some reason */
         medianAdvance = advances[(startIdx + GLYPH_COUNT)/2].width;
     }
     
-    // Record the descender
+    /*
+     * 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);
+    fontAscender = [screenFont ascender];
+    if (fontAscender < bounds.origin.y + bounds.size.height) {
+       fontAscender = bounds.origin.y + bounds.size.height;
+    }
     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 (fontDescender > bounds.origin.y) {
+       fontDescender = bounds.origin.y;
+    }
+
+    /*
+     * Record the tile size.  Round both values up to have tile boundaries
+     * match pixel boundaries.
+     */
+    tileSize.width = ceil(medianAdvance);
+    tileSize.height = ceil(fontAscender - fontDescender);
+
+    /*
+     * Determine whether neighboring columns need to redrawn when a character
+     * changes.
+     */
+    CGRect boxes[GLYPH_COUNT] = {};
+    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] <= tileSize.width) {
+           compression = 1.;
+           offset = 0.5 * (tileSize.width - glyphWidths[i]);
+       } else {
+           compression = 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;
+       }
+    }
+    ncol_pre = ceil(-beyond_left / tileSize.width);
+    if (beyond_right > tileSize.width) {
+       ncol_post = ceil((beyond_right - tileSize.width) / tileSize.width);
+    } else {
+       ncol_post = 0;
+    }
 }
 
 - (void)updateImage
@@ -580,7 +849,9 @@ static int compare_advances(const void *ap, const void *bp)
     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. */
+        /* 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)
         {
@@ -591,19 +862,35 @@ static int compare_advances(const void *ap, const void *bp)
             size = [activeView bounds].size;
         }
     }
-    
-    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
+    /* 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]);
+        }
+    }
+
+    /* 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);
+
+    /* Create the layer at the appropriate size */
+    size.width = fmax(1, ceil(size.width * angbandLayerScale));
+    size.height = fmax(1, ceil(size.height * angbandLayerScale));
     angbandLayer = CGLayerCreateWithContext(exampleCtx, *(CGSize *)&size, NULL);
+
     CFRelease(exampleCtx);
 
+    /* Set the new context of the layer to draw at the correct scale */
+    CGContextRef ctx = CGLayerGetContext(angbandLayer);
+    CGContextScaleCTM(ctx, angbandLayerScale, angbandLayerScale);
+
     [self lockFocus];
     [[NSColor blackColor] set];
     NSRectFill((NSRect){NSZeroPoint, [self baseSize]});
@@ -644,7 +931,7 @@ static int compare_advances(const void *ap, const void *bp)
     {
         [self updateImage];
         
-        [self setNeedsDisplay:YES]; //we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects
+        [self setNeedsDisplay:YES]; /* We'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */
         [self requestRedraw];
     }
 #endif
@@ -660,13 +947,16 @@ static int compare_advances(const void *ap, const void *bp)
     {
         [self updateImage];
         
-        [self setNeedsDisplay:YES]; //we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects
+        [self setNeedsDisplay:YES]; /* We'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */
         [self requestRedraw];
     }
 #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. */
+/**
+ * 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
 {
     if (frames_per_second > 0)
@@ -683,15 +973,14 @@ static int compare_advances(const void *ap, const void *bp)
     lastRefreshTime = CFAbsoluteTimeGetCurrent();
 }
 
-- (void)drawWChar:(wchar_t)wchar inRect:(NSRect)tile
+- (void)drawWChar:(wchar_t)wchar inRect:(NSRect)tile context:(CGContextRef)ctx
 {
-    CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
-    CGFloat tileOffsetY = CTFontGetAscent( (CTFontRef)[angbandViewFont screenFont] );
+    CGFloat tileOffsetY = fontAscender;
     CGFloat tileOffsetX = 0.0;
     NSFont *screenFont = [angbandViewFont screenFont];
     UniChar unicharString[2] = {(UniChar)wchar, 0};
 
-    // Get glyph and advance
+    /* Get glyph and advance */
     CGGlyph thisGlyphArray[1] = { 0 };
     CGSize advances[1] = { { 0, 0 } };
     CTFontGetGlyphsForCharacters((CTFontRef)screenFont, unicharString, thisGlyphArray, 1);
@@ -699,7 +988,10 @@ static int compare_advances(const void *ap, const void *bp)
     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. */
+    /* 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))
     {
@@ -731,7 +1023,7 @@ static int compare_advances(const void *ap, const void *bp)
 
     textMatrix = CGAffineTransformScale( textMatrix, 1.0, -1.0 );
     CGContextSetTextMatrix(ctx, textMatrix);
-    CGContextShowGlyphsWithAdvances(ctx, &glyph, &CGSizeZero, 1);
+    CGContextShowGlyphsAtPositions(ctx, &glyph, &CGPointZero, 1);
     
     /* Restore the text matrix if we messed with the compression ratio */
     if (compressionRatio != 1.)
@@ -744,14 +1036,8 @@ static int compare_advances(const void *ap, const void *bp)
     CGContextSetTextMatrix(ctx, textMatrix);
 }
 
-/* Indication that we're redrawing everything, so get rid of the overdraw cache. */
-- (void)clearOverdrawCache
-{
-    memset(charOverdrawCache, 0, self->cols * self->rows * sizeof *charOverdrawCache);
-    memset(attrOverdrawCache, 0, self->cols * self->rows * sizeof *attrOverdrawCache);
-}
-
-/* Lock and unlock focus on our image or layer, setting up the CTM appropriately. */
+/* Lock and unlock focus on our image or layer, setting up the CTM
+ * appropriately. */
 - (CGContextRef)lockFocusUnscaled
 {
     /* Create an NSGraphicsContext representing this CGLayer */
@@ -802,19 +1088,31 @@ static int compare_advances(const void *ap, const void *bp)
 
     if( adjustTerminal )
     {
-        // adjust terminal to fit window with new font; save the new columns and rows since they could be changed
+        /* 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];
-    
-    /* Clear our overdraw cache */
-    [self clearOverdrawCache];
-    
-    /* Get redrawn */
-    [self requestRedraw];
 }
 
 - (id)init
@@ -828,20 +1126,30 @@ static int compare_advances(const void *ap, const void *bp)
         /* Default border size */
         self->borderSize = NSMakeSize(2, 2);
 
-        /* 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];
         
+       self->changes = create_pending_changes(self->cols, self->rows);
+       if (self->changes == 0) {
+           NSLog(@"AngbandContext init:  out of memory for pending changes");
+       }
+       self->ncol_pre = 0;
+       self->ncol_post = 0;
+
+       self->in_fullscreen_transition = NO;
+
         /* Make the image. Since we have no views, it'll just be a puny 1x1 image. */
-        [self updateImage];        
+        [self updateImage];
+
+        _windowVisibilityChecked = NO;
     }
     return self;
 }
 
-/* Destroy all the receiver's stuff. This is intended to be callable more than once. */
+/**
+ * Destroy all the receiver's stuff. This is intended to be callable more than
+ * once.
+ */
 - (void)dispose
 {
     terminal = NULL;
@@ -864,12 +1172,10 @@ static int compare_advances(const void *ap, const void *bp)
     [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;
+
+    /* Pending changes */
+    destroy_pending_changes(self->changes);
+    self->changes = 0;
 }
 
 /* Usual Cocoa fare */
@@ -879,150 +1185,56 @@ static int compare_advances(const void *ap, const void *bp)
     [super dealloc];
 }
 
-
-
-#pragma mark -
-#pragma mark Directories and Paths Setup
-
-/**
- *  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];
-
-    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 );
-    }
-
-    // angband requires the trailing slash for the directory path
-    return [bundleLibPath stringByAppendingString: @"/"];
-}
-
-/**
- *  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];
-
-#if defined(SAFE_DIRECTORY)
-    NSString *versionedDirectory = [NSString stringWithFormat: @"%@-%s", AngbandDirectoryNameBase, VERSION_STRING];
-    return [[documents stringByAppendingPathComponent: versionedDirectory] stringByAppendingString: @"/"];
-#else
-    return [[documents stringByAppendingPathComponent: AngbandDirectoryNameBase] stringByAppendingString: @"/"];
-#endif
-}
-
-/**
- *  Give Angband the base paths that should be used for the various directories it needs. It will create any needed directories.
+#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.
  */
-+ (void)prepareFilePathsAndDirectories
+static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n)
 {
-    char libpath[PATH_MAX + 1] = "\0";
-    char basepath[PATH_MAX + 1] = "\0";
-
-    [[self libDirectoryPath] getFileSystemRepresentation: libpath maxLength: sizeof(libpath)];
-    [[self angbandDocumentsPath] getFileSystemRepresentation: basepath maxLength: sizeof(basepath)];
-
-    init_file_paths( libpath, libpath, basepath );
-    create_needed_dirs();
-}
+    int i;
+    int count = 0;
 
-#pragma mark -
-
-/* 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];
-
-    // 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();
-
-    /* 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
+    /* 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.
      */
-        
-    [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)); 
-
-    /* Play the game */
-    play_game(new_game);
-}
-
-+ (void)endGame
-{    
-    /* Hack -- Forget messages */
-    msg_flag = FALSE;
-    
-    p_ptr->playing = FALSE;
-    p_ptr->leaving = TRUE;
-    quit_when_ready = TRUE;
-}
-
-
-- (IBAction)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:@"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();
+    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;
 }
+#endif
 
 - (void)addAngbandView:(AngbandView *)view
 {
@@ -1030,12 +1242,18 @@ static int compare_advances(const void *ap, const void *bp)
     {
         [angbandViews addObject:view];
         [self updateImage];
-        [self setNeedsDisplay:YES]; //we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects
+        [self setNeedsDisplay:YES]; /* We'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */
         [self requestRedraw];
     }
 }
 
-/* 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. */
+/**
+ * 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 ([angbandViews count] == 1)
@@ -1057,12 +1275,13 @@ static int compare_advances(const void *ap, const void *bp)
 
 - (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 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])
     {
         [self updateImage];
         
-        [self setNeedsDisplay:YES]; //we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects
+        [self setNeedsDisplay:YES]; /*we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */
         [self requestRedraw];
     }
 }
@@ -1074,48 +1293,25 @@ static int compare_advances(const void *ap, const void *bp)
     {
         [angbandViews removeObject:view];
         [self updateImage];
-        [self setNeedsDisplay:YES]; //we'll need to redisplay everything anyways, so avoid creating all those little redisplay rects
+        [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];
-}
-
-
-- (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;
-    }
-}
-
 - (NSWindow *)makePrimaryWindow
 {
     if (! primaryWindow)
     {
-        // this has to be done after the font is set, which it already is in term_init_cocoa()
+        /* 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 every window other than the main window closable
+        /* Make every window other than the main window closable */
         if( angband_term[0]->data != self )
         {
             styleMask |= NSClosableWindowMask;
@@ -1125,7 +1321,7 @@ static NSMenuItem *superitem(NSMenuItem *self)
 
         /* Not to be released when closed */
         [primaryWindow setReleasedWhenClosed:NO];
-        [primaryWindow setExcludedFromWindowsMenu: YES]; // we're using custom window menu handling
+        [primaryWindow setExcludedFromWindowsMenu: YES]; /* we're using custom window menu handling */
 
         /* Make the view */
         AngbandView *angbandView = [[AngbandView alloc] initWithFrame:contentRect];
@@ -1137,7 +1333,8 @@ static NSMenuItem *superitem(NSMenuItem *self)
         /* We are its delegate */
         [primaryWindow setDelegate:self];
 
-        /* Update our image, since this is probably the first angband view we've gotten. */
+        /* Update our image, since this is probably the first angband view
+                * we've gotten. */
         [self updateImage];
     }
     return primaryWindow;
@@ -1147,10 +1344,13 @@ static NSMenuItem *superitem(NSMenuItem *self)
 
 #pragma mark View/Window Passthrough
 
-/* This is what our views call to get us to draw to the window */
+/**
+ * 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. */
+    /* Take this opportunity to throttle so we don't flush faster than desired.
+        */
     BOOL viewInLiveResize = [view inLiveResize];
     if (! viewInLiveResize) [self throttle];
 
@@ -1163,12 +1363,6 @@ static NSMenuItem *superitem(NSMenuItem *self)
     if (viewInLiveResize) CGContextSetInterpolationQuality(context, kCGInterpolationDefault);
 }
 
-
-- (void)orderFront
-{
-    [[[angbandViews lastObject] window] makeKeyAndOrderFront:self];
-}
-
 - (BOOL)isOrderedIn
 {
     return [[[angbandViews lastObject] window] isVisible];
@@ -1179,11 +1373,6 @@ static NSMenuItem *superitem(NSMenuItem *self)
     return [[[angbandViews lastObject] window] isMainWindow];
 }
 
-- (void)orderOut
-{
-    [[[angbandViews lastObject] window] orderOut:self];
-}
-
 - (void)setNeedsDisplay:(BOOL)val
 {
     for (NSView *angbandView in angbandViews)
@@ -1205,17 +1394,19 @@ static NSMenuItem *superitem(NSMenuItem *self)
     [[self activeView] displayIfNeeded];
 }
 
-- (void)resizeOverdrawCache
+- (int)terminalIndex
 {
-    /* 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;
+       int termIndex = 0;
 
-    /* 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);
+       for( termIndex = 0; termIndex < ANGBAND_TERM_MAX; termIndex++ )
+       {
+               if( angband_term[termIndex] == self->terminal )
+               {
+                       break;
+               }
+       }
+
+       return termIndex;
 }
 
 - (void)resizeTerminalWithContentRect: (NSRect)contentRect saveToDefaults: (BOOL)saveToDefaults
@@ -1223,29 +1414,27 @@ static NSMenuItem *superitem(NSMenuItem *self)
     CGFloat newRows = floor( (contentRect.size.height - (borderSize.height * 2.0)) / tileSize.height );
     CGFloat newColumns = ceil( (contentRect.size.width - (borderSize.width * 2.0)) / tileSize.width );
 
+    if (newRows < 1 || newColumns < 1) return;
     self->cols = newColumns;
     self->rows = newRows;
-    [self resizeOverdrawCache];
+
+    if (resize_pending_changes(self->changes, self->rows) != 0) {
+       destroy_pending_changes(self->changes);
+       self->changes = 0;
+       NSLog(@"out of memory for pending changes with resize of terminal %d",
+             [self terminalIndex]);
+    }
 
     if( saveToDefaults )
     {
-        int termIndex = 0;
-
-        for( termIndex = 0; termIndex < ANGBAND_TERM_MAX; termIndex++ )
-        {
-            if( angband_term[termIndex] == self->terminal )
-            {
-                break;
-            }
-        }
-
+        int termIndex = [self terminalIndex];
         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];
+            [mutableTerm setValue: [NSNumber numberWithUnsignedInt: self->cols] forKey: AngbandTerminalColumnsDefaultsKey];
+            [mutableTerm setValue: [NSNumber numberWithUnsignedInt: self->rows] forKey: AngbandTerminalRowsDefaultsKey];
 
             NSMutableArray *mutableTerminals = [[NSMutableArray alloc] initWithArray: terminals];
             [mutableTerminals replaceObjectAtIndex: termIndex withObject: mutableTerm];
@@ -1254,6 +1443,7 @@ static NSMenuItem *superitem(NSMenuItem *self)
             [mutableTerminals release];
             [mutableTerm release];
         }
+        [[NSUserDefaults standardUserDefaults] synchronize];
     }
 
     term *old = Term;
@@ -1263,35 +1453,111 @@ static NSMenuItem *superitem(NSMenuItem *self)
     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];
+               [mutableTerminals release];
+               [mutableTerm release];
+       }
+}
+
+- (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)windowWillStartLiveResize: (NSNotification *)notification
+{ 
+}*/ 
 
 - (void)windowDidEndLiveResize: (NSNotification *)notification
 {
     NSWindow *window = [notification object];
     NSRect contentRect = [window contentRectForFrameRect: [window frame]];
-    [self resizeTerminalWithContentRect: contentRect saveToDefaults: YES];
+    [self resizeTerminalWithContentRect: contentRect saveToDefaults: !(self->in_fullscreen_transition)];
 }
 
-//- (NSSize)windowWillResize: (NSWindow *)sender toSize: (NSSize)frameSize
-//{
-//}
+/*- (NSSize)windowWillResize: (NSWindow *)sender toSize: (NSSize)frameSize
+{
+} */
+
+- (void)windowWillEnterFullScreen: (NSNotification *)notification
+{
+    self->in_fullscreen_transition = YES;
+}
 
 - (void)windowDidEnterFullScreen: (NSNotification *)notification
 {
     NSWindow *window = [notification object];
     NSRect contentRect = [window contentRectForFrameRect: [window frame]];
+    self->in_fullscreen_transition = NO;
     [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
 }
 
+- (void)windowWillExitFullScreen: (NSNotification *)notification
+{
+    self->in_fullscreen_transition = YES;
+}
+
 - (void)windowDidExitFullScreen: (NSNotification *)notification
 {
     NSWindow *window = [notification object];
     NSRect contentRect = [window contentRectForFrameRect: [window frame]];
+    self->in_fullscreen_transition = NO;
     [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
 }
 
@@ -1304,16 +1570,7 @@ static NSMenuItem *superitem(NSMenuItem *self)
         return;
     }
 
-    int termIndex = 0;
-
-    for( termIndex = 0; termIndex < ANGBAND_TERM_MAX; termIndex++ )
-    {
-        if( angband_term[termIndex] == self->terminal )
-        {
-            break;
-        }
-    }
-
+    int termIndex = [self terminalIndex];
     NSMenuItem *item = [[[NSApplication sharedApplication] windowsMenu] itemWithTag: AngbandWindowMenuItemTagBase + termIndex];
     [item setState: NSOnState];
 
@@ -1332,20 +1589,16 @@ static NSMenuItem *superitem(NSMenuItem *self)
         return;
     }
 
-    int termIndex = 0;
-
-    for( termIndex = 0; termIndex < ANGBAND_TERM_MAX; termIndex++ )
-    {
-        if( angband_term[termIndex] == self->terminal )
-        {
-            break;
-        }
-    }
-
+    int termIndex = [self terminalIndex];
     NSMenuItem *item = [[[NSApplication sharedApplication] windowsMenu] itemWithTag: AngbandWindowMenuItemTagBase + termIndex];
     [item setState: NSOffState];
 }
 
+- (void)windowWillClose: (NSNotification *)notification
+{
+       [self saveWindowVisibleToDefaults: NO];
+}
+
 @end
 
 
@@ -1405,16 +1658,21 @@ static NSMenuItem *superitem(NSMenuItem *self)
 
 @end
 
-/*
+/**
  * Delay handling of double-clicked savefiles
  */
 Boolean open_when_ready = FALSE;
 
 
 
-/*** Some generic functions ***/
+/**
+ * ------------------------------------------------------------------------
+ * Some generic functions
+ * ------------------------------------------------------------------------ */
 
-/* Sets an Angband color at a given index */
+/**
+ * Sets an Angband color at a given index
+ */
 static void set_color_for_index(int idx)
 {
     u16b rv, gv, bv;
@@ -1427,7 +1685,10 @@ static void set_color_for_index(int idx)
     CGContextSetRGBFillColor([[NSGraphicsContext currentContext] graphicsPort], rv/255., gv/255., bv/255., 1.);
 }
 
-/* Remember the current character in UserDefaults so we can select it by default next time. */
+/**
+ * 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];
@@ -1435,17 +1696,37 @@ static void record_current_savefile(void)
     {
         NSUserDefaults *angbandDefs = [NSUserDefaults angbandDefaults];
         [angbandDefs setObject:savefileString forKey:@"SaveFile"];
-        [angbandDefs synchronize];        
+        [angbandDefs synchronize];
     }
 }
 
 
-/*** Support for the "z-term.c" package ***/
+#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 release];
+    return result;
+}
+#endif /* JP */
 
-/*
+
+/**
+ * ------------------------------------------------------------------------
+ * Support for the "z-term.c" package
+ * ------------------------------------------------------------------------ */
+
+
+/**
  * Initialize a new Term
- *
  */
 static void Term_init_cocoa(term *t)
 {
@@ -1473,14 +1754,21 @@ static void Term_init_cocoa(term *t)
             break;
         }
     }
-    
+
     /* Set its font. */
-    NSString *fontName = [[NSUserDefaults angbandDefaults] 
-        stringForKey:[NSString stringWithFormat:@"FontName-%d", termIdx]];
+    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];
+
+    /* 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 : [default_font 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];
@@ -1490,29 +1778,42 @@ static void Term_init_cocoa(term *t)
     if( termIdx < (int)[terminalDefaults count] )
     {
         NSDictionary *term = [terminalDefaults objectAtIndex: termIdx];
-        rows = [[term valueForKey: AngbandTerminalRowsDefaultsKey] integerValue];
-        columns = [[term valueForKey: AngbandTerminalColumnsDefaultsKey] integerValue];
+        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 resizeOverdrawCache];
+
+    if (resize_pending_changes(context->changes, context->rows) != 0) {
+       destroy_pending_changes(context->changes);
+       context->changes = 0;
+       NSLog(@"initializing terminal %d:  out of memory for pending changes",
+             termIdx);
+    }
 
     /* Get the window */
     NSWindow *window = [context makePrimaryWindow];
-    
+
     /* Set its title and, for auxiliary terms, tentative size */
-    if (termIdx == 0)
-    {
-        [window setTitle:@"PosChengband"];
-    }
-    else
-    {
-        [window setTitle:[NSString stringWithFormat:@"Term %d", 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. */
+    NSString *title = [NSString stringWithCString:angband_term_name[termIdx]
+#ifdef JP
+                               encoding:NSJapaneseEUCStringEncoding
+#else
+                               encoding:NSMacOSRomanStringEncoding
+#endif
+    ];
+    [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];
@@ -1525,22 +1826,88 @@ static void Term_init_cocoa(term *t)
     {
         [window setRestorable:NO];
     }
-    
-    /* 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. */
+
+       /* 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 load_prefs() 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 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];
+    /* 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();
     
@@ -1551,7 +1918,7 @@ static void Term_init_cocoa(term *t)
 
 
 
-/*
+/**
  * Nuke an old Term
  */
 static void Term_nuke_cocoa(term *t)
@@ -1573,17 +1940,16 @@ static void Term_nuke_cocoa(term *t)
     
     [pool drain];
 }
-#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)
+
+/**
+ * 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;
     
-    /* Get the path to the image */
-    NSBundle *bundle = [NSBundle bundleForClass:[AngbandView class]];
-    NSString *path = [bundle pathForImageResource:name];
-    
-    /* Try using ImageIO to load it */
+    /* Try using ImageIO to load the image */
     if (path)
     {
         NSURL *url = [[NSURL alloc] initFileURLWithPath:path isDirectory:NO];
@@ -1593,7 +1959,8 @@ static CGImageRef create_angband_image(NSString *name)
             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 */
+                /* We really want the largest image, but in practice there's
+                                * only going to be one */
                 decodedImage = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)options);
                 CFRelease(source);
             }
@@ -1602,7 +1969,8 @@ static CGImageRef create_angband_image(NSString *name)
         }
     }
     
-    /* Draw the sucker to defeat ImageIO's weird desire to cache and decode on demand. Our images aren't that big! */
+    /* 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);
@@ -1623,20 +1991,25 @@ static CGImageRef create_angband_image(NSString *name)
                 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);
-        CGContextSetBlendMode(ctx, kCGBlendModeCopy);
-        CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), decodedImage);
-        result = CGBitmapContextCreateImage(ctx);
-        
-        /* Done with these things */
-        CFRelease(ctx);
+       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;
 }
-#endif
-/*
+
+/**
  * React to changes
  */
 static errr Term_xtra_cocoa_react(void)
@@ -1645,17 +2018,18 @@ static errr Term_xtra_cocoa_react(void)
     if (!initialized || !game_in_progress) return (-1);
 
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    //AngbandContext *angbandContext = Term->data;
-#if 0    
+    AngbandContext *angbandContext = Term->data;
+
     /* Handle graphics */
-    int expected_graf_mode = (current_graphics_mode ? current_graphics_mode->grafID : GRAF_MODE_NONE);
+    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 != GRAF_MODE_NONE) {
-                       new_mode = get_graphics_mode(graf_mode_req);
-               } else {
-                       new_mode = NULL;
+       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. */
@@ -1665,27 +2039,54 @@ static errr Term_xtra_cocoa_react(void)
         /* 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);
+            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, set the new desired mode to NULL */
-            if (! pict_image)
+            /* 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];
+               [[NSUserDefaults angbandDefaults] synchronize];
+
+               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;
+               NSModalResponse result = [alert runModal];
+               [alert release];
+           }
         }
         
         /* Record what we did */
-        use_graphics = (new_mode != NULL);
-        use_transparency = (new_mode != NULL);
-        ANGBAND_GRAF = (new_mode ? new_mode->pref : NULL);
+        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. */
+        /* 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. */
+            /* 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);
         }
@@ -1696,20 +2097,535 @@ static errr Term_xtra_cocoa_react(void)
         }
         
         /* Reset visuals */
-        if (initialized && game_in_progress)
+        if (arg_bigtile == use_bigtile)
         {
-            reset_visuals(TRUE);
+            reset_visuals();
         }
     }
-#endif    
-    [pool drain];
-    
-    /* Success */
-    return (0);
+
+    if (arg_bigtile != use_bigtile) {
+       /* Reset visuals */
+       reset_visuals();
+
+       Term_activate(angband_term[0]);
+       Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
+    }
+
+    [pool drain];
+    
+    /* Success */
+    return (0);
+}
+
+
+/**
+ * Draws one tile as a helper function for Term_xtra_cocoa_fresh().
+ */
+static void draw_image_tile(
+    NSGraphicsContext* nsContext,
+    CGContextRef cgContext,
+    CGImageRef image,
+    NSRect srcRect,
+    NSRect dstRect,
+    NSCompositingOperation op)
+{
+    /* 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);
+
+    /*
+     * 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, flippedSourceRect);
+    [nsContext setCompositingOperation:op];
+    CGContextDrawImage(cgContext, NSRectToCGRect(dstRect), subimage);
+    CGImageRelease(subimage);
+}
+
+
+/**
+ * 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(
+    struct PendingRowChange* prc, int iy, int npre, int* pclip, int* prend)
+{
+    int start = *prend;
+    int i = start - 1;
+
+    while (1) {
+       if (i < 0 || i < start - npre) {
+           break;
+       }
+
+       if (prc->cell_changes[i].change_type == CELL_CHANGE_PICT) {
+           /*
+            * 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 (prc->cell_changes[i].change_type == 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)) {
+                   prc->cell_changes[i - 1].c.w =
+                       convert_two_byte_eucjp_to_utf16_native(c);
+                   prc->cell_changes[i - 1].a = a[0];
+                   prc->cell_changes[i - 1].tcol = 1;
+                   prc->cell_changes[i].c.w = 0;
+                   prc->cell_changes[i].a = a[0];
+                   prc->cell_changes[i].tcol = 0;
+                   *pclip = i - 1;
+                   *prend = i - 1;
+                   --i;
+               } else {
+                   prc->cell_changes[i].c.w = c[1];
+                   prc->cell_changes[i].a = a[1];
+                   prc->cell_changes[i].tcol = 0;
+                   *pclip = i;
+                   *prend = i;
+               }
+           } else {
+               prc->cell_changes[i].c.w = c[1];
+               prc->cell_changes[i].a = a[1];
+               prc->cell_changes[i].tcol = 0;
+               *pclip = i;
+               *prend = i;
+           }
+#else
+           prc->cell_changes[i].c.w = c[1];
+           prc->cell_changes[i].a = a[1];
+           prc->cell_changes[i].tcol = 0;
+           *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;
+       }
+    }
+}
+
+
+/**
+ * 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 void query_after_text(
+    struct PendingRowChange* prc,
+    int iy,
+    int ncol,
+    int npost,
+    int* pclip,
+    int* prend)
+{
+    int end = *prend;
+    int i = end + 1;
+
+    while (1) {
+       /*
+        * 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->ncol_pre is zero or one.
+        * For larger values of ncol_pre, would need to do something more to
+        * avoid extra redraws.
+        */
+       if (i >= ncol ||
+           (i > end + npost &&
+            prc->cell_changes[i].change_type != CELL_CHANGE_TEXT &&
+            prc->cell_changes[i].change_type != CELL_CHANGE_WIPE)) {
+           break;
+       }
+
+       if (prc->cell_changes[i].change_type == CELL_CHANGE_PICT) {
+           /*
+            * 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 (prc->cell_changes[i].change_type == 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)) {
+                   prc->cell_changes[i].c.w =
+                       convert_two_byte_eucjp_to_utf16_native(c);
+                   prc->cell_changes[i].a = a[0];
+                   prc->cell_changes[i].tcol = 1;
+                   prc->cell_changes[i + 1].c.w = 0;
+                   prc->cell_changes[i + 1].a = a[0];
+                   prc->cell_changes[i + 1].tcol = 0;
+                   *pclip = i + 1;
+                   *prend = i + 1;
+                   ++i;
+               } else {
+                   prc->cell_changes[i].c.w = c[0];
+                   prc->cell_changes[i].a = a[0];
+                   prc->cell_changes[i].tcol = 0;
+                   *pclip = i;
+                   *prend = i;
+               }
+           } else {
+               prc->cell_changes[i].c.w = c[0];
+               prc->cell_changes[i].a = a[0];
+               prc->cell_changes[i].tcol = 0;
+               *pclip = i;
+               *prend = i;
+           }
+#else
+           prc->cell_changes[i].c.w = c[0];
+           prc->cell_changes[i].a = a[0];
+           prc->cell_changes[i].tcol = 0;
+           *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;
+       }
+    }
+}
+
+
+/**
+ * Draw the pending changes saved in angbandContext->changes.
+ */
+static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext)
+{
+    int graf_width, graf_height, alphablend;
+
+    if (angbandContext->changes->has_pict) {
+       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;
+    }
+
+    CGContextRef ctx = [angbandContext lockFocus];
+
+    if (angbandContext->changes->has_text ||
+       angbandContext->changes->has_wipe) {
+       NSFont *selectionFont = [[angbandContext selectionFont] screenFont];
+       [selectionFont set];
+    }
+
+    int iy;
+    for (iy = angbandContext->changes->ymin;
+        iy <= angbandContext->changes->ymax;
+        ++iy) {
+       struct PendingRowChange* prc = angbandContext->changes->rows[iy];
+       int ix;
+
+       /* Skip untouched rows. */
+       if (prc == 0) {
+           continue;
+       }
+
+       ix = prc->xmin;
+       while (1) {
+           int jx;
+
+           if (ix > prc->xmax) {
+               break;
+           }
+
+           switch (prc->cell_changes[ix].change_type) {
+           case CELL_CHANGE_NONE:
+               ++ix;
+               break;
+
+           case CELL_CHANGE_PICT:
+               {
+                   /*
+                    * 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 <= prc->xmax &&
+                          prc->cell_changes[jx].change_type
+                          == CELL_CHANGE_PICT) {
+                       NSRect destinationRect =
+                           [angbandContext rectInImageForTileAtX:jx Y:iy];
+                       NSRect sourceRect, terrainRect;
+
+                       destinationRect.size.width *= step;
+                       sourceRect.origin.x = graf_width *
+                           prc->cell_changes[jx].c.c;
+                       sourceRect.origin.y = graf_height *
+                           prc->cell_changes[jx].a;
+                       sourceRect.size.width = graf_width;
+                       sourceRect.size.height = graf_height;
+                       terrainRect.origin.x = graf_width *
+                           prc->cell_changes[jx].tcol;
+                       terrainRect.origin.y = graf_height *
+                           prc->cell_changes[jx].trow;
+                       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:
+               /*
+                * 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).
+                */
+               jx = ix + 1;
+               while (jx < angbandContext->cols &&
+                      (prc->cell_changes[jx].change_type
+                       == CELL_CHANGE_TEXT
+                       || prc->cell_changes[jx].change_type
+                       == CELL_CHANGE_WIPE)) {
+                   ++jx;
+               }
+               {
+                   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(
+                       prc, iy, angbandContext->ncol_pre, &isclip, &isrend);
+                   query_after_text(
+                       prc,
+                       iy,
+                       angbandContext->cols,
+                       angbandContext->ncol_post,
+                       &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) {
+                       NSRect rectToDraw;
+
+                       if (prc->cell_changes[k].change_type
+                           == CELL_CHANGE_WIPE) {
+                           /* Skip over since no rendering is necessary. */
+                           ++k;
+                           continue;
+                       }
+
+                       if (set_color || alast != prc->cell_changes[k].a) {
+                           set_color = 0;
+                           alast = prc->cell_changes[k].a;
+                           set_color_for_index(alast % MAX_COLORS);
+                       }
+
+                       rectToDraw =
+                           [angbandContext rectInImageForTileAtX:k Y:iy];
+                       if (prc->cell_changes[k].tcol) {
+                           rectToDraw.size.width *= 2.0;
+                           [angbandContext drawWChar:prc->cell_changes[k].c.w
+                                           inRect:rectToDraw context:ctx];
+                           k += 2;
+                       } else {
+                           [angbandContext drawWChar:prc->cell_changes[k].c.w
+                                           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;
+           }
+       }
+    }
+
+    if (angbandContext->changes->xcurs >= 0 &&
+       angbandContext->changes->ycurs >= 0) {
+       NSRect rect = [angbandContext
+                         rectInImageForTileAtX:angbandContext->changes->xcurs
+                         Y:angbandContext->changes->ycurs];
+
+       if (angbandContext->changes->bigcurs) {
+           rect.size.width += angbandContext->tileSize.width;
+       }
+       [[NSColor yellowColor] set];
+       NSFrameRectWithWidth(rect, 1);
+       /* Invalidate that rect */
+       [angbandContext setNeedsDisplayInBaseRect:rect];
+    }
+
+    [angbandContext unlockFocus];
 }
 
 
-/*
+/**
  * Do a "special thing"
  */
 static errr Term_xtra_cocoa(int n, int v)
@@ -1722,20 +2638,25 @@ static errr Term_xtra_cocoa(int n, int v)
     /* Analyze */
     switch (n)
     {
-            /* Make a noise */
+               /* Make a noise */
         case TERM_XTRA_NOISE:
         {
-            /* Make a noise */
             NSBeep();
             
             /* Success */
             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
+            /* Show or hide cocoa windows based on the subwindow flags set by
+                        * the user */
             AngbandUpdateWindowVisibility();
 
             /* Process an event */
@@ -1745,7 +2666,7 @@ static errr Term_xtra_cocoa(int n, int v)
             break;
         }
             
-            /* Process pending events */
+               /* Process pending events */
         case TERM_XTRA_EVENT:
         {
             /* Process an event */
@@ -1755,7 +2676,7 @@ static errr Term_xtra_cocoa(int n, int v)
             break;
         }
             
-            /* Flush all pending events (if any) */
+               /* Flush all pending events (if any) */
         case TERM_XTRA_FLUSH:
         {
             /* Hack -- flush all events */
@@ -1765,35 +2686,36 @@ static errr Term_xtra_cocoa(int n, int v)
             break;
         }
             
-            /* Hack -- Change the "soft level" */
+               /* 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. */            
+            /* 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 */
+               /* Clear the screen */
         case TERM_XTRA_CLEAR:
         {        
             [angbandContext lockFocus];
             [[NSColor blackColor] set];
-            NSRect imageRect = {NSZeroPoint, [angbandContext imageSize]};            
+            NSRect imageRect = {NSZeroPoint, [angbandContext imageSize]};
             NSRectFillUsingOperation(imageRect, NSCompositeCopy);
             [angbandContext unlockFocus];
-            [angbandContext clearOverdrawCache];
             [angbandContext setNeedsDisplay:YES];
             /* Success */
             break;
         }
             
-            /* React to changes */
+               /* React to changes */
         case TERM_XTRA_REACT:
         {
             /* React to changes */
             return (Term_xtra_cocoa_react());
         }
             
-            /* Delay (milliseconds) */
+               /* Delay (milliseconds) */
         case TERM_XTRA_DELAY:
         {
             /* If needed */
@@ -1819,11 +2741,12 @@ static errr Term_xtra_cocoa(int n, int v)
         }
             
         case TERM_XTRA_FRESH:
-        {
-            /* No-op -- see #1669 
-             * [angbandContext displayIfNeeded]; */
+           /* Draw the pending changes. */
+           if (angbandContext->changes != 0) {
+               Term_xtra_cocoa_fresh(angbandContext);
+               clear_pending_changes(angbandContext->changes);
+           }
             break;
-        }
             
         default:
             /* Oops */
@@ -1837,348 +2760,247 @@ static errr Term_xtra_cocoa(int n, int v)
     return result;
 }
 
-static errr Term_curs_cocoa(int x, int y)
+static errr Term_curs_cocoa(TERM_LEN x, TERM_LEN y)
 {
-    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;
 
-    /* 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];
-    
+    if (angbandContext->changes == 0) {
+       /* Bail out; there was an earlier memory allocation failure. */
+       return 1;
+    }
+    angbandContext->changes->xcurs = x;
+    angbandContext->changes->ycurs = y;
+    angbandContext->changes->bigcurs = 0;
+
     /* Success */
-    [pool drain];
     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 errr Term_bigcurs_cocoa(TERM_LEN x, TERM_LEN y)
+{
+    AngbandContext *angbandContext = Term->data;
+
+    if (angbandContext->changes == 0) {
+       /* Bail out; there was an earlier memory allocation failure. */
+       return 1;
+    }
+    angbandContext->changes->xcurs = x;
+    angbandContext->changes->ycurs = y;
+    angbandContext->changes->bigcurs = 1;
+
+    /* Success */
+    return 0;
+}
+
+/**
  * Low level graphics (Assumes valid input)
  *
  * Erase "n" characters starting at (x,y)
  */
-static errr Term_wipe_cocoa(int x, int y, int n)
+static errr Term_wipe_cocoa(TERM_LEN x, TERM_LEN y, int n)
 {
-    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];
+    struct PendingCellChange *pc;
+
+    if (angbandContext->changes == 0) {
+       /* Bail out; there was an earlier memory allocation failure. */
+       return 1;
+    }
+    if (angbandContext->changes->rows[y] == 0) {
+       angbandContext->changes->rows[y] =
+           create_row_change(angbandContext->cols);
+       if (angbandContext->changes->rows[y] == 0) {
+           NSLog(@"failed to allocate changes for row %d", y);
+           return 1;
+       }
+       if (angbandContext->changes->ymin > y) {
+           angbandContext->changes->ymin = y;
+       }
+       if (angbandContext->changes->ymax < y) {
+           angbandContext->changes->ymax = y;
+       }
+    }
+
+    angbandContext->changes->has_wipe = 1;
+    if (angbandContext->changes->rows[y]->xmin > x) {
+       angbandContext->changes->rows[y]->xmin = x;
+    }
+    if (angbandContext->changes->rows[y]->xmax < x + n - 1) {
+       angbandContext->changes->rows[y]->xmax = x + n - 1;
+    }
+    for (pc = angbandContext->changes->rows[y]->cell_changes + x;
+        pc != angbandContext->changes->rows[y]->cell_changes + x + n;
+        ++pc) {
+       pc->change_type = CELL_CHANGE_WIPE;
+    }
     
     /* Success */
     return (0);
 }
-#if 0
-static void draw_image_tile(CGImageRef image, NSRect srcRect, NSRect dstRect, NSCompositingOperation op)
-{
-    /* 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);
-}
 
-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)
+static errr Term_pict_cocoa(TERM_LEN x, TERM_LEN y, int n,
+                           TERM_COLOR *ap, concptr cp,
+                           const TERM_COLOR *tap, concptr tcp)
 {
     
     /* 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;
+    int any_change = 0;
+    int step = (use_bigtile) ? 2 : 1;
+    struct PendingCellChange *pc;
 
-    /* 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);
-    
-    /* Expand our destinationRect */
-    destinationRect = crack_rect(destinationRect, AngbandScaleIdentity, push_options(x, y));
-    
-    /* Scan the input */
-    int i;
-    int graf_width = current_graphics_mode->cell_width;
-    int graf_height = current_graphics_mode->cell_height;
-
-    for (i = 0; i < n; i++)
-    {
-        
-        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;
-            
+    if (angbandContext->changes == 0) {
+       /* Bail out; there was an earlier memory allocation failure. */
+       return 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.
+     */
+    if (angbandContext->changes->rows[y] == 0) {
+       angbandContext->changes->rows[y] =
+           create_row_change(angbandContext->cols);
+       if (angbandContext->changes->rows[y] == 0) {
+           NSLog(@"failed to allocate changes for row %d", y);
+           return 1;
+       }
+       if (angbandContext->changes->ymin > y) {
+           angbandContext->changes->ymin = y;
+       }
+       if (angbandContext->changes->ymax < y) {
+           angbandContext->changes->ymax = y;
+       }
+    }
 
-            /* 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);
-            }
-        }        
+    if (angbandContext->changes->rows[y]->xmin > x) {
+       angbandContext->changes->rows[y]->xmin = x;
+    }
+    if (angbandContext->changes->rows[y]->xmax < x + step * (n - 1)) {
+       angbandContext->changes->rows[y]->xmax = x + step * (n - 1);
+    }
+    for (pc = angbandContext->changes->rows[y]->cell_changes + x;
+        pc != angbandContext->changes->rows[y]->cell_changes + x + step * n;
+        pc += step) {
+       TERM_COLOR a = *ap++;
+       char c = *cp++;
+       TERM_COLOR ta = *tap++;
+       char tc = *tcp++;
+
+       if (use_graphics && (a & 0x80) && (c & 0x80)) {
+           pc->c.c = ((byte)c & 0x7F) % pict_cols;
+           pc->a = ((byte)a & 0x7F) % pict_rows;
+           pc->tcol = ((byte)tc & 0x7F) % pict_cols;
+           pc->trow = ((byte)ta & 0x7F) % pict_rows;
+           pc->change_type = CELL_CHANGE_PICT;
+           any_change = 1;
+       }
+    }
+    if (any_change) {
+       angbandContext->changes->has_pict = 1;
     }
-    
-    [angbandContext unlockFocus];
-    [angbandContext setNeedsDisplayInBaseRect:redisplayRect];
-    
-    [pool drain];
     
     /* Success */
     return (0);
 }
-#endif
-/*
+
+/**
  * Low level graphics.  Assumes valid input.
  *
  * Draw several ("n") chars, with an attr, at a given location.
  */
-static errr Term_text_cocoa(int x, int y, int n, byte_hack a, cptr cp)
+static errr Term_text_cocoa(
+    TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr cp)
 {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
-    /* 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;
+    struct PendingCellChange *pc;
+
+    if (angbandContext->changes == 0) {
+       /* Bail out; there was an earlier memory allocation failure. */
+       return 1;
+    }
+    if (angbandContext->changes->rows[y] == 0) {
+       angbandContext->changes->rows[y] =
+           create_row_change(angbandContext->cols);
+       if (angbandContext->changes->rows[y] == 0) {
+           NSLog(@"failed to allocate changes for row %d", y);
+           return 1;
+       }
+       if (angbandContext->changes->ymin > y) {
+           angbandContext->changes->ymin = y;
+       }
+       if (angbandContext->changes->ymax < y) {
+           angbandContext->changes->ymax = y;
+       }
     }
-    
-    /* Focus on our layer */
-    [angbandContext lockFocus];
 
-    /* Starting pixel */
-    NSRect charRect = [angbandContext rectInImageForTileAtX:x Y:y];
-    
-    const CGFloat tileWidth = angbandContext->tileSize.width;
-    
-    /* 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;
+    angbandContext->changes->has_text = 1;
+    if (angbandContext->changes->rows[y]->xmin > x) {
+       angbandContext->changes->rows[y]->xmin = x;
     }
-#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];
-        
-        // 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;
-               }
-#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];
-                }
-            }
-        }
+    if (angbandContext->changes->rows[y]->xmax < x + n - 1) {
+       angbandContext->changes->rows[y]->xmax = x + n - 1;
     }
-    
-    /* 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;
+    pc = angbandContext->changes->rows[y]->cell_changes + x;
+    while (pc != angbandContext->changes->rows[y]->cell_changes + x + n) {
+#ifdef JP
+       if (iskanji(*cp)) {
+           if (pc + 1 ==
+               angbandContext->changes->rows[y]->cell_changes + x + n) {
+               /*
+                * The second byte of the character is past the end.  Ignore
+                * the character.
+                */
+               break;
+           } else {
+               pc->c.w = convert_two_byte_eucjp_to_utf16_native(cp);
+               pc->a = a;
+               pc->tcol = 1;
+               pc->change_type = CELL_CHANGE_TEXT;
+               ++pc;
+               /*
+                * Fill in a dummy value since the previous character will take
+                * up two columns.
+                */
+               pc->c.w = 0;
+               pc->a = a;
+               pc->tcol = 0;
+               pc->change_type = CELL_CHANGE_TEXT;
+               ++pc;
+               cp += 2;
+           }
+       } else {
+           pc->c.w = *cp;
+           pc->a = a;
+           pc->tcol = 0;
+           pc->change_type = CELL_CHANGE_TEXT;
+           ++pc;
+           ++cp;
+       }
+#else
+       pc->c.w = *cp;
+       pc->a = a;
+       pc->tcol = 0;
+       pc->change_type = CELL_CHANGE_TEXT;
+       ++pc;
+       ++cp;
+#endif
     }
-
-    
-    // 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.
- */
-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;
-        }
-        count++;
-    }
-    return count;
-}
-#endif
-/* Post a nonsense event so that our event loop wakes up */
+/**
+ * Post a nonsense event so that our event loop wakes up
+ */
 static void wakeup_event_loop(void)
 {
     /* Big hack - send a nonsense event to make us update */
@@ -2187,7 +3009,7 @@ static void wakeup_event_loop(void)
 }
 
 
-/*
+/**
  * Create and initialize window number "i"
  */
 static term *term_data_link(int i)
@@ -2210,11 +3032,14 @@ static term *term_data_link(int i)
     term_init(newterm, columns, rows, 256 /* keypresses, for some reason? */);
     
     /* Differentiate between BS/^h, Tab/^i, etc. */
-    //newterm->complex_input = TRUE;
+    /* 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 = ' ';
@@ -2227,9 +3052,10 @@ static term *term_data_link(int i)
     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;
+    newterm->pict_hook = Term_pict_cocoa;
+    /* newterm->mbcs_hook = Term_mbcs_cocoa; */
     
     /* Global pointer */
     angband_term[i] = newterm;
@@ -2237,7 +3063,7 @@ static term *term_data_link(int i)
     return newterm;
 }
 
-/*
+/**
  * Load preferences from preferences file for current host+current user+
  * current application.
  */
@@ -2247,57 +3073,118 @@ static void load_prefs()
     
     /* Make some default defaults */
     NSMutableArray *defaultTerms = [[NSMutableArray alloc] init];
-    NSDictionary *standardTerm = @{
-                                   AngbandTerminalRowsDefaultsKey : @24,
-                                   AngbandTerminalColumnsDefaultsKey : @80,
-                                   };
-    
-    for( NSUInteger i = 0; i < 9; i++ )
+
+    /* 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], @"FramesPerSecond",
-                              [NSNumber numberWithBool:YES], @"AllowSound",
-                              [NSNumber numberWithInt:GRAPHICS_NONE], @"GraphicsID",
+                              [NSNumber numberWithInt:60], AngbandFrameRateDefaultsKey,
+                              [NSNumber numberWithBool:YES], AngbandSoundDefaultsKey,
+                              [NSNumber numberWithInt:GRAPHICS_NONE], AngbandGraphicsDefaultsKey,
+                              [NSNumber numberWithBool:YES], AngbandBigTileDefaultsKey,
                               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"];
+    /* 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 */
+    use_sound = ([defs boolForKey:AngbandSoundDefaultsKey] == YES) ? TRUE : FALSE;
     
     /* fps */
-    frames_per_second = [[NSUserDefaults angbandDefaults] integerForKey:@"FramesPerSecond"];
+    frames_per_second = [defs integerForKey:AngbandFrameRateDefaultsKey];
     
-    /* font */
+    /* 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];
 }
 
-/* Arbitary limit on number of possible samples per event */
-#define MAX_SAMPLES            8
+/**
+ * Arbitary limit on number of possible samples per event
+ */
+#define MAX_SAMPLES            16
 
-/* Struct representing all data for a set of event samples */
+/**
+ * Struct representing all data for a set of event samples
+ */
 typedef struct
 {
        int num;        /* Number of available samples for this event */
        NSSound *sound[MAX_SAMPLES];
 } sound_sample_list;
 
-/* Array of event sound structs */
-//static sound_sample_list samples[MSG_MAX];
+/**
+ * Array of event sound structs
+ */
+static sound_sample_list samples[MSG_MAX];
 
 
-/*
+/**
  * Load sound effects based on sound.cfg within the xtra/sound directory;
  * bridge to Cocoa to use NSSound for simple loading and playback, avoiding
  * I/O latency by cacheing all sounds at the start.  Inherits full sound
@@ -2307,18 +3194,17 @@ typedef struct
  */
 static void load_sounds(void)
 {
-#if 0
-       char path[2048];
+       char sound_dir[1024];
+       char path[1024];
        char buffer[2048];
-       ang_file *fff;
+       FILE *fff;
     
        /* Build the "sound" path */
-       path_build(path, sizeof(path), ANGBAND_DIR_XTRA, "sound");
-       ANGBAND_DIR_XTRA_SOUND = string_make(path);
+       path_build(sound_dir, sizeof(sound_dir), ANGBAND_DIR_XTRA, "sound");
     
        /* Find and open the config file */
-       path_build(path, sizeof(path), ANGBAND_DIR_XTRA_SOUND, "sound.cfg");
-       fff = file_open(path, MODE_READ, -1);
+       path_build(path, sizeof(path), sound_dir, "sound.cfg");
+       fff = my_fopen(path, "r");
     
        /* Handle errors */
        if (!fff)
@@ -2331,7 +3217,8 @@ static void load_sounds(void)
        NSAutoreleasePool *autorelease_pool;
        autorelease_pool = [[NSAutoreleasePool alloc] init];
     
-    /* Use a dictionary to unique sounds, so we can share NSSounds across multiple events */
+    /* Use a dictionary to unique sounds, so we can share NSSounds across
+        * multiple events */
     NSMutableDictionary *sound_dict = [NSMutableDictionary dictionary];
     
        /*
@@ -2341,7 +3228,7 @@ static void load_sounds(void)
     
        /* Parse the file */
        /* Lines are always of the form "name = sample [sample ...]" */
-       while (file_getl(fff, buffer, sizeof(buffer)))
+        while (my_fgets(fff, buffer, sizeof(buffer)) == 0)
        {
                char *msg_name;
                char *cfg_sample_list;
@@ -2403,10 +3290,12 @@ static void load_sounds(void)
             
             if (! sound)
             {
+               struct stat stb;
+
                 /* 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))
-                {
+                path_build(path, sizeof(path), sound_dir, cur_token);
+                if (stat(path, &stb) == 0)
+               {
                     
                     /* Load the sound into memory */
                     sound = [[[NSSound alloc] initWithContentsOfFile:[NSString stringWithUTF8String:path] byReference:YES] autorelease];
@@ -2450,26 +3339,21 @@ static void load_sounds(void)
        [autorelease_pool release];
     
        /* Close the file */
-       file_close(fff);
-#endif
+       my_fclose(fff);
 }
-#if 0
-/*
+
+/**
  * 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)
 {    
-    /* 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)
-    {
+    if (!loaded) {
         loaded = YES;
         load_sounds();
     }
@@ -2494,7 +3378,7 @@ static void play_sound(int event)
     /* Release the autorelease pool */
     [autorelease_pool drain];
 }
-#endif
+
 /*
  * 
  */
@@ -2513,49 +3397,7 @@ static void init_windows(void)
     Term_activate(primary);
 }
 
-#if 0
-/*
- *    Run the event loop and return a gameplay status to init_angband
- */
-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; 
-} 
-
-
-/* Return a command */
-static errr cocoa_get_cmd(cmd_context context, bool wait)
-{
-    if (context == CMD_INIT) 
-        return get_cmd_init();
-    else 
-        return textui_get_cmd(context, wait);
-}
-#endif
-/* Return the directory into which we put data (save and config) */
-static NSString *get_data_directory(void)
-{
-    return [@"~/Documents/PosChengband/" stringByExpandingTildeInPath];
-}
-
-/*
+/**
  * Handle the "open_when_ready" flag
  */
 static void handle_open_when_ready(void)
@@ -2570,12 +3412,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 +3425,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 +3459,81 @@ 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 = 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 +3555,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 +3597,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,12 +3648,11 @@ 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)
@@ -2835,20 +3694,34 @@ static BOOL check_events(int wait)
     
 }
 
-/*
+/**
  * 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;
+       NSModalResponse result = [alert runModal];
+       [alert release];
     }
 }
 
 
-/*
+/**
  * Hook to tell the user something, and then quit
  */
 static void hook_quit(const char * str)
@@ -2856,63 +3729,120 @@ static void hook_quit(const char * str)
     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];
+       NSModalResponse result = [alert runModal];
+       [alert release];
+       exit( 0 );
     }
 
-    return FALSE;
+    return bundleLibPath;
 }
-#endif
-/*** Main program ***/
 
-@interface AngbandAppDelegate : NSObject {
-    IBOutlet NSMenu *terminalsMenu;
-    NSMenu *_commandMenu;
-    NSDictionary *_commandMenuTagMap;
+/**
+ * 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
 }
 
-@property (nonatomic, retain) IBOutlet NSMenu *commandMenu;
-@property (nonatomic, retain) NSDictionary *commandMenuTagMap;
+/**
+ * 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;
+       }
 
-- (IBAction)newGame:sender;
-- (IBAction)editFont:sender;
-- (IBAction)openGame:sender;
+       if (![originalPath hasSuffix: @"/"]) {
+               return [originalPath stringByAppendingString: @"/"];
+       }
 
-- (IBAction)selectWindow: (id)sender;
+       return originalPath;
+}
 
-@end
+/**
+ * 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();
+}
+
+/**
+ * ------------------------------------------------------------------------
+ * Main program
+ * ------------------------------------------------------------------------ */
 
 @implementation AngbandAppDelegate
 
+@synthesize graphicsMenu=_graphicsMenu;
 @synthesize commandMenu=_commandMenu;
 @synthesize commandMenuTagMap=_commandMenuTagMap;
 
@@ -2940,6 +3870,12 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
     [panel orderFront:self];
 }
 
+/**
+ * Implent 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;
@@ -2954,7 +3890,7 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
     
     NSFont *oldFont = default_font;
     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) {
@@ -2978,6 +3914,14 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
     [(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
@@ -2985,37 +3929,37 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
     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 = @"";
+    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:YES];
+    [panel setAllowsMultipleSelection:NO];
     [panel setTreatsFilePackagesAsDirectories:YES];
+    [panel setDirectoryURL:startingDirectoryURL];
     
     /* Run it */
-    panelResult = [panel runModalForDirectory:startingDirectory file:savefileName types:nil];
+    panelResult = [panel runModal];
     if (panelResult == NSOKButton)
     {
-        NSArray* filenames = [panel filenames];
-        if ([filenames count] > 0)
+        NSArray* fileURLs = [panel URLs];
+        if ([fileURLs count] > 0 && [[fileURLs objectAtIndex:0] isFileURL])
         {
-            selectedSomething = [[filenames objectAtIndex:0] getFileSystemRepresentation:savefile maxLength:sizeof savefile];
+            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();
         
@@ -3035,10 +3979,99 @@ 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();
 }
 
+/**
+ * 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 */
+    prepare_paths_and_directories();
+
+    /* Note the "system" */
+    ANGBAND_SYS = "coc";
+
+    /* Load possible graphics modes */
+    init_graphics_modes();
+
+    /* Load preferences */
+    load_prefs();
+
+    /* Prepare the windows */
+    init_windows();
+
+    /* 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. */
+    int message_row = (Term->hgt - 23) / 5 + 23;
+    Term_erase(0, message_row, 255);
+    put_str(
+#ifdef JP
+       "['ファイル' メニューから '新' または '開く' を選択します]",
+       message_row, (Term->wid - 57) / 2
+#else
+       "[Choose 'New' or 'Open' from the 'File' menu]",
+       message_row, (Term->wid - 45) / 2
+#endif
+    );
+    Term_fresh();
+
+    [pool drain];
+
+    while (!game_in_progress) {
+        NSAutoreleasePool *splashScreenPool = [[NSAutoreleasePool alloc] init];
+        NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES];
+        if (event) [NSApp sendEvent:event];
+        [splashScreenPool drain];
+    }
+
+    /*
+     * 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 +4081,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 +4112,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,33 +4149,49 @@ 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];
 }
 
-- (IBAction)selectWindow: (id)sender
+- (void)selectWindow: (id)sender
 {
     NSInteger subwindowNumber = [(NSMenuItem *)sender tag] - AngbandWindowMenuItemTagBase;
     AngbandContext *context = angband_term[subwindowNumber]->data;
     [context->primaryWindow makeKeyAndOrderFront: self];
+       [context saveWindowVisibleToDefaults: YES];
 }
 
 - (void)prepareWindowsMenu
 {
-    // get the window menu with default items and add a separator and item for the main window
+    /* 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]];
 
-    NSMenuItem *angbandItem = [[NSMenuItem alloc] initWithTitle: @"PosChengband" action: @selector(selectWindow:) keyEquivalent: @"0"];
+    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];
     [angbandItem release];
 
-    // add items for the additional term windows
+    /* 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: @""];
+        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];
@@ -3130,17 +4199,84 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
     }
 }
 
+- (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];
+    [[NSUserDefaults angbandDefaults] synchronize];
+    
+    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) toggleSound: (NSMenuItem *) sender
+{
+    BOOL is_on = (sender.state == NSOnState);
+
+    /* Toggle the state and update the Angband global and preferences. */
+    sender.state = (is_on) ? NSOffState : NSOnState;
+    use_sound = (is_on) ? FALSE : TRUE;
+    [[NSUserDefaults angbandDefaults] setBool:(! is_on)
+                                     forKey:AngbandSoundDefaultsKey];
+}
+
+- (IBAction)toggleWideTiles:(NSMenuItem *) sender
+{
+    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];
+    [[NSUserDefaults angbandDefaults] synchronize];
+    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();
+       }
+    }
+}
+
 /**
- *  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])];
+    NSString *command = [self.commandMenuTagMap objectForKey: [NSNumber numberWithInteger: [menuItem tag]]];
     NSInteger windowNumber = [((AngbandContext *)angband_term[0]->data)->primaryWindow windowNumber];
 
-    // send a \ to bypass keymaps
+    /* Send a \ to bypass keymaps */
     NSEvent *escape = [NSEvent keyEventWithType: NSKeyDown
                                        location: NSZeroPoint
                                   modifierFlags: 0
@@ -3153,7 +4289,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
@@ -3175,6 +4311,7 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
     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 )
@@ -3185,7 +4322,9 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
         keyModifiers |= (useShiftModifier) ? NSShiftKeyMask : 0;
         keyModifiers |= (useOptionModifier) ? NSAlternateKeyMask : 0;
 
-        NSString *title = [item valueForKey: @"Title"];
+        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];
@@ -3195,7 +4334,7 @@ static bool cocoa_get_file(const char *suggested_name, char *path, size_t len)
         [menuItem release];
 
         NSString *angbandCommand = [item valueForKey: @"AngbandCommand"];
-        [angbandCommands setObject: angbandCommand forKey: @([menuItem tag])];
+        [angbandCommands setObject: angbandCommand forKey: [NSNumber numberWithInteger: [menuItem tag]]];
         tagOffset++;
     }
 
@@ -3217,9 +4356,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 +4377,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 +4410,82 @@ 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];
+    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];
+           /* Make the item */
+           NSMenuItem *item = [menu addItemWithTitle:title action:action keyEquivalent:@""];
+           [key release];
+           [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;
+    new_game = FALSE;
+
+    /* 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 */