OSDN Git Service

Made beginGame() an instance method of AngbandAppDelegate rather than a class method...
[hengbandforosx/hengbandosx.git] / src / main-cocoa.m
index 87aff8e..d6ea506 100644 (file)
@@ -23,7 +23,7 @@
 #if defined(MACH_O_COCOA)
 
 /* Mac headers */
-#include <Cocoa/Cocoa.h>
+#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_Escape 0x35
 #define kVK_ANSI_KeypadEnter 0x4C
 
-static NSSize const AngbandScaleIdentity = {1.0, 1.0};
 static NSString * const AngbandDirectoryNameLib = @"lib";
 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;
@@ -92,7 +95,251 @@ 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
@@ -132,8 +379,8 @@ static NSFont *default_font;
     /* 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 */
@@ -141,7 +388,17 @@ static NSFont *default_font;
     
     /* Last time we drew, so we can throttle drawing */
     CFAbsoluteTime lastRefreshTime;
-    
+
+    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;
@@ -166,7 +423,7 @@ 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;
@@ -210,20 +467,19 @@ static NSFont *default_font;
  * 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;
 - (void)saveWindowVisibleToDefaults: (BOOL)windowVisible;
 - (BOOL)windowVisibleUsingDefaults;
 
-/* Class methods */
-
-/* Begins an Angband game. This is the entry point for starting off. */
-+ (void)beginGame;
-
-/* Ends an Angband game. */
-+ (void)endGame;
-
 /* Internal method */
 - (AngbandView *)activeView;
 
@@ -324,57 +580,6 @@ static void AngbandUpdateWindowVisibility(void)
 }
 
 /**
- * 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
  * ------------------------------------------------------------------------ */
@@ -417,6 +622,10 @@ 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);
@@ -425,6 +634,9 @@ static void play_sound(int event);
 static BOOL check_events(int wait);
 static BOOL send_event(NSEvent *event);
 static void record_current_savefile(void);
+#ifdef JP
+static wchar_t convert_two_byte_eucjp_to_utf16_native(const char *cp);
+#endif
 
 /**
  * Available values for 'wait'
@@ -460,7 +672,7 @@ static bool initialized = FALSE;
 /* The NSView subclass that draws our Angband image */
 @interface AngbandView : NSView
 {
-    IBOutlet AngbandContext *angbandContext;
+    AngbandContext *angbandContext;
 }
 
 - (void)setAngbandContext:(AngbandContext *)context;
@@ -564,14 +776,70 @@ static int compare_advances(const void *ap, const void *bp)
         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
@@ -705,10 +973,9 @@ 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};
@@ -756,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.)
@@ -824,14 +1091,28 @@ static int compare_advances(const void *ap, const void *bp)
         /* 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];
-    
-    /* Get redrawn */
-    [self requestRedraw];
 }
 
 - (id)init
@@ -848,6 +1129,15 @@ static int compare_advances(const void *ap, const void *bp)
         /* 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];
 
@@ -882,6 +1172,10 @@ static int compare_advances(const void *ap, const void *bp)
     [primaryWindow close];
     [primaryWindow release];
     primaryWindow = nil;
+
+    /* Pending changes */
+    destroy_pending_changes(self->changes);
+    self->changes = 0;
 }
 
 /* Usual Cocoa fare */
