OSDN Git Service

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