OSDN Git Service

Merge branch 'master' of git://git.osdn.net/gitroot/hengband/hengband
[hengbandforosx/hengbandosx.git] / src / main-cocoa.m
index 7437244..f8b5087 100644 (file)
@@ -32,7 +32,6 @@
 #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";
 
@@ -95,10 +94,249 @@ static NSFont *default_font;
 
 @class AngbandView;
 
+/*
+ * 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 (the glyphArray and
- * glyphWidths members of AngbandContext are only used in updateGlyphInfo()).
- * The rendering in drawWChar will work for glyphs not in updateGlyphInfo()'s
+ * 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
@@ -149,7 +387,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;
@@ -174,7 +422,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;
@@ -218,6 +466,9 @@ static NSFont *default_font;
  * defaults */
 - (void)resizeTerminalWithContentRect: (NSRect)contentRect saveToDefaults: (BOOL)saveToDefaults;
 
+/* Change the minimum size for the window associated with the context. */
+- (void)setMinimumWindowSize;
+
 /* Called from the view to indicate that it is starting or ending live resize */
 - (void)viewWillStartLiveResize:(AngbandView *)view;
 - (void)viewDidEndLiveResize:(AngbandView *)view;
@@ -332,57 +583,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
  * ------------------------------------------------------------------------ */
@@ -576,37 +776,69 @@ static int compare_advances(const void *ap, const void *bp)
     }
     
     /*
-     * Some fonts, for instance DIN Condensed and Rockwell in 10.14, the ascent
-     * on '@' exceeds that reported by [screenFont ascender] and both of those
-     * fonts have clearing artifacts along the tops of drawn glyphs.  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.
+     * 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);
-
-    /* Record the ascender and descender */
+    CGRect bounds = CTFontGetBoundingRectsForGlyphs((CTFontRef) screenFont, kCTFontHorizontalOrientation, glyphArray, NULL, GLYPH_COUNT);
     fontAscender = [screenFont ascender];
     if (fontAscender < bounds.origin.y + bounds.size.height) {
-       NSLog(@"adjusted ascender for %@: %.2f to %.2f",
-             CTFontCopyFamilyName((CTFontRef) screenFont),
-             (double) fontAscender,
-             (double) (bounds.origin.y + bounds.size.height));
        fontAscender = bounds.origin.y + bounds.size.height;
     }
     fontDescender = [screenFont descender];
     if (fontDescender > bounds.origin.y) {
-       NSLog(@"adjusted descender for %@: %.2f to %.2f",
-             CTFontCopyFamilyName((CTFontRef) screenFont),
-             (double) fontDescender, (double) bounds.origin.y);
        fontDescender = bounds.origin.y;
     }
 
-    /* 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 = fontAscender - fontDescender;
+    /*
+     * 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
@@ -740,9 +972,8 @@ 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 = fontAscender;
     CGFloat tileOffsetX = 0.0;
     NSFont *screenFont = [angbandViewFont screenFont];
@@ -791,7 +1022,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.)
@@ -859,14 +1090,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];
+       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
@@ -883,6 +1128,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];
 
@@ -917,6 +1171,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 */
@@ -1101,9 +1359,15 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n)
     /* Initialize file paths */
     [self prepareFilePathsAndDirectories];
 
+    /* Note the "system" */
+    ANGBAND_SYS = "coc";
+
     /* Load preferences */
     load_prefs();
     
+    /* Load possible graphics modes */
+    init_graphics_modes();
+
     /* Prepare the windows */
     init_windows();
     
@@ -1116,12 +1380,6 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n)
     /* 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();
     
@@ -1384,6 +1642,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];
@@ -1412,6 +1677,24 @@ static NSMenuItem *superitem(NSMenuItem *self)
     Term_activate( old );
 }
 
+- (void)setMinimumWindowSize
+{
+    NSSize minsize;
+
+    if ([self terminalIndex] == 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];
@@ -1469,24 +1752,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];
 }
 
@@ -1625,7 +1920,7 @@ static void record_current_savefile(void)
     {
         NSUserDefaults *angbandDefs = [NSUserDefaults angbandDefaults];
         [angbandDefs setObject:savefileString forKey:@"SaveFile"];
-        [angbandDefs synchronize];        
+        [angbandDefs synchronize];
     }
 }
 
@@ -1717,45 +2012,26 @@ 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];
-    NSString *title;
 
     /* Set its title and, for auxiliary terms, tentative size */