@@ -891,89 +1185,6 @@ 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", @"Hengband was unable to find required resources and must quit. Please report a bug on the Angband forums.", @"Quit", nil, nil );
-        exit( 0 );
-    }
-
-       return bundleLibPath;
-}
-
-/**
- * Return the path for the directory where Angband should look for its standard
- * user file tree.
- */
-+ (NSString *)angbandDocumentsPath
-{
-       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
-}
-
-/**
- * Adjust directory paths as needed to correct for any differences needed by
- * Angband. \c init_file_paths() currently requires that all paths provided have
- * a trailing slash and all other platforms honor this.
- *
- * \param originalPath The directory path to adjust.
- * \return A path suitable for Angband or nil if an error occurred.
- */
-static NSString *AngbandCorrectedDirectoryPath(NSString *originalPath)
-{
-       if ([originalPath length] == 0) {
-               return nil;
-       }
-
-       if (![originalPath hasSuffix: @"/"]) {
-               return [originalPath stringByAppendingString: @"/"];
-       }
-
-       return originalPath;
-}
-
-/**
- * Give Angband the base paths that should be used for the various directories
- * it needs. It will create any needed directories.
- */
-+ (void)prepareFilePathsAndDirectories
-{
-       char libpath[PATH_MAX + 1] = "\0";
-       NSString *libDirectoryPath = AngbandCorrectedDirectoryPath([self libDirectoryPath]);
-       [libDirectoryPath getFileSystemRepresentation: libpath maxLength: sizeof(libpath)];
-
-       char basepath[PATH_MAX + 1] = "\0";
-       NSString *angbandDocumentsPath = AngbandCorrectedDirectoryPath([self angbandDocumentsPath]);
-       [angbandDocumentsPath getFileSystemRepresentation: basepath maxLength: sizeof(basepath)];
-
-       init_file_paths(libpath, libpath, basepath);
-       create_needed_dirs();
-}
-
-#pragma mark -
-
 #if 0
 /* From the Linux mbstowcs(3) man page:
  *   If dest is NULL, n is ignored, and the conversion  proceeds  as  above,
@@ -1016,98 +1227,14 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n)
                             ((unsigned char)src[i+3] & 0x3f);
             i += 3;
         } else {
-            /* Found an invalid multibyte sequence */
-            return (size_t)-1;
-        }
-        count++;
-    }
-    return count;
-}
-#endif
-
-/**
- * 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();
-    
-    /* Prepare the windows */
-    init_windows();
-    
-    /* Set up game event handlers */
-    /* init_display(); */
-    
-    /* Register the sound hook */
-    /* sound_hook = play_sound; */
-    
-    /* Initialise game */
-    init_angband();
-
-    /* This is not incorporated into Hengband's init_angband() yet. */
-    init_graphics_modes();
-
-    /* Note the "system" */
-    ANGBAND_SYS = "mac";
-    
-    /* 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("[Choose 'New' or 'Open' from the 'File' menu]",
-            message_row, (Term->wid - 45) / 2);
-    Term_fresh();
-
-    /*
-     * Play a game -- "new_game" is set by "new", "open" or the open document
-     * even handler as appropriate
-     */
-        
-    [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];
-    }
-
-    Term_fresh();
-    play_game(new_game);
-
-    quit(NULL);
-}
-
-+ (void)endGame
-{    
-    /* Hack -- Forget messages */
-    msg_flag = FALSE;
-    
-    p_ptr->playing = FALSE;
-    p_ptr->leaving = TRUE;
-    quit_when_ready = TRUE;
+            /* Found an invalid multibyte sequence */
+            return (size_t)-1;
+        }
+        count++;
+    }
+    return count;
 }
+#endif
 
 - (void)addAngbandView:(AngbandView *)view
 {
@@ -1172,30 +1299,6 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n)
 }
 
 
-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)
@@ -1315,6 +1418,13 @@ static NSMenuItem *superitem(NSMenuItem *self)
     self->cols = newColumns;
     self->rows = newRows;
 
+    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 = [self terminalIndex];
@@ -1343,6 +1453,24 @@ 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];
@@ -1400,24 +1528,36 @@ static NSMenuItem *superitem(NSMenuItem *self)
 {
     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
 {
 } */
 
+- (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];
 }
 
@@ -1556,11 +1696,29 @@ static void record_current_savefile(void)
     {
         NSUserDefaults *angbandDefs = [NSUserDefaults angbandDefaults];
         [angbandDefs setObject:savefileString forKey:@"SaveFile"];
-        [angbandDefs synchronize];        
+        [angbandDefs synchronize];
     }
 }
 
 
+#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
@@ -1630,31 +1788,27 @@ static void Term_init_cocoa(term *t)
     context->cols = columns;
     context->rows = rows;
 
