OSDN Git Service

d9692b2c64e27545716db1c91fc5d867760aba15
[hengbandforosx/hengbandosx.git] / src / main-cocoa.m
1 /**
2  * \file main-cocoa.m
3  * \brief OS X front end
4  *
5  * Copyright (c) 2011 Peter Ammon
6  *
7  * This work is free software; you can redistribute it and/or modify it
8  * under the terms of either:
9  *
10  * a) the GNU General Public License as published by the Free Software
11  *    Foundation, version 2, or
12  *
13  * b) the "Angband licence":
14  *    This software may be copied and distributed for educational, research,
15  *    and not for profit purposes provided that this copyright and statement
16  *    are included in all such copies.  Other copyrights may also apply.
17  */
18
19 #include "angband.h"
20 /* This is not included in angband.h in Hengband. */
21 #include "grafmode.h"
22
23 #if defined(MACH_O_COCOA)
24
25 /* Mac headers */
26 #import "cocoa/AppDelegate.h"
27 //#include <Carbon/Carbon.h> /* For keycodes */
28 /* Hack - keycodes to enable compiling in macOS 10.14 */
29 #define kVK_Return 0x24
30 #define kVK_Tab    0x30
31 #define kVK_Delete 0x33
32 #define kVK_Escape 0x35
33 #define kVK_ANSI_KeypadEnter 0x4C
34
35 static NSString * const AngbandDirectoryNameLib = @"lib";
36 static NSString * const AngbandDirectoryNameBase = @"Hengband";
37
38 static NSString * const AngbandMessageCatalog = @"Localizable";
39 static NSString * const AngbandTerminalsDefaultsKey = @"Terminals";
40 static NSString * const AngbandTerminalRowsDefaultsKey = @"Rows";
41 static NSString * const AngbandTerminalColumnsDefaultsKey = @"Columns";
42 static NSString * const AngbandTerminalVisibleDefaultsKey = @"Visible";
43 static NSString * const AngbandGraphicsDefaultsKey = @"GraphicsID";
44 static NSString * const AngbandBigTileDefaultsKey = @"UseBigTiles";
45 static NSString * const AngbandFrameRateDefaultsKey = @"FramesPerSecond";
46 static NSString * const AngbandSoundDefaultsKey = @"AllowSound";
47 static NSInteger const AngbandWindowMenuItemTagBase = 1000;
48 static NSInteger const AngbandCommandMenuItemTagBase = 2000;
49
50 /* Global defines etc from Angband 3.5-dev - NRM */
51 #define ANGBAND_TERM_MAX 8
52
53 #define MAX_COLORS 256
54 #define MSG_MAX SOUND_MAX
55
56 /* End Angband stuff - NRM */
57
58 /* Application defined event numbers */
59 enum
60 {
61     AngbandEventWakeup = 1
62 };
63
64 /* Delay handling of pre-emptive "quit" event */
65 static BOOL quit_when_ready = FALSE;
66
67 /* Set to indicate the game is over and we can quit without delay */
68 static Boolean game_is_finished = FALSE;
69
70 /* Our frames per second (e.g. 60). A value of 0 means unthrottled. */
71 static int frames_per_second;
72
73 /* Force a new game or not? */
74 static bool new_game = FALSE;
75
76 @class AngbandView;
77
78 #ifdef JP
79 static wchar_t convert_two_byte_eucjp_to_utf16_native(const char *cp);
80 #endif
81
82 /**
83  * Load sound effects based on sound.cfg within the xtra/sound directory;
84  * bridge to Cocoa to use NSSound for simple loading and playback, avoiding
85  * I/O latency by caching all sounds at the start.  Inherits full sound
86  * format support from Quicktime base/plugins.
87  * pelpel favoured a plist-based parser for the future but .cfg support
88  * improves cross-platform compatibility.
89  */
90 @interface AngbandSoundCatalog : NSObject {
91 @private
92     /**
93      * Stores instances of NSSound keyed by path so the same sound can be
94      * used for multiple events.
95      */
96     NSMutableDictionary *soundsByPath;
97     /**
98      * Stores arrays of NSSound keyed by event number.
99      */
100     NSMutableDictionary *soundArraysByEvent;
101 }
102
103 /**
104  * If NO, then playSound effectively becomes a do nothing operation.
105  */
106 @property (getter=isEnabled) BOOL enabled;
107
108 /**
109  * Set up for lazy initialization in playSound().  Set enabled to NO.
110  */
111 - (id)init;
112
113 /**
114  * If self.enabled is YES and the given event has one or more sounds
115  * corresponding to it in the catalog, plays one of those sounds, chosen at
116  * random.
117  */
118 - (void)playSound:(int)event;
119
120 /**
121  * Impose an arbitary limit on number of possible samples per event.
122  * Currently not declaring this as a class property for compatibility with
123  * versions of Xcode prior to 8.
124  */
125 + (int)maxSamples;
126
127 /**
128  * Return the shared sound catalog instance, creating it if it does not
129  * exist yet.  Currently not declaring this as a class property for
130  * compatibility with versions of Xcode prior to 8.
131  */
132 + (AngbandSoundCatalog*)sharedSounds;
133
134 /**
135  * Release any resouces associated with shared sounds.
136  */
137 + (void)clearSharedSounds;
138
139 @end
140
141 @implementation AngbandSoundCatalog
142
143 - (id)init {
144     if (self = [super init]) {
145         self->soundsByPath = nil;
146         self->soundArraysByEvent = nil;
147         self->_enabled = NO;
148     }
149     return self;
150 }
151
152 - (void)playSound:(int)event {
153     if (! self.enabled) {
154         return;
155     }
156
157     /* Initialize when the first sound is played. */
158     if (self->soundArraysByEvent == nil) {
159         /* Build the "sound" path */
160         char sound_dir[1024];
161         path_build(sound_dir, sizeof(sound_dir), ANGBAND_DIR_XTRA, "sound");
162
163         /* Find and open the config file */
164         char path[1024];
165         path_build(path, sizeof(path), sound_dir, "sound.cfg");
166         FILE *fff = my_fopen(path, "r");
167
168         /* Handle errors */
169         if (!fff) {
170             NSLog(@"The sound configuration file could not be opened.");
171             return;
172         }
173
174         self->soundsByPath = [[NSMutableDictionary alloc] init];
175         self->soundArraysByEvent = [[NSMutableDictionary alloc] init];
176         @autoreleasepool {
177             /*
178              * This loop may take a while depending on the count and size of
179              * samples to load.
180              */
181
182             /* Parse the file */
183             /* Lines are always of the form "name = sample [sample ...]" */
184             char buffer[2048];
185             while (my_fgets(fff, buffer, sizeof(buffer)) == 0) {
186                 char *msg_name;
187                 char *cfg_sample_list;
188                 char *search;
189                 char *cur_token;
190                 char *next_token;
191                 int event;
192
193                 /* Skip anything not beginning with an alphabetic character */
194                 if (!buffer[0] || !isalpha((unsigned char)buffer[0])) continue;
195
196                 /* Split the line into two: message name, and the rest */
197                 search = strchr(buffer, ' ');
198                 cfg_sample_list = strchr(search + 1, ' ');
199                 if (!search) continue;
200                 if (!cfg_sample_list) continue;
201
202                 /* Set the message name, and terminate at first space */
203                 msg_name = buffer;
204                 search[0] = '\0';
205
206                 /* Make sure this is a valid event name */
207                 for (event = MSG_MAX - 1; event >= 0; event--) {
208                     if (strcmp(msg_name, angband_sound_name[event]) == 0)
209                         break;
210                 }
211                 if (event < 0) continue;
212
213                 /*
214                  * Advance the sample list pointer so it's at the beginning of
215                  * text.
216                  */
217                 cfg_sample_list++;
218                 if (!cfg_sample_list[0]) continue;
219
220                 /* Terminate the current token */
221                 cur_token = cfg_sample_list;
222                 search = strchr(cur_token, ' ');
223                 if (search) {
224                     search[0] = '\0';
225                     next_token = search + 1;
226                 } else {
227                     next_token = NULL;
228                 }
229
230                 /*
231                  * Now we find all the sample names and add them one by one
232                  */
233                 while (cur_token) {
234                     NSMutableArray *soundSamples =
235                         [self->soundArraysByEvent
236                              objectForKey:[NSNumber numberWithInteger:event]];
237                     if (soundSamples == nil) {
238                         soundSamples = [[NSMutableArray alloc] init];
239                         [self->soundArraysByEvent
240                              setObject:soundSamples
241                              forKey:[NSNumber numberWithInteger:event]];
242                     }
243                     int num = (int) soundSamples.count;
244
245                     /* Don't allow too many samples */
246                     if (num >= [AngbandSoundCatalog maxSamples]) break;
247
248                     NSString *token_string =
249                         [NSString stringWithUTF8String:cur_token];
250                     NSSound *sound =
251                         [self->soundsByPath objectForKey:token_string];
252
253                     if (! sound) {
254                         /*
255                          * We have to load the sound. Build the path to the
256                          * sample.
257                          */
258                         path_build(path, sizeof(path), sound_dir, cur_token);
259                         struct stat stb;
260                         if (stat(path, &stb) == 0) {
261                             /* Load the sound into memory */
262                             sound = [[NSSound alloc]
263                                          initWithContentsOfFile:[NSString stringWithUTF8String:path]
264                                          byReference:YES];
265                             if (sound) {
266                                 [self->soundsByPath setObject:sound
267                                             forKey:token_string];
268                             }
269                         }
270                     }
271
272                     /* Store it if we loaded it */
273                     if (sound) {
274                         [soundSamples addObject:sound];
275                     }
276
277                     /* Figure out next token */
278                     cur_token = next_token;
279                     if (next_token) {
280                          /* Try to find a space */
281                          search = strchr(cur_token, ' ');
282
283                          /*
284                           * If we can find one, terminate, and set new "next".
285                           */
286                          if (search) {
287                              search[0] = '\0';
288                              next_token = search + 1;
289                          } else {
290                              /* Otherwise prevent infinite looping */
291                              next_token = NULL;
292                          }
293                     }
294                 }
295             }
296         }
297
298         /* Close the file */
299         my_fclose(fff);
300     }
301
302     @autoreleasepool {
303         NSMutableArray *samples =
304             [self->soundArraysByEvent
305                  objectForKey:[NSNumber numberWithInteger:event]];
306
307         if (samples == nil || samples.count == 0) {
308             return;
309         }
310
311         /* Choose a random event. */
312         int s = randint0((int) samples.count);
313         NSSound *sound = samples[s];
314
315         if ([sound isPlaying])
316             [sound stop];
317
318         /* Play the sound. */
319         [sound play];
320     }
321 }
322
323 + (int)maxSamples {
324     return 16;
325 }
326
327 /**
328  * For sharedSounds and clearSharedSounds.
329  */
330 static __strong AngbandSoundCatalog* gSharedSounds = nil;
331
332 + (AngbandSoundCatalog*)sharedSounds {
333     if (gSharedSounds == nil) {
334         gSharedSounds = [[AngbandSoundCatalog alloc] init];
335     }
336     return gSharedSounds;;
337 }
338
339 + (void)clearSharedSounds {
340     gSharedSounds = nil;
341 }
342
343 @end
344
345 /**
346  * Each location in the terminal either stores a character, a tile,
347  * padding for a big tile, or padding for a big character (for example a
348  * kanji that takes two columns).  These structures represent that.  Note
349  * that tiles do not overlap with each other (excepting the double-height
350  * tiles, i.e. from the Shockbolt set; that's handled as a special case).
351  * Characters can overlap horizontally:  that is for handling fonts that
352  * aren't fixed width.
353  */
354 struct TerminalCellChar {
355     wchar_t glyph;
356     int attr;
357 };
358 struct TerminalCellTile {
359     /*
360      * These are the coordinates, within the tile set, for the foreground
361      * tile and background tile.
362      */
363     char fgdCol, fgdRow, bckCol, bckRow;
364 };
365 struct TerminalCellPadding {
366        /*
367         * If the cell at (x, y) is padding, the cell at (x - hoff, y - voff)
368         * has the attributes affecting the padded region.
369         */
370     unsigned char hoff, voff;
371 };
372 struct TerminalCell {
373     union {
374         struct TerminalCellChar ch;
375         struct TerminalCellTile ti;
376         struct TerminalCellPadding pd;
377     } v;
378     /*
379      * Used for big characters or tiles which are hscl x vscl cells.
380      * The upper left corner of the big tile or character is marked as
381      * TERM_CELL_TILE or TERM_CELL_CHAR.  The remainder are marked as
382      * TERM_CELL_TILE_PADDING or TERM_CELL_CHAR_PADDING and have hscl and
383      * vscl set to matcn what's in the upper left corner.  Big tiles are
384      * tiles scaled up to occupy more space.  Big characters, on the other
385      * hand, are characters that naturally take up more space than standard
386      * for the font with the assumption that vscl will be one for any big
387      * character and hscl will hold the number of columns it occupies (likely
388      * just 2, i.e. for Japanese kanji).
389      */
390     unsigned char hscl;
391     unsigned char vscl;
392     /*
393      * Hold the offsets, as fractions of the tile size expressed as the
394      * rational numbers hoff_n / hoff_d and voff_n / voff_d, within the tile
395      * or character.  For something that is not a big tile or character, these
396      * will be 0, 0, 1, and 1.  For a big tile or character, these will be
397      * set when the tile or character is changed to be 0, 0, hscl, and vscl
398      * for the upper left corner and i, j, hscl, vscl for the padding element
399      * at (i, j) relative to the upper left corner.  For a big tile or
400      * character that is partially overwritten, these are not modified in the
401      * parts that are not overwritten while hscl, vscl, and, for padding,
402      * v.pd.hoff and v.pd.voff are.
403      */
404     unsigned char hoff_n;
405     unsigned char voff_n;
406     unsigned char hoff_d;
407     unsigned char voff_d;
408     /*
409      * Is either TERM_CELL_CHAR, TERM_CELL_CHAR_PADDING, TERM_CELL_TILE, or
410      * TERM_CELL_TILE_PADDING.
411      */
412     unsigned char form;
413 };
414 #define TERM_CELL_CHAR (0x1)
415 #define TERM_CELL_CHAR_PADDING (0x2)
416 #define TERM_CELL_TILE (0x4)
417 #define TERM_CELL_TILE_PADDING (0x8)
418
419 struct TerminalCellBlock {
420     int ulcol, ulrow, w, h;
421 };
422
423 struct TerminalCellLocation {
424     int col, row;
425 };
426
427 typedef int (*TerminalCellPredicate)(const struct TerminalCell*);
428
429 static int isTileTop(const struct TerminalCell *c)
430 {
431     return (c->form == TERM_CELL_TILE ||
432             (c->form == TERM_CELL_TILE_PADDING && c->v.pd.voff == 0)) ? 1 : 0;
433 }
434
435 static int isPartiallyOverwrittenBigChar(const struct TerminalCell *c)
436 {
437     if ((c->form & (TERM_CELL_CHAR | TERM_CELL_CHAR_PADDING)) != 0) {
438         /*
439          * When the tile is set in Term_pict_cocoa, hoff_d is the same as hscl
440          * and voff_d is the same as vscl.  hoff_d and voff_d aren't modified
441          * after that, but hscl and vscl are in response to partial overwrites.
442          * If they're diffent, an overwrite has occurred.
443          */
444         return ((c->hoff_d > 1 || c->voff_d > 1) &&
445                 (c->hoff_d != c->hscl || c->voff_d != c->vscl)) ? 1 : 0;
446     }
447     return 0;
448 }
449
450 static int isCharNoPartial(const struct TerminalCell *c)
451 {
452     return ((c->form & (TERM_CELL_CHAR | TERM_CELL_CHAR_PADDING)) != 0 &&
453             ! isPartiallyOverwrittenBigChar(c)) ? 1 : 0;
454 }
455
456 /**
457  * Since the drawing is decoupled from Angband's calls to the text_hook,
458  * pict_hook, wipe_hook, curs_hook, and bigcurs_hook callbacks of a terminal,
459  * maintain a version of the Terminal contents.
460  */
461 @interface TerminalContents : NSObject {
462 @private
463     struct TerminalCell *cells;
464 }
465
466 /**
467  * Initialize with zero columns and zero rows.
468  */
469 - (id)init;
470
471 /**
472  * Initialize with nCol columns and nRow rows.  All elements will be set to
473  * blanks.
474  */
475 - (id)initWithColumns:(int)nCol rows:(int)nRow NS_DESIGNATED_INITIALIZER;
476
477 /**
478  * Resize to be nCol by nRow.  Current contents still within the new bounds
479  * are preserved.  Added areas are filled with blanks.
480  */
481 - (void)resizeWithColumns:(int)nCol rows:(int)nRow;
482
483 /**
484  * Get the contents of a given cell.
485  */
486 - (const struct TerminalCell*)getCellAtColumn:(int)icol row:(int)irow;
487
488 /**
489  * Scans the row, irow, starting at the column, icol0, and stopping before the
490  * column, icol1.  Returns the column index for the first cell that's within
491  * the given type mask, tm.  If all of the cells in that range are not within
492  * the given type mask, returns icol1.
493  */
494 - (int)scanForTypeMaskInRow:(int)irow mask:(unsigned int)tm col0:(int)icol0
495                        col1:(int)icol1;
496
497 /**
498  * Scans the w x h block whose upper left corner is at (icol, irow).  The
499  * scan starts at (icol + pcurs->col, irow + pcurs->row) and proceeds from
500  * left to right and top to bottom.  At exit, pcurs will have the location
501  * (relative to icol, irow) of the first cell encountered that's within the
502  * given type mask, tm.  If no such cell was found, pcurs->col will be w
503  * and pcurs->row will be h.
504  */
505 - (void)scanForTypeMaskInBlockAtColumn:(int)icol row:(int)irow width:(int)w
506                                 height:(int)h mask:(unsigned int)tm
507                                 cursor:(struct TerminalCellLocation*)pcurs;
508
509 /**
510  * Scans the row, irow, starting at the column, icol0, and stopping before the
511  * column, icol1.  Returns the column index for the first cell that
512  * func(cell_address) != rval.  If all of the cells in the range satisfy the
513  * predicate, returns icol1.
514  */
515 - (int)scanForPredicateInRow:(int)irow
516                    predicate:(TerminalCellPredicate)func
517                      desired:(int)rval
518                         col0:(int)icol0
519                         col1:(int)icol1;
520
521 /**
522  * Change the contents to have the given string of n characters appear with
523  * the leftmost character at (icol, irow).
524  */
525 - (void)setUniformAttributeTextRunAtColumn:(int)icol
526                                        row:(int)irow
527                                          n:(int)n
528                                     glyphs:(const char*)g
529                                  attribute:(int)a;
530
531 /**
532  * Change the contents to have a tile scaled to w x h appear with its upper
533  * left corner at (icol, irow).
534  */
535 - (void)setTileAtColumn:(int)icol
536                     row:(int)irow
537        foregroundColumn:(char)fgdCol
538           foregroundRow:(char)fgdRow
539        backgroundColumn:(char)bckCol
540           backgroundRow:(char)bckRow
541               tileWidth:(int)w
542              tileHeight:(int)h;
543
544 /**
545  * Wipe the w x h block whose upper left corner is at (icol, irow).
546  */
547 - (void)wipeBlockAtColumn:(int)icol row:(int)irow width:(int)w height:(int)h;
548
549 /**
550  * Wipe all the contents.
551  */
552 - (void)wipe;
553
554 /**
555  * Wipe any tiles.
556  */
557 - (void)wipeTiles;
558
559 /**
560  * Thie is a helper function for wipeBlockAtColumn.
561  */
562 - (void)wipeBlockAuxAtColumn:(int)icol row:(int)irow width:(int)w
563                       height:(int)h;
564
565 /**
566  * This is a helper function for checkForBigStuffOverwriteAtColumn.
567  */
568 - (void) splitBlockAtColumn:(int)icol row:(int)irow n:(int)nsub
569                      blocks:(const struct TerminalCellBlock*)b;
570
571 /**
572  * This is a helper function for setUniformAttributeTextRunAtColumn,
573  * setTileAtColumn, and wipeBlockAtColumn.  If a modification could partially
574  * overwrite a big character or tile, make adjustments so what's left can
575  * be handled appropriately in rendering.
576  */
577 - (void)checkForBigStuffOverwriteAtColumn:(int)icol row:(int)irow
578                                     width:(int)w height:(int)h;
579
580 /**
581  * Position the upper left corner of the cursor at (icol, irow) and have it
582  * encompass w x h cells.
583  */
584 - (void)setCursorAtColumn:(int)icol row:(int)irow width:(int)w height:(int)h;
585
586 /**
587  * Remove the cursor.  cursorColumn and cursorRow will be -1 until
588  * setCursorAtColumn is called.
589  */
590 - (void)removeCursor;
591
592 /**
593  * Verify that everying is consistent.
594  */
595 - (void)assertInvariants;
596
597 /**
598  * Is the number of columns.
599  */
600 @property (readonly) int columnCount;
601
602 /**
603  * Is the number of rows.
604  */
605 @property (readonly) int rowCount;
606
607 /**
608  * Is the column index for the upper left corner of the cursor.  It will be -1
609  * if the cursor is disabled.
610  */
611 @property (readonly) int cursorColumn;
612
613 /**
614  * Is the row index for the upper left corner of the cursor.  It will be -1
615  * if the cursor is disabled.
616  */
617 @property (readonly) int cursorRow;
618
619 /**
620  * Is the cursor width in number of cells.
621  */
622 @property (readonly) int cursorWidth;
623
624 /**
625  * Is the cursor height in number of cells.
626  */
627 @property (readonly) int cursorHeight;
628
629 /**
630  * Return the character to be used for blanks.
631  */
632 + (wchar_t)getBlankChar;
633
634 /**
635  * Return the attribute to be used for blanks.
636  */
637 + (int)getBlankAttribute;
638
639 @end
640
641 @implementation TerminalContents
642
643 - (id)init
644 {
645     return [self initWithColumns:0 rows:0];
646 }
647
648 - (id)initWithColumns:(int)nCol rows:(int)nRow
649 {
650     if (self = [super init]) {
651         self->cells = malloc(nCol * nRow * sizeof(struct TerminalCell));
652         self->_columnCount = nCol;
653         self->_rowCount = nRow;
654         self->_cursorColumn = -1;
655         self->_cursorRow = -1;
656         self->_cursorWidth = 1;
657         self->_cursorHeight = 1;
658         [self wipe];
659     }
660     return self;
661 }
662
663 - (void)dealloc
664 {
665     if (self->cells != 0) {
666         free(self->cells);
667         self->cells = 0;
668     }
669 }
670
671 - (void)resizeWithColumns:(int)nCol rows:(int)nRow
672 {
673     /*
674      * Potential issue: big tiles or characters can become clipped by the
675      * resize.  That will only matter if drawing occurs before the contents
676      * are updated by Angband.  Even then, unless the drawing mode is used
677      * where AppKit doesn't clip to the window bounds, the only artifact will
678      * be clipping when drawn which is acceptable and doesn't require
679      * additional logic to either filter out the clipped big stuff here or
680      * to just clear it when drawing.
681      */
682     struct TerminalCell *newCells =
683         malloc(nCol * nRow * sizeof(struct TerminalCell));
684     struct TerminalCell *cellsOutCursor = newCells;
685     const struct TerminalCell *cellsInCursor = self->cells;
686     int nColCommon = (nCol < self.columnCount) ? nCol : self.columnCount;
687     int nRowCommon = (nRow < self.rowCount) ? nRow : self.rowCount;
688     wchar_t blank = [TerminalContents getBlankChar];
689     int blank_attr = [TerminalContents getBlankAttribute];
690     int i;
691
692     for (i = 0; i < nRowCommon; ++i) {
693         (void) memcpy(
694             cellsOutCursor,
695             cellsInCursor,
696             nColCommon * sizeof(struct TerminalCell));
697         cellsInCursor += self.columnCount;
698         for (int j = nColCommon; j < nCol; ++j) {
699             cellsOutCursor[j].v.ch.glyph = blank;
700             cellsOutCursor[j].v.ch.attr = blank_attr;
701             cellsOutCursor[j].hscl = 1;
702             cellsOutCursor[j].vscl = 1;
703             cellsOutCursor[j].hoff_n = 0;
704             cellsOutCursor[j].voff_n = 0;
705             cellsOutCursor[j].hoff_d = 1;
706             cellsOutCursor[j].voff_d = 1;
707             cellsOutCursor[j].form = TERM_CELL_CHAR;
708         }
709         cellsOutCursor += nCol;
710     }
711     while (cellsOutCursor != newCells + nCol * nRow) {
712         cellsOutCursor->v.ch.glyph = blank;
713         cellsOutCursor->v.ch.attr = blank_attr;
714         cellsOutCursor->hscl = 1;
715         cellsOutCursor->vscl = 1;
716         cellsOutCursor->hoff_n = 0;
717         cellsOutCursor->voff_n = 0;
718         cellsOutCursor->hoff_d = 1;
719         cellsOutCursor->voff_d = 1;
720         cellsOutCursor->form = TERM_CELL_CHAR;
721         ++cellsOutCursor;
722     }
723
724     free(self->cells);
725     self->cells = newCells;
726     self->_columnCount = nCol;
727     self->_rowCount = nRow;
728     if (self->_cursorColumn >= nCol || self->_cursorRow >= nRow) {
729         self->_cursorColumn = -1;
730         self->_cursorRow = -1;
731     } else {
732         if (self->_cursorColumn + self->_cursorWidth > nCol) {
733             self->_cursorWidth = nCol - self->_cursorColumn;
734         }
735         if (self->_cursorRow + self->_cursorHeight > nRow) {
736             self->_cursorHeight = nRow - self->_cursorRow;
737         }
738     }
739 }
740
741 - (const struct TerminalCell*)getCellAtColumn:(int)icol row:(int)irow
742 {
743     return self->cells + icol + irow * self.columnCount;
744 }
745
746 - (int)scanForTypeMaskInRow:(int)irow mask:(unsigned int)tm col0:(int)icol0
747                        col1:(int)icol1
748 {
749     int i = icol0;
750     const struct TerminalCell *cellsRow =
751         self->cells + irow * self.columnCount;
752
753     while (1) {
754         if (i >= icol1) {
755             return icol1;
756         }
757         if ((cellsRow[i].form & tm) != 0) {
758             return i;
759         }
760         ++i;
761     }
762 }
763
764 - (void)scanForTypeMaskInBlockAtColumn:(int)icol row:(int)irow width:(int)w
765                                 height:(int)h mask:(unsigned int)tm
766                                 cursor:(struct TerminalCellLocation*)pcurs
767 {
768     const struct TerminalCell *cellsRow =
769         self->cells + (irow + pcurs->row) * self.columnCount;
770     while (1) {
771         if (pcurs->col == w) {
772             if (pcurs->row >= h - 1) {
773                 pcurs->row = h;
774                 return;
775             }
776             ++pcurs->row;
777             pcurs->col = 0;
778             cellsRow += self.columnCount;
779         }
780
781         if ((cellsRow[icol + pcurs->col].form & tm) != 0) {
782             return;
783         }
784
785         ++pcurs->col;
786     }
787 }
788
789 - (int)scanForPredicateInRow:(int)irow
790                    predicate:(TerminalCellPredicate)func
791                      desired:(int)rval
792                         col0:(int)icol0
793                         col1:(int)icol1
794 {
795     int i = icol0;
796     const struct TerminalCell *cellsRow =
797         self->cells + irow * self.columnCount;
798
799     while (1) {
800         if (i >= icol1) {
801             return icol1;
802         }
803         if (func(cellsRow + i) != rval) {
804             return i;
805         }
806         ++i;
807     }
808 }
809
810 - (void)setUniformAttributeTextRunAtColumn:(int)icol
811                                        row:(int)irow
812                                          n:(int)n
813                                     glyphs:(const char*)g
814                                  attribute:(int)a
815 {
816     [self checkForBigStuffOverwriteAtColumn:icol row:irow width:n height:1];
817
818     struct TerminalCell *cellsRow = self->cells + irow * self.columnCount;
819     int i = icol;
820
821     while (i < icol + n) {
822 #ifdef JP
823         if (iskanji(*g)) {
824             if (i == n - 1) {
825                 /*
826                  * The second byte of the character is past the end.  Ignore
827                  * the character.
828                  */
829                 break;
830             }
831             cellsRow[i].v.ch.glyph = convert_two_byte_eucjp_to_utf16_native(g);
832             cellsRow[i].v.ch.attr = a;
833             cellsRow[i].hscl = 2;
834             cellsRow[i].vscl = 1;
835             cellsRow[i].hoff_n = 0;
836             cellsRow[i].voff_n = 0;
837             cellsRow[i].hoff_d = 2;
838             cellsRow[i].voff_d = 1;
839             cellsRow[i].form = TERM_CELL_CHAR;
840             ++i;
841             cellsRow[i].v.pd.hoff = 1;
842             cellsRow[i].v.pd.voff = 0;
843             cellsRow[i].hscl = 2;
844             cellsRow[i].vscl = 1;
845             cellsRow[i].hoff_n = 1;
846             cellsRow[i].voff_n = 0;
847             cellsRow[i].hoff_d = 2;
848             cellsRow[i].voff_d = 1;
849             cellsRow[i].form = TERM_CELL_CHAR_PADDING;
850             ++i;
851             g += 2;
852         } else {
853             cellsRow[i].v.ch.glyph = *g++;
854             cellsRow[i].v.ch.attr = a;
855             cellsRow[i].hscl = 1;
856             cellsRow[i].vscl = 1;
857             cellsRow[i].hoff_n = 0;
858             cellsRow[i].voff_n = 0;
859             cellsRow[i].hoff_d = 1;
860             cellsRow[i].voff_d = 1;
861             cellsRow[i].form = TERM_CELL_CHAR;
862             ++i;
863         }
864 #else
865         cellsRow[i].v.ch.glyph = *g++;
866         cellsRow[i].v.ch.attr = a;
867         cellsRow[i].hscl = 1;
868         cellsRow[i].vscl = 1;
869         cellsRow[i].hoff_n = 0;
870         cellsRow[i].voff_n = 0;
871         cellsRow[i].hoff_d = 1;
872         cellsRow[i].voff_d = 1;
873         cellsRow[i].form = TERM_CELL_CHAR;
874         ++i;
875 #endif /* JP */
876     }
877 }
878
879 - (void)setTileAtColumn:(int)icol
880                     row:(int)irow
881        foregroundColumn:(char)fgdCol
882           foregroundRow:(char)fgdRow
883        backgroundColumn:(char)bckCol
884           backgroundRow:(char)bckRow
885               tileWidth:(int)w
886              tileHeight:(int)h
887 {
888     [self checkForBigStuffOverwriteAtColumn:icol row:irow width:w height:h];
889
890     struct TerminalCell *cellsRow = self->cells + irow * self.columnCount;
891
892     cellsRow[icol].v.ti.fgdCol = fgdCol;
893     cellsRow[icol].v.ti.fgdRow = fgdRow;
894     cellsRow[icol].v.ti.bckCol = bckCol;
895     cellsRow[icol].v.ti.bckRow = bckRow;
896     cellsRow[icol].hscl = w;
897     cellsRow[icol].vscl = h;
898     cellsRow[icol].hoff_n = 0;
899     cellsRow[icol].voff_n = 0;
900     cellsRow[icol].hoff_d = w;
901     cellsRow[icol].voff_d = h;
902     cellsRow[icol].form = TERM_CELL_TILE;
903
904     int ic;
905     for (ic = icol + 1; ic < icol + w; ++ic) {
906         cellsRow[ic].v.pd.hoff = ic - icol;
907         cellsRow[ic].v.pd.voff = 0;
908         cellsRow[ic].hscl = w;
909         cellsRow[ic].vscl = h;
910         cellsRow[ic].hoff_n = ic - icol;
911         cellsRow[ic].voff_n = 0;
912         cellsRow[ic].hoff_d = w;
913         cellsRow[ic].voff_d = h;
914         cellsRow[ic].form = TERM_CELL_TILE_PADDING;
915     }
916     cellsRow += self.columnCount;
917     for (int ir = irow + 1; ir < irow + h; ++ir) {
918         for (ic = icol; ic < icol + w; ++ic) {
919             cellsRow[ic].v.pd.hoff = ic - icol;
920             cellsRow[ic].v.pd.voff = ir - irow;
921             cellsRow[ic].hscl = w;
922             cellsRow[ic].vscl = h;
923             cellsRow[ic].hoff_n = ic - icol;
924             cellsRow[ic].voff_n = ir - irow;
925             cellsRow[ic].hoff_d = w;
926             cellsRow[ic].voff_d = h;
927             cellsRow[ic].form = TERM_CELL_TILE_PADDING;
928         }
929         cellsRow += self.columnCount;
930     }
931 }
932
933 - (void)wipeBlockAtColumn:(int)icol row:(int)irow width:(int)w height:(int)h
934 {
935     [self checkForBigStuffOverwriteAtColumn:icol row:irow width:w height:h];
936     [self wipeBlockAuxAtColumn:icol row:irow width:w height:h];
937 }
938
939 - (void)wipe
940 {
941     wchar_t blank = [TerminalContents getBlankChar];
942     int blank_attr = [TerminalContents getBlankAttribute];
943     struct TerminalCell *cellCursor = self->cells +
944         self.columnCount * self.rowCount;
945
946     while (cellCursor != self->cells) {
947         --cellCursor;
948         cellCursor->v.ch.glyph = blank;
949         cellCursor->v.ch.attr = blank_attr;
950         cellCursor->hscl = 1;
951         cellCursor->vscl = 1;
952         cellCursor->hoff_n = 0;
953         cellCursor->voff_n = 0;
954         cellCursor->hoff_d = 1;
955         cellCursor->voff_d = 1;
956         cellCursor->form = TERM_CELL_CHAR;
957     }
958 }
959
960 - (void)wipeTiles
961 {
962     wchar_t blank = [TerminalContents getBlankChar];
963     int blank_attr = [TerminalContents getBlankAttribute];
964     struct TerminalCell *cellCursor = self->cells +
965         self.columnCount * self.rowCount;
966
967     while (cellCursor != self->cells) {
968         --cellCursor;
969         if ((cellCursor->form &
970              (TERM_CELL_TILE | TERM_CELL_TILE_PADDING)) != 0) {
971             cellCursor->v.ch.glyph = blank;
972             cellCursor->v.ch.attr = blank_attr;
973             cellCursor->hscl = 1;
974             cellCursor->vscl = 1;
975             cellCursor->hoff_n = 0;
976             cellCursor->voff_n = 0;
977             cellCursor->hoff_d = 1;
978             cellCursor->voff_d = 1;
979             cellCursor->form = TERM_CELL_CHAR;
980         }
981     }
982 }
983
984 - (void)wipeBlockAuxAtColumn:(int)icol row:(int)irow width:(int)w
985                       height:(int)h
986 {
987     struct TerminalCell *cellsRow = self->cells + irow * self.columnCount;
988     wchar_t blank = [TerminalContents getBlankChar];
989     int blank_attr = [TerminalContents getBlankAttribute];
990
991     for (int ir = irow; ir < irow + h; ++ir) {
992         for (int ic = icol; ic < icol + w; ++ic) {
993             cellsRow[ic].v.ch.glyph = blank;
994             cellsRow[ic].v.ch.attr = blank_attr;
995             cellsRow[ic].hscl = 1;
996             cellsRow[ic].vscl = 1;
997             cellsRow[ic].hoff_n = 0;
998             cellsRow[ic].voff_n = 0;
999             cellsRow[ic].hoff_d = 1;
1000             cellsRow[ic].voff_d = 1;
1001             cellsRow[ic].form = TERM_CELL_CHAR;
1002         }
1003         cellsRow += self.columnCount;
1004     }
1005 }
1006
1007 - (void) splitBlockAtColumn:(int)icol row:(int)irow n:(int)nsub
1008                      blocks:(const struct TerminalCellBlock*)b
1009 {
1010     const struct TerminalCell *pulold = [self getCellAtColumn:icol row:irow];
1011
1012     for (int isub = 0; isub < nsub; ++isub) {
1013         struct TerminalCell* cellsRow =
1014             self->cells + b[isub].ulrow * self.columnCount;
1015
1016         /*
1017          * Copy the data from the upper left corner of the big block to
1018          * the upper left corner of the piece.
1019          */
1020         if (b[isub].ulcol != icol || b[isub].ulrow != irow) {
1021             if (pulold->form == TERM_CELL_CHAR) {
1022                 cellsRow[b[isub].ulcol].v.ch = pulold->v.ch;
1023                 cellsRow[b[isub].ulcol].form = TERM_CELL_CHAR;
1024             } else {
1025                 cellsRow[b[isub].ulcol].v.ti = pulold->v.ti;
1026                 cellsRow[b[isub].ulcol].form = TERM_CELL_TILE;
1027             }
1028         }
1029         cellsRow[b[isub].ulcol].hscl = b[isub].w;
1030         cellsRow[b[isub].ulcol].vscl = b[isub].h;
1031
1032         /*
1033          * Point the padding elements in the piece to the new upper left
1034          * corner.
1035          */
1036         int ic;
1037         for (ic = b[isub].ulcol + 1; ic < b[isub].ulcol + b[isub].w; ++ic) {
1038             cellsRow[ic].v.pd.hoff = ic - b[isub].ulcol;
1039             cellsRow[ic].v.pd.voff = 0;
1040             cellsRow[ic].hscl = b[isub].w;
1041             cellsRow[ic].vscl = b[isub].h;
1042         }
1043         cellsRow += self.columnCount;
1044         for (int ir = b[isub].ulrow + 1;
1045              ir < b[isub].ulrow + b[isub].h;
1046              ++ir) {
1047             for (ic = b[isub].ulcol; ic < b[isub].ulcol + b[isub].w; ++ic) {
1048                 cellsRow[ic].v.pd.hoff = ic - b[isub].ulcol;
1049                 cellsRow[ic].v.pd.voff = ir - b[isub].ulrow;
1050                 cellsRow[ic].hscl = b[isub].w;
1051                 cellsRow[ic].vscl = b[isub].h;
1052             }
1053             cellsRow += self.columnCount;
1054         }
1055     }
1056 }
1057
1058 - (void)checkForBigStuffOverwriteAtColumn:(int)icol row:(int)irow
1059                                     width:(int)w height:(int)h
1060 {
1061     int ire = irow + h, ice = icol + w;
1062
1063     for (int ir = irow; ir < ire; ++ir) {
1064         for (int ic = icol; ic < ice; ++ic) {
1065             const struct TerminalCell *pcell =
1066                 [self getCellAtColumn:ic row:ir];
1067
1068             if ((pcell->form & (TERM_CELL_CHAR | TERM_CELL_TILE)) != 0 &&
1069                 (pcell->hscl > 1 || pcell->vscl > 1)) {
1070                 /*
1071                  * Lost chunk including upper left corner.  Split into at most
1072                  * two new blocks.
1073                  */
1074                 /*
1075                  * Tolerate blocks that were clipped by a resize at some point.
1076                  */
1077                 int wb = (ic + pcell->hscl <= self.columnCount) ?
1078                     pcell->hscl : self.columnCount - ic;
1079                 int hb = (ir + pcell->vscl <= self.rowCount) ?
1080                     pcell->vscl : self.rowCount - ir;
1081                 struct TerminalCellBlock blocks[2];
1082                 int nsub = 0, ww, hw;
1083
1084                 if (ice < ic + wb) {
1085                     /* Have something to the right not overwritten. */
1086                     blocks[nsub].ulcol = ice;
1087                     blocks[nsub].ulrow = ir;
1088                     blocks[nsub].w = ic + wb - ice;
1089                     blocks[nsub].h = (ire < ir + hb) ? ire - ir : hb;
1090                     ++nsub;
1091                     ww = ice - ic;
1092                 } else {
1093                     ww = wb;
1094                 }
1095                 if (ire < ir + hb) {
1096                     /* Have something below not overwritten. */
1097                     blocks[nsub].ulcol = ic;
1098                     blocks[nsub].ulrow = ire;
1099                     blocks[nsub].w = wb;
1100                     blocks[nsub].h = ir + hb - ire;
1101                     ++nsub;
1102                     hw = ire - ir;
1103                 } else {
1104                     hw = hb;
1105                 }
1106                 if (nsub > 0) {
1107                     [self splitBlockAtColumn:ic row:ir n:nsub blocks:blocks];
1108                 }
1109                 /*
1110                  * Wipe the part of the block that's destined to be overwritten
1111                  * so it doesn't receive further consideration in this loop.
1112                  * For efficiency, would like to have the loop skip over it or
1113                  * fill it with the desired content, but this is easier to
1114                  * implement.
1115                  */
1116                 [self wipeBlockAuxAtColumn:ic row:ir width:ww height:hw];
1117             } else if ((pcell->form & (TERM_CELL_CHAR_PADDING |
1118                                        TERM_CELL_TILE_PADDING)) != 0) {
1119                 /*
1120                  * Lost a chunk that doesn't cover the upper left corner.  In
1121                  * general will split into up to four new blocks (one above,
1122                  * one to the left, one to the right, and one below).
1123                  */
1124                 int pcol = ic - pcell->v.pd.hoff;
1125                 int prow = ir - pcell->v.pd.voff;
1126                 const struct TerminalCell *pcell2 =
1127                     [self getCellAtColumn:pcol row:prow];
1128
1129                 /*
1130                  * Tolerate blocks that were clipped by a resize at some point.
1131                  */
1132                 int wb = (pcol + pcell2->hscl <= self.columnCount) ?
1133                     pcell2->hscl : self.columnCount - pcol;
1134                 int hb = (prow + pcell2->vscl <= self.rowCount) ?
1135                     pcell2->vscl : self.rowCount - prow;
1136                 struct TerminalCellBlock blocks[4];
1137                 int nsub = 0, ww, hw;
1138
1139                 if (prow < ir) {
1140                     /* Have something above not overwritten. */
1141                     blocks[nsub].ulcol = pcol;
1142                     blocks[nsub].ulrow = prow;
1143                     blocks[nsub].w = wb;
1144                     blocks[nsub].h = ir - prow;
1145                     ++nsub;
1146                 }
1147                 if (pcol < ic) {
1148                     /* Have something to the left not overwritten. */
1149                     blocks[nsub].ulcol = pcol;
1150                     blocks[nsub].ulrow = ir;
1151                     blocks[nsub].w = ic - pcol;
1152                     blocks[nsub].h =
1153                         (ire < prow + hb) ? ire - ir : prow + hb - ir;
1154                     ++nsub;
1155                 }
1156                 if (ice < pcol + wb) {
1157                     /* Have something to the right not overwritten. */
1158                     blocks[nsub].ulcol = ice;
1159                     blocks[nsub].ulrow = ir;
1160                     blocks[nsub].w = pcol + wb - ice;
1161                     blocks[nsub].h =
1162                         (ire < prow + hb) ? ire - ir : prow + hb - ir;
1163                     ++nsub;
1164                     ww = ice - ic;
1165                 } else {
1166                     ww = pcol + wb - ic;
1167                 }
1168                 if (ire < prow + hb) {
1169                     /* Have something below not overwritten. */
1170                     blocks[nsub].ulcol = pcol;
1171                     blocks[nsub].ulrow = ire;
1172                     blocks[nsub].w = wb;
1173                     blocks[nsub].h = prow + hb - ire;
1174                     ++nsub;
1175                     hw = ire - ir;
1176                 } else {
1177                     hw = prow + hb - ir;
1178                 }
1179
1180                 [self splitBlockAtColumn:pcol row:prow n:nsub blocks:blocks];
1181                 /* Same rationale for wiping as above. */
1182                 [self wipeBlockAuxAtColumn:ic row:ir width:ww height:hw];
1183             }
1184         }
1185     }
1186 }
1187
1188 - (void)setCursorAtColumn:(int)icol row:(int)irow width:(int)w height:(int)h
1189 {
1190     self->_cursorColumn = icol;
1191     self->_cursorRow = irow;
1192     self->_cursorWidth = w;
1193     self->_cursorHeight = h;
1194 }
1195
1196 - (void)removeCursor
1197 {
1198     self->_cursorColumn = -1;
1199     self->_cursorHeight = -1;
1200     self->_cursorWidth = 1;
1201     self->_cursorHeight = 1;
1202 }
1203
1204 - (void)assertInvariants
1205 {
1206     const struct TerminalCell *cellsRow = self->cells;
1207
1208     /*
1209      * The comments with the definition for TerminalCell define the
1210      * relationships of hoff_n, voff_n, hoff_d, voff_d, hscl, and vscl
1211      * asserted here.
1212      */
1213     for (int ir = 0; ir < self.rowCount; ++ir) {
1214         for (int ic = 0; ic < self.columnCount; ++ic) {
1215             switch (cellsRow[ic].form) {
1216             case TERM_CELL_CHAR:
1217                 assert(cellsRow[ic].hscl > 0 && cellsRow[ic].vscl > 0);
1218                 assert(cellsRow[ic].hoff_n < cellsRow[ic].hoff_d &&
1219                        cellsRow[ic].voff_n < cellsRow[ic].voff_d);
1220                 if (cellsRow[ic].hscl == cellsRow[ic].hoff_d) {
1221                     assert(cellsRow[ic].hoff_n == 0);
1222                 }
1223                 if (cellsRow[ic].vscl == cellsRow[ic].voff_d) {
1224                     assert(cellsRow[ic].voff_n == 0);
1225                 }
1226                 /*
1227                  * Verify that the padding elements have the correct tag
1228                  * and point back to this cell.
1229                  */
1230                 if (cellsRow[ic].hscl > 1 || cellsRow[ic].vscl > 1) {
1231                     const struct TerminalCell *cellsRow2 = cellsRow;
1232
1233                     for (int ir2 = ir; ir2 < ir + cellsRow[ic].vscl; ++ir2) {
1234                         for (int ic2 = ic;
1235                              ic2 < ic + cellsRow[ic].hscl;
1236                              ++ic2) {
1237                             if (ir2 == ir && ic2 == ic) {
1238                                 continue;
1239                             }
1240                             assert(cellsRow2[ic2].form ==
1241                                    TERM_CELL_CHAR_PADDING);
1242                             assert(ic2 - cellsRow2[ic2].v.pd.hoff == ic &&
1243                                    ir2 - cellsRow2[ic2].v.pd.voff == ir);
1244                         }
1245                         cellsRow2 += self.columnCount;
1246                     }
1247                 }
1248                 break;
1249
1250             case TERM_CELL_TILE:
1251                 assert(cellsRow[ic].hscl > 0 && cellsRow[ic].vscl > 0);
1252                 assert(cellsRow[ic].hoff_n < cellsRow[ic].hoff_d &&
1253                        cellsRow[ic].voff_n < cellsRow[ic].voff_d);
1254                 if (cellsRow[ic].hscl == cellsRow[ic].hoff_d) {
1255                     assert(cellsRow[ic].hoff_n == 0);
1256                 }
1257                 if (cellsRow[ic].vscl == cellsRow[ic].voff_d) {
1258                     assert(cellsRow[ic].voff_n == 0);
1259                 }
1260                 /*
1261                  * Verify that the padding elements have the correct tag
1262                  * and point back to this cell.
1263                  */
1264                 if (cellsRow[ic].hscl > 1 || cellsRow[ic].vscl > 1) {
1265                     const struct TerminalCell *cellsRow2 = cellsRow;
1266
1267                     for (int ir2 = ir; ir2 < ir + cellsRow[ic].vscl; ++ir2) {
1268                         for (int ic2 = ic;
1269                              ic2 < ic + cellsRow[ic].hscl;
1270                              ++ic2) {
1271                             if (ir2 == ir && ic2 == ic) {
1272                                 continue;
1273                             }
1274                             assert(cellsRow2[ic2].form ==
1275                                    TERM_CELL_TILE_PADDING);
1276                             assert(ic2 - cellsRow2[ic2].v.pd.hoff == ic &&
1277                                    ir2 - cellsRow2[ic2].v.pd.voff == ir);
1278                         }
1279                         cellsRow2 += self.columnCount;
1280                     }
1281                 }
1282                 break;
1283
1284             case TERM_CELL_CHAR_PADDING:
1285                 assert(cellsRow[ic].hscl > 0 && cellsRow[ic].vscl > 0);
1286                 assert(cellsRow[ic].hoff_n < cellsRow[ic].hoff_d &&
1287                        cellsRow[ic].voff_n < cellsRow[ic].voff_d);
1288                 assert(cellsRow[ic].hoff_n > 0 || cellsRow[ic].voff_n > 0);
1289                 if (cellsRow[ic].hscl == cellsRow[ic].hoff_d) {
1290                     assert(cellsRow[ic].hoff_n == cellsRow[ic].v.pd.hoff);
1291                 }
1292                 if (cellsRow[ic].vscl == cellsRow[ic].voff_d) {
1293                     assert(cellsRow[ic].voff_n == cellsRow[ic].v.pd.voff);
1294                 }
1295                 assert(ic >= cellsRow[ic].v.pd.hoff &&
1296                        ir >= cellsRow[ic].v.pd.voff);
1297                 /*
1298                  * Verify that it's padding for something that can point
1299                  * back to it.
1300                  */
1301                 {
1302                     const struct TerminalCell *parent =
1303                         [self getCellAtColumn:(ic - cellsRow[ic].v.pd.hoff)
1304                               row:(ir - cellsRow[ic].v.pd.voff)];
1305
1306                     assert(parent->form == TERM_CELL_CHAR);
1307                     assert(parent->hscl > cellsRow[ic].v.pd.hoff &&
1308                            parent->vscl > cellsRow[ic].v.pd.voff);
1309                     assert(parent->hscl == cellsRow[ic].hscl &&
1310                            parent->vscl == cellsRow[ic].vscl);
1311                     assert(parent->hoff_d == cellsRow[ic].hoff_d &&
1312                            parent->voff_d == cellsRow[ic].voff_d);
1313                 }
1314                 break;
1315
1316             case TERM_CELL_TILE_PADDING:
1317                 assert(cellsRow[ic].hscl > 0 && cellsRow[ic].vscl > 0);
1318                 assert(cellsRow[ic].hoff_n < cellsRow[ic].hoff_d &&
1319                        cellsRow[ic].voff_n < cellsRow[ic].voff_d);
1320                 assert(cellsRow[ic].hoff_n > 0 || cellsRow[ic].voff_n > 0);
1321                 if (cellsRow[ic].hscl == cellsRow[ic].hoff_d) {
1322                     assert(cellsRow[ic].hoff_n == cellsRow[ic].v.pd.hoff);
1323                 }
1324                 if (cellsRow[ic].vscl == cellsRow[ic].voff_d) {
1325                     assert(cellsRow[ic].voff_n == cellsRow[ic].v.pd.voff);
1326                 }
1327                 assert(ic >= cellsRow[ic].v.pd.hoff &&
1328                        ir >= cellsRow[ic].v.pd.voff);
1329                 /*
1330                  * Verify that it's padding for something that can point
1331                  * back to it.
1332                  */
1333                 {
1334                     const struct TerminalCell *parent =
1335                         [self getCellAtColumn:(ic - cellsRow[ic].v.pd.hoff)
1336                               row:(ir - cellsRow[ic].v.pd.voff)];
1337
1338                     assert(parent->form == TERM_CELL_TILE);
1339                     assert(parent->hscl > cellsRow[ic].v.pd.hoff &&
1340                            parent->vscl > cellsRow[ic].v.pd.voff);
1341                     assert(parent->hscl == cellsRow[ic].hscl &&
1342                            parent->vscl == cellsRow[ic].vscl);
1343                     assert(parent->hoff_d == cellsRow[ic].hoff_d &&
1344                            parent->voff_d == cellsRow[ic].voff_d);
1345                 }
1346                 break;
1347
1348             default:
1349                 assert(0);
1350             }
1351         }
1352         cellsRow += self.columnCount;
1353     }
1354 }
1355
1356 + (wchar_t)getBlankChar
1357 {
1358     return L' ';
1359 }
1360
1361 + (int)getBlankAttribute
1362 {
1363     return 0;
1364 }
1365
1366 @end
1367
1368 /**
1369  * TerminalChanges is used to track changes made via the text_hook, pict_hook,
1370  * wipe_hook, curs_hook, and bigcurs_hook callbacks on the terminal since the
1371  * last call to xtra_hook for TERM_XTRA_FRESH.  The locations marked as changed
1372  * can then be used to make bounding rectangles for the regions that need to
1373  * be redisplayed.
1374  */
1375 @interface TerminalChanges : NSObject {
1376     int* colBounds;
1377     /*
1378      * Outside of firstChangedRow, lastChangedRow and what's in colBounds, the
1379      * contents of this are handled lazily.
1380      */
1381     BOOL* marks;
1382 }
1383
1384 /**
1385  * Initialize with zero columns and zero rows.
1386  */
1387 - (id)init;
1388
1389 /**
1390  * Initialize with nCol columns and nRow rows.  No changes will be marked.
1391  */
1392 - (id)initWithColumns:(int)nCol rows:(int)nRow NS_DESIGNATED_INITIALIZER;
1393
1394 /**
1395  * Resize to be nCol by nRow.  Current contents still within the new bounds
1396  * are preserved.  Added areas are marked as unchanged.
1397  */
1398 - (void)resizeWithColumns:(int)nCol rows:(int)nRow;
1399
1400 /**
1401  * Clears all marked changes.
1402  */
1403 - (void)clear;
1404
1405 - (BOOL)isChangedAtColumn:(int)icol row:(int)irow;
1406
1407 /**
1408  * Scans the row, irow, starting at the column, icol0, and stopping before the
1409  * column, icol1.  Returns the column index for the first cell that is
1410  * changed.  The returned index will be equal to icol1 if all of the cells in
1411  * the range are unchanged.
1412  */
1413 - (int)scanForChangedInRow:(int)irow col0:(int)icol0 col1:(int)icol1;
1414
1415 /**
1416  * Scans the row, irow, starting at the column, icol0, and stopping before the
1417  * column, icol1.  returns the column index for the first cell that has not
1418  * changed.  The returned index will be equal to icol1 if all of the cells in
1419  * the range have changed.
1420  */
1421 - (int)scanForUnchangedInRow:(int)irow col0:(int)icol0 col1:(int)icol1;
1422
1423 - (void)markChangedAtColumn:(int)icol row:(int)irow;
1424
1425 - (void)markChangedRangeAtColumn:(int)icol row:(int)irow width:(int)w;
1426
1427 /**
1428  * Marks the block as changed who's upper left hand corner is at (icol, irow).
1429  */
1430 - (void)markChangedBlockAtColumn:(int)icol
1431                              row:(int)irow
1432                            width:(int)w
1433                           height:(int)h;
1434
1435 /**
1436  * Returns the index of the first changed column in the given row.  That index
1437  * will be equal to the number of columns if there are no changes in the row.
1438  */
1439 - (int)getFirstChangedColumnInRow:(int)irow;
1440
1441 /**
1442  * Returns the index of the last changed column in the given row.  That index
1443  * will be equal to -1 if there are no changes in the row.
1444  */
1445 - (int)getLastChangedColumnInRow:(int)irow;
1446
1447 /**
1448  * Is the number of columns.
1449  */
1450 @property (readonly) int columnCount;
1451
1452 /**
1453  * Is the number of rows.
1454  */
1455 @property (readonly) int rowCount;
1456
1457 /**
1458  * Is the index of the first row with changes.  Will be equal to the number
1459  * of rows if there are no changes.
1460  */
1461 @property (readonly) int firstChangedRow;
1462
1463 /**
1464  * Is the index of the last row with changes.  Will be equal to -1 if there
1465  * are no changes.
1466  */
1467 @property (readonly) int lastChangedRow;
1468
1469 @end
1470
1471 @implementation TerminalChanges
1472
1473 - (id)init
1474 {
1475     return [self initWithColumns:0 rows:0];
1476 }
1477
1478 - (id)initWithColumns:(int)nCol rows:(int)nRow
1479 {
1480     if (self = [super init]) {
1481         self->colBounds = malloc(2 * nRow * sizeof(int));
1482         self->marks = malloc(nCol * nRow * sizeof(BOOL));
1483         self->_columnCount = nCol;
1484         self->_rowCount = nRow;
1485         [self clear];
1486     }
1487     return self;
1488 }
1489
1490 - (void)dealloc
1491 {
1492     if (self->marks != 0) {
1493         free(self->marks);
1494         self->marks = 0;
1495     }
1496     if (self->colBounds != 0) {
1497         free(self->colBounds);
1498         self->colBounds = 0;
1499     }
1500 }
1501
1502 - (void)resizeWithColumns:(int)nCol rows:(int)nRow
1503 {
1504     int* newColBounds = malloc(2 * nRow * sizeof(int));
1505     BOOL* newMarks = malloc(nCol * nRow * sizeof(BOOL));
1506     int nRowCommon = (nRow < self.rowCount) ? nRow : self.rowCount;
1507
1508     if (self.firstChangedRow <= self.lastChangedRow &&
1509         self.firstChangedRow < nRowCommon) {
1510         BOOL* marksOutCursor = newMarks + self.firstChangedRow * nCol;
1511         const BOOL* marksInCursor =
1512             self->marks + self.firstChangedRow * self.columnCount;
1513         int nColCommon = (nCol < self.columnCount) ? nCol : self.columnCount;
1514
1515         if (self.lastChangedRow >= nRowCommon) {
1516             self->_lastChangedRow = nRowCommon - 1;
1517         }
1518         for (int i = self.firstChangedRow; i <= self.lastChangedRow; ++i) {
1519             if (self->colBounds[i + i] < nColCommon) {
1520                 newColBounds[i + i] = self->colBounds[i + i];
1521                 newColBounds[i + i + 1] =
1522                     (self->colBounds[i + i + 1] < nColCommon) ?
1523                     self->colBounds[i + i + 1] : nColCommon - 1;
1524                 (void) memcpy(
1525                     marksOutCursor + self->colBounds[i + i],
1526                     marksInCursor + self->colBounds[i + i],
1527                     (newColBounds[i + i + 1] - newColBounds[i + i] + 1) *
1528                         sizeof(BOOL));
1529                 marksInCursor += self.columnCount;
1530                 marksOutCursor += nCol;
1531             } else {
1532                 self->colBounds[i + i] = nCol;
1533                 self->colBounds[i + i + 1] = -1;
1534             }
1535         }
1536     } else {
1537         self->_firstChangedRow = nRow;
1538         self->_lastChangedRow = -1;
1539     }
1540
1541     free(self->colBounds);
1542     self->colBounds = newColBounds;
1543     free(self->marks);
1544     self->marks = newMarks;
1545     self->_columnCount = nCol;
1546     self->_rowCount = nRow;
1547 }
1548
1549 - (void)clear
1550 {
1551     self->_firstChangedRow = self.rowCount;
1552     self->_lastChangedRow = -1;
1553 }
1554
1555 - (BOOL)isChangedAtColumn:(int)icol row:(int)irow
1556 {
1557     if (irow < self.firstChangedRow || irow > self.lastChangedRow) {
1558         return NO;
1559     }
1560     if (icol < self->colBounds[irow + irow] ||
1561         icol > self->colBounds[irow + irow + 1]) {
1562         return NO;
1563     }
1564     return self->marks[icol + irow * self.columnCount];
1565 }
1566
1567 - (int)scanForChangedInRow:(int)irow col0:(int)icol0 col1:(int)icol1
1568 {
1569     if (irow < self.firstChangedRow || irow > self.lastChangedRow ||
1570         icol0 > self->colBounds[irow + irow + 1]) {
1571         return icol1;
1572     }
1573
1574     int i = (icol0 > self->colBounds[irow + irow]) ?
1575         icol0 : self->colBounds[irow + irow];
1576     int i1 = (icol1 <= self->colBounds[irow + irow + 1]) ?
1577         icol1 : self->colBounds[irow + irow + 1] + 1;
1578     const BOOL* marksCursor = self->marks + irow * self.columnCount;
1579     while (1) {
1580         if (i >= i1) {
1581             return icol1;
1582         }
1583         if (marksCursor[i]) {
1584             return i;
1585         }
1586         ++i;
1587     }
1588 }
1589
1590 - (int)scanForUnchangedInRow:(int)irow col0:(int)icol0 col1:(int)icol1
1591 {
1592     if (irow < self.firstChangedRow || irow > self.lastChangedRow ||
1593         icol0 < self->colBounds[irow + irow] ||
1594         icol0 > self->colBounds[irow + irow + 1]) {
1595         return icol0;
1596     }
1597
1598     int i = icol0;
1599     int i1 = (icol1 <= self->colBounds[irow + irow + 1]) ?
1600         icol1 : self->colBounds[irow + irow + 1] + 1;
1601     const BOOL* marksCursor = self->marks + irow * self.columnCount;
1602     while (1) {
1603         if (i >= i1 || ! marksCursor[i]) {
1604             return i;
1605         }
1606         ++i;
1607     }
1608 }
1609
1610 - (void)markChangedAtColumn:(int)icol row:(int)irow
1611 {
1612     [self markChangedBlockAtColumn:icol row:irow width:1 height:1];
1613 }
1614
1615 - (void)markChangedRangeAtColumn:(int)icol row:(int)irow width:(int)w
1616 {
1617     [self markChangedBlockAtColumn:icol row:irow width:w height:1];
1618 }
1619
1620 - (void)markChangedBlockAtColumn:(int)icol
1621                              row:(int)irow
1622                            width:(int)w
1623                           height:(int)h
1624 {
1625     if (irow + h <= self.firstChangedRow) {
1626         /* All prior marked regions are on rows after the requested block. */
1627         if (self.firstChangedRow > self.lastChangedRow) {
1628             self->_lastChangedRow = irow + h - 1;
1629         } else {
1630             for (int i = irow + h; i < self.firstChangedRow; ++i) {
1631                 self->colBounds[i + i] = self.columnCount;
1632                 self->colBounds[i + i + 1] = -1;
1633             }
1634         }
1635         self->_firstChangedRow = irow;
1636
1637         BOOL* marksCursor = self->marks + irow * self.columnCount;
1638         for (int i = irow; i < irow + h; ++i) {
1639             self->colBounds[i + i] = icol;
1640             self->colBounds[i + i + 1] = icol + w - 1;
1641             for (int j = icol; j < icol + w; ++j) {
1642                 marksCursor[j] = YES;
1643             }
1644             marksCursor += self.columnCount;
1645         }
1646     } else if (irow > self.lastChangedRow) {
1647         /* All prior marked regions are on rows before the requested block. */
1648         int i;
1649
1650         for (i = self.lastChangedRow + 1; i < irow; ++i) {
1651             self->colBounds[i + i] = self.columnCount;
1652             self->colBounds[i + i + 1] = -1;
1653         }
1654         self->_lastChangedRow = irow + h - 1;
1655
1656         BOOL* marksCursor = self->marks + irow * self.columnCount;
1657         for (i = irow; i < irow + h; ++i) {
1658             self->colBounds[i + i] = icol;
1659             self->colBounds[i + i + 1] = icol + w - 1;
1660             for (int j = icol; j < icol + w; ++j) {
1661                 marksCursor[j] = YES;
1662             }
1663             marksCursor += self.columnCount;
1664         }
1665     } else {
1666         /*
1667          * There's overlap between the rows of the requested block and prior
1668          * marked regions.
1669          */
1670         BOOL* marksCursor = self->marks + irow * self.columnCount;
1671         int irow0, h0;
1672
1673         if (irow < self.firstChangedRow) {
1674             /* Handle any leading rows where there's no overlap. */
1675             for (int i = irow; i < self.firstChangedRow; ++i) {
1676                 self->colBounds[i + i] = icol;
1677                 self->colBounds[i + i + 1] = icol + w - 1;
1678                 for (int j = icol; j < icol + w; ++j) {
1679                     marksCursor[j] = YES;
1680                 }
1681                 marksCursor += self.columnCount;
1682             }
1683             irow0 = self.firstChangedRow;
1684             h0 = irow + h - self.firstChangedRow;
1685             self->_firstChangedRow = irow;
1686         } else {
1687             irow0 = irow;
1688             h0 = h;
1689         }
1690
1691         /* Handle potentially overlapping rows */
1692         if (irow0 + h0 > self.lastChangedRow + 1) {
1693             h0 = self.lastChangedRow + 1 - irow0;
1694             self->_lastChangedRow = irow + h - 1;
1695         }
1696
1697         int i;
1698         for (i = irow0; i < irow0 + h0; ++i) {
1699             if (icol + w <= self->colBounds[i + i]) {
1700                 int j;
1701
1702                 for (j = icol; j < icol + w; ++j) {
1703                     marksCursor[j] = YES;
1704                 }
1705                 if (self->colBounds[i + i] > self->colBounds[i + i + 1]) {
1706                     self->colBounds[i + i + 1] = icol + w - 1;
1707                 } else {
1708                     for (j = icol + w; j < self->colBounds[i + i]; ++j) {
1709                         marksCursor[j] = NO;
1710                     }
1711                 }
1712                 self->colBounds[i + i] = icol;
1713             } else if (icol > self->colBounds[i + i + 1]) {
1714                 int j;
1715
1716                 for (j = self->colBounds[i + i + 1] + 1; j < icol; ++j) {
1717                     marksCursor[j] = NO;
1718                 }
1719                 for (j = icol; j < icol + w; ++j) {
1720                     marksCursor[j] = YES;
1721                 }
1722                 self->colBounds[i + i + 1] = icol + w - 1;
1723             } else {
1724                 if (icol < self->colBounds[i + i]) {
1725                     self->colBounds[i + i] = icol;
1726                 }
1727                 if (icol + w > self->colBounds[i + i + 1]) {
1728                     self->colBounds[i + i + 1] = icol + w - 1;
1729                 }
1730                 for (int j = icol; j < icol + w; ++j) {
1731                     marksCursor[j] = YES;
1732                 }
1733             }
1734             marksCursor += self.columnCount;
1735         }
1736
1737         /* Handle any trailing rows where there's no overlap. */
1738         for (i = irow0 + h0; i < irow + h; ++i) {
1739             self->colBounds[i + i] = icol;
1740             self->colBounds[i + i + 1] = icol + w - 1;
1741             for (int j = icol; j < icol + w; ++j) {
1742                 marksCursor[j] = YES;
1743             }
1744             marksCursor += self.columnCount;
1745         }
1746     }
1747 }
1748
1749 - (int)getFirstChangedColumnInRow:(int)irow
1750 {
1751     if (irow < self.firstChangedRow || irow > self.lastChangedRow) {
1752         return self.columnCount;
1753     }
1754     return self->colBounds[irow + irow];
1755 }
1756
1757 - (int)getLastChangedColumnInRow:(int)irow
1758 {
1759     if (irow < self.firstChangedRow || irow > self.lastChangedRow) {
1760         return -1;
1761     }
1762     return self->colBounds[irow + irow + 1];
1763 }
1764
1765 @end
1766
1767
1768 /**
1769  * Draws one tile as a helper function for AngbandContext's drawRect.
1770  */
1771 static void draw_image_tile(
1772     NSGraphicsContext* nsContext,
1773     CGContextRef cgContext,
1774     CGImageRef image,
1775     NSRect srcRect,
1776     NSRect dstRect,
1777     NSCompositingOperation op)
1778 {
1779     /* Flip the source rect since the source image is flipped */
1780     CGAffineTransform flip = CGAffineTransformIdentity;
1781     flip = CGAffineTransformTranslate(flip, 0.0, CGImageGetHeight(image));
1782     flip = CGAffineTransformScale(flip, 1.0, -1.0);
1783     CGRect flippedSourceRect =
1784         CGRectApplyAffineTransform(NSRectToCGRect(srcRect), flip);
1785
1786     /*
1787      * When we use high-quality resampling to draw a tile, pixels from outside
1788      * the tile may bleed in, causing graphics artifacts. Work around that.
1789      */
1790     CGImageRef subimage =
1791         CGImageCreateWithImageInRect(image, flippedSourceRect);
1792     [nsContext setCompositingOperation:op];
1793     CGContextDrawImage(cgContext, NSRectToCGRect(dstRect), subimage);
1794     CGImageRelease(subimage);
1795 }
1796
1797
1798 /* The max number of glyphs we support.  Currently this only affects
1799  * updateGlyphInfo() for the calculation of the tile size, fontAscender,
1800  * fontDescender, nColPre, and nColPost.  The rendering in drawWChar() will
1801  * work for a glyph not in updateGlyphInfo()'s set, and that is used for
1802  * rendering Japanese characters, though there may be clipping or clearing
1803  * artifacts because it wasn't included in updateGlyphInfo()'s calculations.
1804  */
1805 #define GLYPH_COUNT 256
1806
1807 /* An AngbandContext represents a logical Term (i.e. what Angband thinks is
1808  * a window). */
1809 @interface AngbandContext : NSObject <NSWindowDelegate>
1810 {
1811 @public
1812
1813     /* The Angband term */
1814     term *terminal;
1815
1816 @private
1817     /* Is the last time we drew, so we can throttle drawing. */
1818     CFAbsoluteTime lastRefreshTime;
1819
1820     /* Flags whether or not a fullscreen transition is in progress. */
1821     BOOL inFullscreenTransition;
1822
1823     /* Our view */
1824     AngbandView *angbandView;
1825 }
1826
1827 /* Column and row counts, by default 80 x 24 */
1828 @property (readonly) int cols;
1829 @property (readonly) int rows;
1830
1831 /* The size of the border between the window edge and the contents */
1832 @property (readonly) NSSize borderSize;
1833
1834 /* The font of this context */
1835 @property NSFont *angbandViewFont;
1836
1837 /* The size of one tile */
1838 @property (readonly) NSSize tileSize;
1839
1840 /* Font's ascender and descender */
1841 @property (readonly) CGFloat fontAscender;
1842 @property (readonly) CGFloat fontDescender;
1843
1844 /*
1845  * These are the number of columns before or after, respectively, a text
1846  * change that may need to be redrawn.
1847  */
1848 @property (readonly) int nColPre;
1849 @property (readonly) int nColPost;
1850
1851 /* If this context owns a window, here it is. */
1852 @property NSWindow *primaryWindow;
1853
1854 /* Holds our version of the contents of the terminal. */
1855 @property TerminalContents *contents;
1856
1857 /*
1858  * Marks which locations have been changed by the text_hook, pict_hook,
1859  * wipe_hook, curs_hook, and bigcurs_hhok callbacks on the terminal since
1860  * the last call to xtra_hook with TERM_XTRA_FRESH.
1861  */
1862 @property TerminalChanges *changes;
1863
1864 @property (nonatomic, assign) BOOL hasSubwindowFlags;
1865 @property (nonatomic, assign) BOOL windowVisibilityChecked;
1866
1867 - (void)resizeWithColumns:(int)nCol rows:(int)nRow;
1868
1869 /**
1870  * Based on what has been marked as changed, inform AppKit of the bounding
1871  * rectangles for the changed areas.
1872  */
1873 - (void)computeInvalidRects;
1874
1875 - (void)drawRect:(NSRect)rect inView:(NSView *)view;
1876
1877 /* Called at initialization to set the term */
1878 - (void)setTerm:(term *)t;
1879
1880 /* Called when the context is going down. */
1881 - (void)dispose;
1882
1883 /*
1884  * Return the rect in view coordinates for the block of cells whose upper
1885  * left corner is (x,y).
1886  */
1887 - (NSRect)viewRectForCellBlockAtX:(int)x y:(int)y width:(int)w height:(int)h;
1888
1889 /* Draw the given wide character into the given tile rect. */
1890 - (void)drawWChar:(wchar_t)wchar inRect:(NSRect)tile screenFont:(NSFont*)font
1891           context:(CGContextRef)ctx;
1892
1893 /* Returns the primary window for this angband context, creating it if
1894  * necessary */
1895 - (NSWindow *)makePrimaryWindow;
1896
1897 /* Handle becoming the main window */
1898 - (void)windowDidBecomeMain:(NSNotification *)notification;
1899
1900 /* Return whether the context's primary window is ordered in or not */
1901 - (BOOL)isOrderedIn;
1902
1903 /*
1904  * Return whether the context's primary window is the main window.
1905  * Since the terminals other than terminal 0 are configured as panels in
1906  * Hengband, this will only be true for terminal 0.
1907  */
1908 - (BOOL)isMainWindow;
1909
1910 /*
1911  * Return whether the context's primary window is the destination for key
1912  * input.
1913  */
1914 - (BOOL)isKeyWindow;
1915
1916 /* Invalidate the whole image */
1917 - (void)setNeedsDisplay:(BOOL)val;
1918
1919 /* Invalidate part of the image, with the rect expressed in view coordinates */
1920 - (void)setNeedsDisplayInRect:(NSRect)rect;
1921
1922 /* Display (flush) our Angband views */
1923 - (void)displayIfNeeded;
1924
1925 /* Resize context to size of contentRect, and optionally save size to
1926  * defaults */
1927 - (void)resizeTerminalWithContentRect: (NSRect)contentRect saveToDefaults: (BOOL)saveToDefaults;
1928
1929 /*
1930  * Change the minimum size and size increments for the window associated with
1931  * the context.  termIdx is the index for the terminal:  pass it so this
1932  * function can be used when self->terminal has not yet been set.
1933  */
1934 - (void)constrainWindowSize:(int)termIdx;
1935
1936 - (void)saveWindowVisibleToDefaults: (BOOL)windowVisible;
1937 - (BOOL)windowVisibleUsingDefaults;
1938
1939 /* Class methods */
1940 /**
1941  * Gets the default font for all contexts.  Currently not declaring this as
1942  * a class property for compatibility with versions of Xcode prior to 8.
1943  */
1944 + (NSFont*)defaultFont;
1945 /**
1946  * Sets the default font for all contexts.
1947  */
1948 + (void)setDefaultFont:(NSFont*)font;
1949
1950 /* Internal methods */
1951 /* Set the title for the primary window. */
1952 - (void)setDefaultTitle:(int)termIdx;
1953
1954 @end
1955
1956 /**
1957  * Generate a mask for the subwindow flags. The mask is just a safety check to
1958  * make sure that our windows show and hide as expected.  This function allows
1959  * for future changes to the set of flags without needed to update it here
1960  * (unless the underlying types change).
1961  */
1962 u32b AngbandMaskForValidSubwindowFlags(void)
1963 {
1964     int windowFlagBits = sizeof(*(window_flag)) * CHAR_BIT;
1965     int maxBits = MIN( 16, windowFlagBits );
1966     u32b mask = 0;
1967
1968     for( int i = 0; i < maxBits; i++ )
1969     {
1970         if( window_flag_desc[i] != NULL )
1971         {
1972             mask |= (1 << i);
1973         }
1974     }
1975
1976     return mask;
1977 }
1978
1979 /**
1980  * Check for changes in the subwindow flags and update window visibility.
1981  * This seems to be called for every user event, so we don't
1982  * want to do any unnecessary hiding or showing of windows.
1983  */
1984 static void AngbandUpdateWindowVisibility(void)
1985 {
1986     /* Because this function is called frequently, we'll make the mask static.
1987          * It doesn't change between calls, as the flags themselves are hardcoded */
1988     static u32b validWindowFlagsMask = 0;
1989
1990     if( validWindowFlagsMask == 0 )
1991     {
1992         validWindowFlagsMask = AngbandMaskForValidSubwindowFlags();
1993     }
1994
1995     /* Loop through all of the subwindows and see if there is a change in the
1996          * flags. If so, show or hide the corresponding window. We don't care about
1997          * the flags themselves; we just want to know if any are set. */
1998     for( int i = 1; i < ANGBAND_TERM_MAX; i++ )
1999     {
2000         AngbandContext *angbandContext =
2001             (__bridge AngbandContext*) (angband_term[i]->data);
2002
2003         if( angbandContext == nil )
2004         {
2005             continue;
2006         }
2007
2008         /* This horrible mess of flags is so that we can try to maintain some
2009                  * user visibility preference. This should allow the user a window and
2010                  * have it stay closed between application launches. However, this
2011                  * means that when a subwindow is turned on, it will no longer appear
2012                  * automatically. Angband has no concept of user control over window
2013                  * visibility, other than the subwindow flags. */
2014         if( !angbandContext.windowVisibilityChecked )
2015         {
2016             if( [angbandContext windowVisibleUsingDefaults] )
2017             {
2018                 [angbandContext.primaryWindow orderFront: nil];
2019                 angbandContext.windowVisibilityChecked = YES;
2020             }
2021             else
2022             {
2023                 [angbandContext.primaryWindow close];
2024                 angbandContext.windowVisibilityChecked = NO;
2025             }
2026         }
2027         else
2028         {
2029             BOOL termHasSubwindowFlags = ((window_flag[i] & validWindowFlagsMask) > 0);
2030
2031             if( angbandContext.hasSubwindowFlags && !termHasSubwindowFlags )
2032             {
2033                 [angbandContext.primaryWindow close];
2034                 angbandContext.hasSubwindowFlags = NO;
2035                 [angbandContext saveWindowVisibleToDefaults: NO];
2036             }
2037             else if( !angbandContext.hasSubwindowFlags && termHasSubwindowFlags )
2038             {
2039                 [angbandContext.primaryWindow orderFront: nil];
2040                 angbandContext.hasSubwindowFlags = YES;
2041                 [angbandContext saveWindowVisibleToDefaults: YES];
2042             }
2043         }
2044     }
2045
2046     /* Make the main window key so that user events go to the right spot */
2047     AngbandContext *mainWindow =
2048         (__bridge AngbandContext*) (angband_term[0]->data);
2049     [mainWindow.primaryWindow makeKeyAndOrderFront: nil];
2050 }
2051
2052 /**
2053  * ------------------------------------------------------------------------
2054  * Graphics support
2055  * ------------------------------------------------------------------------ */
2056
2057 /**
2058  * The tile image
2059  */
2060 static CGImageRef pict_image;
2061
2062 /**
2063  * Numbers of rows and columns in a tileset,
2064  * calculated by the PICT/PNG loading code
2065  */
2066 static int pict_cols = 0;
2067 static int pict_rows = 0;
2068
2069 /**
2070  * Requested graphics mode (as a grafID).
2071  * The current mode is stored in current_graphics_mode.
2072  */
2073 static int graf_mode_req = 0;
2074
2075 /**
2076  * Helper function to check the various ways that graphics can be enabled,
2077  * guarding against NULL
2078  */
2079 static BOOL graphics_are_enabled(void)
2080 {
2081     return current_graphics_mode
2082         && current_graphics_mode->grafID != GRAPHICS_NONE;
2083 }
2084
2085 /**
2086  * Like graphics_are_enabled(), but test the requested graphics mode.
2087  */
2088 static BOOL graphics_will_be_enabled(void)
2089 {
2090     if (graf_mode_req == GRAPHICS_NONE) {
2091         return NO;
2092     }
2093
2094     graphics_mode *new_mode = get_graphics_mode(graf_mode_req);
2095     return new_mode && new_mode->grafID != GRAPHICS_NONE;
2096 }
2097
2098 /**
2099  * Hack -- game in progress
2100  */
2101 static Boolean game_in_progress = FALSE;
2102
2103
2104 #pragma mark Prototypes
2105 static BOOL redraw_for_tiles_or_term0_font(void);
2106 static void wakeup_event_loop(void);
2107 static void hook_plog(const char *str);
2108 static void hook_quit(const char * str);
2109 static NSString* get_lib_directory(void);
2110 static NSString* get_doc_directory(void);
2111 static NSString* AngbandCorrectedDirectoryPath(NSString *originalPath);
2112 static void prepare_paths_and_directories(void);
2113 static void load_prefs(void);
2114 static void init_windows(void);
2115 static void handle_open_when_ready(void);
2116 static void play_sound(int event);
2117 static BOOL check_events(int wait);
2118 static BOOL send_event(NSEvent *event);
2119 static void set_color_for_index(int idx);
2120 static void record_current_savefile(void);
2121
2122 /**
2123  * Available values for 'wait'
2124  */
2125 #define CHECK_EVENTS_DRAIN -1
2126 #define CHECK_EVENTS_NO_WAIT    0
2127 #define CHECK_EVENTS_WAIT 1
2128
2129
2130 /**
2131  * Note when "open"/"new" become valid
2132  */
2133 static bool initialized = FALSE;
2134
2135 /* Methods for getting the appropriate NSUserDefaults */
2136 @interface NSUserDefaults (AngbandDefaults)
2137 + (NSUserDefaults *)angbandDefaults;
2138 @end
2139
2140 @implementation NSUserDefaults (AngbandDefaults)
2141 + (NSUserDefaults *)angbandDefaults
2142 {
2143     return [NSUserDefaults standardUserDefaults];
2144 }
2145 @end
2146
2147 /* Methods for pulling images out of the Angband bundle (which may be separate
2148  * from the current bundle in the case of a screensaver */
2149 @interface NSImage (AngbandImages)
2150 + (NSImage *)angbandImage:(NSString *)name;
2151 @end
2152
2153 /* The NSView subclass that draws our Angband image */
2154 @interface AngbandView : NSView {
2155 @private
2156     NSBitmapImageRep *cacheForResize;
2157     NSRect cacheBounds;
2158 }
2159
2160 @property (nonatomic, weak) AngbandContext *angbandContext;
2161
2162 @end
2163
2164 @implementation NSImage (AngbandImages)
2165
2166 /* Returns an image in the resource directoy of the bundle containing the
2167  * Angband view class. */
2168 + (NSImage *)angbandImage:(NSString *)name
2169 {
2170     NSBundle *bundle = [NSBundle bundleForClass:[AngbandView class]];
2171     NSString *path = [bundle pathForImageResource:name];
2172     return (path) ? [[NSImage alloc] initByReferencingFile:path] : nil;
2173 }
2174
2175 @end
2176
2177
2178 @implementation AngbandContext
2179
2180 @synthesize hasSubwindowFlags=_hasSubwindowFlags;
2181 @synthesize windowVisibilityChecked=_windowVisibilityChecked;
2182
2183 - (NSSize)baseSize
2184 {
2185     /*
2186      * We round the base size down. If we round it up, I believe we may end up
2187      * with pixels that nobody "owns" that may accumulate garbage. In general
2188      * rounding down is harmless, because any lost pixels may be sopped up by
2189      * the border.
2190      */
2191     return NSMakeSize(
2192         floor(self.cols * self.tileSize.width + 2 * self.borderSize.width),
2193         floor(self.rows * self.tileSize.height + 2 * self.borderSize.height));
2194 }
2195
2196 /* qsort-compatible compare function for CGSizes */
2197 static int compare_advances(const void *ap, const void *bp)
2198 {
2199     const CGSize *a = ap, *b = bp;
2200     return (a->width > b->width) - (a->width < b->width);
2201 }
2202
2203 /**
2204  * Precompute certain metrics (tileSize, fontAscender, fontDescender, nColPre,
2205  * and nColPost) for the current font.
2206  */
2207 - (void)updateGlyphInfo
2208 {
2209     NSFont *screenFont = [self.angbandViewFont screenFont];
2210
2211     /* Generate a string containing each MacRoman character */
2212     /*
2213      * Here and below, dynamically allocate working arrays rather than put them
2214      * on the stack in case limited stack space is an issue.
2215      */
2216     unsigned char *latinString = malloc(GLYPH_COUNT);
2217     if (latinString == 0) {
2218         NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
2219                                         reason:@"latinString in updateGlyphInfo"
2220                                         userInfo:nil];
2221         @throw exc;
2222     }
2223     size_t i;
2224     for (i=0; i < GLYPH_COUNT; i++) latinString[i] = (unsigned char)i;
2225
2226     /* Turn that into unichar. Angband uses ISO Latin 1. */
2227     NSString *allCharsString = [[NSString alloc] initWithBytes:latinString
2228         length:GLYPH_COUNT encoding:NSISOLatin1StringEncoding];
2229     unichar *unicharString = malloc(GLYPH_COUNT * sizeof(unichar));
2230     if (unicharString == 0) {
2231         free(latinString);
2232         NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
2233                                         reason:@"unicharString in updateGlyphInfo"
2234                                         userInfo:nil];
2235         @throw exc;
2236     }
2237     unicharString[0] = 0;
2238     [allCharsString getCharacters:unicharString range:NSMakeRange(0, MIN(GLYPH_COUNT, [allCharsString length]))];
2239     allCharsString = nil;
2240     free(latinString);
2241
2242     /* Get glyphs */
2243     CGGlyph *glyphArray = calloc(GLYPH_COUNT, sizeof(CGGlyph));
2244     if (glyphArray == 0) {
2245         free(unicharString);
2246         NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
2247                                         reason:@"glyphArray in updateGlyphInfo"
2248                                         userInfo:nil];
2249         @throw exc;
2250     }
2251     CTFontGetGlyphsForCharacters((CTFontRef)screenFont, unicharString,
2252                                  glyphArray, GLYPH_COUNT);
2253     free(unicharString);
2254
2255     /* Get advances. Record the max advance. */
2256     CGSize *advances = malloc(GLYPH_COUNT * sizeof(CGSize));
2257     if (advances == 0) {
2258         free(glyphArray);
2259         NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
2260                                         reason:@"advances in updateGlyphInfo"
2261                                         userInfo:nil];
2262         @throw exc;
2263     }
2264     CTFontGetAdvancesForGlyphs(
2265         (CTFontRef)screenFont, kCTFontHorizontalOrientation, glyphArray,
2266         advances, GLYPH_COUNT);
2267     CGFloat *glyphWidths = malloc(GLYPH_COUNT * sizeof(CGFloat));
2268     if (glyphWidths == 0) {
2269         free(glyphArray);
2270         free(advances);
2271         NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
2272                                         reason:@"glyphWidths in updateGlyphInfo"
2273                                         userInfo:nil];
2274         @throw exc;
2275     }
2276     for (i=0; i < GLYPH_COUNT; i++) {
2277         glyphWidths[i] = advances[i].width;
2278     }
2279
2280     /*
2281      * For good non-mono-font support, use the median advance. Start by sorting
2282      * all advances.
2283      */
2284     qsort(advances, GLYPH_COUNT, sizeof *advances, compare_advances);
2285
2286     /* Skip over any initially empty run */
2287     size_t startIdx;
2288     for (startIdx = 0; startIdx < GLYPH_COUNT; startIdx++)
2289     {
2290         if (advances[startIdx].width > 0) break;
2291     }
2292
2293     /* Pick the center to find the median */
2294     CGFloat medianAdvance = 0;
2295     /* In case we have all zero advances for some reason */
2296     if (startIdx < GLYPH_COUNT)
2297     {
2298         medianAdvance = advances[(startIdx + GLYPH_COUNT)/2].width;
2299     }
2300
2301     free(advances);
2302
2303     /*
2304      * Record the ascender and descender.  Some fonts, for instance DIN
2305      * Condensed and Rockwell in 10.14, the ascent on '@' exceeds that
2306      * reported by [screenFont ascender].  Get the overall bounding box
2307      * for the glyphs and use that instead of the ascender and descender
2308      * values if the bounding box result extends farther from the baseline.
2309      */
2310     CGRect bounds = CTFontGetBoundingRectsForGlyphs(
2311         (CTFontRef) screenFont, kCTFontHorizontalOrientation, glyphArray,
2312         NULL, GLYPH_COUNT);
2313     self->_fontAscender = [screenFont ascender];
2314     if (self->_fontAscender < bounds.origin.y + bounds.size.height) {
2315         self->_fontAscender = bounds.origin.y + bounds.size.height;
2316     }
2317     self->_fontDescender = [screenFont descender];
2318     if (self->_fontDescender > bounds.origin.y) {
2319         self->_fontDescender = bounds.origin.y;
2320     }
2321
2322     /*
2323      * Record the tile size.  Round both values up to have tile boundaries
2324      * match pixel boundaries.
2325      */
2326     self->_tileSize.width = ceil(medianAdvance);
2327     self->_tileSize.height = ceil(self.fontAscender - self.fontDescender);
2328
2329     /*
2330      * Determine whether neighboring columns need to be redrawn when a
2331      * character changes.
2332      */
2333     CGRect *boxes = malloc(GLYPH_COUNT * sizeof(CGRect));
2334     if (boxes == 0) {
2335         free(glyphWidths);
2336         free(glyphArray);
2337         NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
2338                                         reason:@"boxes in updateGlyphInfo"
2339                                         userInfo:nil];
2340         @throw exc;
2341     }
2342     CGFloat beyond_right = 0.;
2343     CGFloat beyond_left = 0.;
2344     CTFontGetBoundingRectsForGlyphs(
2345         (CTFontRef)screenFont,
2346         kCTFontHorizontalOrientation,
2347         glyphArray,
2348         boxes,
2349         GLYPH_COUNT);
2350     for (i = 0; i < GLYPH_COUNT; i++) {
2351         /* Account for the compression and offset used by drawWChar(). */
2352         CGFloat compression, offset;
2353         CGFloat v;
2354
2355         if (glyphWidths[i] <= self.tileSize.width) {
2356             compression = 1.;
2357             offset = 0.5 * (self.tileSize.width - glyphWidths[i]);
2358         } else {
2359             compression = self.tileSize.width / glyphWidths[i];
2360             offset = 0.;
2361         }
2362         v = (offset + boxes[i].origin.x) * compression;
2363         if (beyond_left > v) {
2364             beyond_left = v;
2365         }
2366         v = (offset + boxes[i].origin.x + boxes[i].size.width) * compression;
2367         if (beyond_right < v) {
2368             beyond_right = v;
2369         }
2370     }
2371     free(boxes);
2372     self->_nColPre = ceil(-beyond_left / self.tileSize.width);
2373     if (beyond_right > self.tileSize.width) {
2374         self->_nColPost =
2375             ceil((beyond_right - self.tileSize.width) / self.tileSize.width);
2376     } else {
2377         self->_nColPost = 0;
2378     }
2379
2380     free(glyphWidths);
2381     free(glyphArray);
2382 }
2383
2384
2385 - (void)requestRedraw
2386 {
2387     if (! self->terminal) return;
2388     
2389     term *old = Term;
2390     
2391     /* Activate the term */
2392     Term_activate(self->terminal);
2393     
2394     /* Redraw the contents */
2395     Term_redraw();
2396     
2397     /* Flush the output */
2398     Term_fresh();
2399     
2400     /* Restore the old term */
2401     Term_activate(old);
2402 }
2403
2404 - (void)setTerm:(term *)t
2405 {
2406     self->terminal = t;
2407 }
2408
2409 /**
2410  * If we're trying to limit ourselves to a certain number of frames per second,
2411  * then compute how long it's been since we last drew, and then wait until the
2412  * next frame has passed. */
2413 - (void)throttle
2414 {
2415     if (frames_per_second > 0)
2416     {
2417         CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
2418         CFTimeInterval timeSinceLastRefresh = now - self->lastRefreshTime;
2419         CFTimeInterval timeUntilNextRefresh = (1. / (double)frames_per_second) - timeSinceLastRefresh;
2420         
2421         if (timeUntilNextRefresh > 0)
2422         {
2423             usleep((unsigned long)(timeUntilNextRefresh * 1000000.));
2424         }
2425     }
2426     self->lastRefreshTime = CFAbsoluteTimeGetCurrent();
2427 }
2428
2429 - (void)drawWChar:(wchar_t)wchar inRect:(NSRect)tile screenFont:(NSFont*)font
2430           context:(CGContextRef)ctx
2431 {
2432     CGFloat tileOffsetY = self.fontAscender;
2433     CGFloat tileOffsetX = 0.0;
2434     UniChar unicharString[2] = {(UniChar)wchar, 0};
2435
2436     /* Get glyph and advance */
2437     CGGlyph thisGlyphArray[1] = { 0 };
2438     CGSize advances[1] = { { 0, 0 } };
2439     CTFontGetGlyphsForCharacters(
2440         (CTFontRef)font, unicharString, thisGlyphArray, 1);
2441     CGGlyph glyph = thisGlyphArray[0];
2442     CTFontGetAdvancesForGlyphs(
2443         (CTFontRef)font, kCTFontHorizontalOrientation, thisGlyphArray,
2444         advances, 1);
2445     CGSize advance = advances[0];
2446
2447     /* If our font is not monospaced, our tile width is deliberately not big
2448          * enough for every character. In that event, if our glyph is too wide, we
2449          * need to compress it horizontally. Compute the compression ratio.
2450          * 1.0 means no compression. */
2451     double compressionRatio;
2452     if (advance.width <= NSWidth(tile))
2453     {
2454         /* Our glyph fits, so we can just draw it, possibly with an offset */
2455         compressionRatio = 1.0;
2456         tileOffsetX = (NSWidth(tile) - advance.width)/2;
2457     }
2458     else
2459     {
2460         /* Our glyph doesn't fit, so we'll have to compress it */
2461         compressionRatio = NSWidth(tile) / advance.width;
2462         tileOffsetX = 0;
2463     }
2464
2465     /* Now draw it */
2466     CGAffineTransform textMatrix = CGContextGetTextMatrix(ctx);
2467     CGFloat savedA = textMatrix.a;
2468
2469     /* Set the position */
2470     textMatrix.tx = tile.origin.x + tileOffsetX;
2471     textMatrix.ty = tile.origin.y + tileOffsetY;
2472
2473     /* Maybe squish it horizontally. */
2474     if (compressionRatio != 1.)
2475     {
2476         textMatrix.a *= compressionRatio;
2477     }
2478
2479     CGContextSetTextMatrix(ctx, textMatrix);
2480     CGContextShowGlyphsAtPositions(ctx, &glyph, &CGPointZero, 1);
2481
2482     /* Restore the text matrix if we messed with the compression ratio */
2483     if (compressionRatio != 1.)
2484     {
2485         textMatrix.a = savedA;
2486     }
2487
2488     CGContextSetTextMatrix(ctx, textMatrix);
2489 }
2490
2491 - (NSRect)viewRectForCellBlockAtX:(int)x y:(int)y width:(int)w height:(int)h
2492 {
2493     return NSMakeRect(
2494         x * self.tileSize.width + self.borderSize.width,
2495         y * self.tileSize.height + self.borderSize.height,
2496         w * self.tileSize.width, h * self.tileSize.height);
2497 }
2498
2499 - (void)setSelectionFont:(NSFont*)font adjustTerminal: (BOOL)adjustTerminal
2500 {
2501     /* Record the new font */
2502     self.angbandViewFont = font;
2503
2504     /* Update our glyph info */
2505     [self updateGlyphInfo];
2506
2507     if( adjustTerminal )
2508     {
2509         /* Adjust terminal to fit window with new font; save the new columns
2510                  * and rows since they could be changed */
2511         NSRect contentRect =
2512             [self.primaryWindow
2513                  contentRectForFrameRect: [self.primaryWindow frame]];
2514
2515         [self constrainWindowSize:[self terminalIndex]];
2516         NSSize size = self.primaryWindow.contentMinSize;
2517         BOOL windowNeedsResizing = NO;
2518         if (contentRect.size.width < size.width) {
2519             contentRect.size.width = size.width;
2520             windowNeedsResizing = YES;
2521         }
2522         if (contentRect.size.height < size.height) {
2523             contentRect.size.height = size.height;
2524             windowNeedsResizing = YES;
2525         }
2526         if (windowNeedsResizing) {
2527             size.width = contentRect.size.width;
2528             size.height = contentRect.size.height;
2529             [self.primaryWindow setContentSize:size];
2530         }
2531         [self resizeTerminalWithContentRect: contentRect saveToDefaults: YES];
2532     }
2533 }
2534
2535 - (id)init
2536 {
2537     if ((self = [super init]))
2538     {
2539         /* Default rows and cols */
2540         self->_cols = 80;
2541         self->_rows = 24;
2542
2543         /* Default border size */
2544         self->_borderSize = NSMakeSize(2, 2);
2545
2546         self->_nColPre = 0;
2547         self->_nColPost = 0;
2548
2549         self->_contents =
2550             [[TerminalContents alloc] initWithColumns:self->_cols
2551                                       rows:self->_rows];
2552         self->_changes =
2553             [[TerminalChanges alloc] initWithColumns:self->_cols
2554                                      rows:self->_rows];
2555         self->lastRefreshTime = CFAbsoluteTimeGetCurrent();
2556         self->inFullscreenTransition = NO;
2557
2558         self->_windowVisibilityChecked = NO;
2559     }
2560     return self;
2561 }
2562
2563 /**
2564  * Destroy all the receiver's stuff. This is intended to be callable more than
2565  * once.
2566  */
2567 - (void)dispose
2568 {
2569     self->terminal = NULL;
2570
2571     /* Disassociate ourselves from our view. */
2572     [self->angbandView setAngbandContext:nil];
2573     self->angbandView = nil;
2574
2575     /* Font */
2576     self.angbandViewFont = nil;
2577
2578     /* Window */
2579     [self.primaryWindow setDelegate:nil];
2580     [self.primaryWindow close];
2581     self.primaryWindow = nil;
2582
2583     /* Contents and pending changes */
2584     self.contents = nil;
2585     self.changes = nil;
2586 }
2587
2588 /* Usual Cocoa fare */
2589 - (void)dealloc
2590 {
2591     [self dispose];
2592 }
2593
2594 - (void)resizeWithColumns:(int)nCol rows:(int)nRow
2595 {
2596     [self.contents resizeWithColumns:nCol rows:nRow];
2597     [self.changes resizeWithColumns:nCol rows:nRow];
2598     self->_cols = nCol;
2599     self->_rows = nRow;
2600 }
2601
2602 /**
2603  * For defaultFont and setDefaultFont.
2604  */
2605 static __strong NSFont* gDefaultFont = nil;
2606
2607 + (NSFont*)defaultFont
2608 {
2609     return gDefaultFont;
2610 }
2611
2612 + (void)setDefaultFont:(NSFont*)font
2613 {
2614     gDefaultFont = font;
2615 }
2616
2617 - (void)setDefaultTitle:(int)termIdx
2618 {
2619     NSMutableString *title =
2620         [NSMutableString stringWithCString:angband_term_name[termIdx]
2621 #ifdef JP
2622                          encoding:NSJapaneseEUCStringEncoding
2623 #else
2624                          encoding:NSMacOSRomanStringEncoding
2625 #endif
2626         ];
2627     [title appendFormat:@" %dx%d", self.cols, self.rows];
2628     [[self makePrimaryWindow] setTitle:title];
2629 }
2630
2631 - (NSWindow *)makePrimaryWindow
2632 {
2633     if (! self.primaryWindow)
2634     {
2635         /* This has to be done after the font is set, which it already is in
2636                  * term_init_cocoa() */
2637         NSSize sz = self.baseSize;
2638         NSRect contentRect = NSMakeRect( 0.0, 0.0, sz.width, sz.height );
2639
2640         NSUInteger styleMask = NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask;
2641
2642         /*
2643          * Make every window other than the main window closable, also create
2644          * them as utility panels to get the thinner title bar and other
2645          * attributes that already match up with how those windows are used.
2646          */
2647         if ((__bridge AngbandContext*) (angband_term[0]->data) != self)
2648         {
2649             NSPanel *panel =
2650                 [[NSPanel alloc] initWithContentRect:contentRect
2651                                  styleMask:(styleMask | NSClosableWindowMask |
2652                                             NSUtilityWindowMask)
2653                                  backing:NSBackingStoreBuffered defer:YES];
2654
2655             panel.floatingPanel = NO;
2656             self.primaryWindow = panel;
2657         } else {
2658             self.primaryWindow =
2659                 [[NSWindow alloc] initWithContentRect:contentRect
2660                                   styleMask:styleMask
2661                                   backing:NSBackingStoreBuffered defer:YES];
2662         }
2663
2664
2665         /* Not to be released when closed */
2666         [self.primaryWindow setReleasedWhenClosed:NO];
2667         [self.primaryWindow setExcludedFromWindowsMenu: YES]; /* we're using custom window menu handling */
2668
2669         /* Make the view */
2670         self->angbandView = [[AngbandView alloc] initWithFrame:contentRect];
2671         [angbandView setAngbandContext:self];
2672         [angbandView setNeedsDisplay:YES];
2673         [self.primaryWindow setContentView:angbandView];
2674
2675         /* We are its delegate */
2676         [self.primaryWindow setDelegate:self];
2677     }
2678     return self.primaryWindow;
2679 }
2680
2681
2682 - (void)computeInvalidRects
2683 {
2684     for (int irow = self.changes.firstChangedRow;
2685          irow <= self.changes.lastChangedRow;
2686          ++irow) {
2687         int icol = [self.changes scanForChangedInRow:irow
2688                         col0:0 col1:self.cols];
2689
2690         while (icol < self.cols) {
2691             /* Find the end of the changed region. */
2692             int jcol =
2693                 [self.changes scanForUnchangedInRow:irow col0:(icol + 1)
2694                      col1:self.cols];
2695
2696             /*
2697              * If the last column is a character, extend the region drawn
2698              * because characters can exceed the horizontal bounds of the cell
2699              * and those parts will need to be cleared.  Don't extend into a
2700              * tile because the clipping is set while drawing to never
2701              * extend text into a tile.  For a big character that's been
2702              * partially overwritten, allow what comes after the point
2703              * where the overwrite occurred to influence the stuff before
2704              * but not vice versa.  If extending the region reaches another
2705              * changed block, find the end of that block and repeat the
2706              * process.
2707              */
2708             /*
2709              * A value of zero means checking for a character immediately
2710              * prior to the column, isrch.  A value of one means checking for
2711              * something past the end that could either influence the changed
2712              * region (within nColPre of it and no intervening tile) or be
2713              * influenced by it (within nColPost of it and no intervening
2714              * tile or partially overwritten big character).  A value of two
2715              * means checking for something past the end which is both changed
2716              * and could affect the part of the unchanged region that has to
2717              * be redrawn because it is affected by the prior changed region
2718              * Values of three and four are like one and two, respectively,
2719              * but indicate that a partially overwritten big character was
2720              * found.
2721              */
2722             int stage = 0;
2723             int isrch = jcol;
2724             int irng0 = jcol;
2725             int irng1 = jcol;
2726             while (1) {
2727                 if (stage == 0) {
2728                     const struct TerminalCell *pcell =
2729                         [self.contents getCellAtColumn:(isrch - 1) row:irow];
2730                     if ((pcell->form &
2731                          (TERM_CELL_TILE | TERM_CELL_TILE_PADDING)) != 0) {
2732                         break;
2733                     } else {
2734                         irng0 = isrch + self.nColPre;
2735                         if (irng0 > self.cols) {
2736                             irng0 = self.cols;
2737                         }
2738                         irng1 = isrch + self.nColPost;
2739                         if (irng1 > self.cols) {
2740                             irng1 = self.cols;
2741                         }
2742                         if (isrch < irng0 || isrch < irng1) {
2743                             stage = isPartiallyOverwrittenBigChar(pcell) ?
2744                                 3 : 1;
2745                         } else {
2746                             break;
2747                         }
2748                     }
2749                 }
2750
2751                 if (stage == 1) {
2752                     const struct TerminalCell *pcell =
2753                         [self.contents getCellAtColumn:isrch row:irow];
2754
2755                     if ((pcell->form &
2756                          (TERM_CELL_TILE | TERM_CELL_TILE_PADDING)) != 0) {
2757                         /*
2758                          * Check if still in the region that could be
2759                          * influenced by the changed region.  If so,
2760                          * everything up to the tile will be redrawn anyways
2761                          * so combine the regions if the tile has changed
2762                          * as well.  Otherwise, terminate the search since
2763                          * the tile doesn't allow influence to propagate
2764                          * through it and don't want to affect what's in the
2765                          * tile.
2766                          */
2767                         if (isrch < irng1) {
2768                             if ([self.changes isChangedAtColumn:isrch
2769                                      row:irow]) {
2770                                 jcol = [self.changes scanForUnchangedInRow:irow
2771                                             col0:(isrch + 1) col1:self.cols];
2772                                 if (jcol < self.cols) {
2773                                     stage = 0;
2774                                     isrch = jcol;
2775                                     continue;
2776                                 }
2777                             }
2778                         }
2779                         break;
2780                     } else {
2781                         /*
2782                          * With a changed character, combine the regions (if
2783                          * still in the region affected by the changed region
2784                          * am going to redraw everything up to this new region
2785                          * anyway; if only in the region that can affect the
2786                          * changed region, this changed text could influence
2787                          * the current changed region).
2788                          */
2789                         if ([self.changes isChangedAtColumn:isrch row:irow]) {
2790                             jcol = [self.changes scanForUnchangedInRow:irow
2791                                         col0:(isrch + 1) col1:self.cols];
2792                             if (jcol < self.cols) {
2793                                 stage = 0;
2794                                 isrch = jcol;
2795                                 continue;
2796                             }
2797                             break;
2798                         }
2799
2800                         if (isrch < irng1) {
2801                             /*
2802                              * Can be affected by the changed region so
2803                              * has to be redrawn.
2804                              */
2805                             ++jcol;
2806                         }
2807                         ++isrch;
2808                         if (isrch >= irng1) {
2809                             irng0 = jcol + self.nColPre;
2810                             if (irng0 > self.cols) {
2811                                 irng0 = self.cols;
2812                             }
2813                             if (isrch >= irng0) {
2814                                 break;
2815                             }
2816                             stage = isPartiallyOverwrittenBigChar(pcell) ?
2817                                 4 : 2;
2818                         } else if (isPartiallyOverwrittenBigChar(pcell)) {
2819                             stage = 3;
2820                         }
2821                     }
2822                 }
2823
2824                 if (stage == 2) {
2825                     /*
2826                      * Looking for a later changed region that could influence
2827                      * the region that has to be redrawn.  The region that has
2828                      * to be redrawn ends just before jcol.
2829                      */
2830                     const struct TerminalCell *pcell =
2831                         [self.contents getCellAtColumn:isrch row:irow];
2832
2833                     if ((pcell->form &
2834                          (TERM_CELL_TILE | TERM_CELL_TILE_PADDING)) != 0) {
2835                         /* Can not spread influence through a tile. */
2836                         break;
2837                     }
2838                     if ([self.changes isChangedAtColumn:isrch row:irow]) {
2839                         /*
2840                          * Found one.  Combine with the one ending just before
2841                          * jcol.
2842                          */
2843                         jcol = [self.changes scanForUnchangedInRow:irow
2844                                     col0:(isrch + 1) col1:self.cols];
2845                         if (jcol < self.cols) {
2846                             stage = 0;
2847                             isrch = jcol;
2848                             continue;
2849                         }
2850                         break;
2851                     }
2852
2853                     ++isrch;
2854                     if (isrch >= irng0) {
2855                         break;
2856                     }
2857                     if (isPartiallyOverwrittenBigChar(pcell)) {
2858                         stage = 4;
2859                     }
2860                 }
2861
2862                 if (stage == 3) {
2863                     const struct TerminalCell *pcell =
2864                         [self.contents getCellAtColumn:isrch row:irow];
2865
2866                     /*
2867                      * Have encountered a partially overwritten big character
2868                      * but still may be in the region that could be influenced
2869                      * by the changed region.  That influence can not extend
2870                      * past the past the padding for the partially overwritten
2871                      * character.
2872                      */
2873                     if ((pcell->form & (TERM_CELL_CHAR | TERM_CELL_TILE |
2874                                         TERM_CELL_TILE_PADDING)) != 0) {
2875                         if (isrch < irng1) {
2876                             /*
2877                              * Still can be affected by the changed region
2878                              * so everything up to isrch will be redrawn
2879                              * anyways.  If this location has changed,
2880                              * merge the changed regions.
2881                              */
2882                             if ([self.changes isChangedAtColumn:isrch
2883                                      row:irow]) {
2884                                 jcol = [self.changes scanForUnchangedInRow:irow
2885                                             col0:(isrch + 1) col1:self.cols];
2886                                 if (jcol < self.cols) {
2887                                     stage = 0;
2888                                     isrch = jcol;
2889                                     continue;
2890                                 }
2891                                 break;
2892                             }
2893                         }
2894                         if ((pcell->form &
2895                              (TERM_CELL_TILE | TERM_CELL_TILE_PADDING)) != 0) {
2896                             /*
2897                              * It's a tile.  That blocks influence in either
2898                              * direction.
2899                              */
2900                             break;
2901                         }
2902
2903                         /*
2904                          * The partially overwritten big character was
2905                          * overwritten by a character.  Check to see if it
2906                          * can either influence the unchanged region that
2907                          * has to redrawn or the changed region prior to
2908                          * that.
2909                          */
2910                         if (isrch >= irng0) {
2911                             break;
2912                         }
2913                         stage = 4;
2914                     } else {
2915                         if (isrch < irng1) {
2916                             /*
2917                              * Can be affected by the changed region so has to
2918                              * be redrawn.
2919                              */
2920                             ++jcol;
2921                         }
2922                         ++isrch;
2923                         if (isrch >= irng1) {
2924                             irng0 = jcol + self.nColPre;
2925                             if (irng0 > self.cols) {
2926                                 irng0 = self.cols;
2927                             }
2928                             if (isrch >= irng0) {
2929                                 break;
2930                             }
2931                             stage = 4;
2932                         }
2933                     }
2934                 }
2935
2936                 if (stage == 4) {
2937                     /*
2938                      * Have already encountered a partially overwritten big
2939                      * character.  Looking for a later changed region that
2940                      * could influence the region that has to be redrawn
2941                      * The region that has to be redrawn ends just before jcol.
2942                      */
2943                     const struct TerminalCell *pcell =
2944                         [self.contents getCellAtColumn:isrch row:irow];
2945
2946                     if ((pcell->form &
2947                          (TERM_CELL_TILE | TERM_CELL_TILE_PADDING)) != 0) {
2948                         /* Can not spread influence through a tile. */
2949                         break;
2950                     }
2951                     if (pcell->form == TERM_CELL_CHAR) {
2952                         if ([self.changes isChangedAtColumn:isrch row:irow]) {
2953                             /*
2954                              * Found a changed region.  Combine with the one
2955                              * ending just before jcol.
2956                              */
2957                             jcol = [self.changes scanForUnchangedInRow:irow
2958                                         col0:(isrch + 1) col1:self.cols];
2959                             if (jcol < self.cols) {
2960                                 stage = 0;
2961                                 isrch = jcol;
2962                                 continue;
2963                             }
2964                             break;
2965                         }
2966                     }
2967                     ++isrch;
2968                     if (isrch >= irng0) {
2969                         break;
2970                     }
2971                 }
2972             }
2973
2974             /*
2975              * Check to see if there's characters before the changed region
2976              * that would have to be redrawn because it's influenced by the
2977              * changed region.  Do not have to check for merging with a prior
2978              * region because of the screening already done.
2979              */
2980             if (self.nColPre > 0 &&
2981                 ([self.contents getCellAtColumn:icol row:irow]->form &
2982                  (TERM_CELL_CHAR | TERM_CELL_CHAR_PADDING)) != 0) {
2983                 int irng = icol - self.nColPre;
2984
2985                 if (irng < 0) {
2986                     irng = 0;
2987                 }
2988                 while (icol > irng &&
2989                        ([self.contents getCellAtColumn:(icol - 1)
2990                              row:irow]->form &
2991                         (TERM_CELL_CHAR | TERM_CELL_CHAR_PADDING)) != 0) {
2992                     --icol;
2993                 }
2994             }
2995
2996             NSRect r = [self viewRectForCellBlockAtX:icol y:irow
2997                              width:(jcol - icol) height:1];
2998             [self setNeedsDisplayInRect:r];
2999
3000             icol = [self.changes scanForChangedInRow:irow col0:jcol
3001                         col1:self.cols];
3002         }
3003     }
3004 }
3005
3006
3007 #pragma mark View/Window Passthrough
3008
3009 /*
3010  * This is a qsort-compatible compare function for NSRect, to get them in
3011  * ascending order by y origin.
3012  */
3013 static int compare_nsrect_yorigin_greater(const void *ap, const void *bp)
3014 {
3015     const NSRect *arp = ap;
3016     const NSRect *brp = bp;
3017     return (arp->origin.y > brp->origin.y) - (arp->origin.y < brp->origin.y);
3018 }
3019
3020 /**
3021  * This is a helper function for drawRect.
3022  */
3023 - (void)renderTileRunInRow:(int)irow col0:(int)icol0 col1:(int)icol1
3024                      nsctx:(NSGraphicsContext*)nsctx ctx:(CGContextRef)ctx
3025                  grafWidth:(int)graf_width grafHeight:(int)graf_height
3026                overdrawRow:(int)overdraw_row overdrawMax:(int)overdraw_max
3027 {
3028     /* Save the compositing mode since it is modified below. */
3029     NSCompositingOperation op = nsctx.compositingOperation;
3030
3031     while (icol0 < icol1) {
3032         const struct TerminalCell *pcell =
3033             [self.contents getCellAtColumn:icol0 row:irow];
3034         NSRect destinationRect =
3035             [self viewRectForCellBlockAtX:icol0 y:irow
3036                   width:pcell->hscl height:pcell->vscl];
3037         NSRect fgdRect = NSMakeRect(
3038             graf_width * (pcell->v.ti.fgdCol +
3039                           pcell->hoff_n / (1.0 * pcell->hoff_d)),
3040             graf_height * (pcell->v.ti.fgdRow +
3041                            pcell->voff_n / (1.0 * pcell->voff_d)),
3042             graf_width * pcell->hscl / (1.0 * pcell->hoff_d),
3043             graf_height * pcell->vscl / (1.0 * pcell->voff_d));
3044         NSRect bckRect = NSMakeRect(
3045             graf_width * (pcell->v.ti.bckCol +
3046                           pcell->hoff_n / (1.0 * pcell->hoff_d)),
3047             graf_height * (pcell->v.ti.bckRow +
3048                            pcell->voff_n / (1.0 * pcell->voff_d)),
3049             graf_width * pcell->hscl / (1.0 * pcell->hoff_d),
3050             graf_height * pcell->vscl / (1.0 * pcell->voff_d));
3051         int dbl_height_bck = overdraw_row && (irow > 2) &&
3052             (pcell->v.ti.bckRow >= overdraw_row &&
3053              pcell->v.ti.bckRow <= overdraw_max);
3054         int dbl_height_fgd = overdraw_row && (irow > 2) &&
3055             (pcell->v.ti.fgdRow >= overdraw_row) &&
3056             (pcell->v.ti.fgdRow <= overdraw_max);
3057         int aligned_row = 0, aligned_col = 0;
3058         int is_first_piece = 0, simple_upper = 0;
3059
3060         /* Initialize stuff for handling a double-height tile. */
3061         if (dbl_height_bck || dbl_height_fgd) {
3062             if (self->terminal == angband_term[0]) {
3063                 aligned_col = ((icol0 - COL_MAP) / pcell->hoff_d) *
3064                     pcell->hoff_d + COL_MAP;
3065             } else {
3066                 aligned_col = (icol0 / pcell->hoff_d) * pcell->hoff_d;
3067             }
3068             aligned_row = ((irow - ROW_MAP) / pcell->voff_d) *
3069                 pcell->voff_d + ROW_MAP;
3070
3071             /*
3072              * If the lower half has been broken into multiple pieces, only
3073              * do the work of rendering whatever is necessary for the upper
3074              * half when drawing the first piece (the one closest to the
3075              * upper left corner).
3076              */
3077             struct TerminalCellLocation curs = { 0, 0 };
3078
3079             [self.contents scanForTypeMaskInBlockAtColumn:aligned_col
3080                  row:aligned_row width:pcell->hoff_d height:pcell->voff_d
3081                  mask:TERM_CELL_TILE cursor:&curs];
3082             if (curs.col + aligned_col == icol0 &&
3083                 curs.row + aligned_row == irow) {
3084                 is_first_piece = 1;
3085
3086                 /*
3087                  * Hack:  lookup the previous row to determine how much of the
3088                  * tile there is shown to apply it the upper half of the
3089                  * double-height tile.  That will do the right thing if there
3090                  * is a menu displayed in that row but isn't right if there's
3091                  * an object/creature/feature there that doesn't have a
3092                  * mapping to the tile set and is rendered with a character.
3093                  */
3094                 curs.col = 0;
3095                 curs.row = 0;
3096                 [self.contents scanForTypeMaskInBlockAtColumn:aligned_col
3097                      row:(aligned_row - pcell->voff_d) width:pcell->hoff_d
3098                      height:pcell->voff_d mask:TERM_CELL_TILE cursor:&curs];
3099                 if (curs.col == 0 && curs.row == 0) {
3100                     const struct TerminalCell *pcell2 =
3101                         [self.contents
3102                              getCellAtColumn:(aligned_col + curs.col)
3103                              row:(aligned_row + curs.row - pcell->voff_d)];
3104
3105                     if (pcell2->hscl == pcell2->hoff_d &&
3106                         pcell2->vscl == pcell2->voff_d) {
3107                         /*
3108                          * The tile in the previous row hasn't been clipped
3109                          * or partially overwritten.  Use a streamlined
3110                          * rendering procedure.
3111                          */
3112                         simple_upper = 1;
3113                     }
3114                 }
3115             }
3116         }
3117
3118         /*
3119          * Draw the background.  For a double-height tile, this is only the
3120          * the lower half.
3121          */
3122         draw_image_tile(
3123             nsctx, ctx, pict_image, bckRect, destinationRect, NSCompositeCopy);
3124         if (dbl_height_bck && is_first_piece) {
3125             /* Combine upper half with previously drawn row. */
3126             if (simple_upper) {
3127                 const struct TerminalCell *pcell2 =
3128                     [self.contents getCellAtColumn:aligned_col
3129                          row:(aligned_row - pcell->voff_d)];
3130                 NSRect drect2 =
3131                     [self viewRectForCellBlockAtX:aligned_col
3132                           y:(aligned_row - pcell->voff_d)
3133                           width:pcell2->hscl height:pcell2->vscl];
3134                 NSRect brect2 = NSMakeRect(
3135                     graf_width * pcell->v.ti.bckCol,
3136                     graf_height * (pcell->v.ti.bckRow - 1),
3137                     graf_width, graf_height);
3138
3139                 draw_image_tile(nsctx, ctx, pict_image, brect2, drect2,
3140                                 NSCompositeSourceOver);
3141             } else {
3142                 struct TerminalCellLocation curs = { 0, 0 };
3143
3144                 [self.contents scanForTypeMaskInBlockAtColumn:aligned_col
3145                      row:(aligned_row - pcell->voff_d) width:pcell->hoff_d
3146                      height:pcell->voff_d mask:TERM_CELL_TILE
3147                      cursor:&curs];
3148                 while (curs.col < pcell->hoff_d &&
3149                        curs.row < pcell->voff_d) {
3150                     const struct TerminalCell *pcell2 =
3151                         [self.contents getCellAtColumn:(aligned_col + curs.col)
3152                              row:(aligned_row + curs.row - pcell->voff_d)];
3153                     NSRect drect2 =
3154                         [self viewRectForCellBlockAtX:(aligned_col + curs.col)
3155                               y:(aligned_row + curs.row - pcell->voff_d)
3156                               width:pcell2->hscl height:pcell2->vscl];
3157                     /*
3158                      * Column and row in the tile set are from the
3159                      * double-height tile at *pcell, but the offsets within
3160                      * that and size are from what's visible for *pcell2.
3161                      */
3162                     NSRect brect2 = NSMakeRect(
3163                         graf_width * (pcell->v.ti.bckCol +
3164                                       pcell2->hoff_n / (1.0 * pcell2->hoff_d)),
3165                         graf_height * (pcell->v.ti.bckRow - 1 +
3166                                        pcell2->voff_n /
3167                                        (1.0 * pcell2->voff_d)),
3168                         graf_width * pcell2->hscl / (1.0 * pcell2->hoff_d),
3169                         graf_height * pcell2->vscl / (1.0 * pcell2->voff_d));
3170
3171                     draw_image_tile(nsctx, ctx, pict_image, brect2, drect2,
3172                                     NSCompositeSourceOver);
3173                     curs.col += pcell2->hscl;
3174                     [self.contents
3175                          scanForTypeMaskInBlockAtColumn:aligned_col
3176                          row:(aligned_row - pcell->voff_d)
3177                          width:pcell->hoff_d height:pcell->voff_d
3178                          mask:TERM_CELL_TILE cursor:&curs];
3179                 }
3180             }
3181         }
3182
3183         /* Skip drawing the foreground if it is the same as the background. */
3184         if (fgdRect.origin.x != bckRect.origin.x ||
3185             fgdRect.origin.y != bckRect.origin.y) {
3186             if (is_first_piece && dbl_height_fgd) {
3187                 if (simple_upper) {
3188                     if (pcell->hoff_n == 0 && pcell->voff_n == 0 &&
3189                         pcell->hscl == pcell->hoff_d) {
3190                         /*
3191                          * Render upper and lower parts as one since they
3192                          * are contiguous.
3193                          */
3194                         fgdRect.origin.y -= graf_height;
3195                         fgdRect.size.height += graf_height;
3196                         destinationRect.origin.y -=
3197                             destinationRect.size.height;
3198                         destinationRect.size.height +=
3199                             destinationRect.size.height;
3200                     } else {
3201                         /* Not contiguous.  Render the upper half. */
3202                         NSRect drect2 =
3203                             [self viewRectForCellBlockAtX:aligned_col
3204                                   y:(aligned_row - pcell->voff_d)
3205                                   width:pcell->hoff_d height:pcell->voff_d];
3206                         NSRect frect2 = NSMakeRect(
3207                             graf_width * pcell->v.ti.fgdCol,
3208                             graf_height * (pcell->v.ti.fgdRow - 1),
3209                             graf_width, graf_height);
3210
3211                         draw_image_tile(
3212                             nsctx, ctx, pict_image, frect2, drect2,
3213                             NSCompositeSourceOver);
3214                     }
3215                 } else {
3216                     /* Render the upper half pieces. */
3217                     struct TerminalCellLocation curs = { 0, 0 };
3218
3219                     while (1) {
3220                         [self.contents
3221                              scanForTypeMaskInBlockAtColumn:aligned_col
3222                              row:(aligned_row - pcell->voff_d)
3223                              width:pcell->hoff_d height:pcell->voff_d
3224                              mask:TERM_CELL_TILE cursor:&curs];
3225
3226                         if (curs.col >= pcell->hoff_d ||
3227                             curs.row >= pcell->voff_d) {
3228                             break;
3229                         }
3230
3231                         const struct TerminalCell *pcell2 =
3232                             [self.contents
3233                                  getCellAtColumn:(aligned_col + curs.col)
3234                                  row:(aligned_row + curs.row - pcell->voff_d)];
3235                         NSRect drect2 =
3236                             [self viewRectForCellBlockAtX:(aligned_col + curs.col)
3237                                   y:(aligned_row + curs.row - pcell->voff_d)
3238                                   width:pcell2->hscl height:pcell2->vscl];
3239                         NSRect frect2 = NSMakeRect(
3240                             graf_width * (pcell->v.ti.fgdCol +
3241                                           pcell2->hoff_n /
3242                                           (1.0 * pcell2->hoff_d)),
3243                             graf_height * (pcell->v.ti.fgdRow - 1 +
3244                                            pcell2->voff_n /
3245                                            (1.0 * pcell2->voff_d)),
3246                             graf_width * pcell2->hscl / (1.0 * pcell2->hoff_d),
3247                             graf_height * pcell2->vscl /
3248                                 (1.0 * pcell2->voff_d));
3249
3250                         draw_image_tile(nsctx, ctx, pict_image, frect2, drect2,
3251                                         NSCompositeSourceOver);
3252                         curs.col += pcell2->hscl;
3253                     }
3254                 }
3255             }
3256             /*
3257              * Render the foreground (if a double height tile and the bottom
3258              * part is contiguous with the upper part this also render the
3259              * upper part.
3260              */
3261             draw_image_tile(
3262                 nsctx, ctx, pict_image, fgdRect, destinationRect,
3263                 NSCompositeSourceOver);
3264         }
3265         icol0 = [self.contents scanForTypeMaskInRow:irow mask:TERM_CELL_TILE
3266                      col0:(icol0+pcell->hscl) col1:icol1];
3267     }
3268
3269     /* Restore the compositing mode. */
3270     nsctx.compositingOperation = op;
3271 }
3272
3273 /**
3274  * This is what our views call to get us to draw to the window
3275  */
3276 - (void)drawRect:(NSRect)rect inView:(NSView *)view
3277 {
3278     /* Take this opportunity to throttle so we don't flush faster than desired.
3279          */
3280     [self throttle];
3281
3282     CGFloat bottomY =
3283         self.borderSize.height + self.tileSize.height * self.rows;
3284     CGFloat rightX =
3285         self.borderSize.width + self.tileSize.width * self.cols;
3286
3287     const NSRect *invalidRects;
3288     NSInteger invalidCount;
3289     [view getRectsBeingDrawn:&invalidRects count:&invalidCount];
3290
3291     /*
3292      * If the non-border areas need rendering, set some things up so they can
3293      * be reused for each invalid rectangle.
3294      */
3295     NSGraphicsContext *nsctx = nil;
3296     CGContextRef ctx = 0;
3297     NSFont* screenFont = nil;
3298     term *old = 0;
3299     int graf_width = 0, graf_height = 0;
3300     int overdraw_row = 0, overdraw_max = 0;
3301     wchar_t blank = 0;
3302     if (rect.origin.x < rightX &&
3303         rect.origin.x + rect.size.width > self.borderSize.width &&
3304         rect.origin.y < bottomY &&
3305         rect.origin.y + rect.size.height > self.borderSize.height) {
3306         old = Term;
3307         Term_activate(self->terminal);
3308         nsctx = [NSGraphicsContext currentContext];
3309         ctx = [nsctx graphicsPort];
3310         screenFont = [self.angbandViewFont screenFont];
3311         [screenFont set];
3312         blank = [TerminalContents getBlankChar];
3313         if (use_graphics) {
3314             graf_width = current_graphics_mode->cell_width;
3315             graf_height = current_graphics_mode->cell_height;
3316             overdraw_row = current_graphics_mode->overdrawRow;
3317             overdraw_max = current_graphics_mode->overdrawMax;
3318         }
3319     }
3320
3321     /*
3322      * With double height tiles, need to have rendered prior rows (i.e.
3323      * smaller y) before the current one.  Since the invalid rectanges are
3324      * processed in order, ensure that by sorting the invalid rectangles in
3325      * increasing order of y origin (AppKit guarantees the invalid rectanges
3326      * are non-overlapping).
3327      */
3328     NSRect* sortedRects = 0;
3329     const NSRect* workingRects;
3330     if (overdraw_row && invalidCount > 1) {
3331         sortedRects = malloc(invalidCount * sizeof(NSRect));
3332         if (sortedRects == 0) {
3333             if (old != 0) {
3334                 Term_activate(old);
3335             }
3336             NSException *exc = [NSException exceptionWithName:@"OutOfMemory"
3337                                             reason:@"sorted rects in drawRect"
3338                                             userInfo:nil];
3339             @throw exc;
3340         }
3341         (void) memcpy(
3342             sortedRects, invalidRects, invalidCount * sizeof(NSRect));
3343         qsort(sortedRects, invalidCount, sizeof(NSRect),
3344               compare_nsrect_yorigin_greater);
3345         workingRects = sortedRects;
3346     } else {
3347         workingRects = invalidRects;
3348     }
3349
3350     /*
3351      * Use -2 for unknown.  Use -1 for Cocoa's blackColor.  All others are the
3352      * Angband color index.
3353      */
3354     int alast = -2;
3355     int redrawCursor = 0;
3356
3357     for (NSInteger irect = 0; irect < invalidCount; ++irect) {
3358         NSRect modRect, clearRect;
3359         CGFloat edge;
3360         int iRowFirst, iRowLast;
3361         int iColFirst, iColLast;
3362
3363         /* Handle the top border. */
3364         if (workingRects[irect].origin.y < self.borderSize.height) {
3365             edge =
3366                 workingRects[irect].origin.y + workingRects[irect].size.height;
3367             if (edge <= self.borderSize.height) {
3368                 if (alast != -1) {
3369                     [[NSColor blackColor] set];
3370                     alast = -1;
3371                 }
3372                 NSRectFill(workingRects[irect]);
3373                 continue;
3374             }
3375             clearRect = workingRects[irect];
3376             clearRect.size.height =
3377                 self.borderSize.height - workingRects[irect].origin.y;
3378             if (alast != -1) {
3379                 [[NSColor blackColor] set];
3380                 alast = -1;
3381             }
3382             NSRectFill(clearRect);
3383             modRect.origin.x = workingRects[irect].origin.x;
3384             modRect.origin.y = self.borderSize.height;
3385             modRect.size.width = workingRects[irect].size.width;
3386             modRect.size.height = edge - self.borderSize.height;
3387         } else {
3388             modRect = workingRects[irect];
3389         }
3390
3391         /* Handle the left border. */
3392         if (modRect.origin.x < self.borderSize.width) {
3393             edge = modRect.origin.x + modRect.size.width;
3394             if (edge <= self.borderSize.width) {
3395                 if (alast != -1) {
3396                     alast = -1;
3397                     [[NSColor blackColor] set];
3398                 }
3399                 NSRectFill(modRect);
3400                 continue;
3401             }
3402             clearRect = modRect;
3403             clearRect.size.width = self.borderSize.width - clearRect.origin.x;
3404             if (alast != -1) {
3405                 alast = -1;
3406                 [[NSColor blackColor] set];
3407             }
3408             NSRectFill(clearRect);
3409             modRect.origin.x = self.borderSize.width;
3410             modRect.size.width = edge - self.borderSize.width;
3411         }
3412
3413         iRowFirst = floor((modRect.origin.y - self.borderSize.height) /
3414                           self.tileSize.height);
3415         iColFirst = floor((modRect.origin.x - self.borderSize.width) /
3416                           self.tileSize.width);
3417         edge = modRect.origin.y + modRect.size.height;
3418         if (edge <= bottomY) {
3419             iRowLast =
3420                 ceil((edge - self.borderSize.height) / self.tileSize.height);
3421         } else {
3422             iRowLast = self.rows;
3423         }
3424         edge = modRect.origin.x + modRect.size.width;
3425         if (edge <= rightX) {
3426             iColLast =
3427                 ceil((edge - self.borderSize.width) / self.tileSize.width);
3428         } else {
3429             iColLast = self.cols;
3430         }
3431
3432         if (self.contents.cursorColumn != -1 &&
3433             self.contents.cursorRow != -1 &&
3434             self.contents.cursorColumn + self.contents.cursorWidth - 1 >=
3435             iColFirst &&
3436             self.contents.cursorColumn < iColLast &&
3437             self.contents.cursorRow + self.contents.cursorHeight - 1 >=
3438             iRowFirst &&
3439             self.contents.cursorRow < iRowLast) {
3440             redrawCursor = 1;
3441         }
3442
3443         for (int irow = iRowFirst; irow < iRowLast; ++irow) {
3444             int icol =
3445                 [self.contents scanForTypeMaskInRow:irow
3446                      mask:(TERM_CELL_CHAR | TERM_CELL_TILE)
3447                      col0:iColFirst col1:iColLast];
3448
3449             while (1) {
3450                 if (icol >= iColLast) {
3451                     break;
3452                 }
3453
3454                 if ([self.contents getCellAtColumn:icol row:irow]->form ==
3455                     TERM_CELL_TILE) {
3456                     /*
3457                      * It is a tile.  Identify how far the run of tiles goes.
3458                      */
3459                     int jcol = [self.contents scanForPredicateInRow:irow
3460                                     predicate:isTileTop desired:1
3461                                     col0:(icol + 1) col1:iColLast];
3462
3463                     [self renderTileRunInRow:irow col0:icol col1:jcol
3464                           nsctx:nsctx ctx:ctx
3465                           grafWidth:graf_width grafHeight:graf_height
3466                           overdrawRow:overdraw_row overdrawMax:overdraw_max];
3467                     icol = jcol;
3468                 } else {
3469                     /*
3470                      * It is a character.  Identify how far the run of
3471                      * characters goes.
3472                      */
3473                     int jcol = [self.contents scanForPredicateInRow:irow
3474                                     predicate:isCharNoPartial desired:1
3475                                     col0:(icol + 1) col1:iColLast];
3476                     int jcol2;
3477
3478                     if (jcol < iColLast &&
3479                         isPartiallyOverwrittenBigChar(
3480                             [self.contents getCellAtColumn:jcol row:irow])) {
3481                         jcol2 = [self.contents scanForTypeMaskInRow:irow
3482                                      mask:~TERM_CELL_CHAR_PADDING
3483                                      col0:(jcol + 1) col1:iColLast];
3484                     } else {
3485                         jcol2 = jcol;
3486                     }
3487
3488                     /*
3489                      * Set up clipping rectangle for text.  Save the
3490                      * graphics context so the clipping rectangle can be
3491                      * forgotten.  Use CGContextBeginPath to clear the current
3492                      * path so it does not affect clipping.  Do not call
3493                      * CGContextSetTextDrawingMode() to include clipping since
3494                      * that does not appear to necessary on 10.14 and is
3495                      * actually detrimental:  when displaying more than one
3496                      * character, only the first is visible.
3497                      */
3498                     CGContextSaveGState(ctx);
3499                     CGContextBeginPath(ctx);
3500                     NSRect r = [self viewRectForCellBlockAtX:icol y:irow
3501                                      width:(jcol2 - icol) height:1];
3502                     CGContextClipToRect(ctx, r);
3503
3504                     /*
3505                      * See if the region to be rendered needs to be expanded:
3506                      * adjacent text that could influence what's in the clipped
3507                      * region.
3508                      */
3509                     int isrch = icol;
3510                     int irng = icol - self.nColPost;
3511                     if (irng < 1) {
3512                         irng = 1;
3513                     }
3514
3515                     while (1) {
3516                         if (isrch <= irng) {
3517                             break;
3518                         }
3519
3520                         const struct TerminalCell *pcell2 =
3521                             [self.contents getCellAtColumn:(isrch - 1)
3522                                  row:irow];
3523                         if (pcell2->form == TERM_CELL_CHAR) {
3524                             --isrch;
3525                             if (pcell2->v.ch.glyph != blank) {
3526                                 icol = isrch;
3527                             }
3528                         } else if (pcell2->form == TERM_CELL_CHAR_PADDING) {
3529                             /*
3530                              * Only extend the rendering if this is padding
3531                              * for a character that hasn't been partially
3532                              * overwritten.
3533                              */
3534                             if (! isPartiallyOverwrittenBigChar(pcell2)) {
3535                                 if (isrch - pcell2->v.pd.hoff >= 0) {
3536                                     const struct TerminalCell* pcell3 =
3537                                         [self.contents
3538                                              getCellAtColumn:(isrch - pcell2->v.pd.hoff)
3539                                              row:irow];
3540
3541                                     if (pcell3->v.ch.glyph != blank) {
3542                                         icol = isrch - pcell2->v.pd.hoff;
3543                                         isrch = icol - 1;
3544                                     } else {
3545                                         isrch = isrch - pcell2->v.pd.hoff - 1;
3546                                     }
3547                                 } else {
3548                                     /* Should not happen, corrupt offset. */
3549                                     --isrch;
3550                                 }
3551                             } else {
3552                                 break;
3553                             }
3554                         } else {
3555                             /*
3556                              * Tiles or tile padding block anything before
3557                              * them from rendering after them.
3558                              */
3559                             break;
3560                         }
3561                     }
3562
3563                     isrch = jcol2;
3564                     irng = jcol2 + self.nColPre;
3565                     if (irng > self.cols) {
3566                         irng = self.cols;
3567                     }
3568                     while (1) {
3569                         if (isrch >= irng) {
3570                             break;
3571                         }
3572
3573                         const struct TerminalCell *pcell2 =
3574                             [self.contents getCellAtColumn:isrch row:irow];
3575                         if (pcell2->form == TERM_CELL_CHAR) {
3576                             if (pcell2->v.ch.glyph != blank) {
3577                                 jcol2 = isrch;
3578                             }
3579                             ++isrch;
3580                         } else if (pcell2->form == TERM_CELL_CHAR_PADDING) {
3581                             ++isrch;
3582                         } else {
3583                             break;
3584                         }
3585                     }
3586
3587                     /* Render text. */
3588                     /* Clear where rendering will be done. */
3589                     if (alast != -1) {
3590                         [[NSColor blackColor] set];
3591                         alast = -1;
3592                     }
3593                     r = [self viewRectForCellBlockAtX:icol y:irow
3594                               width:(jcol - icol) height:1];
3595                     NSRectFill(r);
3596
3597                     while (icol < jcol) {
3598                         const struct TerminalCell *pcell =
3599                             [self.contents getCellAtColumn:icol row:irow];
3600
3601                         /*
3602                          * For blanks, clearing was all that was necessary.
3603                          * Don't redraw them.
3604                          */
3605                         if (pcell->v.ch.glyph != blank) {
3606                             int a = pcell->v.ch.attr % MAX_COLORS;
3607
3608                             if (alast != a) {
3609                                 alast = a;
3610                                 set_color_for_index(a);
3611                             }
3612                             r = [self viewRectForCellBlockAtX:icol
3613                                       y:irow width:pcell->hscl
3614                                       height:1];
3615                             [self drawWChar:pcell->v.ch.glyph inRect:r
3616                                   screenFont:screenFont context:ctx];
3617                         }
3618                         icol += pcell->hscl;
3619                     }
3620
3621                     /*
3622                      * Forget the clipping rectangle.  As a side effect, lose
3623                      * the color.
3624                      */
3625                     CGContextRestoreGState(ctx);
3626                     alast = -2;
3627                 }
3628                 icol =
3629                     [self.contents scanForTypeMaskInRow:irow
3630                          mask:(TERM_CELL_CHAR | TERM_CELL_TILE)
3631                          col0:icol col1:iColLast];
3632             }
3633         }
3634
3635         /* Handle the right border. */
3636         edge = modRect.origin.x + modRect.size.width;
3637         if (edge > rightX) {
3638             if (modRect.origin.x >= rightX) {
3639                 if (alast != -1) {
3640                     alast = -1;
3641                     [[NSColor blackColor] set];
3642                 }
3643                 NSRectFill(modRect);
3644                 continue;
3645             }
3646             clearRect = modRect;
3647             clearRect.origin.x = rightX;
3648             clearRect.size.width = edge - rightX;
3649             if (alast != -1) {
3650                 alast = -1;
3651                 [[NSColor blackColor] set];
3652             }
3653             NSRectFill(clearRect);
3654             modRect.size.width = edge - modRect.origin.x;
3655         }
3656
3657         /* Handle the bottom border. */
3658         edge = modRect.origin.y + modRect.size.height;
3659         if (edge > bottomY) {
3660             if (modRect.origin.y < bottomY) {
3661                 modRect.origin.y = bottomY;
3662                 modRect.size.height = edge - bottomY;
3663             }
3664             if (alast != -1) {
3665                 alast = -1;
3666                 [[NSColor blackColor] set];
3667             }
3668             NSRectFill(modRect);
3669         }
3670     }
3671
3672     if (redrawCursor) {
3673         NSRect r = [self viewRectForCellBlockAtX:self.contents.cursorColumn
3674                          y:self.contents.cursorRow
3675                          width:self.contents.cursorWidth
3676                          height:self.contents.cursorHeight];
3677         [[NSColor yellowColor] set];
3678         NSFrameRectWithWidth(r, 1);
3679     }
3680
3681     free(sortedRects);
3682     if (old != 0) {
3683         Term_activate(old);
3684     }
3685 }
3686
3687 - (BOOL)isOrderedIn
3688 {
3689     return [[self->angbandView window] isVisible];
3690 }
3691
3692 - (BOOL)isMainWindow
3693 {
3694     return [[self->angbandView window] isMainWindow];
3695 }
3696
3697 - (BOOL)isKeyWindow
3698 {
3699     return [[self->angbandView window] isKeyWindow];
3700 }
3701
3702 - (void)setNeedsDisplay:(BOOL)val
3703 {
3704     [self->angbandView setNeedsDisplay:val];
3705 }
3706
3707 - (void)setNeedsDisplayInRect:(NSRect)rect
3708 {
3709     [self->angbandView setNeedsDisplayInRect:rect];
3710 }
3711
3712 - (void)displayIfNeeded
3713 {
3714     [self->angbandView displayIfNeeded];
3715 }
3716
3717 - (int)terminalIndex
3718 {
3719         int termIndex = 0;
3720
3721         for( termIndex = 0; termIndex < ANGBAND_TERM_MAX; termIndex++ )
3722         {
3723                 if( angband_term[termIndex] == self->terminal )
3724                 {
3725                         break;
3726                 }
3727         }
3728
3729         return termIndex;
3730 }
3731
3732 - (void)resizeTerminalWithContentRect: (NSRect)contentRect saveToDefaults: (BOOL)saveToDefaults
3733 {
3734     CGFloat newRows = floor(
3735         (contentRect.size.height - (self.borderSize.height * 2.0)) /
3736         self.tileSize.height);
3737     CGFloat newColumns = floor(
3738         (contentRect.size.width - (self.borderSize.width * 2.0)) /
3739         self.tileSize.width);
3740
3741     if (newRows < 1 || newColumns < 1) return;
3742     [self resizeWithColumns:newColumns rows:newRows];
3743
3744     int termIndex = [self terminalIndex];
3745     [self setDefaultTitle:termIndex];
3746
3747     if( saveToDefaults )
3748     {
3749         NSArray *terminals = [[NSUserDefaults standardUserDefaults] valueForKey: AngbandTerminalsDefaultsKey];
3750
3751         if( termIndex < (int)[terminals count] )
3752         {
3753             NSMutableDictionary *mutableTerm = [[NSMutableDictionary alloc] initWithDictionary: [terminals objectAtIndex: termIndex]];
3754             [mutableTerm setValue: [NSNumber numberWithInteger: self.cols]
3755                          forKey: AngbandTerminalColumnsDefaultsKey];
3756             [mutableTerm setValue: [NSNumber numberWithInteger: self.rows]
3757                          forKey: AngbandTerminalRowsDefaultsKey];
3758
3759             NSMutableArray *mutableTerminals = [[NSMutableArray alloc] initWithArray: terminals];
3760             [mutableTerminals replaceObjectAtIndex: termIndex withObject: mutableTerm];
3761
3762             [[NSUserDefaults standardUserDefaults] setValue: mutableTerminals forKey: AngbandTerminalsDefaultsKey];
3763         }
3764     }
3765
3766     term *old = Term;
3767     Term_activate( self->terminal );
3768     Term_resize( self.cols, self.rows );
3769     Term_redraw();
3770     Term_activate( old );
3771 }
3772
3773 - (void)constrainWindowSize:(int)termIdx
3774 {
3775     NSSize minsize;
3776
3777     if (termIdx == 0) {
3778         minsize.width = 80;
3779         minsize.height = 24;
3780     } else {
3781         minsize.width = 1;
3782         minsize.height = 1;
3783     }
3784     minsize.width =
3785         minsize.width * self.tileSize.width + self.borderSize.width * 2.0;
3786     minsize.height =
3787         minsize.height * self.tileSize.height + self.borderSize.height * 2.0;
3788     [[self makePrimaryWindow] setContentMinSize:minsize];
3789     self.primaryWindow.contentResizeIncrements = self.tileSize;
3790 }
3791
3792 - (void)saveWindowVisibleToDefaults: (BOOL)windowVisible
3793 {
3794         int termIndex = [self terminalIndex];
3795         BOOL safeVisibility = (termIndex == 0) ? YES : windowVisible; /* Ensure main term doesn't go away because of these defaults */
3796         NSArray *terminals = [[NSUserDefaults standardUserDefaults] valueForKey: AngbandTerminalsDefaultsKey];
3797
3798         if( termIndex < (int)[terminals count] )
3799         {
3800                 NSMutableDictionary *mutableTerm = [[NSMutableDictionary alloc] initWithDictionary: [terminals objectAtIndex: termIndex]];
3801                 [mutableTerm setValue: [NSNumber numberWithBool: safeVisibility] forKey: AngbandTerminalVisibleDefaultsKey];
3802
3803                 NSMutableArray *mutableTerminals = [[NSMutableArray alloc] initWithArray: terminals];
3804                 [mutableTerminals replaceObjectAtIndex: termIndex withObject: mutableTerm];
3805
3806                 [[NSUserDefaults standardUserDefaults] setValue: mutableTerminals forKey: AngbandTerminalsDefaultsKey];
3807         }
3808 }
3809
3810 - (BOOL)windowVisibleUsingDefaults
3811 {
3812         int termIndex = [self terminalIndex];
3813
3814         if( termIndex == 0 )
3815         {
3816                 return YES;
3817         }
3818
3819         NSArray *terminals = [[NSUserDefaults standardUserDefaults] valueForKey: AngbandTerminalsDefaultsKey];
3820         BOOL visible = NO;
3821
3822         if( termIndex < (int)[terminals count] )
3823         {
3824                 NSDictionary *term = [terminals objectAtIndex: termIndex];
3825                 NSNumber *visibleValue = [term valueForKey: AngbandTerminalVisibleDefaultsKey];
3826
3827                 if( visibleValue != nil )
3828                 {
3829                         visible = [visibleValue boolValue];
3830                 }
3831         }
3832
3833         return visible;
3834 }
3835
3836 #pragma mark -
3837 #pragma mark NSWindowDelegate Methods
3838
3839 /*- (void)windowWillStartLiveResize: (NSNotification *)notification
3840
3841 }*/ 
3842
3843 - (void)windowDidEndLiveResize: (NSNotification *)notification
3844 {
3845     NSWindow *window = [notification object];
3846     NSRect contentRect = [window contentRectForFrameRect: [window frame]];
3847     [self resizeTerminalWithContentRect: contentRect saveToDefaults: !(self->inFullscreenTransition)];
3848 }
3849
3850 /*- (NSSize)windowWillResize: (NSWindow *)sender toSize: (NSSize)frameSize
3851 {
3852 } */
3853
3854 - (void)windowWillEnterFullScreen: (NSNotification *)notification
3855 {
3856     self->inFullscreenTransition = YES;
3857 }
3858
3859 - (void)windowDidEnterFullScreen: (NSNotification *)notification
3860 {
3861     NSWindow *window = [notification object];
3862     NSRect contentRect = [window contentRectForFrameRect: [window frame]];
3863     self->inFullscreenTransition = NO;
3864     [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
3865 }
3866
3867 - (void)windowWillExitFullScreen: (NSNotification *)notification
3868 {
3869     self->inFullscreenTransition = YES;
3870 }
3871
3872 - (void)windowDidExitFullScreen: (NSNotification *)notification
3873 {
3874     NSWindow *window = [notification object];
3875     NSRect contentRect = [window contentRectForFrameRect: [window frame]];
3876     self->inFullscreenTransition = NO;
3877     [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
3878 }
3879
3880 - (void)windowDidBecomeMain:(NSNotification *)notification
3881 {
3882     NSWindow *window = [notification object];
3883
3884     if( window != self.primaryWindow )
3885     {
3886         return;
3887     }
3888
3889     int termIndex = [self terminalIndex];
3890     NSMenuItem *item = [[[NSApplication sharedApplication] windowsMenu] itemWithTag: AngbandWindowMenuItemTagBase + termIndex];
3891     [item setState: NSOnState];
3892
3893     if( [[NSFontPanel sharedFontPanel] isVisible] )
3894     {
3895         [[NSFontPanel sharedFontPanel] setPanelFont:self.angbandViewFont
3896                                        isMultiple: NO];
3897     }
3898 }
3899
3900 - (void)windowDidResignMain: (NSNotification *)notification
3901 {
3902     NSWindow *window = [notification object];
3903
3904     if( window != self.primaryWindow )
3905     {
3906         return;
3907     }
3908
3909     int termIndex = [self terminalIndex];
3910     NSMenuItem *item = [[[NSApplication sharedApplication] windowsMenu] itemWithTag: AngbandWindowMenuItemTagBase + termIndex];
3911     [item setState: NSOffState];
3912 }
3913
3914 - (void)windowWillClose: (NSNotification *)notification
3915 {
3916     /*
3917      * If closing only because the application is terminating, don't update
3918      * the visible state for when the application is relaunched.
3919      */
3920     if (! quit_when_ready) {
3921         [self saveWindowVisibleToDefaults: NO];
3922     }
3923 }
3924
3925 @end
3926
3927
3928 @implementation AngbandView
3929
3930 - (BOOL)isOpaque
3931 {
3932     return YES;
3933 }
3934
3935 - (BOOL)isFlipped
3936 {
3937     return YES;
3938 }
3939
3940 - (void)drawRect:(NSRect)rect
3941 {
3942     if ([self inLiveResize]) {
3943         /*
3944          * Always anchor the cached area to the upper left corner of the view.
3945          * Any parts on the right or bottom that can't be drawn from the cached
3946          * area are simply cleared.  Will fill them with appropriate content
3947          * when resizing is done.
3948          */
3949         const NSRect *rects;
3950         NSInteger count;
3951
3952         [self getRectsBeingDrawn:&rects count:&count];
3953         if (count > 0) {
3954             NSRect viewRect = [self visibleRect];
3955
3956             [[NSColor blackColor] set];
3957             while (count-- > 0) {
3958                 CGFloat drawTop = rects[count].origin.y - viewRect.origin.y;
3959                 CGFloat drawBottom = drawTop + rects[count].size.height;
3960                 CGFloat drawLeft = rects[count].origin.x - viewRect.origin.x;
3961                 CGFloat drawRight = drawLeft + rects[count].size.width;
3962                 /*
3963                  * modRect and clrRect, like rects[count], are in the view
3964                  * coordinates with y flipped.  cacheRect is in the bitmap
3965                  * coordinates and y is not flipped.
3966                  */
3967                 NSRect modRect, clrRect, cacheRect;
3968
3969                 /*
3970                  * Clip by bottom edge of cached area.  Clear what's below
3971                  * that.
3972                  */
3973                 if (drawTop >= self->cacheBounds.size.height) {
3974                     NSRectFill(rects[count]);
3975                     continue;
3976                 }
3977                 modRect.origin.x = rects[count].origin.x;
3978                 modRect.origin.y = rects[count].origin.y;
3979                 modRect.size.width = rects[count].size.width;
3980                 cacheRect.origin.y = drawTop;
3981                 if (drawBottom > self->cacheBounds.size.height) {
3982                     CGFloat excess =
3983                         drawBottom - self->cacheBounds.size.height;
3984
3985                     modRect.size.height = rects[count].size.height - excess;
3986                     cacheRect.origin.y = 0;
3987                     clrRect.origin.x = modRect.origin.x;
3988                     clrRect.origin.y = modRect.origin.y + modRect.size.height;
3989                     clrRect.size.width = modRect.size.width;
3990                     clrRect.size.height = excess;
3991                     NSRectFill(clrRect);
3992                 } else {
3993                     modRect.size.height = rects[count].size.height;
3994                     cacheRect.origin.y = self->cacheBounds.size.height -
3995                         rects[count].size.height;
3996                 }
3997                 cacheRect.size.height = modRect.size.height;
3998
3999                 /*
4000                  * Clip by right edge of cached area.  Clear what's to the
4001                  * right of that and copy the remainder from the cache.
4002                  */
4003                 if (drawLeft >= self->cacheBounds.size.width) {
4004                     NSRectFill(modRect);
4005                     continue;
4006                 }
4007                 cacheRect.origin.x = drawLeft;
4008                 if (drawRight > self->cacheBounds.size.width) {
4009                     CGFloat excess = drawRight - self->cacheBounds.size.width;
4010
4011                     modRect.size.width -= excess;
4012                     cacheRect.size.width =
4013                         self->cacheBounds.size.width - drawLeft;
4014                     clrRect.origin.x = modRect.origin.x + modRect.size.width;
4015                     clrRect.origin.y = modRect.origin.y;
4016                     clrRect.size.width = excess;
4017                     clrRect.size.height = modRect.size.height;
4018                     NSRectFill(clrRect);
4019                 } else {
4020                     cacheRect.size.width = drawRight - drawLeft;
4021                 }
4022                 [self->cacheForResize drawInRect:modRect fromRect:cacheRect
4023                      operation:NSCompositeCopy fraction:1.0
4024                      respectFlipped:YES hints:nil];
4025             }
4026         }
4027     } else if (! self.angbandContext) {
4028         /* Draw bright orange, 'cause this ain't right */
4029         [[NSColor orangeColor] set];
4030         NSRectFill([self bounds]);
4031     } else {
4032         /* Tell the Angband context to draw into us */
4033         [self.angbandContext drawRect:rect inView:self];
4034     }
4035 }
4036
4037 /**
4038  * Override NSView's method to set up a cache that's used in drawRect to
4039  * handle drawing during a resize.
4040  */
4041 - (void)viewWillStartLiveResize
4042 {
4043     [super viewWillStartLiveResize];
4044     self->cacheBounds = [self visibleRect];
4045     self->cacheForResize =
4046         [self bitmapImageRepForCachingDisplayInRect:self->cacheBounds];
4047     if (self->cacheForResize != nil) {
4048         [self cacheDisplayInRect:self->cacheBounds
4049               toBitmapImageRep:self->cacheForResize];
4050     } else {
4051         self->cacheBounds.size.width = 0.;
4052         self->cacheBounds.size.height = 0.;
4053     }
4054 }
4055
4056 /**
4057  * Override NSView's method to release the cache set up in
4058  * viewWillStartLiveResize.
4059  */
4060 - (void)viewDidEndLiveResize
4061 {
4062     [super viewDidEndLiveResize];
4063     self->cacheForResize = nil;
4064     [self setNeedsDisplay:YES];
4065 }
4066
4067 @end
4068
4069 /**
4070  * Delay handling of double-clicked savefiles
4071  */
4072 Boolean open_when_ready = FALSE;
4073
4074
4075
4076 /**
4077  * ------------------------------------------------------------------------
4078  * Some generic functions
4079  * ------------------------------------------------------------------------ */
4080
4081 /**
4082  * Sets an Angband color at a given index
4083  */
4084 static void set_color_for_index(int idx)
4085 {
4086     u16b rv, gv, bv;
4087     
4088     /* Extract the R,G,B data */
4089     rv = angband_color_table[idx][1];
4090     gv = angband_color_table[idx][2];
4091     bv = angband_color_table[idx][3];
4092     
4093     CGContextSetRGBFillColor([[NSGraphicsContext currentContext] graphicsPort], rv/255., gv/255., bv/255., 1.);
4094 }
4095
4096 /**
4097  * Remember the current character in UserDefaults so we can select it by
4098  * default next time.
4099  */
4100 static void record_current_savefile(void)
4101 {
4102     NSString *savefileString = [[NSString stringWithCString:savefile encoding:NSMacOSRomanStringEncoding] lastPathComponent];
4103     if (savefileString)
4104     {
4105         NSUserDefaults *angbandDefs = [NSUserDefaults angbandDefaults];
4106         [angbandDefs setObject:savefileString forKey:@"SaveFile"];
4107     }
4108 }
4109
4110
4111 #ifdef JP
4112 /**
4113  * Convert a two-byte EUC-JP encoded character (both *cp and (*cp + 1) are in
4114  * the range, 0xA1-0xFE, or *cp is 0x8E) to a utf16 value in the native byte
4115  * ordering.
4116  */
4117 static wchar_t convert_two_byte_eucjp_to_utf16_native(const char *cp)
4118 {
4119     NSString* str = [[NSString alloc] initWithBytes:cp length:2
4120                                       encoding:NSJapaneseEUCStringEncoding];
4121     wchar_t result = [str characterAtIndex:0];
4122     str = nil;
4123     return result;
4124 }
4125 #endif /* JP */
4126
4127
4128 /**
4129  * ------------------------------------------------------------------------
4130  * Support for the "z-term.c" package
4131  * ------------------------------------------------------------------------ */
4132
4133
4134 /**
4135  * Initialize a new Term
4136  */
4137 static void Term_init_cocoa(term *t)
4138 {
4139     @autoreleasepool {
4140         AngbandContext *context = [[AngbandContext alloc] init];
4141
4142         /* Give the term ownership of the context */
4143         t->data = (void *)CFBridgingRetain(context);
4144
4145         /* Handle graphics */
4146         t->higher_pict = !! use_graphics;
4147         t->always_pict = FALSE;
4148
4149         NSDisableScreenUpdates();
4150
4151         /*
4152          * Figure out the frame autosave name based on the index of this term
4153          */
4154         NSString *autosaveName = nil;
4155         int termIdx;
4156         for (termIdx = 0; termIdx < ANGBAND_TERM_MAX; termIdx++)
4157         {
4158             if (angband_term[termIdx] == t)
4159             {
4160                 autosaveName =
4161                     [NSString stringWithFormat:@"AngbandTerm-%d", termIdx];
4162                 break;
4163             }
4164         }
4165
4166         /* Set its font. */
4167         NSString *fontName =
4168             [[NSUserDefaults angbandDefaults]
4169                 stringForKey:[NSString stringWithFormat:@"FontName-%d", termIdx]];
4170         if (! fontName) fontName = [[AngbandContext defaultFont] fontName];
4171
4172         /*
4173          * Use a smaller default font for the other windows, but only if the
4174          * font hasn't been explicitly set.
4175          */
4176         float fontSize =
4177             (termIdx > 0) ? 10.0 : [[AngbandContext defaultFont] pointSize];
4178         NSNumber *fontSizeNumber =
4179             [[NSUserDefaults angbandDefaults]
4180                 valueForKey: [NSString stringWithFormat: @"FontSize-%d", termIdx]];
4181
4182         if( fontSizeNumber != nil )
4183         {
4184             fontSize = [fontSizeNumber floatValue];
4185         }
4186
4187         [context setSelectionFont:[NSFont fontWithName:fontName size:fontSize]
4188                  adjustTerminal: NO];
4189
4190         NSArray *terminalDefaults =
4191             [[NSUserDefaults standardUserDefaults]
4192                 valueForKey: AngbandTerminalsDefaultsKey];
4193         NSInteger rows = 24;
4194         NSInteger columns = 80;
4195
4196         if( termIdx < (int)[terminalDefaults count] )
4197         {
4198             NSDictionary *term = [terminalDefaults objectAtIndex: termIdx];
4199             NSInteger defaultRows =
4200                 [[term valueForKey: AngbandTerminalRowsDefaultsKey]
4201                     integerValue];
4202             NSInteger defaultColumns =
4203                 [[term valueForKey: AngbandTerminalColumnsDefaultsKey]
4204                     integerValue];
4205
4206             if (defaultRows > 0) rows = defaultRows;
4207             if (defaultColumns > 0) columns = defaultColumns;
4208         }
4209
4210         [context resizeWithColumns:columns rows:rows];
4211
4212         /* Get the window */
4213         NSWindow *window = [context makePrimaryWindow];
4214
4215         /* Set its title and, for auxiliary terms, tentative size */
4216         [context setDefaultTitle:termIdx];
4217         [context constrainWindowSize:termIdx];
4218
4219         /*
4220          * If this is the first term, and we support full screen (Mac OS X Lion
4221          * or later), then allow it to go full screen (sweet). Allow other
4222          * terms to be FullScreenAuxilliary, so they can at least show up.
4223          * Unfortunately in Lion they don't get brought to the full screen
4224          * space; but they would only make sense on multiple displays anyways
4225          * so it's not a big loss.
4226          */
4227         if ([window respondsToSelector:@selector(toggleFullScreen:)])
4228         {
4229             NSWindowCollectionBehavior behavior = [window collectionBehavior];
4230             behavior |=
4231                 (termIdx == 0 ?
4232                  NSWindowCollectionBehaviorFullScreenPrimary :
4233                  NSWindowCollectionBehaviorFullScreenAuxiliary);
4234             [window setCollectionBehavior:behavior];
4235         }
4236
4237         /* No Resume support yet, though it would not be hard to add */
4238         if ([window respondsToSelector:@selector(setRestorable:)])
4239         {
4240             [window setRestorable:NO];
4241         }
4242
4243         /* default window placement */ {
4244             static NSRect overallBoundingRect;
4245
4246             if( termIdx == 0 )
4247             {
4248                 /*
4249                  * This is a bit of a trick to allow us to display multiple
4250                  * windows in the "standard default" window position in OS X:
4251                  * the upper center of the screen.  The term sizes set in
4252                  * load_prefs() are based on a 5-wide by 3-high grid, with the
4253                  * main term being 4/5 wide by 2/3 high (hence the scaling to
4254                  * find what the containing rect would be).
4255                  */
4256                 NSRect originalMainTermFrame = [window frame];
4257                 NSRect scaledFrame = originalMainTermFrame;
4258                 scaledFrame.size.width *= 5.0 / 4.0;
4259                 scaledFrame.size.height *= 3.0 / 2.0;
4260                 scaledFrame.size.width += 1.0; /* spacing between window columns */
4261                 scaledFrame.size.height += 1.0; /* spacing between window rows */
4262                 [window setFrame: scaledFrame  display: NO];
4263                 [window center];
4264                 overallBoundingRect = [window frame];
4265                 [window setFrame: originalMainTermFrame display: NO];
4266             }
4267
4268             static NSRect mainTermBaseRect;
4269             NSRect windowFrame = [window frame];
4270
4271             if( termIdx == 0 )
4272             {
4273                 /*
4274                  * The height and width adjustments were determined
4275                  * experimentally, so that the rest of the windows line up
4276                  * nicely without overlapping.
4277                  */
4278                 windowFrame.size.width += 7.0;
4279                 windowFrame.size.height += 9.0;
4280                 windowFrame.origin.x = NSMinX( overallBoundingRect );
4281                 windowFrame.origin.y =
4282                     NSMaxY( overallBoundingRect ) - NSHeight( windowFrame );
4283                 mainTermBaseRect = windowFrame;
4284             }
4285             else if( termIdx == 1 )
4286             {
4287                 windowFrame.origin.x = NSMinX( mainTermBaseRect );
4288                 windowFrame.origin.y =
4289                     NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0;
4290             }
4291             else if( termIdx == 2 )
4292             {
4293                 windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0;
4294                 windowFrame.origin.y =
4295                     NSMaxY( mainTermBaseRect ) - NSHeight( windowFrame );
4296             }
4297             else if( termIdx == 3 )
4298             {
4299                 windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0;
4300                 windowFrame.origin.y =
4301                     NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0;
4302             }
4303             else if( termIdx == 4 )
4304             {
4305                 windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0;
4306                 windowFrame.origin.y = NSMinY( mainTermBaseRect );
4307             }
4308             else if( termIdx == 5 )
4309             {
4310                 windowFrame.origin.x =
4311                     NSMinX( mainTermBaseRect ) + NSWidth( windowFrame ) + 1.0;
4312                 windowFrame.origin.y =
4313                     NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0;
4314             }
4315
4316             [window setFrame: windowFrame display: NO];
4317         }
4318
4319         /* Override the default frame above if the user has adjusted windows in
4320          * the past */
4321         if (autosaveName) [window setFrameAutosaveName:autosaveName];
4322
4323         /*
4324          * Tell it about its term. Do this after we've sized it so that the
4325          * sizing doesn't trigger redrawing and such.
4326          */
4327         [context setTerm:t];
4328
4329         /*
4330          * Only order front if it's the first term. Other terms will be ordered
4331          * front from AngbandUpdateWindowVisibility(). This is to work around a
4332          * problem where Angband aggressively tells us to initialize terms that
4333          * don't do anything!
4334          */
4335         if (t == angband_term[0])
4336             [context.primaryWindow makeKeyAndOrderFront: nil];
4337
4338         NSEnableScreenUpdates();
4339
4340         /* Set "mapped" flag */
4341         t->mapped_flag = true;
4342     }
4343 }
4344
4345
4346
4347 /**
4348  * Nuke an old Term
4349  */
4350 static void Term_nuke_cocoa(term *t)
4351 {
4352     @autoreleasepool {
4353         AngbandContext *context = (__bridge AngbandContext*) (t->data);
4354         if (context)
4355         {
4356             /* Tell the context to get rid of its windows, etc. */
4357             [context dispose];
4358
4359             /* Balance our CFBridgingRetain from when we created it */
4360             CFRelease(t->data);
4361
4362             /* Done with it */
4363             t->data = NULL;
4364         }
4365     }
4366 }
4367
4368 /**
4369  * Returns the CGImageRef corresponding to an image with the given path.
4370  * Transfers ownership to the caller.
4371  */
4372 static CGImageRef create_angband_image(NSString *path)
4373 {
4374     CGImageRef decodedImage = NULL, result = NULL;
4375     
4376     /* Try using ImageIO to load the image */
4377     if (path)
4378     {
4379         NSURL *url = [[NSURL alloc] initFileURLWithPath:path isDirectory:NO];
4380         if (url)
4381         {
4382             NSDictionary *options = [[NSDictionary alloc] initWithObjectsAndKeys:(id)kCFBooleanTrue, kCGImageSourceShouldCache, nil];
4383             CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, (CFDictionaryRef)options);
4384             if (source)
4385             {
4386                 /*
4387                  * We really want the largest image, but in practice there's
4388                  * only going to be one
4389                  */
4390                 decodedImage = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)options);
4391                 CFRelease(source);
4392             }
4393         }
4394     }
4395     
4396     /*
4397      * Draw the sucker to defeat ImageIO's weird desire to cache and decode on
4398      * demand. Our images aren't that big!
4399      */
4400     if (decodedImage)
4401     {
4402         size_t width = CGImageGetWidth(decodedImage), height = CGImageGetHeight(decodedImage);
4403         
4404         /* Compute our own bitmap info */
4405         CGBitmapInfo imageBitmapInfo = CGImageGetBitmapInfo(decodedImage);
4406         CGBitmapInfo contextBitmapInfo = kCGBitmapByteOrderDefault;
4407         
4408         switch (imageBitmapInfo & kCGBitmapAlphaInfoMask) {
4409             case kCGImageAlphaNone:
4410             case kCGImageAlphaNoneSkipLast:
4411             case kCGImageAlphaNoneSkipFirst:
4412                 /* No alpha */
4413                 contextBitmapInfo |= kCGImageAlphaNone;
4414                 break;
4415             default:
4416                 /* Some alpha, use premultiplied last which is most efficient. */
4417                 contextBitmapInfo |= kCGImageAlphaPremultipliedLast;
4418                 break;
4419         }
4420
4421         /* Draw the source image flipped, since the view is flipped */
4422         CGContextRef ctx = CGBitmapContextCreate(NULL, width, height, CGImageGetBitsPerComponent(decodedImage), CGImageGetBytesPerRow(decodedImage), CGImageGetColorSpace(decodedImage), contextBitmapInfo);
4423         if (ctx) {
4424             CGContextSetBlendMode(ctx, kCGBlendModeCopy);
4425             CGContextTranslateCTM(ctx, 0.0, height);
4426             CGContextScaleCTM(ctx, 1.0, -1.0);
4427             CGContextDrawImage(
4428                 ctx, CGRectMake(0, 0, width, height), decodedImage);
4429             result = CGBitmapContextCreateImage(ctx);
4430             CFRelease(ctx);
4431         }
4432
4433         CGImageRelease(decodedImage);
4434     }
4435     return result;
4436 }
4437
4438 /**
4439  * React to changes
4440  */
4441 static errr Term_xtra_cocoa_react(void)
4442 {
4443     /* Don't actually switch graphics until the game is running */
4444     if (!initialized || !game_in_progress) return (-1);
4445
4446     @autoreleasepool {
4447         /* Handle graphics */
4448         int expected_graf_mode = (current_graphics_mode) ?
4449             current_graphics_mode->grafID : GRAPHICS_NONE;
4450         if (graf_mode_req != expected_graf_mode)
4451         {
4452             graphics_mode *new_mode;
4453             if (graf_mode_req != GRAPHICS_NONE) {
4454                 new_mode = get_graphics_mode(graf_mode_req);
4455             } else {
4456                 new_mode = NULL;
4457             }
4458
4459             /* Get rid of the old image. CGImageRelease is NULL-safe. */
4460             CGImageRelease(pict_image);
4461             pict_image = NULL;
4462
4463             /* Try creating the image if we want one */
4464             if (new_mode != NULL)
4465             {
4466                 NSString *img_path =
4467                     [NSString stringWithFormat:@"%s/%s", new_mode->path, new_mode->file];
4468                 pict_image = create_angband_image(img_path);
4469
4470                 /* If we failed to create the image, revert to ASCII. */
4471                 if (! pict_image) {
4472                     new_mode = NULL;
4473                     if (use_bigtile) {
4474                         arg_bigtile = FALSE;
4475                     }
4476                     [[NSUserDefaults angbandDefaults]
4477                         setInteger:GRAPHICS_NONE
4478                         forKey:AngbandGraphicsDefaultsKey];
4479
4480                     NSString *msg = NSLocalizedStringWithDefaultValue(
4481                         @"Error.TileSetLoadFailed",
4482                         AngbandMessageCatalog,
4483                         [NSBundle mainBundle],
4484                         @"Failed to Load Tile Set",
4485                         @"Alert text for failed tile set load");
4486                     NSString *info = NSLocalizedStringWithDefaultValue(
4487                         @"Error.TileSetRevertToASCII",
4488                         AngbandMessageCatalog,
4489                         [NSBundle mainBundle],
4490                         @"Could not load the tile set.  Switched back to ASCII.",
4491                         @"Alert informative message for failed tile set load");
4492                     NSAlert *alert = [[NSAlert alloc] init];
4493
4494                     alert.messageText = msg;
4495                     alert.informativeText = info;
4496                     [alert runModal];
4497                 }
4498             }
4499
4500             if (graphics_are_enabled()) {
4501                 /*
4502                  * The contents stored in the AngbandContext may have
4503                  * references to the old tile set.  Out of an abundance
4504                  * of caution, clear those references in case there's an
4505                  * attempt to redraw the contents before the core has the
4506                  * chance to update it via the text_hook, pict_hook, and
4507                  * wipe_hook.
4508                  */
4509                 for (int iterm = 0; iterm < ANGBAND_TERM_MAX; ++iterm) {
4510                     AngbandContext* aContext =
4511                         (__bridge AngbandContext*) (angband_term[iterm]->data);
4512
4513                     [aContext.contents wipeTiles];
4514                 }
4515             }
4516
4517             /* Record what we did */
4518             use_graphics = new_mode ? new_mode->grafID : 0;
4519             ANGBAND_GRAF = (new_mode ? new_mode->graf : "ascii");
4520             current_graphics_mode = new_mode;
4521
4522             /* Enable or disable higher picts.  */
4523             for (int iterm = 0; iterm < ANGBAND_TERM_MAX; ++iterm) {
4524                 if (angband_term[iterm]) {
4525                     angband_term[iterm]->higher_pict = !! use_graphics;
4526                 }
4527             }
4528
4529             if (pict_image && current_graphics_mode)
4530             {
4531                 /*
4532                  * Compute the row and column count via the image height and
4533                  * width.
4534                  */
4535                 pict_rows = (int)(CGImageGetHeight(pict_image) /
4536                                   current_graphics_mode->cell_height);
4537                 pict_cols = (int)(CGImageGetWidth(pict_image) /
4538                                   current_graphics_mode->cell_width);
4539             }
4540             else
4541             {
4542                 pict_rows = 0;
4543                 pict_cols = 0;
4544             }
4545
4546             /* Reset visuals */
4547             if (arg_bigtile == use_bigtile && character_generated)
4548             {
4549                 reset_visuals();
4550             }
4551         }
4552
4553         if (arg_bigtile != use_bigtile) {
4554             if (character_generated)
4555             {
4556                 /* Reset visuals */
4557                 reset_visuals();
4558             }
4559
4560             Term_activate(angband_term[0]);
4561             Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
4562         }
4563     }
4564
4565     /* Success */
4566     return (0);
4567 }
4568
4569
4570 /**
4571  * Do a "special thing"
4572  */
4573 static errr Term_xtra_cocoa(int n, int v)
4574 {
4575     errr result = 0;
4576     @autoreleasepool {
4577         AngbandContext* angbandContext =
4578             (__bridge AngbandContext*) (Term->data);
4579
4580         /* Analyze */
4581         switch (n) {
4582             /* Make a noise */
4583         case TERM_XTRA_NOISE:
4584             NSBeep();
4585             break;
4586
4587             /*  Make a sound */
4588         case TERM_XTRA_SOUND:
4589             play_sound(v);
4590             break;
4591
4592             /* Process random events */
4593         case TERM_XTRA_BORED:
4594             /*
4595              * Show or hide cocoa windows based on the subwindow flags set by
4596              * the user.
4597              */
4598             AngbandUpdateWindowVisibility();
4599             /* Process an event */
4600             (void)check_events(CHECK_EVENTS_NO_WAIT);
4601             break;
4602
4603             /* Process pending events */
4604         case TERM_XTRA_EVENT:
4605             /* Process an event */
4606             (void)check_events(v);
4607             break;
4608
4609             /* Flush all pending events (if any) */
4610         case TERM_XTRA_FLUSH:
4611             /* Hack -- flush all events */
4612             while (check_events(CHECK_EVENTS_DRAIN)) /* loop */;
4613
4614             break;
4615
4616             /* Hack -- Change the "soft level" */
4617         case TERM_XTRA_LEVEL:
4618             /*
4619              * Here we could activate (if requested), but I don't think
4620              * Angband should be telling us our window order (the user
4621              * should decide that), so do nothing.
4622              */
4623             break;
4624
4625             /* Clear the screen */
4626         case TERM_XTRA_CLEAR:
4627             [angbandContext.contents wipe];
4628             [angbandContext setNeedsDisplay:YES];
4629             break;
4630
4631             /* React to changes */
4632         case TERM_XTRA_REACT:
4633             result = Term_xtra_cocoa_react();
4634             break;
4635
4636             /* Delay (milliseconds) */
4637         case TERM_XTRA_DELAY:
4638             /* If needed */
4639             if (v > 0) {
4640                 double seconds = v / 1000.;
4641                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:seconds];
4642                 do {
4643                     NSEvent* event;
4644                     do {
4645                         event = [NSApp nextEventMatchingMask:-1
4646                                        untilDate:date
4647                                        inMode:NSDefaultRunLoopMode
4648                                        dequeue:YES];
4649                         if (event) send_event(event);
4650                     } while (event);
4651                 } while ([date timeIntervalSinceNow] >= 0);
4652             }
4653             break;
4654
4655             /* Draw the pending changes. */
4656         case TERM_XTRA_FRESH:
4657             {
4658                 /*
4659                  * Check the cursor visibility since the core will tell us
4660                  * explicitly to draw it, but tells us implicitly to forget it
4661                  * by simply telling us to redraw a location.
4662                  */
4663                 int isVisible = 0;
4664
4665                 Term_get_cursor(&isVisible);
4666                 if (! isVisible) {
4667                     [angbandContext.contents removeCursor];
4668                 }
4669                 [angbandContext computeInvalidRects];
4670                 [angbandContext.changes clear];
4671             }
4672             break;
4673
4674         default:
4675             /* Oops */
4676             result = 1;
4677             break;
4678         }
4679     }
4680
4681     return result;
4682 }
4683
4684 static errr Term_curs_cocoa(TERM_LEN x, TERM_LEN y)
4685 {
4686     AngbandContext *angbandContext = (__bridge AngbandContext*) (Term->data);
4687
4688     [angbandContext.contents setCursorAtColumn:x row:y width:1 height:1];
4689     /*
4690      * Unfortunately, this (and the same logic in Term_bigcurs_cocoa) will
4691      * also trigger what's under the cursor to be redrawn as well, even if
4692      * it has not changed.  In the current drawing implementation, that
4693      * inefficiency seems unavoidable.
4694      */
4695     [angbandContext.changes markChangedAtColumn:x row:y];
4696
4697     /* Success */
4698     return 0;
4699 }
4700
4701 /**
4702  * Draw a cursor that's two tiles wide.  For Japanese, that's used when
4703  * the cursor points at a kanji character, irregardless of whether operating
4704  * in big tile mode.
4705  */
4706 static errr Term_bigcurs_cocoa(TERM_LEN x, TERM_LEN y)
4707 {
4708     AngbandContext *angbandContext = (__bridge AngbandContext*) (Term->data);
4709
4710     [angbandContext.contents setCursorAtColumn:x row:y width:2 height:1];
4711     [angbandContext.changes markChangedBlockAtColumn:x row:y width:2 height:1];
4712
4713     /* Success */
4714     return 0;
4715 }
4716
4717 /**
4718  * Low level graphics (Assumes valid input)
4719  *
4720  * Erase "n" characters starting at (x,y)
4721  */
4722 static errr Term_wipe_cocoa(TERM_LEN x, TERM_LEN y, int n)
4723 {
4724     AngbandContext *angbandContext = (__bridge AngbandContext*) (Term->data);
4725
4726     [angbandContext.contents wipeBlockAtColumn:x row:y width:n height:1];
4727     [angbandContext.changes markChangedRangeAtColumn:x row:y width:n];
4728
4729     /* Success */
4730     return 0;
4731 }
4732
4733 static errr Term_pict_cocoa(TERM_LEN x, TERM_LEN y, int n,
4734                             TERM_COLOR *ap, concptr cp,
4735                             const TERM_COLOR *tap, concptr tcp)
4736 {
4737     /* Paranoia: Bail if graphics aren't enabled */
4738     if (! graphics_are_enabled()) return -1;
4739
4740     AngbandContext* angbandContext = (__bridge AngbandContext*) (Term->data);
4741     int step = (use_bigtile) ? 2 : 1;
4742
4743     int alphablend;
4744     if (use_graphics) {
4745         CGImageAlphaInfo ainfo = CGImageGetAlphaInfo(pict_image);
4746
4747         alphablend = (ainfo & (kCGImageAlphaPremultipliedFirst |
4748                                kCGImageAlphaPremultipliedLast)) ? 1 : 0;
4749     } else {
4750         alphablend = 0;
4751     }
4752
4753     for (int i = x; i < x + n * step; i += step) {
4754         TERM_COLOR a = *ap;
4755         char c = *cp;
4756         TERM_COLOR ta = *tap;
4757         char tc = *tcp;
4758
4759         ap += step;
4760         cp += step;
4761         tap += step;
4762         tcp += step;
4763         if (use_graphics && (a & 0x80) && (c & 0x80)) {
4764             char fgdRow = ((byte)a & 0x7F) % pict_rows;
4765             char fgdCol = ((byte)c & 0x7F) % pict_cols;
4766             char bckRow, bckCol;
4767
4768             if (alphablend) {
4769                 bckRow = ((byte)ta & 0x7F) % pict_rows;
4770                 bckCol = ((byte)tc & 0x7F) % pict_cols;
4771             } else {
4772                 /*
4773                  * Not blending so make the background the same as the
4774                  * the foreground.
4775                  */
4776                 bckRow = fgdRow;
4777                 bckCol = fgdCol;
4778             }
4779             [angbandContext.contents setTileAtColumn:i row:y
4780                            foregroundColumn:fgdCol
4781                            foregroundRow:fgdRow
4782                            backgroundColumn:bckCol
4783                            backgroundRow:bckRow
4784                            tileWidth:step
4785                            tileHeight:1];
4786             [angbandContext.changes markChangedBlockAtColumn:i row:y
4787                            width:step height:1];
4788         }
4789     }
4790
4791     /* Success */
4792     return (0);
4793 }
4794
4795 /**
4796  * Low level graphics.  Assumes valid input.
4797  *
4798  * Draw several ("n") chars, with an attr, at a given location.
4799  */
4800 static errr Term_text_cocoa(
4801     TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr cp)
4802 {
4803     AngbandContext* angbandContext = (__bridge AngbandContext*) (Term->data);
4804
4805     [angbandContext.contents setUniformAttributeTextRunAtColumn:x
4806                    row:y n:n glyphs:cp attribute:a];
4807     [angbandContext.changes markChangedRangeAtColumn:x row:y width:n];
4808
4809     /* Success */
4810     return 0;
4811 }
4812
4813 #if 0
4814 /* From the Linux mbstowcs(3) man page:
4815  *   If dest is NULL, n is ignored, and the conversion  proceeds  as  above,
4816  *   except  that  the converted wide characters are not written out to mem‐
4817  *   ory, and that no length limit exists.
4818  */
4819 static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n)
4820 {
4821     int i;
4822     int count = 0;
4823
4824     /* Unicode code point to UTF-8
4825      *  0x0000-0x007f:   0xxxxxxx
4826      *  0x0080-0x07ff:   110xxxxx 10xxxxxx
4827      *  0x0800-0xffff:   1110xxxx 10xxxxxx 10xxxxxx
4828      * 0x10000-0x1fffff: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
4829      * Note that UTF-16 limits Unicode to 0x10ffff. This code is not
4830      * endian-agnostic.
4831      */
4832     for (i = 0; i < n || dest == NULL; i++) {
4833         if ((src[i] & 0x80) == 0) {
4834             if (dest != NULL) dest[count] = src[i];
4835             if (src[i] == 0) break;
4836         } else if ((src[i] & 0xe0) == 0xc0) {
4837             if (dest != NULL) dest[count] =
4838                             (((unsigned char)src[i] & 0x1f) << 6)|
4839                             ((unsigned char)src[i+1] & 0x3f);
4840             i++;
4841         } else if ((src[i] & 0xf0) == 0xe0) {
4842             if (dest != NULL) dest[count] =
4843                             (((unsigned char)src[i] & 0x0f) << 12) |
4844                             (((unsigned char)src[i+1] & 0x3f) << 6) |
4845                             ((unsigned char)src[i+2] & 0x3f);
4846             i += 2;
4847         } else if ((src[i] & 0xf8) == 0xf0) {
4848             if (dest != NULL) dest[count] =
4849                             (((unsigned char)src[i] & 0x0f) << 18) |
4850                             (((unsigned char)src[i+1] & 0x3f) << 12) |
4851                             (((unsigned char)src[i+2] & 0x3f) << 6) |
4852                             ((unsigned char)src[i+3] & 0x3f);
4853             i += 3;
4854         } else {
4855             /* Found an invalid multibyte sequence */
4856             return (size_t)-1;
4857         }
4858         count++;
4859     }
4860     return count;
4861 }
4862 #endif
4863
4864 /**
4865  * Handle redrawing for a change to the tile set, tile scaling, or main window
4866  * font.  Returns YES if the redrawing was initiated.  Otherwise returns NO.
4867  */
4868 static BOOL redraw_for_tiles_or_term0_font(void)
4869 {
4870     /*
4871      * In Angband 4.2, do_cmd_redraw() will always clear, but only provides
4872      * something to replace the erased content if a character has been
4873      * generated.  In Hengband, do_cmd_redraw() isn't safe to call unless a
4874      * character has been generated.  Therefore, only call it if a character
4875      * has been generated.
4876      */
4877     if (character_generated) {
4878         do_cmd_redraw();
4879         wakeup_event_loop();
4880         return YES;
4881     }
4882     return NO;
4883 }
4884
4885 /**
4886  * Post a nonsense event so that our event loop wakes up
4887  */
4888 static void wakeup_event_loop(void)
4889 {
4890     /* Big hack - send a nonsense event to make us update */
4891     NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:NULL subtype:AngbandEventWakeup data1:0 data2:0];
4892     [NSApp postEvent:event atStart:NO];
4893 }
4894
4895
4896 /**
4897  * Handle the "open_when_ready" flag
4898  */
4899 static void handle_open_when_ready(void)
4900 {
4901     /* Check the flag XXX XXX XXX make a function for this */
4902     if (open_when_ready && initialized && !game_in_progress)
4903     {
4904         /* Forget */
4905         open_when_ready = FALSE;
4906         
4907         /* Game is in progress */
4908         game_in_progress = TRUE;
4909         
4910         /* Wait for a keypress */
4911         pause_line(Term->hgt - 1);
4912     }
4913 }
4914
4915
4916 /**
4917  * Handle quit_when_ready, by Peter Ammon,
4918  * slightly modified to check inkey_flag.
4919  */
4920 static void quit_calmly(void)
4921 {
4922     /* Quit immediately if game's not started */
4923     if (!game_in_progress || !character_generated) quit(NULL);
4924
4925     /* Save the game and Quit (if it's safe) */
4926     if (inkey_flag)
4927     {
4928         /* Hack -- Forget messages and term */
4929         msg_flag = FALSE;
4930                 Term->mapped_flag = FALSE;
4931
4932         /* Save the game */
4933         do_cmd_save_game(FALSE);
4934         record_current_savefile();
4935
4936         /* Quit */
4937         quit(NULL);
4938     }
4939
4940     /* Wait until inkey_flag is set */
4941 }
4942
4943
4944
4945 /**
4946  * Returns YES if we contain an AngbandView (and hence should direct our events
4947  * to Angband)
4948  */
4949 static BOOL contains_angband_view(NSView *view)
4950 {
4951     if ([view isKindOfClass:[AngbandView class]]) return YES;
4952     for (NSView *subview in [view subviews]) {
4953         if (contains_angband_view(subview)) return YES;
4954     }
4955     return NO;
4956 }
4957
4958
4959 /**
4960  * Queue mouse presses if they occur in the map section of the main window.
4961  */
4962 static void AngbandHandleEventMouseDown( NSEvent *event )
4963 {
4964 #if 0
4965         AngbandContext *angbandContext = [[[event window] contentView] angbandContext];
4966         AngbandContext *mainAngbandContext =
4967             (__bridge AngbandContext*) (angband_term[0]->data);
4968
4969         if (mainAngbandContext.primaryWindow &&
4970             [[event window] windowNumber] ==
4971             [mainAngbandContext.primaryWindow windowNumber])
4972         {
4973                 int cols, rows, x, y;
4974                 Term_get_size(&cols, &rows);
4975                 NSSize tileSize = angbandContext.tileSize;
4976                 NSSize border = angbandContext.borderSize;
4977                 NSPoint windowPoint = [event locationInWindow];
4978
4979                 /* Adjust for border; add border height because window origin is at
4980                  * bottom */
4981                 windowPoint = NSMakePoint( windowPoint.x - border.width, windowPoint.y + border.height );
4982
4983                 NSPoint p = [[[event window] contentView] convertPoint: windowPoint fromView: nil];
4984                 x = floor( p.x / tileSize.width );
4985                 y = floor( p.y / tileSize.height );
4986
4987                 /* Being safe about this, since xcode doesn't seem to like the
4988                  * bool_hack stuff */
4989                 BOOL displayingMapInterface = ((int)inkey_flag != 0);
4990
4991                 /* Sidebar plus border == thirteen characters; top row is reserved. */
4992                 /* Coordinates run from (0,0) to (cols-1, rows-1). */
4993                 BOOL mouseInMapSection = (x > 13 && x <= cols - 1 && y > 0  && y <= rows - 2);
4994
4995                 /* If we are displaying a menu, allow clicks anywhere within
4996                  * the terminal bounds; if we are displaying the main game
4997                  * interface, only allow clicks in the map section */
4998                 if ((!displayingMapInterface && x >= 0 && x < cols &&
4999                      y >= 0 && y < rows) ||
5000                      (displayingMapInterface && mouseInMapSection))
5001                 {
5002                         /* [event buttonNumber] will return 0 for left click,
5003                          * 1 for right click, but this is safer */
5004                         int button = ([event type] == NSLeftMouseDown) ? 1 : 2;
5005
5006 #ifdef KC_MOD_ALT
5007                         NSUInteger eventModifiers = [event modifierFlags];
5008                         byte angbandModifiers = 0;
5009                         angbandModifiers |= (eventModifiers & NSShiftKeyMask) ? KC_MOD_SHIFT : 0;
5010                         angbandModifiers |= (eventModifiers & NSControlKeyMask) ? KC_MOD_CONTROL : 0;
5011                         angbandModifiers |= (eventModifiers & NSAlternateKeyMask) ? KC_MOD_ALT : 0;
5012                         button |= (angbandModifiers & 0x0F) << 4; /* encode modifiers in the button number (see Term_mousepress()) */
5013 #endif
5014
5015                         Term_mousepress(x, y, button);
5016                 }
5017         }
5018 #endif
5019
5020         /* Pass click through to permit focus change, resize, etc. */
5021         [NSApp sendEvent:event];
5022 }
5023
5024
5025
5026 /**
5027  * Encodes an NSEvent Angband-style, or forwards it along.  Returns YES if the
5028  * event was sent to Angband, NO if Cocoa (or nothing) handled it */
5029 static BOOL send_event(NSEvent *event)
5030 {
5031
5032     /* If the receiving window is not an Angband window, then do nothing */
5033     if (! contains_angband_view([[event window] contentView]))
5034     {
5035         [NSApp sendEvent:event];
5036         return NO;
5037     }
5038
5039     /* Analyze the event */
5040     switch ([event type])
5041     {
5042         case NSKeyDown:
5043         {
5044             /* Try performing a key equivalent */
5045             if ([[NSApp mainMenu] performKeyEquivalent:event]) break;
5046             
5047             unsigned modifiers = [event modifierFlags];
5048             
5049             /* Send all NSCommandKeyMasks through */
5050             if (modifiers & NSCommandKeyMask)
5051             {
5052                 [NSApp sendEvent:event];
5053                 break;
5054             }
5055             
5056             if (! [[event characters] length]) break;
5057             
5058             
5059             /* Extract some modifiers */
5060             int mc = !! (modifiers & NSControlKeyMask);
5061             int ms = !! (modifiers & NSShiftKeyMask);
5062             int mo = !! (modifiers & NSAlternateKeyMask);
5063             int kp = !! (modifiers & NSNumericPadKeyMask);
5064             
5065             
5066             /* Get the Angband char corresponding to this unichar */
5067             unichar c = [[event characters] characterAtIndex:0];
5068             char ch;
5069             /*
5070              * Have anything from the numeric keypad generate a macro
5071              * trigger so that shift or control modifiers can be passed.
5072              */
5073             if (c <= 0x7F && !kp)
5074             {
5075                 ch = (char) c;
5076             }
5077             else {
5078                 /*
5079                  * The rest of Hengband uses Angband 2.7's or so key handling:
5080                  * so for the rest do something like the encoding that
5081                  * main-win.c does:  send a macro trigger with the Unicode
5082                  * value encoded into printable ASCII characters.
5083                  */
5084                 ch = '\0';
5085             }
5086             
5087             /* override special keys */
5088             switch([event keyCode]) {
5089                 case kVK_Return: ch = '\r'; break;
5090                 case kVK_Escape: ch = 27; break;
5091                 case kVK_Tab: ch = '\t'; break;
5092                 case kVK_Delete: ch = '\b'; break;
5093                 case kVK_ANSI_KeypadEnter: ch = '\r'; kp = TRUE; break;
5094             }
5095
5096             /* Hide the mouse pointer */
5097             [NSCursor setHiddenUntilMouseMoves:YES];
5098             
5099             /* Enqueue it */
5100             if (ch != '\0')
5101             {
5102                 Term_keypress(ch);
5103             }
5104             else
5105             {
5106                 /*
5107                  * Could use the hexsym global but some characters overlap with
5108                  * those used to indicate modifiers.
5109                  */
5110                 const char encoded[16] = {
5111                     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
5112                     'c', 'd', 'e', 'f'
5113                 };
5114
5115                 /* Begin the macro trigger. */
5116                 Term_keypress(31);
5117
5118                 /* Send the modifiers. */
5119                 if (mc) Term_keypress('C');
5120                 if (ms) Term_keypress('S');
5121                 if (mo) Term_keypress('O');
5122                 if (kp) Term_keypress('K');
5123
5124                 do {
5125                     Term_keypress(encoded[c & 0xF]);
5126                     c >>= 4;
5127                 } while (c > 0);
5128
5129                 /* End the macro trigger. */
5130                 Term_keypress(13);
5131             }
5132             
5133             break;
5134         }
5135             
5136         case NSLeftMouseDown:
5137                 case NSRightMouseDown:
5138                         AngbandHandleEventMouseDown(event);
5139             break;
5140
5141         case NSApplicationDefined:
5142         {
5143             if ([event subtype] == AngbandEventWakeup)
5144             {
5145                 return YES;
5146             }
5147             break;
5148         }
5149             
5150         default:
5151             [NSApp sendEvent:event];
5152             return YES;
5153     }
5154     return YES;
5155 }
5156
5157 /**
5158  * Check for Events, return TRUE if we process any
5159  */
5160 static BOOL check_events(int wait)
5161 {
5162     BOOL result = YES;
5163
5164     @autoreleasepool {
5165         /* Handles the quit_when_ready flag */
5166         if (quit_when_ready) quit_calmly();
5167
5168         NSDate* endDate;
5169         if (wait == CHECK_EVENTS_WAIT) endDate = [NSDate distantFuture];
5170         else endDate = [NSDate distantPast];
5171
5172         NSEvent* event;
5173         for (;;) {
5174             if (quit_when_ready)
5175             {
5176                 /* send escape events until we quit */
5177                 Term_keypress(0x1B);
5178                 result = NO;
5179                 break;
5180             }
5181             else {
5182                 event = [NSApp nextEventMatchingMask:-1 untilDate:endDate
5183                                inMode:NSDefaultRunLoopMode dequeue:YES];
5184                 if (! event) {
5185                     result = NO;
5186                     break;
5187                 }
5188                 if (send_event(event)) break;
5189             }
5190         }
5191     }
5192
5193     return result;
5194 }
5195
5196 /**
5197  * Hook to tell the user something important
5198  */
5199 static void hook_plog(const char * str)
5200 {
5201     if (str)
5202     {
5203         NSString *msg = NSLocalizedStringWithDefaultValue(
5204             @"Warning", AngbandMessageCatalog, [NSBundle mainBundle],
5205             @"Warning", @"Alert text for generic warning");
5206         NSString *info = [NSString stringWithCString:str
5207 #ifdef JP
5208                                    encoding:NSJapaneseEUCStringEncoding
5209 #else
5210                                    encoding:NSMacOSRomanStringEncoding
5211 #endif
5212         ];
5213         NSAlert *alert = [[NSAlert alloc] init];
5214
5215         alert.messageText = msg;
5216         alert.informativeText = info;
5217         [alert runModal];
5218     }
5219 }
5220
5221
5222 /**
5223  * Hook to tell the user something, and then quit
5224  */
5225 static void hook_quit(const char * str)
5226 {
5227     for (int i = ANGBAND_TERM_MAX - 1; i >= 0; --i) {
5228         if (angband_term[i]) {
5229             term_nuke(angband_term[i]);
5230         }
5231     }
5232     [AngbandSoundCatalog clearSharedSounds];
5233     [AngbandContext setDefaultFont:nil];
5234     plog(str);
5235     exit(0);
5236 }
5237
5238 /**
5239  * Return the path for Angband's lib directory and bail if it isn't found. The
5240  * lib directory should be in the bundle's resources directory, since it's
5241  * copied when built.
5242  */
5243 static NSString* get_lib_directory(void)
5244 {
5245     NSString *bundleLibPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: AngbandDirectoryNameLib];
5246     BOOL isDirectory = NO;
5247     BOOL libExists = [[NSFileManager defaultManager] fileExistsAtPath: bundleLibPath isDirectory: &isDirectory];
5248
5249     if( !libExists || !isDirectory )
5250     {
5251         NSLog( @"Hengband: can't find %@/ in bundle: isDirectory: %d libExists: %d", AngbandDirectoryNameLib, isDirectory, libExists );
5252
5253         NSString *msg = NSLocalizedStringWithDefaultValue(
5254             @"Error.MissingResources",
5255             AngbandMessageCatalog,
5256             [NSBundle mainBundle],
5257             @"Missing Resources",
5258             @"Alert text for missing resources");
5259         NSString *info = NSLocalizedStringWithDefaultValue(
5260             @"Error.MissingAngbandLib",
5261             AngbandMessageCatalog,
5262             [NSBundle mainBundle],
5263             @"Hengband was unable to find required resources and must quit. Please report a bug on the Angband forums.",
5264             @"Alert informative message for missing Angband lib/ folder");
5265         NSString *quit_label = NSLocalizedStringWithDefaultValue(
5266             @"Label.Quit", AngbandMessageCatalog, [NSBundle mainBundle],
5267             @"Quit", @"Quit");
5268         NSAlert *alert = [[NSAlert alloc] init];
5269
5270         /*
5271          * Note that NSCriticalAlertStyle was deprecated in 10.10.  The
5272          * replacement is NSAlertStyleCritical.
5273          */
5274         alert.alertStyle = NSCriticalAlertStyle;
5275         alert.messageText = msg;
5276         alert.informativeText = info;
5277         [alert addButtonWithTitle:quit_label];
5278         [alert runModal];
5279         exit( 0 );
5280     }
5281
5282     return bundleLibPath;
5283 }
5284
5285 /**
5286  * Return the path for the directory where Angband should look for its standard
5287  * user file tree.
5288  */
5289 static NSString* get_doc_directory(void)
5290 {
5291         NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
5292
5293 #if defined(SAFE_DIRECTORY)
5294         NSString *versionedDirectory = [NSString stringWithFormat: @"%@-%s", AngbandDirectoryNameBase, VERSION_STRING];
5295         return [documents stringByAppendingPathComponent: versionedDirectory];
5296 #else
5297         return [documents stringByAppendingPathComponent: AngbandDirectoryNameBase];
5298 #endif
5299 }
5300
5301 /**
5302  * Adjust directory paths as needed to correct for any differences needed by
5303  * Angband.  init_file_paths() currently requires that all paths provided have
5304  * a trailing slash and all other platforms honor this.
5305  *
5306  * \param originalPath The directory path to adjust.
5307  * \return A path suitable for Angband or nil if an error occurred.
5308  */
5309 static NSString* AngbandCorrectedDirectoryPath(NSString *originalPath)
5310 {
5311         if ([originalPath length] == 0) {
5312                 return nil;
5313         }
5314
5315         if (![originalPath hasSuffix: @"/"]) {
5316                 return [originalPath stringByAppendingString: @"/"];
5317         }
5318
5319         return originalPath;
5320 }
5321
5322 /**
5323  * Give Angband the base paths that should be used for the various directories
5324  * it needs. It will create any needed directories.
5325  */
5326 static void prepare_paths_and_directories(void)
5327 {
5328         char libpath[PATH_MAX + 1] = "\0";
5329         NSString *libDirectoryPath =
5330             AngbandCorrectedDirectoryPath(get_lib_directory());
5331         [libDirectoryPath getFileSystemRepresentation: libpath maxLength: sizeof(libpath)];
5332
5333         char basepath[PATH_MAX + 1] = "\0";
5334         NSString *angbandDocumentsPath =
5335             AngbandCorrectedDirectoryPath(get_doc_directory());
5336         [angbandDocumentsPath getFileSystemRepresentation: basepath maxLength: sizeof(basepath)];
5337
5338         init_file_paths(libpath, basepath);
5339         create_needed_dirs();
5340 }
5341
5342 /**
5343  * Create and initialize Angband terminal number "i".
5344  */
5345 static term *term_data_link(int i)
5346 {
5347     NSArray *terminalDefaults = [[NSUserDefaults standardUserDefaults]
5348                                     valueForKey: AngbandTerminalsDefaultsKey];
5349     NSInteger rows = 24;
5350     NSInteger columns = 80;
5351
5352     if (i < (int)[terminalDefaults count]) {
5353         NSDictionary *term = [terminalDefaults objectAtIndex:i];
5354         rows = [[term valueForKey: AngbandTerminalRowsDefaultsKey]
5355                    integerValue];
5356         columns = [[term valueForKey: AngbandTerminalColumnsDefaultsKey]
5357                       integerValue];
5358     }
5359
5360     /* Allocate */
5361     term *newterm = ZNEW(term);
5362
5363     /* Initialize the term */
5364     term_init(newterm, columns, rows, 256 /* keypresses, for some reason? */);
5365
5366     /* Differentiate between BS/^h, Tab/^i, etc. */
5367     /* newterm->complex_input = TRUE; */
5368
5369     /* Use a "software" cursor */
5370     newterm->soft_cursor = TRUE;
5371
5372     /* Disable the per-row flush notifications since they are not used. */
5373     newterm->never_frosh = TRUE;
5374
5375     /* Erase with "white space" */
5376     newterm->attr_blank = TERM_WHITE;
5377     newterm->char_blank = ' ';
5378
5379     /* Prepare the init/nuke hooks */
5380     newterm->init_hook = Term_init_cocoa;
5381     newterm->nuke_hook = Term_nuke_cocoa;
5382
5383     /* Prepare the function hooks */
5384     newterm->xtra_hook = Term_xtra_cocoa;
5385     newterm->wipe_hook = Term_wipe_cocoa;
5386     newterm->curs_hook = Term_curs_cocoa;
5387     newterm->bigcurs_hook = Term_bigcurs_cocoa;
5388     newterm->text_hook = Term_text_cocoa;
5389     newterm->pict_hook = Term_pict_cocoa;
5390     /* newterm->mbcs_hook = Term_mbcs_cocoa; */
5391
5392     /* Global pointer */
5393     angband_term[i] = newterm;
5394
5395     return newterm;
5396 }
5397
5398 /**
5399  * Load preferences from preferences file for current host+current user+
5400  * current application.
5401  */
5402 static void load_prefs(void)
5403 {
5404     NSUserDefaults *defs = [NSUserDefaults angbandDefaults];
5405
5406     /* Make some default defaults */
5407     NSMutableArray *defaultTerms = [[NSMutableArray alloc] init];
5408
5409     /*
5410      * The following default rows/cols were determined experimentally by first
5411      * finding the ideal window/font size combinations. But because of awful
5412      * temporal coupling in Term_init_cocoa(), it's impossible to set up the
5413      * defaults there, so we do it this way.
5414      */
5415     for (NSUInteger i = 0; i < ANGBAND_TERM_MAX; i++) {
5416         int columns, rows;
5417         BOOL visible = YES;
5418
5419         switch (i) {
5420         case 0:
5421             columns = 129;
5422             rows = 32;
5423             break;
5424         case 1:
5425             columns = 84;
5426             rows = 20;
5427             break;
5428         case 2:
5429             columns = 42;
5430             rows = 24;
5431             break;
5432         case 3:
5433             columns = 42;
5434             rows = 20;
5435             break;
5436         case 4:
5437             columns = 42;
5438             rows = 16;
5439             break;
5440         case 5:
5441             columns = 84;
5442             rows = 20;
5443             break;
5444         default:
5445             columns = 80;
5446             rows = 24;
5447             visible = NO;
5448             break;
5449         }
5450
5451         NSDictionary *standardTerm =
5452             [NSDictionary dictionaryWithObjectsAndKeys:
5453                           [NSNumber numberWithInt: rows], AngbandTerminalRowsDefaultsKey,
5454                           [NSNumber numberWithInt: columns], AngbandTerminalColumnsDefaultsKey,
5455                           [NSNumber numberWithBool: visible], AngbandTerminalVisibleDefaultsKey,
5456                           nil];
5457         [defaultTerms addObject: standardTerm];
5458     }
5459
5460     NSDictionary *defaults = [[NSDictionary alloc] initWithObjectsAndKeys:
5461 #ifdef JP
5462                               @"Osaka", @"FontName",
5463 #else
5464                               @"Menlo", @"FontName",
5465 #endif
5466                               [NSNumber numberWithFloat:13.f], @"FontSize",
5467                               [NSNumber numberWithInt:60], AngbandFrameRateDefaultsKey,
5468                               [NSNumber numberWithBool:YES], AngbandSoundDefaultsKey,
5469                               [NSNumber numberWithInt:GRAPHICS_NONE], AngbandGraphicsDefaultsKey,
5470                               [NSNumber numberWithBool:YES], AngbandBigTileDefaultsKey,
5471                               defaultTerms, AngbandTerminalsDefaultsKey,
5472                               nil];
5473     [defs registerDefaults:defaults];
5474
5475     /* Preferred graphics mode */
5476     graf_mode_req = [defs integerForKey:AngbandGraphicsDefaultsKey];
5477     if (graphics_will_be_enabled() &&
5478         [defs boolForKey:AngbandBigTileDefaultsKey] == YES) {
5479         use_bigtile = TRUE;
5480         arg_bigtile = TRUE;
5481     } else {
5482         use_bigtile = FALSE;
5483         arg_bigtile = FALSE;
5484     }
5485
5486     /* Use sounds; set the Angband global */
5487     if ([defs boolForKey:AngbandSoundDefaultsKey] == YES) {
5488         use_sound = TRUE;
5489         [AngbandSoundCatalog sharedSounds].enabled = YES;
5490     } else {
5491         use_sound = FALSE;
5492         [AngbandSoundCatalog sharedSounds].enabled = NO;
5493     }
5494
5495     /* fps */
5496     frames_per_second = [defs integerForKey:AngbandFrameRateDefaultsKey];
5497
5498     /* Font */
5499     [AngbandContext
5500         setDefaultFont:[NSFont fontWithName:[defs valueForKey:@"FontName-0"]
5501                                size:[defs floatForKey:@"FontSize-0"]]];
5502     if (! [AngbandContext defaultFont])
5503         [AngbandContext
5504             setDefaultFont:[NSFont fontWithName:@"Menlo" size:13.]];
5505 }
5506
5507 /**
5508  * Play sound effects asynchronously.  Select a sound from any available
5509  * for the required event, and bridge to Cocoa to play it.
5510  */
5511 static void play_sound(int event)
5512 {
5513     [[AngbandSoundCatalog sharedSounds] playSound:event];
5514 }
5515
5516 /**
5517  * Allocate the primary Angband terminal and activate it.  Allocate the other
5518  * Angband terminals.
5519  */
5520 static void init_windows(void)
5521 {
5522     /* Create the primary window */
5523     term *primary = term_data_link(0);
5524
5525     /* Prepare to create any additional windows */
5526     for (int i = 1; i < ANGBAND_TERM_MAX; i++) {
5527         term_data_link(i);
5528     }
5529
5530     /* Activate the primary term */
5531     Term_activate(primary);
5532 }
5533
5534 /**
5535  * ------------------------------------------------------------------------
5536  * Main program
5537  * ------------------------------------------------------------------------ */
5538
5539 @implementation AngbandAppDelegate
5540
5541 @synthesize graphicsMenu=_graphicsMenu;
5542 @synthesize commandMenu=_commandMenu;
5543 @synthesize commandMenuTagMap=_commandMenuTagMap;
5544
5545 - (IBAction)newGame:sender
5546 {
5547     /* Game is in progress */
5548     game_in_progress = TRUE;
5549     new_game = TRUE;
5550 }
5551
5552 - (IBAction)editFont:sender
5553 {
5554     NSFontPanel *panel = [NSFontPanel sharedFontPanel];
5555     NSFont *termFont = [AngbandContext defaultFont];
5556
5557     int i;
5558     for (i=0; i < ANGBAND_TERM_MAX; i++) {
5559         AngbandContext *context =
5560             (__bridge AngbandContext*) (angband_term[i]->data);
5561         if ([context isKeyWindow]) {
5562             termFont = [context angbandViewFont];
5563             break;
5564         }
5565     }
5566
5567     [panel setPanelFont:termFont isMultiple:NO];
5568     [panel orderFront:self];
5569 }
5570
5571 /**
5572  * Implement NSObject's changeFont() method to receive a notification about the
5573  * changed font.  Note that, as of 10.14, changeFont() is deprecated in
5574  * NSObject - it will be removed at some point and the application delegate
5575  * will have to be declared as implementing the NSFontChanging protocol.
5576  */
5577 - (void)changeFont:(id)sender
5578 {
5579     int mainTerm;
5580     for (mainTerm=0; mainTerm < ANGBAND_TERM_MAX; mainTerm++) {
5581         AngbandContext *context =
5582             (__bridge AngbandContext*) (angband_term[mainTerm]->data);
5583         if ([context isKeyWindow]) {
5584             break;
5585         }
5586     }
5587
5588     /* Bug #1709: Only change font for angband windows */
5589     if (mainTerm == ANGBAND_TERM_MAX) return;
5590
5591     NSFont *oldFont = [AngbandContext defaultFont];
5592     NSFont *newFont = [sender convertFont:oldFont];
5593     if (! newFont) return; /*paranoia */
5594
5595     /* Store as the default font if we changed the first term */
5596     if (mainTerm == 0) {
5597         [AngbandContext setDefaultFont:newFont];
5598     }
5599
5600     /* Record it in the preferences */
5601     NSUserDefaults *defs = [NSUserDefaults angbandDefaults];
5602     [defs setValue:[newFont fontName] 
5603         forKey:[NSString stringWithFormat:@"FontName-%d", mainTerm]];
5604     [defs setFloat:[newFont pointSize]
5605         forKey:[NSString stringWithFormat:@"FontSize-%d", mainTerm]];
5606
5607     NSDisableScreenUpdates();
5608
5609     /* Update window */
5610     AngbandContext *angbandContext =
5611         (__bridge AngbandContext*) (angband_term[mainTerm]->data);
5612     [(id)angbandContext setSelectionFont:newFont adjustTerminal: YES];
5613
5614     NSEnableScreenUpdates();
5615
5616     if (mainTerm != 0 || ! redraw_for_tiles_or_term0_font()) {
5617         [(id)angbandContext requestRedraw];
5618     }
5619 }
5620
5621 - (IBAction)openGame:sender
5622 {
5623     @autoreleasepool {
5624         BOOL selectedSomething = NO;
5625         int panelResult;
5626
5627         /* Get where we think the save files are */
5628         NSURL *startingDirectoryURL =
5629             [NSURL fileURLWithPath:[NSString stringWithCString:ANGBAND_DIR_SAVE encoding:NSASCIIStringEncoding]
5630                    isDirectory:YES];
5631
5632         /* Set up an open panel */
5633         NSOpenPanel* panel = [NSOpenPanel openPanel];
5634         [panel setCanChooseFiles:YES];
5635         [panel setCanChooseDirectories:NO];
5636         [panel setResolvesAliases:YES];
5637         [panel setAllowsMultipleSelection:NO];
5638         [panel setTreatsFilePackagesAsDirectories:YES];
5639         [panel setDirectoryURL:startingDirectoryURL];
5640
5641         /* Run it */
5642         panelResult = [panel runModal];
5643         if (panelResult == NSOKButton)
5644         {
5645             NSArray* fileURLs = [panel URLs];
5646             if ([fileURLs count] > 0 && [[fileURLs objectAtIndex:0] isFileURL])
5647             {
5648                 NSURL* savefileURL = (NSURL *)[fileURLs objectAtIndex:0];
5649                 /*
5650                  * The path property doesn't do the right thing except for
5651                  * URLs with the file scheme. We had
5652                  * getFileSystemRepresentation here before, but that wasn't
5653                  * introduced until OS X 10.9.
5654                  */
5655                 selectedSomething = [[savefileURL path]
5656                                         getCString:savefile
5657                                         maxLength:sizeof savefile
5658                                         encoding:NSMacOSRomanStringEncoding];
5659             }
5660         }
5661
5662         if (selectedSomething)
5663         {
5664             /* Remember this so we can select it by default next time */
5665             record_current_savefile();
5666
5667             /* Game is in progress */
5668             game_in_progress = TRUE;
5669         }
5670     }
5671 }
5672
5673 - (IBAction)saveGame:sender
5674 {
5675     /* Hack -- Forget messages */
5676     msg_flag = FALSE;
5677     
5678     /* Save the game */
5679     do_cmd_save_game(FALSE);
5680     
5681     /*
5682      * Record the current save file so we can select it by default next time.
5683      * It's a little sketchy that this only happens when we save through the
5684      * menu; ideally game-triggered saves would trigger it too.
5685      */
5686     record_current_savefile();
5687 }
5688
5689 /**
5690  * Entry point for initializing Angband
5691  */
5692 - (void)beginGame
5693 {
5694     @autoreleasepool {
5695         /* Hooks in some "z-util.c" hooks */
5696         plog_aux = hook_plog;
5697         quit_aux = hook_quit;
5698
5699         /* Initialize file paths */
5700         prepare_paths_and_directories();
5701
5702         /* Note the "system" */
5703         ANGBAND_SYS = "coc";
5704
5705         /* Load possible graphics modes */
5706         init_graphics_modes();
5707
5708         /* Load preferences */
5709         load_prefs();
5710
5711         /* Prepare the windows */
5712         init_windows();
5713
5714         /* Set up game event handlers */
5715         /* init_display(); */
5716
5717         /* Register the sound hook */
5718         /* sound_hook = play_sound; */
5719
5720         /* Initialize some save file stuff */
5721         player_euid = geteuid();
5722         player_egid = getegid();
5723
5724         /* Initialise game */
5725         init_angband();
5726
5727         /* We are now initialized */
5728         initialized = TRUE;
5729
5730         /* Handle "open_when_ready" */
5731         handle_open_when_ready();
5732
5733         /* Handle pending events (most notably update) and flush input */
5734         Term_flush();
5735
5736         /*
5737          * Prompt the user; assume the splash screen is 80 x 23 and position
5738          * relative to that rather than center based on the full size of the
5739          * window.
5740          */
5741         int message_row = 23;
5742         Term_erase(0, message_row, 255);
5743         put_str(
5744 #ifdef JP
5745             "['ファイル' メニューから '新規' または '開く' を選択します]",
5746             message_row, (80 - 59) / 2
5747 #else
5748             "[Choose 'New' or 'Open' from the 'File' menu]",
5749             message_row, (80 - 45) / 2
5750 #endif
5751         );
5752         Term_fresh();
5753     }
5754
5755     while (!game_in_progress) {
5756         @autoreleasepool {
5757             NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES];
5758             if (event) [NSApp sendEvent:event];
5759         }
5760     }
5761
5762     /*
5763      * Play a game -- "new_game" is set by "new", "open" or the open document
5764      * even handler as appropriate
5765      */
5766     Term_fresh();
5767     play_game(new_game);
5768
5769     quit(NULL);
5770 }
5771
5772 /**
5773  * Implement NSObject's validateMenuItem() method to override enabling or
5774  * disabling a menu item.  Note that, as of 10.14, validateMenuItem() is
5775  * deprecated in NSObject - it will be removed at some point and the
5776  * application delegate will have to be declared as implementing the
5777  * NSMenuItemValidation protocol.
5778  */
5779 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
5780 {
5781     SEL sel = [menuItem action];
5782     NSInteger tag = [menuItem tag];
5783
5784     if( tag >= AngbandWindowMenuItemTagBase && tag < AngbandWindowMenuItemTagBase + ANGBAND_TERM_MAX )
5785     {
5786         if( tag == AngbandWindowMenuItemTagBase )
5787         {
5788             /* The main window should always be available and visible */
5789             return YES;
5790         }
5791         else
5792         {
5793             /*
5794              * Another window is only usable after Term_init_cocoa() has
5795              * been called for it.  For Angband if window_flag[i] is nonzero
5796              * then that has happened for window i.  For Hengband, that is
5797              * not the case so also test angband_term[i]->data.
5798              */
5799             NSInteger subwindowNumber = tag - AngbandWindowMenuItemTagBase;
5800             return (angband_term[subwindowNumber]->data != 0
5801                     && window_flag[subwindowNumber] > 0);
5802         }
5803
5804         return NO;
5805     }
5806
5807     if (sel == @selector(newGame:))
5808     {
5809         return ! game_in_progress;
5810     }
5811     else if (sel == @selector(editFont:))
5812     {
5813         return YES;
5814     }
5815     else if (sel == @selector(openGame:))
5816     {
5817         return ! game_in_progress;
5818     }
5819     else if (sel == @selector(setRefreshRate:) &&
5820              [[menuItem parentItem] tag] == 150)
5821     {
5822         NSInteger fps = [[NSUserDefaults standardUserDefaults] integerForKey:AngbandFrameRateDefaultsKey];
5823         [menuItem setState: ([menuItem tag] == fps)];
5824         return YES;
5825     }
5826     else if( sel == @selector(setGraphicsMode:) )
5827     {
5828         NSInteger requestedGraphicsMode = [[NSUserDefaults standardUserDefaults] integerForKey:AngbandGraphicsDefaultsKey];
5829         [menuItem setState: (tag == requestedGraphicsMode)];
5830         return YES;
5831     }
5832     else if( sel == @selector(toggleSound:) )
5833     {
5834         BOOL is_on = [[NSUserDefaults standardUserDefaults]
5835                          boolForKey:AngbandSoundDefaultsKey];
5836
5837         [menuItem setState: ((is_on) ? NSOnState : NSOffState)];
5838         return YES;
5839     }
5840     else if (sel == @selector(toggleWideTiles:)) {
5841         BOOL is_on = [[NSUserDefaults standardUserDefaults]
5842                          boolForKey:AngbandBigTileDefaultsKey];
5843
5844         [menuItem setState: ((is_on) ? NSOnState : NSOffState)];
5845         return YES;
5846     }
5847     else if( sel == @selector(sendAngbandCommand:) ||
5848              sel == @selector(saveGame:) )
5849     {
5850         /*
5851          * we only want to be able to send commands during an active game
5852          * after the birth screens
5853          */
5854         return !!game_in_progress && character_generated;
5855     }
5856     else return YES;
5857 }
5858
5859
5860 - (IBAction)setRefreshRate:(NSMenuItem *)menuItem
5861 {
5862     frames_per_second = [menuItem tag];
5863     [[NSUserDefaults angbandDefaults] setInteger:frames_per_second forKey:AngbandFrameRateDefaultsKey];
5864 }
5865
5866 - (void)setGraphicsMode:(NSMenuItem *)sender
5867 {
5868     /* We stashed the graphics mode ID in the menu item's tag */
5869     graf_mode_req = [sender tag];
5870
5871     /* Stash it in UserDefaults */
5872     [[NSUserDefaults angbandDefaults] setInteger:graf_mode_req forKey:AngbandGraphicsDefaultsKey];
5873
5874     if (! graphics_will_be_enabled()) {
5875         if (use_bigtile) {
5876             arg_bigtile = FALSE;
5877         }
5878     } else if ([[NSUserDefaults angbandDefaults] boolForKey:AngbandBigTileDefaultsKey] == YES &&
5879                ! use_bigtile) {
5880         arg_bigtile = TRUE;
5881     }
5882
5883     if (arg_bigtile != use_bigtile) {
5884         Term_activate(angband_term[0]);
5885         Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
5886     }
5887     redraw_for_tiles_or_term0_font();
5888 }
5889
5890 - (void)selectWindow: (id)sender
5891 {
5892     NSInteger subwindowNumber =
5893         [(NSMenuItem *)sender tag] - AngbandWindowMenuItemTagBase;
5894     AngbandContext *context =
5895         (__bridge AngbandContext*) (angband_term[subwindowNumber]->data);
5896     [context.primaryWindow makeKeyAndOrderFront: self];
5897     [context saveWindowVisibleToDefaults: YES];
5898 }
5899
5900 - (IBAction) toggleSound: (NSMenuItem *) sender
5901 {
5902     BOOL is_on = (sender.state == NSOnState);
5903
5904     /* Toggle the state and update the Angband global and preferences. */
5905     if (is_on) {
5906         sender.state = NSOffState;
5907         use_sound = FALSE;
5908         [AngbandSoundCatalog sharedSounds].enabled = NO;
5909     } else {
5910         sender.state = NSOnState;
5911         use_sound = TRUE;
5912         [AngbandSoundCatalog sharedSounds].enabled = YES;
5913     }
5914     [[NSUserDefaults angbandDefaults] setBool:(! is_on)
5915                                       forKey:AngbandSoundDefaultsKey];
5916 }
5917
5918 - (IBAction)toggleWideTiles:(NSMenuItem *) sender
5919 {
5920     BOOL is_on = (sender.state == NSOnState);
5921
5922     /* Toggle the state and update the Angband globals and preferences. */
5923     sender.state = (is_on) ? NSOffState : NSOnState;
5924     [[NSUserDefaults angbandDefaults] setBool:(! is_on)
5925                                       forKey:AngbandBigTileDefaultsKey];
5926     if (graphics_are_enabled()) {
5927         arg_bigtile = (is_on) ? FALSE : TRUE;
5928         if (arg_bigtile != use_bigtile) {
5929             Term_activate(angband_term[0]);
5930             Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
5931             redraw_for_tiles_or_term0_font();
5932         }
5933     }
5934 }
5935
5936 - (void)prepareWindowsMenu
5937 {
5938     @autoreleasepool {
5939         /*
5940          * Get the window menu with default items and add a separator and
5941          * item for the main window.
5942          */
5943         NSMenu *windowsMenu = [[NSApplication sharedApplication] windowsMenu];
5944         [windowsMenu addItem: [NSMenuItem separatorItem]];
5945
5946         NSString *title1 = [NSString stringWithCString:angband_term_name[0]
5947 #ifdef JP
5948                                      encoding:NSJapaneseEUCStringEncoding
5949 #else
5950                                      encoding:NSMacOSRomanStringEncoding
5951 #endif
5952         ];
5953         NSMenuItem *angbandItem = [[NSMenuItem alloc] initWithTitle:title1 action: @selector(selectWindow:) keyEquivalent: @"0"];
5954         [angbandItem setTarget: self];
5955         [angbandItem setTag: AngbandWindowMenuItemTagBase];
5956         [windowsMenu addItem: angbandItem];
5957
5958         /* Add items for the additional term windows */
5959         for( NSInteger i = 1; i < ANGBAND_TERM_MAX; i++ )
5960         {
5961             NSString *title = [NSString stringWithCString:angband_term_name[i]
5962 #ifdef JP
5963                                         encoding:NSJapaneseEUCStringEncoding
5964 #else
5965                                         encoding:NSMacOSRomanStringEncoding
5966 #endif
5967             ];
5968             NSString *keyEquivalent =
5969                 [NSString stringWithFormat: @"%ld", (long)i];
5970             NSMenuItem *windowItem =
5971                 [[NSMenuItem alloc] initWithTitle: title
5972                                     action: @selector(selectWindow:)
5973                                     keyEquivalent: keyEquivalent];
5974             [windowItem setTarget: self];
5975             [windowItem setTag: AngbandWindowMenuItemTagBase + i];
5976             [windowsMenu addItem: windowItem];
5977         }
5978     }
5979 }
5980
5981 /**
5982  *  Send a command to Angband via a menu item. This places the appropriate key
5983  * down events into the queue so that it seems like the user pressed them
5984  * (instead of trying to use the term directly).
5985  */
5986 - (void)sendAngbandCommand: (id)sender
5987 {
5988     NSMenuItem *menuItem = (NSMenuItem *)sender;
5989     NSString *command = [self.commandMenuTagMap objectForKey: [NSNumber numberWithInteger: [menuItem tag]]];
5990     AngbandContext* context =
5991         (__bridge AngbandContext*) (angband_term[0]->data);
5992     NSInteger windowNumber = [context.primaryWindow windowNumber];
5993
5994     /* Send a \ to bypass keymaps */
5995     NSEvent *escape = [NSEvent keyEventWithType: NSKeyDown
5996                                        location: NSZeroPoint
5997                                   modifierFlags: 0
5998                                       timestamp: 0.0
5999                                    windowNumber: windowNumber
6000                                         context: nil
6001                                      characters: @"\\"
6002                     charactersIgnoringModifiers: @"\\"
6003                                       isARepeat: NO
6004                                         keyCode: 0];
6005     [[NSApplication sharedApplication] postEvent: escape atStart: NO];
6006
6007     /* Send the actual command (from the original command set) */
6008     NSEvent *keyDown = [NSEvent keyEventWithType: NSKeyDown
6009                                         location: NSZeroPoint
6010                                    modifierFlags: 0
6011                                        timestamp: 0.0
6012                                     windowNumber: windowNumber
6013                                          context: nil
6014                                       characters: command
6015                      charactersIgnoringModifiers: command
6016                                        isARepeat: NO
6017                                          keyCode: 0];
6018     [[NSApplication sharedApplication] postEvent: keyDown atStart: NO];
6019 }
6020
6021 /**
6022  *  Set up the command menu dynamically, based on CommandMenu.plist.
6023  */
6024 - (void)prepareCommandMenu
6025 {
6026     @autoreleasepool {
6027         NSString *commandMenuPath =
6028             [[NSBundle mainBundle] pathForResource: @"CommandMenu"
6029                                    ofType: @"plist"];
6030         NSArray *commandMenuItems =
6031             [[NSArray alloc] initWithContentsOfFile: commandMenuPath];
6032         NSMutableDictionary *angbandCommands =
6033             [[NSMutableDictionary alloc] init];
6034         NSString *tblname = @"CommandMenu";
6035         NSInteger tagOffset = 0;
6036
6037         for( NSDictionary *item in commandMenuItems )
6038         {
6039             BOOL useShiftModifier =
6040                 [[item valueForKey: @"ShiftModifier"] boolValue];
6041             BOOL useOptionModifier =
6042                 [[item valueForKey: @"OptionModifier"] boolValue];
6043             NSUInteger keyModifiers = NSCommandKeyMask;
6044             keyModifiers |= (useShiftModifier) ? NSShiftKeyMask : 0;
6045             keyModifiers |= (useOptionModifier) ? NSAlternateKeyMask : 0;
6046
6047             NSString *lookup = [item valueForKey: @"Title"];
6048             NSString *title = NSLocalizedStringWithDefaultValue(
6049                 lookup, tblname, [NSBundle mainBundle], lookup, @"");
6050             NSString *key = [item valueForKey: @"KeyEquivalent"];
6051             NSMenuItem *menuItem =
6052                 [[NSMenuItem alloc] initWithTitle: title
6053                                     action: @selector(sendAngbandCommand:)
6054                                     keyEquivalent: key];
6055             [menuItem setTarget: self];
6056             [menuItem setKeyEquivalentModifierMask: keyModifiers];
6057             [menuItem setTag: AngbandCommandMenuItemTagBase + tagOffset];
6058             [self.commandMenu addItem: menuItem];
6059
6060             NSString *angbandCommand = [item valueForKey: @"AngbandCommand"];
6061             [angbandCommands setObject: angbandCommand
6062                              forKey: [NSNumber numberWithInteger: [menuItem tag]]];
6063             tagOffset++;
6064         }
6065
6066         self.commandMenuTagMap = [[NSDictionary alloc]
6067                                      initWithDictionary: angbandCommands];
6068     }
6069 }
6070
6071 - (void)awakeFromNib
6072 {
6073     [super awakeFromNib];
6074
6075     [self prepareWindowsMenu];
6076     [self prepareCommandMenu];
6077 }
6078
6079 - (void)applicationDidFinishLaunching:sender
6080 {
6081     [self beginGame];
6082     
6083     /* Once beginGame finished, the game is over - that's how Angband works,
6084          * and we should quit */
6085     game_is_finished = TRUE;
6086     [NSApp terminate:self];
6087 }
6088
6089 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
6090 {
6091     if (p_ptr->playing == FALSE || game_is_finished == TRUE)
6092     {
6093         quit_when_ready = true;
6094         return NSTerminateNow;
6095     }
6096     else if (! inkey_flag)
6097     {
6098         /* For compatibility with other ports, do not quit in this case */
6099         return NSTerminateCancel;
6100     }
6101     else
6102     {
6103         /* Stop playing */
6104         /* player->upkeep->playing = FALSE; */
6105
6106         /* Post an escape event so that we can return from our get-key-event
6107                  * function */
6108         wakeup_event_loop();
6109         quit_when_ready = true;
6110         /* Must return Cancel, not Later, because we need to get out of the
6111                  * run loop and back to Angband's loop */
6112         return NSTerminateCancel;
6113     }
6114 }
6115
6116 /**
6117  * Dynamically build the Graphics menu
6118  */
6119 - (void)menuNeedsUpdate:(NSMenu *)menu {
6120     
6121     /* Only the graphics menu is dynamic */
6122     if (! [menu isEqual:self.graphicsMenu])
6123         return;
6124     
6125     /* If it's non-empty, then we've already built it. Currently graphics modes
6126          * won't change once created; if they ever can we can remove this check.
6127      * Note that the check mark does change, but that's handled in
6128          * validateMenuItem: instead of menuNeedsUpdate: */
6129     if ([menu numberOfItems] > 0)
6130         return;
6131     
6132     /* This is the action for all these menu items */
6133     SEL action = @selector(setGraphicsMode:);
6134     
6135     /* Add an initial Classic ASCII menu item */
6136     NSString *tblname = @"GraphicsMenu";
6137     NSString *key = @"Classic ASCII";
6138     NSString *title = NSLocalizedStringWithDefaultValue(
6139         key, tblname, [NSBundle mainBundle], key, @"");
6140     NSMenuItem *classicItem = [menu addItemWithTitle:title action:action keyEquivalent:@""];
6141     [classicItem setTag:GRAPHICS_NONE];
6142     
6143     /* Walk through the list of graphics modes */
6144     if (graphics_modes) {
6145         NSInteger i;
6146
6147         for (i=0; graphics_modes[i].pNext; i++)
6148         {
6149             const graphics_mode *graf = &graphics_modes[i];
6150
6151             if (graf->grafID == GRAPHICS_NONE) {
6152                 continue;
6153             }
6154             /* Make the title. NSMenuItem throws on a nil title, so ensure it's
6155                    * not nil. */
6156             key = [[NSString alloc] initWithUTF8String:graf->menuname];
6157             title = NSLocalizedStringWithDefaultValue(
6158                 key, tblname, [NSBundle mainBundle], key, @"");
6159
6160             /* Make the item */
6161             NSMenuItem *item = [menu addItemWithTitle:title action:action keyEquivalent:@""];
6162             [item setTag:graf->grafID];
6163         }
6164     }
6165 }
6166
6167 /**
6168  * Delegate method that gets called if we're asked to open a file.
6169  */
6170 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
6171 {
6172     /* Can't open a file once we've started */
6173     if (game_in_progress) {
6174         [[NSApplication sharedApplication]
6175             replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
6176         return;
6177     }
6178
6179     /* We can only open one file. Use the last one. */
6180     NSString *file = [filenames lastObject];
6181     if (! file) {
6182         [[NSApplication sharedApplication]
6183             replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
6184         return;
6185     }
6186
6187     /* Put it in savefile */
6188     if (! [file getFileSystemRepresentation:savefile maxLength:sizeof savefile]) {
6189         [[NSApplication sharedApplication]
6190             replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
6191         return;
6192     }
6193
6194     game_in_progress = TRUE;
6195
6196     /* Wake us up in case this arrives while we're sitting at the Welcome
6197          * screen! */
6198     wakeup_event_loop();
6199
6200     [[NSApplication sharedApplication]
6201         replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
6202 }
6203
6204 @end
6205
6206 int main(int argc, char* argv[])
6207 {
6208     NSApplicationMain(argc, (void*)argv);
6209     return (0);
6210 }
6211
6212 #endif /* MACINTOSH || MACH_O_COCOA */