-    if (termIdx == 0)
-    {
-       title = [NSString stringWithCString:angband_term_name[0]
+    NSString *title = [NSString stringWithCString:angband_term_name[termIdx]
 #ifdef JP
-                             encoding:NSJapaneseEUCStringEncoding
+                               encoding:NSJapaneseEUCStringEncoding
 #else
-                             encoding:NSMacOSRomanStringEncoding
+                               encoding:NSMacOSRomanStringEncoding
 #endif
-       ];
-        [window setTitle:title];
-
-        /* 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
-    {
-       title = [NSString stringWithCString:angband_term_name[termIdx]
-#ifdef JP
-                             encoding:NSJapaneseEUCStringEncoding
-#else
-                             encoding:NSMacOSRomanStringEncoding
-#endif
-       ];
-        [window setTitle:title];
-        /* 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];
-    }
-    
+    ];
+    [window setTitle:title];
+    [context setMinimumWindowSize];
     
     /* 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
@@ -1996,10 +2272,6 @@ static errr Term_xtra_cocoa_react(void)
         
         /* 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;
         
@@ -2026,6 +2298,7 @@ static errr Term_xtra_cocoa_react(void)
             reset_visuals();
         }
     }
+
     [pool drain];
     
     /* Success */
@@ -2034,6 +2307,496 @@ 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 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;
+           }
+           /*
+            * 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;
+
+                   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;
+
+                       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;
+                   }
+
+                   [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)
@@ -2108,7 +2871,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];
@@ -2149,11 +2912,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 */
@@ -2169,29 +2933,38 @@ static errr Term_xtra_cocoa(int n, int v)
 
 static errr Term_curs_cocoa(int x, int 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 */
+    return 0;
+}
+
+/**
+ * Draw a cursor that's two tiles wide.  For Japanese, that's used when
+ * the cursor points at a kanji character, irregardless of whether operating
+ * in big tile mode.
+ */
+static errr Term_bigcurs_cocoa(int x, int y)
+{
+    AngbandContext *angbandContext = Term->data;
+
+    if (angbandContext->changes == 0) {
+       /* Bail out; there was an earlier memory allocation failure. */
+       return 1;
+    }
+    angbandContext->changes->xcurs = x;
+    angbandContext->changes->ycurs = y;
+    angbandContext->changes->bigcurs = 1;
+
     /* Success */
-    [pool drain];
     return 0;
 }
 
@@ -2202,50 +2975,45 @@ static errr Term_curs_cocoa(int x, int y)
  */
 static errr Term_wipe_cocoa(int x, int y, int n)
 {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
     AngbandContext *angbandContext = Term->data;
-    int i;
+    struct PendingCellChange *pc;
 
-    /*
-     * 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];
+    if (angbandContext->changes == 0) {
+       /* Bail out; there was an earlier memory allocation failure. */
+       return 1;
+    }
+    if (angbandContext->changes->rows[y] == 0) {
+       angbandContext->changes->rows[y] =
+           create_row_change(angbandContext->cols);
+       if (angbandContext->changes->rows[y] == 0) {
+           NSLog(@"failed to allocate changes for row %d", y);
+           return 1;
+       }
+       if (angbandContext->changes->ymin > y) {
+           angbandContext->changes->ymin = y;
+       }
+       if (angbandContext->changes->ymax < y) {
+           angbandContext->changes->ymax = y;
+       }
+    }
+
+    angbandContext->changes->has_wipe = 1;
+    if (angbandContext->changes->rows[y]->xmin > x) {
+       angbandContext->changes->rows[y]->xmin = x;
+    }
+    if (angbandContext->changes->rows[y]->xmax < x + n - 1) {
+       angbandContext->changes->rows[y]->xmax = x + n - 1;
+    }
+    for (pc = angbandContext->changes->rows[y]->cell_changes + x;
+        pc != angbandContext->changes->rows[y]->cell_changes + x + n;
+        ++pc) {
+       pc->change_type = CELL_CHANGE_WIPE;
+    }
     
     /* Success */
     return (0);
 }
 
-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);
-}
-
 static errr Term_pict_cocoa(int x, int y, int n, TERM_COLOR *ap,
                             const char *cp, const TERM_COLOR *tap,
                             const char *tcp)