+    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:@"Hengband"];
+    NSString *title = [NSString stringWithCString:angband_term_name[termIdx]
+#ifdef JP
+                               encoding:NSJapaneseEUCStringEncoding
+#else
+                               encoding:NSMacOSRomanStringEncoding
+#endif
+    ];
+    [window setTitle:title];
+    [context setMinimumWindowSize:termIdx];
 
-        /* Set minimum size (80x24) */
-        NSSize minsize;
-        minsize.width = 80 * context->tileSize.width + context->borderSize.width * 2.0;
-        minsize.height = 24 * context->tileSize.height + context->borderSize.height * 2.0;
-        [window setContentMinSize:minsize];
-    }
-    else
-    {
-        [window setTitle:[NSString stringWithFormat:@"Term %d", termIdx]];
-        /* Set minimum size (1x1) */
-        NSSize minsize;
-        minsize.width = context->tileSize.width + context->borderSize.width * 2.0;
-        minsize.height = context->tileSize.height + context->borderSize.height * 2.0;
-        [window setContentMinSize:minsize];
-    }
-    
-    
     /* 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
@@ -1840,14 +1994,16 @@ static CGImageRef create_angband_image(NSString *path)
 
         /* 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);
-        CGContextTranslateCTM(ctx, 0.0, height);
-        CGContextScaleCTM(ctx, 1.0, -1.0);
-        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;
@@ -1886,18 +2042,40 @@ static errr Term_xtra_cocoa_react(void)
             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 ? new_mode->grafID : 0;
-#if 0
-       /* This global is not in Hengband. */
-        use_transparency = (new_mode != NULL);
-#endif
         ANGBAND_GRAF = (new_mode ? new_mode->graf : "ascii");
         current_graphics_mode = new_mode;
         
@@ -1919,11 +2097,20 @@ static errr Term_xtra_cocoa_react(void)
         }
         
         /* Reset visuals */
-        if (initialized && game_in_progress)
+        if (arg_bigtile == use_bigtile)
         {
             reset_visuals();
         }
     }
+
+    if (arg_bigtile != use_bigtile) {
+       /* Reset visuals */
+       reset_visuals();
+
+       Term_activate(angband_term[0]);
+       Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
+    }
+
     [pool drain];
     
     /* Success */
@@ -1932,6 +2119,513 @@ static errr Term_xtra_cocoa_react(void)
 
 
 /**
+ * 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)
@@ -2006,7 +2700,7 @@ static errr Term_xtra_cocoa(int n, int v)
         {        
             [angbandContext lockFocus];
             [[NSColor blackColor] set];
-            NSRect imageRect = {NSZeroPoint, [angbandContext imageSize]};            
+            NSRect imageRect = {NSZeroPoint, [angbandContext imageSize]};
             NSRectFillUsingOperation(imageRect, NSCompositeCopy);
             [angbandContext unlockFocus];
             [angbandContext setNeedsDisplay:YES];
@@ -2047,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 */
@@ -2065,170 +2760,152 @@ 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;
 }
 
 /**
- * Low level graphics (Assumes valid input)
- *
- * Erase "n" characters starting at (x,y)
+ * 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_wipe_cocoa(int x, int y, int n)
+static errr Term_bigcurs_cocoa(TERM_LEN x, TERM_LEN y)
 {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
     AngbandContext *angbandContext = Term->data;
-    int i;
 
-    /*
-     * Erase the block of characters.  Mimic the geometry calculations for
-     * the cleared rectangle in Term_text_cocoa().
-     */
-    NSRect rect = [angbandContext rectInImageForTileAtX:x Y:y];
-    rect.size.width = angbandContext->tileSize.width * n;
-    rect = crack_rect(rect, AngbandScaleIdentity,
-                     (push_options(x, y) & ~PUSH_LEFT)
-                     | (push_options(x + n - 1, y) | PUSH_RIGHT));
-    
-    /* Lock focus and clear */
-    [angbandContext lockFocus];
-    [[NSColor blackColor] set];
-    NSRectFill(rect);
-    [angbandContext unlockFocus];    
-    [angbandContext setNeedsDisplayInBaseRect:rect];
-    
-    [pool drain];
-    
-    /* Success */
-    return (0);
-}
+    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;
 
