OSDN Git Service

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