@@ -2254,83 +3022,55 @@ static errr Term_pict_cocoa(int x, int y, int n, TERM_COLOR *ap,
     /* Paranoia: Bail if we don't have a current graphics mode */
     if (! current_graphics_mode) return -1;
     
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
     AngbandContext* angbandContext = Term->data;
+    int any_change = 0;
+    struct PendingCellChange *pc;
 
-    /* Lock focus */
-    [angbandContext lockFocus];
-    
-    NSRect destinationRect = [angbandContext rectInImageForTileAtX:x Y:y];
-
-    /* Expand the rect to every touching pixel to figure out what to redisplay
-        */
-    NSRect redisplayRect = crack_rect(destinationRect, AngbandScaleIdentity, PUSH_RIGHT | PUSH_TOP | PUSH_BOTTOM | PUSH_LEFT);
-    
-    /* Expand our destinationRect */
-    destinationRect = crack_rect(destinationRect, AngbandScaleIdentity, push_options(x, y));
-    
-    /* Scan the input */
-    int i;
-    int graf_width = current_graphics_mode->cell_width;
-    int graf_height = current_graphics_mode->cell_height;
-
-    for (i = 0; i < n; i++)
-    {
-        
-        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);
-            }
-        }        
+    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) {
+       TERM_COLOR a = *ap++;
+       char c = *cp++;
+       TERM_COLOR ta = *tap++;
+       char tc = *tcp++;
+
+       if (use_graphics && (a & 0x80) && (c & 0x80)) {
+           pc->c.c = ((byte)c & 0x7F) % pict_cols;
+           pc->a = ((byte)a & 0x7F) % pict_rows;
+           pc->tcol = ((byte)tc & 0x7F) % pict_cols;
+           pc->trow = ((byte)ta & 0x7F) % pict_rows;
+           pc->change_type = CELL_CHANGE_PICT;
+           any_change = 1;
+       }
+    }
+    if (any_change) {
+       angbandContext->changes->has_pict = 1;
     }
-    
-    [angbandContext unlockFocus];
-    [angbandContext setNeedsDisplayInBaseRect:redisplayRect];
-    
-    [pool drain];
     
     /* Success */
     return (0);
@@ -2343,79 +3083,80 @@ static errr Term_pict_cocoa(int x, int y, int n, TERM_COLOR *ap,
  */
 static errr Term_text_cocoa(int x, int y, int n, byte_hack 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 = 0;
-    while (i < n) {
+    if (angbandContext->changes->rows[y] == 0) {
+       angbandContext->changes->rows[y] =
+           create_row_change(angbandContext->cols);
+       if (angbandContext->changes->rows[y] == 0) {
+           NSLog(@"failed to allocate changes for row %d", y);
+           return 1;
+       }
+       if (angbandContext->changes->ymin > y) {
+           angbandContext->changes->ymin = y;
+       }
+       if (angbandContext->changes->ymax < y) {
+           angbandContext->changes->ymax = y;
+       }
+    }
+
+    angbandContext->changes->has_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[i])) {
-           CGFloat w = rectToDraw.size.width;
-           wchar_t uv = convert_two_byte_eucjp_to_utf16_native(cp + i);
-
-           rectToDraw.size.width *= 2.0;
-           [angbandContext drawWChar:uv inRect:rectToDraw];
-           rectToDraw.origin.x += tileWidth + tileWidth;
-           rectToDraw.size.width = w;
-           i += 2;
+       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 {
-           [angbandContext drawWChar:cp[i] inRect:rectToDraw];
-           rectToDraw.origin.x += tileWidth;
-           ++i;
+           pc->c.w = *cp;
+           pc->a = a;
+           pc->tcol = 0;
+           pc->change_type = CELL_CHANGE_TEXT;
+           ++pc;
+           ++cp;
        }
 #else
-        [angbandContext drawWChar:cp[i] inRect:rectToDraw];
-       ++i;
-        rectToDraw.origin.x += tileWidth;
+       pc->c.w = *cp;
+       pc->a = a;
+       pc->tcol = 0;
+       pc->change_type = CELL_CHANGE_TEXT;
+       ++pc;
+       ++cp;
 #endif
     }
-
-    [angbandContext unlockFocus];    
-    /* Invalidate what we just drew */    
-    [angbandContext setNeedsDisplayInBaseRect:rectToClear];
-    
-    [pool drain];
     
     /* Success */
     return (0);
@@ -2460,6 +3201,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 = ' ';
@@ -2472,6 +3216,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; */
@@ -2821,7 +3566,7 @@ static void handle_open_when_ready(void)
         game_in_progress = TRUE;
         
         /* Wait for a keypress */
-        pause_line(23);
+        pause_line(Term->hgt - 1);
     }
 }
 
@@ -2845,8 +3590,7 @@ static void quit_calmly(void)
         /* Save the game */
         do_cmd_save_game(FALSE);
         record_current_savefile();
-        
-        
+
         /* Quit */
         quit(NULL);
     }
@@ -2965,10 +3709,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);
@@ -2978,32 +3718,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 */
@@ -3021,22 +3751,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);
@@ -3044,36 +3770,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);
@@ -3283,6 +3986,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
@@ -3367,8 +4078,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;
@@ -3406,10 +4124,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;
 }
@@ -3436,7 +4158,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];
@@ -3445,7 +4174,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];
@@ -3692,7 +4427,7 @@ static void hook_quit(const char * str)
 
 int main(int argc, char* argv[])
 {
-    NSApplicationMain(argc, (void*)argv);    
+    NSApplicationMain(argc, (void*)argv);
     return (0);
 }