-static void draw_image_tile(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);
-    NSGraphicsContext *context = [NSGraphicsContext currentContext];
-    [context setCompositingOperation:op];
-    CGContextDrawImage([context graphicsPort], NSRectToCGRect(dstRect), subimage);
-    CGImageRelease(subimage);
+    /* Success */
+    return 0;
 }
 
-static errr Term_pict_cocoa(int x, int y, int n, TERM_COLOR *ap,
-                            const char *cp, const TERM_COLOR *tap,
-                            const char *tcp)
+/**
+ * Low level graphics (Assumes valid input)
+ *
+ * Erase "n" characters starting at (x,y)
+ */
+static errr Term_wipe_cocoa(TERM_LEN x, TERM_LEN y, int n)
 {
-    
-    /* 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;
-
-    /* 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;
+    AngbandContext *angbandContext = Term->data;
+    struct PendingCellChange *pc;
 
-    for (i = 0; i < n; i++)
-    {
-        
-        TERM_COLOR a = *ap++;
-        char c = *cp++;
-        
-        TERM_COLOR ta = *tap++;
-        char 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;
+    }
+    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);
-            }
-        }        
+    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;
     }
     
-    [angbandContext unlockFocus];
-    [angbandContext setNeedsDisplayInBaseRect:redisplayRect];
+    /* Success */
+    return (0);
+}
+
+static errr Term_pict_cocoa(TERM_LEN x, TERM_LEN y, int n,
+                           TERM_COLOR *ap, concptr cp,
+                           const TERM_COLOR *tap, concptr tcp)
+{
     
-    [pool drain];
+    /* Paranoia: Bail if we don't have a current graphics mode */
+    if (! current_graphics_mode) return -1;
+    
+    AngbandContext* angbandContext = Term->data;
+    int any_change = 0;
+    int step = (use_bigtile) ? 2 : 1;
+    struct PendingCellChange *pc;
+
+    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;
+       }
+    }
+
+    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;
+    }
     
     /* Success */
     return (0);
@@ -2239,63 +2916,83 @@ static errr Term_pict_cocoa(int x, int y, int n, TERM_COLOR *ap,
  *
  * 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, concptr cp)
+static errr Term_text_cocoa(
+    TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr cp)
 {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    NSRect redisplayRect = NSZeroRect;
     AngbandContext* angbandContext = Term->data;
-    
-    /* Focus on our layer */
-    [angbandContext lockFocus];
+    struct PendingCellChange *pc;
 
-    /* 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_LEFT;
-    rightPushOptions |= PUSH_RIGHT;
-#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;
+    if (angbandContext->changes == 0) {
+       /* Bail out; there was an earlier memory allocation failure. */
+       return 1;
     }
-#endif    
-    NSRect rectToClear = charRect;
-    rectToClear.size.width = tileWidth * n;
-    rectToClear = crack_rect(rectToClear, AngbandScaleIdentity,
-                            leftPushOptions | rightPushOptions);
-    NSRectFill(rectToClear);
-    
-    NSFont *selectionFont = [[angbandContext selectionFont] screenFont];
-    [selectionFont set];
-    
-    /* Set the color */
-    set_color_for_index(a % MAX_COLORS);
-    
-    /* Draw each */
-    NSRect rectToDraw = charRect;
-    int i;
-    for (i=0; i < n; i++) {
-        [angbandContext drawWChar:cp[i] inRect:rectToDraw];
-        rectToDraw.origin.x += tileWidth;
+    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 unlockFocus];    
-    /* Invalidate what we just drew */    
-    [angbandContext setNeedsDisplayInBaseRect:rectToClear];
-    
-    [pool drain];
+    angbandContext->changes->has_text = 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;
+    }
+    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
+    }
     
     /* Success */
     return (0);
@@ -2340,6 +3037,9 @@ static term *term_data_link(int i)
     /* 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 = ' ';
@@ -2352,6 +3052,7 @@ 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; */
@@ -2424,11 +3125,16 @@ static void load_prefs()
     }
 
     NSDictionary *defaults = [[NSDictionary alloc] initWithObjectsAndKeys:
+#ifdef JP
+                              @"Osaka", @"FontName",
+#else
                               @"Menlo", @"FontName",
+#endif
                               [NSNumber numberWithFloat:13.f], @"FontSize",
-                              [NSNumber numberWithInt:60], @"FramesPerSecond",
+                              [NSNumber numberWithInt:60], AngbandFrameRateDefaultsKey,
                               [NSNumber numberWithBool:YES], AngbandSoundDefaultsKey,
-                              [NSNumber numberWithInt:GRAPHICS_NONE], @"GraphicsID",
+                              [NSNumber numberWithInt:GRAPHICS_NONE], AngbandGraphicsDefaultsKey,
+                              [NSNumber numberWithBool:YES], AngbandBigTileDefaultsKey,
                               defaultTerms, AngbandTerminalsDefaultsKey,
                               nil];
     [defs registerDefaults:defaults];
@@ -2436,13 +3142,22 @@ static void load_prefs()
     [defaultTerms release];
     
     /* Preferred graphics mode */
-    graf_mode_req = [defs integerForKey:@"GraphicsID"];
-    
+    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 = [defs integerForKey:@"FramesPerSecond"];
+    frames_per_second = [defs integerForKey:AngbandFrameRateDefaultsKey];
     
     /* Font */
     default_font = [[NSFont fontWithName:[defs valueForKey:@"FontName-0"] size:[defs floatForKey:@"FontSize-0"]] retain];
@@ -2697,7 +3412,7 @@ static void handle_open_when_ready(void)
         game_in_progress = TRUE;
         
         /* Wait for a keypress */
-        pause_line(23);
+        pause_line(Term->hgt - 1);
     }
 }
 
@@ -2721,8 +3436,7 @@ static void quit_calmly(void)
         /* Save the game */
         do_cmd_save_game(FALSE);
         record_current_savefile();
-        
-        
+
         /* Quit */
         quit(NULL);
     }
@@ -2841,10 +3555,6 @@ static BOOL send_event(NSEvent *event)
             
             
             /* Extract some modifiers */
-#if 0
-           /* Caught above so don't do anything with it here. */
-            int mx = !! (modifiers & NSCommandKeyMask);
-#endif            
             int mc = !! (modifiers & NSControlKeyMask);
             int ms = !! (modifiers & NSShiftKeyMask);
             int mo = !! (modifiers & NSAlternateKeyMask);
@@ -2854,32 +3564,22 @@ static BOOL send_event(NSEvent *event)
             /* Get the Angband char corresponding to this unichar */
             unichar c = [[event characters] characterAtIndex:0];
             char ch;
-            switch (c) {
+           /*
+            * 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 {
                /*
-                * Convert some special keys to what would be the normal
-                * alternative in the original keyset or, for things lke
-                * Delete, Return, and Escape, what one might use from ASCII.
                 * 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.  Since
-                * macro triggers appear to assume at most two keys plus the
-                * modifiers, can only handle values of c below 4096 with
-                * 64 values per key.
+                * value encoded into printable ASCII characters.
                 */
-               case NSUpArrowFunctionKey: ch = '8'; kp = 0; break;
-               case NSDownArrowFunctionKey: ch = '2'; kp = 0; break;
-               case NSLeftArrowFunctionKey: ch = '4'; kp = 0; break;
-               case NSRightArrowFunctionKey: ch = '6'; kp = 0; break;
-               case NSHelpFunctionKey: ch = '?'; break;
-               case NSDeleteFunctionKey: ch = '\b'; break;
-                    
-                default:
-                    if (c <= 0x7F)
-                        ch = (char)c;
-                    else
-                        ch = '\0';
-                    break;
+               ch = '\0';
             }
             
             /* override special keys */
@@ -2897,22 +3597,18 @@ static BOOL send_event(NSEvent *event)
             /* Enqueue it */
             if (ch != '\0')
             {
-                
-                /* Enqueue the keypress */
-#if 0
-                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 if (c < 4096 || (c >= 0xF700 && c <= 0xF77F)) {
-               unichar part;
-               char cenc;
+            }
+           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);
@@ -2920,36 +3616,13 @@ static BOOL send_event(NSEvent *event)
                /* Send the modifiers. */
                if (mc) Term_keypress('C');
                if (ms) Term_keypress('S');
-               if (mo) Term_keypress('A');
+               if (mo) Term_keypress('O');
                if (kp) Term_keypress('K');
 
-               /*
-                * Put part of the range Apple reserves for special keys
-                * into 0 - 127 since that range has been handled normally.
-                */
-               if (c >= 0xF700) {
-                   c -= 0xF700;
-               }
-
-               /* Encode the value as two printable characters. */
-               part = (c >> 6) & 63;
-               if (part > 38) {
-                   cenc = 'a' + (part - 38);
-               } else if (part > 12) {
-                   cenc = 'A' + (part - 12);
-               } else {
-                   cenc = '0' + part;
-               }
-               Term_keypress(cenc);
-               part = c & 63;
-               if (part > 38) {
-                   cenc = 'a' + (part - 38);
-               } else if (part > 12) {
-                   cenc = 'A' + (part - 12);
-               } else {
-                   cenc = '0' + part;
-               }
-               Term_keypress(cenc);
+               do {
+                   Term_keypress(encoded[c & 0xF]);
+                   c >>= 4;
+               } while (c > 0);
 
                /* End the macro trigger. */
                Term_keypress(13);
@@ -3028,8 +3701,22 @@ 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];
     }
 }
 
@@ -3044,33 +3731,118 @@ static void hook_quit(const char * str)
 }
 
 /**
- * ------------------------------------------------------------------------
- * Main program
- * ------------------------------------------------------------------------ */
+ * 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)
+{
+    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];
+
+       /*
+        * 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 bundleLibPath;
+}
+
+/**
+ * Return the path for the directory where Angband should look for its standard
+ * user file tree.
+ */
+static NSString* get_doc_directory(void)
+{
+       NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
 
-@interface AngbandAppDelegate : NSObject {
-    IBOutlet NSMenu *terminalsMenu;
-    NSMenu *_commandMenu;
-    NSDictionary *_commandMenuTagMap;
+#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;
+       }
+
+       if (![originalPath hasSuffix: @"/"]) {
+               return [originalPath stringByAppendingString: @"/"];
+       }
 
-- (IBAction)newGame:sender;
-- (IBAction)openGame:sender;
+       return originalPath;
+}
 
-- (IBAction)editFont:sender;
-- (IBAction)setGraphicsMode:(NSMenuItem *)sender;
-- (IBAction)toggleSound:(NSMenuItem *)sender;
+/**
+ * Give Angband the base paths that should be used for the various directories
+ * it needs. It will create any needed directories.
+ */
+static void prepare_paths_and_directories(void)
+{
+       char libpath[PATH_MAX + 1] = "\0";
+       NSString *libDirectoryPath =
+           AngbandCorrectedDirectoryPath(get_lib_directory());
+       [libDirectoryPath getFileSystemRepresentation: libpath maxLength: sizeof(libpath)];
 
-- (IBAction)setRefreshRate:(NSMenuItem *)menuItem;
-- (IBAction)selectWindow: (id)sender;
+       char basepath[PATH_MAX + 1] = "\0";
+       NSString *angbandDocumentsPath =
+           AngbandCorrectedDirectoryPath(get_doc_directory());
+       [angbandDocumentsPath getFileSystemRepresentation: basepath maxLength: sizeof(basepath)];
 
-@end
+       init_file_paths(libpath, basepath);
+       create_needed_dirs();
+}
+
+/**
+ * ------------------------------------------------------------------------
+ * Main program
+ * ------------------------------------------------------------------------ */
 
 @implementation AngbandAppDelegate
 
+@synthesize graphicsMenu=_graphicsMenu;
 @synthesize commandMenu=_commandMenu;
 @synthesize commandMenuTagMap=_commandMenuTagMap;
 
@@ -3098,6 +3870,12 @@ static void hook_quit(const char * str)
     [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;
@@ -3136,6 +3914,14 @@ static void hook_quit(const char * str)
     [(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
@@ -3199,6 +3985,93 @@ static void hook_quit(const char * str)
     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];
@@ -3213,8 +4086,15 @@ static void hook_quit(const char * str)
         }
         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;
@@ -3232,15 +4112,16 @@ static void hook_quit(const char * str)
     {
         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;
     }
@@ -3252,10 +4133,14 @@ static void hook_quit(const char * str)
        [menuItem setState: ((is_on) ? NSOnState : NSOffState)];
        return YES;
     }
-    else if( sel == @selector(sendAngbandCommand:) )
+    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;
 }
@@ -3264,10 +4149,10 @@ static void hook_quit(const char * str)
 - (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;
@@ -3282,7 +4167,14 @@ static void hook_quit(const char * str)
     NSMenu *windowsMenu = [[NSApplication sharedApplication] windowsMenu];
     [windowsMenu addItem: [NSMenuItem separatorItem]];
 
-    NSMenuItem *angbandItem = [[NSMenuItem alloc] initWithTitle: @"Hengband" 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];
@@ -3291,7 +4183,13 @@ static void hook_quit(const char * str)
     /* Add items for the additional term windows */
     for( NSInteger i = 1; i < ANGBAND_TERM_MAX; i++ )
     {
-        NSString *title = [NSString stringWithFormat: @"Term %ld", (long)i];
+        NSString *title = [NSString stringWithCString:angband_term_name[i]
+#ifdef JP
+                                   encoding:NSJapaneseEUCStringEncoding
+#else
+                                   encoding:NSMacOSRomanStringEncoding
+#endif
+       ];
         NSString *keyEquivalent = [NSString stringWithFormat: @"%ld", (long)i];
         NSMenuItem *windowItem = [[NSMenuItem alloc] initWithTitle: title action: @selector(selectWindow:) keyEquivalent: keyEquivalent];
         [windowItem setTarget: self];
@@ -3301,17 +4199,32 @@ static void hook_quit(const char * str)
     }
 }
 
-- (IBAction)setGraphicsMode:(NSMenuItem *)sender
+- (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:@"GraphicsID"];
+    [[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();
         
@@ -3331,6 +4244,27 @@ static void hook_quit(const char * str)
                                      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
@@ -3377,6 +4311,7 @@ static void hook_quit(const char * str)
     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 )
@@ -3387,7 +4322,9 @@ static void hook_quit(const char * str)
         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];
@@ -3419,7 +4356,7 @@ static void hook_quit(const char * str)
 
 - (void)applicationDidFinishLaunching:sender
 {
-    [AngbandContext beginGame];
+    [self beginGame];
     
     /* Once beginGame finished, the game is over - that's how Angband works,
         * and we should quit */
@@ -3459,7 +4396,7 @@ static void hook_quit(const char * str)
 - (void)menuNeedsUpdate:(NSMenu *)menu {
     
     /* Only the graphics menu is dynamic */
-    if (! [[menu title] isEqualToString:@"Graphics"])
+    if (! [menu isEqual:self.graphicsMenu])
         return;
     
     /* If it's non-empty, then we've already built it. Currently graphics modes
@@ -3473,7 +4410,11 @@ static void hook_quit(const char * str)
     SEL action = @selector(setGraphicsMode:);
     
     /* Add an initial Classic ASCII menu item */
-    NSMenuItem *classicItem = [menu addItemWithTitle:@"Classic ASCII" action:action keyEquivalent:@""];
+    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 */
@@ -3489,12 +4430,13 @@ static void hook_quit(const char * str)
            }
            /* 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];
+           key = [[NSString alloc] initWithUTF8String:graf->menuname];
+           title = NSLocalizedStringWithDefaultValue(
+               key, tblname, [NSBundle mainBundle], key, @"");
         
            /* Make the item */
            NSMenuItem *item = [menu addItemWithTitle:title action:action keyEquivalent:@""];
+           [key release];
            [item setTag:graf->grafID];
        }
     }
@@ -3503,33 +4445,46 @@ static void hook_quit(const char * str)
 /**
  * Delegate method that gets called if we're asked to open a file.
  */
-- (BOOL)application:(NSApplication *)sender openFiles:(NSArray *)filenames
+- (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;
-    
+    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);
 }