From 8665f47487e76b7317eca28aa7a5e26bb4223a3d Mon Sep 17 00:00:00 2001 From: Eric Branlund Date: Tue, 25 Feb 2020 21:29:06 -0800 Subject: [PATCH] To make further changes easier (to better match with current Apple documentation and code generated with Xcode), converted to automatic reference counting (ARC). To simplify cleanup, made some plain C structs (the storage for sound and pending changes to a terminal) into Objective-C classes. For similar reasons, and for better encapsulation, moved some file global variables into classes and promoted some instance variables in AngbandContext to full properties. For ease of access to the application deleget, made some static functions into instance functions of the application delegate. --- src/Makefile.am | 2 +- src/cocoa/AppDelegate.h | 4 + src/cocoa/AppDelegate.m | 12 + src/main-cocoa.m | 3701 ++++++++++++++++++++++++++--------------------- 4 files changed, 2073 insertions(+), 1646 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index bdfe4a523..846dbee63 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -133,7 +133,7 @@ EXTRA_DIST = \ if COCOA hengband_SOURCES += main-cocoa.m grafmode.h grafmode.c cocoa/AppDelegate.h AM_CFLAGS = -mmacosx-version-min=10.8 -Wunguarded-availability -AM_OBJCFLAGS = -mmacosx-version-min=10.8 -Wunguarded-availability +AM_OBJCFLAGS = -fobjc-arc -mmacosx-version-min=10.8 -Wunguarded-availability hengband_LDFLAGS = -framework cocoa $(AM_LDFLAGS) hengband_LINK = MACOSX_DEPLOYMENT_TARGET=10.8 $(OBJCLINK) $(hengband_LDFLAGS) $(LDFLAGS) -o $@ APPNAME = $(PACKAGE_NAME) diff --git a/src/cocoa/AppDelegate.h b/src/cocoa/AppDelegate.h index e7a6ffec1..dbe55e0ba 100644 --- a/src/cocoa/AppDelegate.h +++ b/src/cocoa/AppDelegate.h @@ -33,5 +33,9 @@ - (IBAction)toggleWideTiles:(NSMenuItem *)sender; - (void)setGraphicsMode:(NSMenuItem *)sender; - (void)selectWindow:(id)sender; +- (void)loadPrefs; +- (void)linkTermData:(int)termIndex; +- (void)initWindows; +- (void)beginGame; @end diff --git a/src/cocoa/AppDelegate.m b/src/cocoa/AppDelegate.m index 3a7450f8f..ed8a410a6 100644 --- a/src/cocoa/AppDelegate.m +++ b/src/cocoa/AppDelegate.m @@ -115,4 +115,16 @@ - (void)selectWindow:(id)sender { } +- (void)linkTermData:(int)termIndex { +} + +- (void)initWindows { +} + +- (void)loadPrefs { +} + +- (void)beginGame { +} + @end diff --git a/src/main-cocoa.m b/src/main-cocoa.m index d6ea506b9..c10020d7e 100644 --- a/src/main-cocoa.m +++ b/src/main-cocoa.m @@ -90,11 +90,292 @@ static Boolean game_is_finished = FALSE; /* Our frames per second (e.g. 60). A value of 0 means unthrottled. */ static int frames_per_second; -/* Function to get the default font */ -static NSFont *default_font; - @class AngbandView; +/** + * Load sound effects based on sound.cfg within the xtra/sound directory; + * bridge to Cocoa to use NSSound for simple loading and playback, avoiding + * I/O latency by caching all sounds at the start. Inherits full sound + * format support from Quicktime base/plugins. + * pelpel favoured a plist-based parser for the future but .cfg support + * improves cross-platform compatibility. + */ +@interface AngbandSoundCatalog : NSObject { +@private + /** + * Stores instances of NSSound keyed by path so the same sound can be + * used for multiple events. + */ + NSMutableDictionary *soundsByPath; + /** + * Stores arrays of NSSound keyed by event number. + */ + NSMutableDictionary *soundArraysByEvent; +} + +/** + * If NO, then playSound effectively becomes a do nothing operation. + */ +@property (getter=isEnabled) BOOL enabled; + +/** + * Set up for lazy initialization in playSound(). Set enabled to NO. + */ +- (id)init; + +/** + * Releases the resources acquired for the catalog. + */ +- (void)dispose; + +/** + * If self.enabled is YES and the given event has one or more sounds + * corresponding to it in the catalog, plays one of those sounds, chosen at + * random. + */ +- (void)playSound:(int)event; + +/** + * Impose an arbitary limit on number of possible samples per event. + * Currently not declaring this as a class property for compatibility with + * versions of Xcode prior to 8. + */ ++ (int)maxSamples; + +/** + * Return the shared sound catalog instance, creating it if it does not + * exist yet. Currently not declaring this as a class property for + * compatibility with versions of Xcode prior to 8. + */ ++ (AngbandSoundCatalog*)sharedSounds; + +/** + * Release any resouces associated with shared sounds. + */ ++ (void)clearSharedSounds; + +@end + +/** + * This is a helper function for AngbandSoundCatalog's sharedSounds and + * clearSharedSounds. Implement a Singleton pattern. + */ +static __strong AngbandSoundCatalog** get_shared_sounds(void) +{ + static __strong AngbandSoundCatalog* catalog = nil; + + return &catalog; +} + +@implementation AngbandSoundCatalog + +- (id)init { + if (self = [super init]) { + self->soundsByPath = nil; + self->soundArraysByEvent = nil; + self->_enabled = NO; + } + return self; +} + +- (void)dispose { + self->soundsByPath = nil; + self->soundArraysByEvent = nil; +} + +- (void)playSound:(int)event { + if (! self.enabled) { + return; + } + + /* Initialize when the first sound is played. */ + if (self->soundArraysByEvent == nil) { + /* Build the "sound" path */ + char sound_dir[1024]; + path_build(sound_dir, sizeof(sound_dir), ANGBAND_DIR_XTRA, "sound"); + + /* Find and open the config file */ + char path[1024]; + path_build(path, sizeof(path), sound_dir, "sound.cfg"); + FILE *fff = my_fopen(path, "r"); + + /* Handle errors */ + if (!fff) { + NSLog(@"The sound configuration file could not be opened."); + return; + } + + self->soundsByPath = [[NSMutableDictionary alloc] init]; + self->soundArraysByEvent = [[NSMutableDictionary alloc] init]; + @autoreleasepool { + /* + * This loop may take a while depending on the count and size of + * samples to load. + */ + + /* Parse the file */ + /* Lines are always of the form "name = sample [sample ...]" */ + char buffer[2048]; + while (my_fgets(fff, buffer, sizeof(buffer)) == 0) { + char *msg_name; + char *cfg_sample_list; + char *search; + char *cur_token; + char *next_token; + int event; + + /* Skip anything not beginning with an alphabetic character */ + if (!buffer[0] || !isalpha((unsigned char)buffer[0])) continue; + + /* Split the line into two: message name, and the rest */ + search = strchr(buffer, ' '); + cfg_sample_list = strchr(search + 1, ' '); + if (!search) continue; + if (!cfg_sample_list) continue; + + /* Set the message name, and terminate at first space */ + msg_name = buffer; + search[0] = '\0'; + + /* Make sure this is a valid event name */ + for (event = MSG_MAX - 1; event >= 0; event--) { + if (strcmp(msg_name, angband_sound_name[event]) == 0) + break; + } + if (event < 0) continue; + + /* + * Advance the sample list pointer so it's at the beginning of + * text. + */ + cfg_sample_list++; + if (!cfg_sample_list[0]) continue; + + /* Terminate the current token */ + cur_token = cfg_sample_list; + search = strchr(cur_token, ' '); + if (search) { + search[0] = '\0'; + next_token = search + 1; + } else { + next_token = NULL; + } + + /* + * Now we find all the sample names and add them one by one + */ + while (cur_token) { + NSMutableArray *soundSamples = + [self->soundArraysByEvent + objectForKey:[NSNumber numberWithInteger:event]]; + if (soundSamples == nil) { + soundSamples = [[NSMutableArray alloc] init]; + [self->soundArraysByEvent + setObject:soundSamples + forKey:[NSNumber numberWithInteger:event]]; + } + int num = (int) soundSamples.count; + + /* Don't allow too many samples */ + if (num >= [AngbandSoundCatalog maxSamples]) break; + + NSString *token_string = + [NSString stringWithUTF8String:cur_token]; + NSSound *sound = + [self->soundsByPath objectForKey:token_string]; + + if (! sound) { + /* + * We have to load the sound. Build the path to the + * sample. + */ + path_build(path, sizeof(path), sound_dir, cur_token); + struct stat stb; + if (stat(path, &stb) == 0) { + /* Load the sound into memory */ + sound = [[NSSound alloc] + initWithContentsOfFile:[NSString stringWithUTF8String:path] + byReference:YES]; + if (sound) { + [self->soundsByPath setObject:sound + forKey:token_string]; + } + } + } + + /* Store it if we loaded it */ + if (sound) { + [soundSamples addObject:sound]; + } + + /* Figure out next token */ + cur_token = next_token; + if (next_token) { + /* Try to find a space */ + search = strchr(cur_token, ' '); + + /* + * If we can find one, terminate, and set new "next". + */ + if (search) { + search[0] = '\0'; + next_token = search + 1; + } else { + /* Otherwise prevent infinite looping */ + next_token = NULL; + } + } + } + } + } + + /* Close the file */ + my_fclose(fff); + } + + @autoreleasepool { + NSMutableArray *samples = + [self->soundArraysByEvent + objectForKey:[NSNumber numberWithInteger:event]]; + + if (samples == nil || samples.count == 0) { + return; + } + + /* Choose a random event. */ + int s = randint0((int) samples.count); + NSSound *sound = samples[s]; + + if ([sound isPlaying]) + [sound stop]; + + /* Play the sound. */ + [sound play]; + } +} + ++ (int)maxSamples { + return 16; +} + +/** + * For sharedSounds and clearSharedSounds. + */ +static __strong AngbandSoundCatalog* gSharedSounds = nil; + ++ (AngbandSoundCatalog*)sharedSounds { + if (gSharedSounds == nil) { + gSharedSounds = [[AngbandSoundCatalog alloc] init]; + } + return gSharedSounds;; +} + ++ (void)clearSharedSounds { + gSharedSounds = nil; +} + +@end + /* * To handle fonts where an individual glyph's bounding box can extend into * neighboring columns, Term_curs_cocoa(), Term_pict_cocoa(), @@ -110,235 +391,582 @@ enum PendingCellChangeType { CELL_CHANGE_NONE = 0, CELL_CHANGE_WIPE, CELL_CHANGE_TEXT, - CELL_CHANGE_PICT + CELL_CHANGE_TILE }; -struct PendingCellChange { +struct PendingTextChange { + wchar_t glyph; + int color; /* - * For text rendering, stores the character as a wchar_t; for tile - * rendering, stores the column in the tile set for the source tile. + * Is YES if glyph is a character that takes up two columns (i.e. + * Japanese kanji); otherwise it is NO. */ - union { wchar_t w; char c; } c; - /* - * For text rendering, stores the color; for tile rendering, stores the - * row in the tile set for the source tile. - */ - TERM_COLOR a; - /* - * For text rendering, is one if wc is a character that takes up two - * columns (i.e. Japanese kanji); otherwise it is zero. For tile - * rendering, Stores the column in the tile set for the terrain tile. */ - char tcol; - /* - * For tile rendering, stores the row in the tile set for the - * terrain tile. - */ - TERM_COLOR trow; - enum PendingCellChangeType change_type; + BOOL doubleWidth; +}; +struct PendingTileChange { + char fgdCol, fgdRow, bckCol, bckRow; }; +struct PendingCellChange { + union { struct PendingTextChange txc; struct PendingTileChange tic; } v; + enum PendingCellChangeType changeType; +}; + +@interface PendingTermChanges : NSObject { +@private + int *colBounds; + struct PendingCellChange **changesByRow; +} + +/** + * Returns YES if nCol and nRow are a feasible size for the pending changes. + * Otherwise, returns NO. + */ ++ (BOOL)isValidSize:(int)nCol rows:(int)nRow; + +/** + * Initialize with zero columns and zero rows. + */ +- (id)init; + +/** + * Initialize with nCol columns and nRow rows. No changes will be marked. + */ +- (id)initWithColumnsRows:(int)nCol rows:(int)nRow NS_DESIGNATED_INITIALIZER; -struct PendingRowChange +/** + * Clears all marked changes. + */ +- (void)clear; + +/** + * Changes the bounds over which changes are recorded. Has the side effect + * of clearing any marked changes. Will throw an exception if nCol or nRow + * is negative. + */ +- (void)resize:(int)nCol rows:(int)nRow; + +/** + * Mark the cell, (iCol, iRow), as having changed text. + */ +- (void)markTextChange:(int)iCol row:(int)iRow glyph:(wchar_t)g color:(int)c + isDoubleWidth:(BOOL)dw; + +/** + * Mark the cell, (iCol, iRow), as having a changed tile. + */ +- (void)markTileChange:(int)iCol row:(int)iRow + foregroundCol:(char)fc foregroundRow:(char)fr + backgroundCol:(char)bc backgroundRow:(char)br; + +/** + * Mark the cells from (iCol, iRow) to (iCol + nCol - 1, iRow) as wiped. + */ +- (void)markWipeRange:(int)iCol row:(int)iRow n:(int)nCol; + +/** + * Mark the location of the cursor. The cursor will be the standard size: + * one cell. + */ +- (void)markCursor:(int)iCol row:(int)iRow; + +/** + * Mark the location of the cursor. The cursor will be w cells wide and + * h cells tall and the given location is the position of the upper left + * corner. + */ +- (void)markBigCursor:(int)iCol row:(int)iRow + cellsWide:(int)w cellsHigh:(int)h; + +/** + * Return the zero-based index of the first column changed for the given + * zero-based row index. If there are no changes in the row, the returned + * value will be the number of columns. + */ +- (int)getFirstChangedColumnInRow:(int)iRow; + +/** + * Return the zero-based index of the last column changed for the given + * zero-based row index. If there are no changes in the row, the returned + * value will be -1. + */ +- (int)getLastChangedColumnInRow:(int)iRow; + +/** + * Return the type of change at the given cell, (iCol, iRow). + */ +- (enum PendingCellChangeType)getCellChangeType:(int)iCol row:(int)iRow; + +/** + * Return the nature of a text change at the given cell, (iCol, iRow). + * Will throw an exception if [obj getCellChangeType:iCol row:iRow] is + * neither CELL_CHANGE_TEXT nor CELL_CHANGE_WIPE. + */ +- (struct PendingTextChange)getCellTextChange:(int)iCol row:(int)iRow; + +/** + * Return the nature of a tile change at the given cell, (iCol, iRow). + * Will throw an exception if [obj getCellChangeType:iCol row:iRow] is + * different than CELL_CHANGE_TILE. + */ +- (struct PendingTileChange)getCellTileChange:(int)iCol row:(int)iRow; + +/** + * Is the number of columns for recording changes. + */ +@property (readonly) int columnCount; + +/** + * Is the number of rows for recording changes. + */ +@property (readonly) int rowCount; + +/** + * Will be YES if there are any pending changes to locations rendered as text. + * Otherwise, it will be NO. + */ +@property (readonly) BOOL hasTextChanges; + +/** + * Will be YES if there are any pending changes to locations rendered as tiles. + * Otherwise, it will be NO. + */ +@property (readonly) BOOL hasTileChanges; + +/** + * Will be YES if there are any pending wipes. Otherwise, it will be NO. + */ +@property (readonly) BOOL hasWipeChanges; + +/** + * Is the zero-based index of the first row with changes. Will be equal to + * the number of rows if there are no changes. + */ +@property (readonly) int firstChangedRow; + +/** + * Is the zero-based index of the last row with changes. Will be equal to + * -1 if there are no changes. + */ +@property (readonly) int lastChangedRow; + +/** + * Is the zero-based index for the column with the upper left corner of the + * cursor. It will be -1 if the cursor position has not been set since the + * changes were cleared. + */ +@property (readonly) int cursorColumn; + +/** + * Is the zero-based index for the row with the upper left corner of the + * cursor. It will be -1 if the cursor position has not been set since the + * changes were cleared. + */ +@property (readonly) int cursorRow; + +/** + * Is the cursor width in number of cells. + */ +@property (readonly) int cursorWidth; + +/** + * Is the cursor height in number of cells. + */ +@property (readonly) int cursorHeight; + +/** + * This is a helper for the mark* messages. + */ +- (void)setupForChange:(int)iCol row:(int)iRow n:(int)nCol; + +/** + * Throw an exception if the given range of column indices is invalid + * (including non-positive values for nCol). + */ +- (void)checkColumnIndices:(int)iCol n:(int)nCol; + +/** + * Throw an exception if the given row index is invalid. + */ +- (void)checkRowIndex:(int)iRow; + +@end + +@implementation PendingTermChanges + ++ (BOOL)isValidSize:(int)nCol rows:(int)nRow { - /* - * These are the first and last columns, inclusive, that have been - * modified. xmin is greater than xmax if no changes have been made. - */ - int xmin, xmax; - /* - * This points to storage for a number of elements equal to the number - * of columns (implicitly gotten from the enclosing AngbandContext). - */ - struct PendingCellChange* cell_changes; -}; + if (nCol < 0 || + (size_t) nCol > SIZE_MAX / sizeof(struct PendingCellChange) || + nRow < 0 || + (size_t) nRow > SIZE_MAX / sizeof(struct PendingCellChange*) || + (size_t) nRow > SIZE_MAX / (2 * sizeof(int))) { + return NO; + } + return YES; +} -static struct PendingRowChange* create_row_change(int ncol) +- (id)init { - struct PendingRowChange* prc = - (struct PendingRowChange*) malloc(sizeof(struct PendingRowChange)); - struct PendingCellChange* pcc = (struct PendingCellChange*) - malloc(ncol * sizeof(struct PendingCellChange)); - int i; + return [self initWithColumnsRows:0 rows:0]; +} - if (prc == 0 || pcc == 0) { - if (pcc != 0) { - free(pcc); +- (id)initWithColumnsRows:(int)nCol rows:(int)nRow +{ + if (self = [super init]) { + if (! [PendingTermChanges isValidSize:nCol rows:nRow]) { + return nil; } - if (prc != 0) { - free(prc); + self->colBounds = malloc((size_t) 2 * sizeof(int) * nRow); + if (self->colBounds == 0 && nRow > 0) { + return nil; } - return 0; + self->changesByRow = calloc(nRow, sizeof(struct PendingCellChange*)); + if (self->changesByRow == 0 && nRow > 0) { + free(self->colBounds); + return nil; + } + for (int i = 0; i < nRow + nRow; i += 2) { + self->colBounds[i] = nCol; + self->colBounds[i + 1] = -1; + } + self->_columnCount = nCol; + self->_rowCount = nRow; + self->_hasTextChanges = NO; + self->_hasTileChanges = NO; + self->_hasWipeChanges = NO; + self->_firstChangedRow = nRow; + self->_lastChangedRow = -1; + self->_cursorColumn = -1; + self->_cursorRow = -1; + self->_cursorWidth = 1; + self->_cursorHeight = 1; } + return self; +} - prc->xmin = ncol; - prc->xmax = -1; - prc->cell_changes = pcc; - for (i = 0; i < ncol; ++i) { - pcc[i].change_type = CELL_CHANGE_NONE; +- (void)dealloc +{ + if (self->changesByRow != 0) { + for (int i = 0; i < self.rowCount; ++i) { + if (self->changesByRow[i] != 0) { + free(self->changesByRow[i]); + self->changesByRow[i] = 0; + } + } + free(self->changesByRow); + self->changesByRow = 0; + } + if (self->colBounds != 0) { + free(self->colBounds); + self->colBounds = 0; } - return prc; } - -static void destroy_row_change(struct PendingRowChange* prc) +- (void)clear { - if (prc != 0) { - if (prc->cell_changes != 0) { - free(prc->cell_changes); + for (int i = 0; i < self.rowCount; ++i) { + self->colBounds[i + i] = self.columnCount; + self->colBounds[i + i + 1] = -1; + if (self->changesByRow[i] != 0) { + free(self->changesByRow[i]); + self->changesByRow[i] = 0; } - free(prc); } + self->_hasTextChanges = NO; + self->_hasTileChanges = NO; + self->_hasWipeChanges = NO; + self->_firstChangedRow = self.rowCount; + self->_lastChangedRow = -1; + self->_cursorColumn = -1; + self->_cursorRow = -1; + self->_cursorWidth = 1; + self->_cursorHeight = 1; } - -struct PendingChanges +- (void)resize:(int)nCol rows:(int)nRow { - /* Hold the number of rows specified at creation. */ - int nrow; - /* - * Hold the position set for the software cursor. Use negative indices - * to indicate that the cursor is not displayed. - */ - int xcurs, ycurs; - /* Is nonzero if the cursor should be drawn at double the tile width. */ - int bigcurs; - /* Record whether the changes include any text, picts, or wipes. */ - int has_text, has_pict, has_wipe; - /* - * These are the first and last rows, inclusive, that have been - * modified. ymin is greater than ymax if no changes have been made. - */ - int ymin, ymax; - /* - * This is an array of pointers to the changes. The number of elements - * is the number of rows. An element will be a NULL pointer if no - * modifications have been made to the row. - */ - struct PendingRowChange** rows; -}; - + if (! [PendingTermChanges isValidSize:nCol rows:nRow]) { + NSException *exc = [NSException + exceptionWithName:@"PendingTermChangesRowsColumns" + reason:@"resize called with number of columns or rows that is negative or too large" + userInfo:nil]; + @throw exc; + } -static struct PendingChanges* create_pending_changes(int ncol, int nrow) -{ - struct PendingChanges* pc = - (struct PendingChanges*) malloc(sizeof(struct PendingChanges)); - struct PendingRowChange** pprc = (struct PendingRowChange**) - malloc(nrow * sizeof(struct PendingRowChange*)); + int *cb = malloc((size_t) 2 * sizeof(int) * nRow); + struct PendingCellChange** cbr = + calloc(nRow, sizeof(struct PendingCellChange*)); int i; - if (pc == 0 || pprc == 0) { - if (pprc != 0) { - free(pprc); + if ((cb == 0 || cbr == 0) && nRow > 0) { + if (cbr != 0) { + free(cbr); } - if (pc != 0) { - free(pc); + if (cb != 0) { + free(cb); } - return 0; + + NSException *exc = [NSException + exceptionWithName:@"OutOfMemory" + reason:@"resize called for PendingTermChanges" + userInfo:nil]; + @throw exc; } - pc->nrow = nrow; - pc->xcurs = -1; - pc->ycurs = -1; - pc->bigcurs = 0; - pc->has_text = 0; - pc->has_pict = 0; - pc->has_wipe = 0; - pc->ymin = nrow; - pc->ymax = -1; - pc->rows = pprc; - for (i = 0; i < nrow; ++i) { - pprc[i] = 0; + for (i = 0; i < nRow; ++i) { + cb[i + i] = nCol; + cb[i + i + 1] = -1; } - return pc; + if (self->changesByRow != 0) { + for (i = 0; i < self.rowCount; ++i) { + if (self->changesByRow[i] != 0) { + free(self->changesByRow[i]); + self->changesByRow[i] = 0; + } + } + free(self->changesByRow); + } + if (self->colBounds != 0) { + free(self->colBounds); + } + + self->colBounds = cb; + self->changesByRow = cbr; + self->_columnCount = nCol; + self->_rowCount = nRow; + self->_hasTextChanges = NO; + self->_hasTileChanges = NO; + self->_hasWipeChanges = NO; + self->_firstChangedRow = self.rowCount; + self->_lastChangedRow = -1; + self->_cursorColumn = -1; + self->_cursorRow = -1; + self->_cursorWidth = 1; + self->_cursorHeight = 1; } +- (void)markTextChange:(int)iCol row:(int)iRow glyph:(wchar_t)g color:(int)c + isDoubleWidth:(BOOL)dw +{ + [self setupForChange:iCol row:iRow n:((dw) ? 2 : 1)]; + struct PendingCellChange *pcc = self->changesByRow[iRow] + iCol; + pcc->v.txc.glyph = g; + pcc->v.txc.color = c; + pcc->v.txc.doubleWidth = dw; + pcc->changeType = CELL_CHANGE_TEXT; + /* + * Fill in a dummy since the previous character will take up two columns. + */ + if (dw) { + pcc[1].v.txc.glyph = 0; + pcc[1].v.txc.color = c; + pcc[1].v.txc.doubleWidth = NO; + pcc[1].changeType = CELL_CHANGE_TEXT; + } + self->_hasTextChanges = YES; +} -static void destroy_pending_changes(struct PendingChanges* pc) +- (void)markTileChange:(int)iCol row:(int)iRow + foregroundCol:(char)fc foregroundRow:(char)fr + backgroundCol:(char)bc backgroundRow:(char)br { - if (pc != 0) { - if (pc->rows != 0) { - int i; + [self setupForChange:iCol row:iRow n:1]; + struct PendingCellChange *pcc = self->changesByRow[iRow] + iCol; + pcc->v.tic.fgdCol = fc; + pcc->v.tic.fgdRow = fr; + pcc->v.tic.bckCol = bc; + pcc->v.tic.bckRow = br; + pcc->changeType = CELL_CHANGE_TILE; + self->_hasTileChanges = YES; +} - for (i = 0; i < pc->nrow; ++i) { - if (pc->rows[i] != 0) { - destroy_row_change(pc->rows[i]); - } - } - free(pc->rows); - } - free(pc); +- (void)markWipeRange:(int)iCol row:(int)iRow n:(int)nCol +{ + [self setupForChange:iCol row:iRow n:nCol]; + struct PendingCellChange *pcc = self->changesByRow[iRow] + iCol; + for (int i = 0; i < nCol; ++i) { + pcc[i].v.txc.glyph = 0; + pcc[i].v.txc.color = 0; + pcc[i].changeType = CELL_CHANGE_WIPE; } + self->_hasWipeChanges = YES; } +- (void)markCursor:(int)iCol row:(int)iRow +{ + /* Allow negative indices to indicate an invalid cursor. */ + [self checkColumnIndices:((iCol >= 0) ? iCol : 0) n:1]; + [self checkRowIndex:((iRow >= 0) ? iRow : 0)]; + self->_cursorColumn = iCol; + self->_cursorRow = iRow; + self->_cursorWidth = 1; + self->_cursorHeight = 1; +} -static void clear_pending_changes(struct PendingChanges* pc) +- (void)markBigCursor:(int)iCol row:(int)iRow + cellsWide:(int)w cellsHigh:(int)h { - pc->xcurs = -1; - pc->ycurs = -1; - pc->bigcurs = 0; - pc->has_text = 0; - pc->has_pict = 0; - pc->has_wipe = 0; - pc->ymin = pc->nrow; - pc->ymax = -1; - if (pc->rows != 0) { - int i; + /* Allow negative indices to indicate an invalid cursor. */ + [self checkColumnIndices:((iCol >= 0) ? iCol : 0) n:1]; + [self checkRowIndex:((iRow >= 0) ? iRow : 0)]; + if (w < 1 || h < 1) { + NSException *exc = [NSException + exceptionWithName:@"InvalidCursorDimensions" + reason:@"markBigCursor called for PendingTermChanges" + userInfo:nil]; + @throw exc; + } + self->_cursorColumn = iCol; + self->_cursorRow = iRow; + self->_cursorWidth = w; + self->_cursorHeight = h; +} - for (i = 0; i < pc->nrow; ++i) { - if (pc->rows[i] != 0) { - destroy_row_change(pc->rows[i]); - pc->rows[i] = 0; - } +- (void)setupForChange:(int)iCol row:(int)iRow n:(int)nCol +{ + [self checkColumnIndices:iCol n:nCol]; + [self checkRowIndex:iRow]; + if (self->changesByRow[iRow] == 0) { + self->changesByRow[iRow] = + malloc(self.columnCount * sizeof(struct PendingCellChange)); + if (self->changesByRow[iRow] == 0 && self.columnCount > 0) { + NSException *exc = [NSException + exceptionWithName:@"OutOfMemory" + reason:@"setupForChange called for PendingTermChanges" + userInfo:nil]; + @throw exc; } + struct PendingCellChange* pcc = self->changesByRow[iRow]; + for (int i = 0; i < self.columnCount; ++i) { + pcc[i].changeType = CELL_CHANGE_NONE; + } + } + if (self.firstChangedRow > iRow) { + self->_firstChangedRow = iRow; + } + if (self.lastChangedRow < iRow) { + self->_lastChangedRow = iRow; + } + if ([self getFirstChangedColumnInRow:iRow] > iCol) { + self->colBounds[iRow + iRow] = iCol; + } + if ([self getLastChangedColumnInRow:iRow] < iCol + nCol - 1) { + self->colBounds[iRow + iRow + 1] = iCol + nCol - 1; } } +- (int)getFirstChangedColumnInRow:(int)iRow +{ + [self checkRowIndex:iRow]; + return self->colBounds[iRow + iRow]; +} -/* Return zero if successful; otherwise return a nonzero value. */ -static int resize_pending_changes(struct PendingChanges* pc, int nrow) +- (int)getLastChangedColumnInRow:(int)iRow { - struct PendingRowChange** pprc; - int i; + [self checkRowIndex:iRow]; + return self->colBounds[iRow + iRow + 1]; +} + +- (enum PendingCellChangeType)getCellChangeType:(int)iCol row:(int)iRow +{ + [self checkColumnIndices:iCol n:1]; + [self checkRowIndex:iRow]; + if (iRow < self.firstChangedRow || iRow > self.lastChangedRow) { + return CELL_CHANGE_NONE; + } + if (iCol < [self getFirstChangedColumnInRow:iRow] || + iCol > [self getLastChangedColumnInRow:iRow]) { + return CELL_CHANGE_NONE; + } + return self->changesByRow[iRow][iCol].changeType; +} + +- (struct PendingTextChange)getCellTextChange:(int)iCol row:(int)iRow +{ + [self checkColumnIndices:iCol n:1]; + [self checkRowIndex:iRow]; + if (iRow < self.firstChangedRow || iRow > self.lastChangedRow || + iCol < [self getFirstChangedColumnInRow:iRow] || + iCol > [self getLastChangedColumnInRow:iRow] || + (self->changesByRow[iRow][iCol].changeType != CELL_CHANGE_TEXT && + self->changesByRow[iRow][iCol].changeType != CELL_CHANGE_WIPE)) { + NSException *exc = [NSException + exceptionWithName:@"NotTextChange" + reason:@"getCellTextChange called for PendingTermChanges" + userInfo:nil]; + @throw exc; + } + return self->changesByRow[iRow][iCol].v.txc; +} - if (pc == 0) { - return 1; +- (struct PendingTileChange)getCellTileChange:(int)iCol row:(int)iRow +{ + [self checkColumnIndices:iCol n:1]; + [self checkRowIndex:iRow]; + if (iRow < self.firstChangedRow || iRow > self.lastChangedRow || + iCol < [self getFirstChangedColumnInRow:iRow] || + iCol > [self getLastChangedColumnInRow:iRow] || + self->changesByRow[iRow][iCol].changeType != CELL_CHANGE_TILE) { + NSException *exc = [NSException + exceptionWithName:@"NotTileChange" + reason:@"getCellTileChange called for PendingTermChanges" + userInfo:nil]; + @throw exc; } + return self->changesByRow[iRow][iCol].v.tic; +} - pprc = (struct PendingRowChange**) - malloc(nrow * sizeof(struct PendingRowChange*)); - if (pprc == 0) { - return 1; +- (void)checkColumnIndices:(int)iCol n:(int)nCol +{ + if (iCol < 0) { + NSException *exc = [NSException + exceptionWithName:@"InvalidColumnIndex" + reason:@"negative column index" + userInfo:nil]; + @throw exc; + } + if (iCol >= self.columnCount || iCol + nCol > self.columnCount) { + NSException *exc = [NSException + exceptionWithName:@"InvalidColumnIndex" + reason:@"column index exceeds number of columns" + userInfo:nil]; + @throw exc; } - for (i = 0; i < nrow; ++i) { - pprc[i] = 0; + if (nCol <= 0) { + NSException *exc = [NSException + exceptionWithName:@"InvalidColumnIndex" + reason:@"empty column range" + userInfo:nil]; + @throw exc; } +} - if (pc->rows != 0) { - for (i = 0; i < pc->nrow; ++i) { - if (pc->rows[i] != 0) { - destroy_row_change(pc->rows[i]); - } - } - free(pc->rows); - } - pc->nrow = nrow; - pc->xcurs = -1; - pc->ycurs = -1; - pc->bigcurs = 0; - pc->has_text = 0; - pc->has_pict = 0; - pc->has_wipe = 0; - pc->ymin = nrow; - pc->ymax = -1; - pc->rows = pprc; - return 0; +- (void)checkRowIndex:(int)iRow +{ + if (iRow < 0) { + NSException *exc = [NSException + exceptionWithName:@"InvalidRowIndex" + reason:@"negative row index" + userInfo:nil]; + @throw exc; + } + if (iRow >= self.rowCount) { + NSException *exc = [NSException + exceptionWithName:@"InvalidRowIndex" + reason:@"row index exceeds number of rows" + userInfo:nil]; + @throw exc; + } } +@end + /* The max number of glyphs we support. Currently this only affects * updateGlyphInfo() for the calculation of the tile size, fontAscender, - * fontDescender, ncol_pre, and ncol_post (the glyphArray and glyphWidths - * members of AngbandContext are only used in updateGlyphInfo()). The - * rendering in drawWChar will work for glyphs not in updateGlyphInfo()'s - * set, and that is used for rendering Japanese characters. + * fontDescender, nColPre, and nColPost. The rendering in drawWChar() will + * work for a glyph not in updateGlyphInfo()'s set, and that is used for + * rendering Japanese characters, though there may be clipping or clearing + * artifacts because it wasn't included in updateGlyphInfo()'s calculations. */ #define GLYPH_COUNT 256 @@ -348,62 +976,59 @@ static int resize_pending_changes(struct PendingChanges* pc, int nrow) @interface AngbandContext : NSObject { @public - + /* The Angband term */ term *terminal; - - /* Column and row cont, by default 80 x 24 */ - size_t cols; - size_t rows; - - /* The size of the border between the window edge and the contents */ - NSSize borderSize; - - /* Our array of views */ - NSMutableArray *angbandViews; - - /* The buffered image */ - CGLayerRef angbandLayer; - /* The font of this context */ - NSFont *angbandViewFont; - - /* If this context owns a window, here it is */ - NSWindow *primaryWindow; - - /* "Glyph info": an array of the CGGlyphs and their widths corresponding to - * the above font. */ - CGGlyph glyphArray[GLYPH_COUNT]; - CGFloat glyphWidths[GLYPH_COUNT]; - - /* The size of one tile */ - NSSize tileSize; - - /* Font's ascender and descender */ - CGFloat fontAscender, fontDescender; - - /* Whether we are currently in live resize, which affects how big we render - * our image */ - int inLiveResize; - - /* Last time we drew, so we can throttle drawing */ +@private + /* Is the last time we drew, so we can throttle drawing. */ CFAbsoluteTime lastRefreshTime; - struct PendingChanges* changes; /* - * These are the number of columns before or after, respectively, a text - * change that may need to be redrawn. + * Whether we are currently in live resize, which affects how big we + * render our image. */ - int ncol_pre, ncol_post; + int inLiveResize; /* Flags whether or not a fullscreen transition is in progress. */ - BOOL in_fullscreen_transition; + BOOL inFullscreenTransition; +} -@private +/* Column and row counts, by default 80 x 24 */ +@property int cols; +@property int rows; - BOOL _hasSubwindowFlags; - BOOL _windowVisibilityChecked; -} +/* The size of the border between the window edge and the contents */ +@property (readonly) NSSize borderSize; + +/* Our array of views */ +@property NSMutableArray *angbandViews; + +/* The buffered image */ +@property CGLayerRef angbandLayer; + +/* The font of this context */ +@property NSFont *angbandViewFont; + +/* The size of one tile */ +@property (readonly) NSSize tileSize; + +/* Font's ascender and descender */ +@property (readonly) CGFloat fontAscender; +@property (readonly) CGFloat fontDescender; + +/* + * These are the number of columns before or after, respectively, a text + * change that may need to be redrawn. + */ +@property (readonly) int nColPre; +@property (readonly) int nColPost; + +/* If this context owns a window, here it is. */ +@property NSWindow *primaryWindow; + +/* Is the record of changes to the contents for the next update. */ +@property PendingTermChanges *changes; @property (nonatomic, assign) BOOL hasSubwindowFlags; @property (nonatomic, assign) BOOL windowVisibilityChecked; @@ -480,6 +1105,17 @@ static int resize_pending_changes(struct PendingChanges* pc, int nrow) - (void)saveWindowVisibleToDefaults: (BOOL)windowVisible; - (BOOL)windowVisibleUsingDefaults; +/* Class methods */ +/** + * Gets the default font for all contexts. Currently not declaring this as + * a class property for compatibility with versions of Xcode prior to 8. + */ ++ (NSFont*)defaultFont; +/** + * Sets the default font for all contexts. + */ ++ (void)setDefaultFont:(NSFont*)font; + /* Internal method */ - (AngbandView *)activeView; @@ -529,7 +1165,8 @@ static void AngbandUpdateWindowVisibility(void) * the flags themselves; we just want to know if any are set. */ for( int i = 1; i < ANGBAND_TERM_MAX; i++ ) { - AngbandContext *angbandContext = angband_term[i]->data; + AngbandContext *angbandContext = + (__bridge AngbandContext*) (angband_term[i]->data); if( angbandContext == nil ) { @@ -546,12 +1183,12 @@ static void AngbandUpdateWindowVisibility(void) { if( [angbandContext windowVisibleUsingDefaults] ) { - [angbandContext->primaryWindow orderFront: nil]; + [angbandContext.primaryWindow orderFront: nil]; angbandContext.windowVisibilityChecked = YES; } else { - [angbandContext->primaryWindow close]; + [angbandContext.primaryWindow close]; angbandContext.windowVisibilityChecked = NO; } } @@ -561,13 +1198,13 @@ static void AngbandUpdateWindowVisibility(void) if( angbandContext.hasSubwindowFlags && !termHasSubwindowFlags ) { - [angbandContext->primaryWindow close]; + [angbandContext.primaryWindow close]; angbandContext.hasSubwindowFlags = NO; [angbandContext saveWindowVisibleToDefaults: NO]; } else if( !angbandContext.hasSubwindowFlags && termHasSubwindowFlags ) { - [angbandContext->primaryWindow orderFront: nil]; + [angbandContext.primaryWindow orderFront: nil]; angbandContext.hasSubwindowFlags = YES; [angbandContext saveWindowVisibleToDefaults: YES]; } @@ -575,10 +1212,12 @@ static void AngbandUpdateWindowVisibility(void) } /* Make the main window key so that user events go to the right spot */ - AngbandContext *mainWindow = angband_term[0]->data; - [mainWindow->primaryWindow makeKeyAndOrderFront: nil]; + AngbandContext *mainWindow = + (__bridge AngbandContext*) (angband_term[0]->data); + [mainWindow.primaryWindow makeKeyAndOrderFront: nil]; } + /** * ------------------------------------------------------------------------ * Graphics support @@ -626,9 +1265,6 @@ static NSString* get_lib_directory(void); static NSString* get_doc_directory(void); static NSString* AngbandCorrectedDirectoryPath(NSString *originalPath); static void prepare_paths_and_directories(void); -static void load_prefs(void); -static void load_sounds(void); -static void init_windows(void); static void handle_open_when_ready(void); static void play_sound(int event); static BOOL check_events(int wait); @@ -688,10 +1324,7 @@ static bool initialized = FALSE; { NSBundle *bundle = [NSBundle bundleForClass:[AngbandView class]]; NSString *path = [bundle pathForImageResource:name]; - NSImage *result; - if (path) result = [[[NSImage alloc] initByReferencingFile:path] autorelease]; - else result = nil; - return result; + return (path) ? [[NSImage alloc] initByReferencingFile:path] : nil; } @end @@ -702,16 +1335,11 @@ static bool initialized = FALSE; @synthesize hasSubwindowFlags=_hasSubwindowFlags; @synthesize windowVisibilityChecked=_windowVisibilityChecked; -- (NSFont *)selectionFont -{ - return angbandViewFont; -} - - (BOOL)useLiveResizeOptimization { /* If we have graphics turned off, text rendering is fast enough that we * don't need to use a live resize optimization. */ - return inLiveResize && graphics_are_enabled(); + return self->inLiveResize && graphics_are_enabled(); } - (NSSize)baseSize @@ -720,7 +1348,9 @@ static bool initialized = FALSE; * with pixels that nobody "owns" that may accumulate garbage. In general * rounding down is harmless, because any lost pixels may be sopped up by * the border. */ - return NSMakeSize(floor(cols * tileSize.width + 2 * borderSize.width), floor(rows * tileSize.height + 2 * borderSize.height)); + return NSMakeSize( + floor(self.cols * self.tileSize.width + 2 * self.borderSize.width), + floor(self.rows * self.tileSize.height + 2 * self.borderSize.height)); } /* qsort-compatible compare function for CGSizes */ @@ -730,44 +1360,93 @@ static int compare_advances(const void *ap, const void *bp) return (a->width > b->width) - (a->width < b->width); } +/** + * Precompute certain metrics (tileSize, fontAscender, fontDescender, nColPre, + * and nColPost) for the current font. + */ - (void)updateGlyphInfo { - /* Update glyphArray and glyphWidths */ - NSFont *screenFont = [angbandViewFont screenFont]; + NSFont *screenFont = [self.angbandViewFont screenFont]; /* Generate a string containing each MacRoman character */ - unsigned char latinString[GLYPH_COUNT]; + /* + * Here and below, dynamically allocate working arrays rather than put them + * on the stack in case limited stack space is an issue. + */ + unsigned char *latinString = malloc(GLYPH_COUNT); + if (latinString == 0) { + NSException *exc = [NSException exceptionWithName:@"OutOfMemory" + reason:@"latinString in updateGlyphInfo" + userInfo:nil]; + @throw exc; + } size_t i; for (i=0; i < GLYPH_COUNT; i++) latinString[i] = (unsigned char)i; - + /* Turn that into unichar. Angband uses ISO Latin 1. */ - unichar unicharString[GLYPH_COUNT] = {0}; NSString *allCharsString = [[NSString alloc] initWithBytes:latinString length:sizeof latinString encoding:NSISOLatin1StringEncoding]; + unichar *unicharString = malloc(GLYPH_COUNT * sizeof(unichar)); + if (unicharString == 0) { + free(latinString); + NSException *exc = [NSException exceptionWithName:@"OutOfMemory" + reason:@"unicharString in updateGlyphInfo" + userInfo:nil]; + @throw exc; + } + unicharString[0] = 0; [allCharsString getCharacters:unicharString range:NSMakeRange(0, MIN(GLYPH_COUNT, [allCharsString length]))]; - [allCharsString autorelease]; - + allCharsString = nil; + free(latinString); + /* Get glyphs */ - memset(glyphArray, 0, sizeof glyphArray); - CTFontGetGlyphsForCharacters((CTFontRef)screenFont, unicharString, glyphArray, GLYPH_COUNT); - + CGGlyph *glyphArray = calloc(GLYPH_COUNT, sizeof(CGGlyph)); + if (glyphArray == 0) { + free(unicharString); + NSException *exc = [NSException exceptionWithName:@"OutOfMemory" + reason:@"glyphArray in updateGlyphInfo" + userInfo:nil]; + @throw exc; + } + CTFontGetGlyphsForCharacters((CTFontRef)screenFont, unicharString, + glyphArray, GLYPH_COUNT); + free(unicharString); + /* Get advances. Record the max advance. */ - CGSize advances[GLYPH_COUNT] = {}; - CTFontGetAdvancesForGlyphs((CTFontRef)screenFont, kCTFontHorizontalOrientation, glyphArray, advances, GLYPH_COUNT); + CGSize *advances = malloc(GLYPH_COUNT * sizeof(CGSize)); + if (advances == 0) { + free(glyphArray); + NSException *exc = [NSException exceptionWithName:@"OutOfMemory" + reason:@"advances in updateGlyphInfo" + userInfo:nil]; + @throw exc; + } + CTFontGetAdvancesForGlyphs( + (CTFontRef)screenFont, kCTFontHorizontalOrientation, glyphArray, + advances, GLYPH_COUNT); + CGFloat *glyphWidths = malloc(GLYPH_COUNT * sizeof(CGFloat)); + if (glyphWidths == 0) { + free(glyphArray); + free(advances); + NSException *exc = [NSException exceptionWithName:@"OutOfMemory" + reason:@"glyphWidths in updateGlyphInfo" + userInfo:nil]; + @throw exc; + } for (i=0; i < GLYPH_COUNT; i++) { glyphWidths[i] = advances[i].width; } - + /* For good non-mono-font support, use the median advance. Start by sorting * all advances. */ qsort(advances, GLYPH_COUNT, sizeof *advances, compare_advances); - + /* Skip over any initially empty run */ size_t startIdx; for (startIdx = 0; startIdx < GLYPH_COUNT; startIdx++) { if (advances[startIdx].width > 0) break; } - + /* Pick the center to find the median */ CGFloat medianAdvance = 0; if (startIdx < GLYPH_COUNT) @@ -775,7 +1454,9 @@ static int compare_advances(const void *ap, const void *bp) /* In case we have all zero advances for some reason */ medianAdvance = advances[(startIdx + GLYPH_COUNT)/2].width; } - + + free(advances); + /* * Record the ascender and descender. Some fonts, for instance DIN * Condensed and Rockwell in 10.14, the ascent on '@' exceeds that @@ -783,28 +1464,38 @@ static int compare_advances(const void *ap, const void *bp) * for the glyphs and use that instead of the ascender and descender * values if the bounding box result extends farther from the baseline. */ - CGRect bounds = CTFontGetBoundingRectsForGlyphs((CTFontRef) screenFont, kCTFontHorizontalOrientation, glyphArray, NULL, GLYPH_COUNT); - fontAscender = [screenFont ascender]; - if (fontAscender < bounds.origin.y + bounds.size.height) { - fontAscender = bounds.origin.y + bounds.size.height; + CGRect bounds = CTFontGetBoundingRectsForGlyphs( + (CTFontRef) screenFont, kCTFontHorizontalOrientation, glyphArray, + NULL, GLYPH_COUNT); + self->_fontAscender = [screenFont ascender]; + if (self->_fontAscender < bounds.origin.y + bounds.size.height) { + self->_fontAscender = bounds.origin.y + bounds.size.height; } - fontDescender = [screenFont descender]; - if (fontDescender > bounds.origin.y) { - fontDescender = bounds.origin.y; + self->_fontDescender = [screenFont descender]; + if (self->_fontDescender > bounds.origin.y) { + self->_fontDescender = bounds.origin.y; } /* * Record the tile size. Round both values up to have tile boundaries * match pixel boundaries. */ - tileSize.width = ceil(medianAdvance); - tileSize.height = ceil(fontAscender - fontDescender); + self->_tileSize.width = ceil(medianAdvance); + self->_tileSize.height = ceil(self.fontAscender - self.fontDescender); /* - * Determine whether neighboring columns need to redrawn when a character - * changes. + * Determine whether neighboring columns need to be redrawn when a + * character changes. */ - CGRect boxes[GLYPH_COUNT] = {}; + CGRect *boxes = malloc(GLYPH_COUNT * sizeof(CGRect)); + if (boxes == 0) { + free(glyphWidths); + free(glyphArray); + NSException *exc = [NSException exceptionWithName:@"OutOfMemory" + reason:@"boxes in updateGlyphInfo" + userInfo:nil]; + @throw exc; + } CGFloat beyond_right = 0.; CGFloat beyond_left = 0.; CTFontGetBoundingRectsForGlyphs( @@ -818,11 +1509,11 @@ static int compare_advances(const void *ap, const void *bp) CGFloat compression, offset; CGFloat v; - if (glyphWidths[i] <= tileSize.width) { + if (glyphWidths[i] <= self.tileSize.width) { compression = 1.; - offset = 0.5 * (tileSize.width - glyphWidths[i]); + offset = 0.5 * (self.tileSize.width - glyphWidths[i]); } else { - compression = tileSize.width / glyphWidths[i]; + compression = self.tileSize.width / glyphWidths[i]; offset = 0.; } v = (offset + boxes[i].origin.x) * compression; @@ -834,12 +1525,17 @@ static int compare_advances(const void *ap, const void *bp) beyond_right = v; } } - ncol_pre = ceil(-beyond_left / tileSize.width); - if (beyond_right > tileSize.width) { - ncol_post = ceil((beyond_right - tileSize.width) / tileSize.width); + free(boxes); + self->_nColPre = ceil(-beyond_left / self.tileSize.width); + if (beyond_right > self.tileSize.width) { + self->_nColPost = + ceil((beyond_right - self.tileSize.width) / self.tileSize.width); } else { - ncol_post = 0; + self->_nColPost = 0; } + + free(glyphWidths); + free(glyphArray); } - (void)updateImage @@ -863,7 +1559,7 @@ static int compare_advances(const void *ap, const void *bp) } } - CGLayerRelease(angbandLayer); + CGLayerRelease(self.angbandLayer); /* Use the highest monitor scale factor on the system to work out what * scale to draw at - not the recommended method, but works where we @@ -883,12 +1579,13 @@ static int compare_advances(const void *ap, const void *bp) /* Create the layer at the appropriate size */ size.width = fmax(1, ceil(size.width * angbandLayerScale)); size.height = fmax(1, ceil(size.height * angbandLayerScale)); - angbandLayer = CGLayerCreateWithContext(exampleCtx, *(CGSize *)&size, NULL); + self.angbandLayer = + CGLayerCreateWithContext(exampleCtx, *(CGSize *)&size, NULL); CFRelease(exampleCtx); /* Set the new context of the layer to draw at the correct scale */ - CGContextRef ctx = CGLayerGetContext(angbandLayer); + CGContextRef ctx = CGLayerGetContext(self.angbandLayer); CGContextScaleCTM(ctx, angbandLayerScale, angbandLayerScale); [self lockFocus]; @@ -918,16 +1615,16 @@ static int compare_advances(const void *ap, const void *bp) - (void)setTerm:(term *)t { - terminal = t; + self->terminal = t; } - (void)viewWillStartLiveResize:(AngbandView *)view { #if USE_LIVE_RESIZE_CACHE - if (inLiveResize < INT_MAX) inLiveResize++; + if (self->inLiveResize < INT_MAX) self->inLiveResize++; else [NSException raise:NSInternalInconsistencyException format:@"inLiveResize overflow"]; - if (inLiveResize == 1 && graphics_are_enabled()) + if (self->inLiveResize == 1 && graphics_are_enabled()) { [self updateImage]; @@ -940,10 +1637,10 @@ static int compare_advances(const void *ap, const void *bp) - (void)viewDidEndLiveResize:(AngbandView *)view { #if USE_LIVE_RESIZE_CACHE - if (inLiveResize > 0) inLiveResize--; + if (self->inLiveResize > 0) self->inLiveResize--; else [NSException raise:NSInternalInconsistencyException format:@"inLiveResize underflow"]; - if (inLiveResize == 0 && graphics_are_enabled()) + if (self->inLiveResize == 0 && graphics_are_enabled()) { [self updateImage]; @@ -962,7 +1659,7 @@ static int compare_advances(const void *ap, const void *bp) if (frames_per_second > 0) { CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); - CFTimeInterval timeSinceLastRefresh = now - lastRefreshTime; + CFTimeInterval timeSinceLastRefresh = now - self->lastRefreshTime; CFTimeInterval timeUntilNextRefresh = (1. / (double)frames_per_second) - timeSinceLastRefresh; if (timeUntilNextRefresh > 0) @@ -970,14 +1667,14 @@ static int compare_advances(const void *ap, const void *bp) usleep((unsigned long)(timeUntilNextRefresh * 1000000.)); } } - lastRefreshTime = CFAbsoluteTimeGetCurrent(); + self->lastRefreshTime = CFAbsoluteTimeGetCurrent(); } - (void)drawWChar:(wchar_t)wchar inRect:(NSRect)tile context:(CGContextRef)ctx { - CGFloat tileOffsetY = fontAscender; + CGFloat tileOffsetY = self.fontAscender; CGFloat tileOffsetX = 0.0; - NSFont *screenFont = [angbandViewFont screenFont]; + NSFont *screenFont = [self.angbandViewFont screenFont]; UniChar unicharString[2] = {(UniChar)wchar, 0}; /* Get glyph and advance */ @@ -1041,7 +1738,7 @@ static int compare_advances(const void *ap, const void *bp) - (CGContextRef)lockFocusUnscaled { /* Create an NSGraphicsContext representing this CGLayer */ - CGContextRef ctx = CGLayerGetContext(angbandLayer); + CGContextRef ctx = CGLayerGetContext(self.angbandLayer); NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:NO]; [NSGraphicsContext saveGraphicsState]; [NSGraphicsContext setCurrentContext:context]; @@ -1060,7 +1757,7 @@ static int compare_advances(const void *ap, const void *bp) - (NSSize)imageSize { /* Return the size of our layer */ - CGSize result = CGLayerGetSize(angbandLayer); + CGSize result = CGLayerGetSize(self.angbandLayer); return NSMakeSize(result.width, result.height); } @@ -1073,16 +1770,16 @@ static int compare_advances(const void *ap, const void *bp) - (NSRect)rectInImageForTileAtX:(int)x Y:(int)y { int flippedY = y; - return NSMakeRect(x * tileSize.width + borderSize.width, flippedY * tileSize.height + borderSize.height, tileSize.width, tileSize.height); + return NSMakeRect(x * self.tileSize.width + self.borderSize.width, + flippedY * self.tileSize.height + self.borderSize.height, + self.tileSize.width, self.tileSize.height); } - (void)setSelectionFont:(NSFont*)font adjustTerminal: (BOOL)adjustTerminal { /* Record the new font */ - [font retain]; - [angbandViewFont release]; - angbandViewFont = font; - + self.angbandViewFont = font; + /* Update our glyph info */ [self updateGlyphInfo]; @@ -1090,10 +1787,12 @@ static int compare_advances(const void *ap, const void *bp) { /* Adjust terminal to fit window with new font; save the new columns * and rows since they could be changed */ - NSRect contentRect = [self->primaryWindow contentRectForFrameRect: [self->primaryWindow frame]]; + NSRect contentRect = + [self.primaryWindow + contentRectForFrameRect: [self.primaryWindow frame]]; [self setMinimumWindowSize:[self terminalIndex]]; - NSSize size = self->primaryWindow.contentMinSize; + NSSize size = self.primaryWindow.contentMinSize; BOOL windowNeedsResizing = NO; if (contentRect.size.width < size.width) { contentRect.size.width = size.width; @@ -1106,7 +1805,7 @@ static int compare_advances(const void *ap, const void *bp) if (windowNeedsResizing) { size.width = contentRect.size.width; size.height = contentRect.size.height; - [self->primaryWindow setContentSize:size]; + [self.primaryWindow setContentSize:size]; } [self resizeTerminalWithContentRect: contentRect saveToDefaults: YES]; } @@ -1120,28 +1819,29 @@ static int compare_advances(const void *ap, const void *bp) if ((self = [super init])) { /* Default rows and cols */ - self->cols = 80; - self->rows = 24; + self->_cols = 80; + self->_rows = 24; /* Default border size */ - self->borderSize = NSMakeSize(2, 2); + self->_borderSize = NSMakeSize(2, 2); /* Allocate our array of views */ - angbandViews = [[NSMutableArray alloc] init]; - - self->changes = create_pending_changes(self->cols, self->rows); - if (self->changes == 0) { - NSLog(@"AngbandContext init: out of memory for pending changes"); - } - self->ncol_pre = 0; - self->ncol_post = 0; + self->_angbandViews = [[NSMutableArray alloc] init]; + + self->_nColPre = 0; + self->_nColPost = 0; - self->in_fullscreen_transition = NO; + self->_changes = + [[PendingTermChanges alloc] initWithColumnsRows:self->_cols + rows:self->_rows]; + self->lastRefreshTime = CFAbsoluteTimeGetCurrent(); + self->inLiveResize = 0; + self->inFullscreenTransition = NO; /* Make the image. Since we have no views, it'll just be a puny 1x1 image. */ [self updateImage]; - _windowVisibilityChecked = NO; + self->_windowVisibilityChecked = NO; } return self; } @@ -1152,37 +1852,32 @@ static int compare_advances(const void *ap, const void *bp) */ - (void)dispose { - terminal = NULL; - + self->terminal = NULL; + /* Disassociate ourselves from our angbandViews */ - [angbandViews makeObjectsPerformSelector:@selector(setAngbandContext:) withObject:nil]; - [angbandViews release]; - angbandViews = nil; - + [self.angbandViews makeObjectsPerformSelector:@selector(setAngbandContext:) withObject:nil]; + self.angbandViews = nil; + /* Destroy the layer/image */ - CGLayerRelease(angbandLayer); - angbandLayer = NULL; + CGLayerRelease(self.angbandLayer); + self.angbandLayer = NULL; /* Font */ - [angbandViewFont release]; - angbandViewFont = nil; - + self.angbandViewFont = nil; + /* Window */ - [primaryWindow setDelegate:nil]; - [primaryWindow close]; - [primaryWindow release]; - primaryWindow = nil; + [self.primaryWindow setDelegate:nil]; + [self.primaryWindow close]; + self.primaryWindow = nil; /* Pending changes */ - destroy_pending_changes(self->changes); - self->changes = 0; + self.changes = nil; } /* Usual Cocoa fare */ - (void)dealloc { [self dispose]; - [super dealloc]; } #if 0 @@ -1234,17 +1929,32 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) } return count; } -#endif +#endif + +- (void)addAngbandView:(AngbandView *)view +{ + if (! [self.angbandViews containsObject:view]) + { + [self.angbandViews addObject:view]; + [self updateImage]; + [self setNeedsDisplay:YES]; /* We'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */ + [self requestRedraw]; + } +} + +/** + * For defaultFont and setDefaultFont. + */ +static __strong NSFont* gDefaultFont = nil; + ++ (NSFont*)defaultFont +{ + return gDefaultFont; +} -- (void)addAngbandView:(AngbandView *)view ++ (void)setDefaultFont:(NSFont*)font { - if (! [angbandViews containsObject:view]) - { - [angbandViews addObject:view]; - [self updateImage]; - [self setNeedsDisplay:YES]; /* We'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */ - [self requestRedraw]; - } + gDefaultFont = font; } /** @@ -1256,12 +1966,12 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) */ - (AngbandView *)activeView { - if ([angbandViews count] == 1) - return [angbandViews objectAtIndex:0]; - + if ([self.angbandViews count] == 1) + return [self.angbandViews objectAtIndex:0]; + AngbandView *result = nil; float maxWidth = 0; - for (AngbandView *angbandView in angbandViews) + for (AngbandView *angbandView in self.angbandViews) { float width = [angbandView frame].size.width; if (width > maxWidth) @@ -1277,7 +1987,7 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) { /* If we're live-resizing with graphics, we're using the live resize * optimization, so don't update the image. Otherwise do it. */ - if (! (inLiveResize && graphics_are_enabled()) && view == [self activeView]) + if (! (self->inLiveResize && graphics_are_enabled()) && view == [self activeView]) { [self updateImage]; @@ -1289,55 +1999,53 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) - (void)removeAngbandView:(AngbandView *)view { - if ([angbandViews containsObject:view]) + if ([self.angbandViews containsObject:view]) { - [angbandViews removeObject:view]; + [self.angbandViews removeObject:view]; [self updateImage]; [self setNeedsDisplay:YES]; /* We'll need to redisplay everything anyways, so avoid creating all those little redisplay rects */ - if ([angbandViews count]) [self requestRedraw]; + if ([self.angbandViews count]) [self requestRedraw]; } } - (NSWindow *)makePrimaryWindow { - if (! primaryWindow) + if (! self.primaryWindow) { /* This has to be done after the font is set, which it already is in * term_init_cocoa() */ - CGFloat width = self->cols * tileSize.width + borderSize.width * 2.0; - CGFloat height = self->rows * tileSize.height + borderSize.height * 2.0; - NSRect contentRect = NSMakeRect( 0.0, 0.0, width, height ); + NSSize sz = self.baseSize; + NSRect contentRect = NSMakeRect( 0.0, 0.0, sz.width, sz.height ); NSUInteger styleMask = NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask; /* Make every window other than the main window closable */ - if( angband_term[0]->data != self ) + if ((__bridge AngbandContext*) (angband_term[0]->data) != self) { styleMask |= NSClosableWindowMask; } - primaryWindow = [[NSWindow alloc] initWithContentRect:contentRect styleMask: styleMask backing:NSBackingStoreBuffered defer:YES]; + self.primaryWindow = [[NSWindow alloc] initWithContentRect:contentRect styleMask: styleMask backing:NSBackingStoreBuffered defer:YES]; /* Not to be released when closed */ - [primaryWindow setReleasedWhenClosed:NO]; - [primaryWindow setExcludedFromWindowsMenu: YES]; /* we're using custom window menu handling */ + [self.primaryWindow setReleasedWhenClosed:NO]; + [self.primaryWindow setExcludedFromWindowsMenu: YES]; /* we're using custom window menu handling */ /* Make the view */ AngbandView *angbandView = [[AngbandView alloc] initWithFrame:contentRect]; [angbandView setAngbandContext:self]; - [angbandViews addObject:angbandView]; - [primaryWindow setContentView:angbandView]; - [angbandView release]; + [self.angbandViews addObject:angbandView]; + [self.primaryWindow setContentView:angbandView]; /* We are its delegate */ - [primaryWindow setDelegate:self]; + [self.primaryWindow setDelegate:self]; /* Update our image, since this is probably the first angband view * we've gotten. */ [self updateImage]; } - return primaryWindow; + return self.primaryWindow; } @@ -1359,23 +2067,23 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) NSRect bounds = [view bounds]; if (viewInLiveResize) CGContextSetInterpolationQuality(context, kCGInterpolationLow); CGContextSetBlendMode(context, kCGBlendModeCopy); - CGContextDrawLayerInRect(context, *(CGRect *)&bounds, angbandLayer); + CGContextDrawLayerInRect(context, *(CGRect *)&bounds, self.angbandLayer); if (viewInLiveResize) CGContextSetInterpolationQuality(context, kCGInterpolationDefault); } - (BOOL)isOrderedIn { - return [[[angbandViews lastObject] window] isVisible]; + return [[[self.angbandViews lastObject] window] isVisible]; } - (BOOL)isMainWindow { - return [[[angbandViews lastObject] window] isMainWindow]; + return [[[self.angbandViews lastObject] window] isMainWindow]; } - (void)setNeedsDisplay:(BOOL)val { - for (NSView *angbandView in angbandViews) + for (NSView *angbandView in self.angbandViews) { [angbandView setNeedsDisplay:val]; } @@ -1383,7 +2091,7 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) - (void)setNeedsDisplayInBaseRect:(NSRect)rect { - for (NSView *angbandView in angbandViews) + for (NSView *angbandView in self.angbandViews) { [angbandView setNeedsDisplayInRect: rect]; } @@ -1411,19 +2119,17 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) - (void)resizeTerminalWithContentRect: (NSRect)contentRect saveToDefaults: (BOOL)saveToDefaults { - CGFloat newRows = floor( (contentRect.size.height - (borderSize.height * 2.0)) / tileSize.height ); - CGFloat newColumns = ceil( (contentRect.size.width - (borderSize.width * 2.0)) / tileSize.width ); + CGFloat newRows = floor( + (contentRect.size.height - (self.borderSize.height * 2.0)) / + self.tileSize.height); + CGFloat newColumns = ceil( + (contentRect.size.width - (self.borderSize.width * 2.0)) / + self.tileSize.width); if (newRows < 1 || newColumns < 1) return; - self->cols = newColumns; - self->rows = newRows; - - if (resize_pending_changes(self->changes, self->rows) != 0) { - destroy_pending_changes(self->changes); - self->changes = 0; - NSLog(@"out of memory for pending changes with resize of terminal %d", - [self terminalIndex]); - } + self->_cols = newColumns; + self->_rows = newRows; + [self.changes resize:self.cols rows:self.rows]; if( saveToDefaults ) { @@ -1433,22 +2139,22 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) if( termIndex < (int)[terminals count] ) { NSMutableDictionary *mutableTerm = [[NSMutableDictionary alloc] initWithDictionary: [terminals objectAtIndex: termIndex]]; - [mutableTerm setValue: [NSNumber numberWithUnsignedInt: self->cols] forKey: AngbandTerminalColumnsDefaultsKey]; - [mutableTerm setValue: [NSNumber numberWithUnsignedInt: self->rows] forKey: AngbandTerminalRowsDefaultsKey]; + [mutableTerm setValue: [NSNumber numberWithInteger: self.cols] + forKey: AngbandTerminalColumnsDefaultsKey]; + [mutableTerm setValue: [NSNumber numberWithInteger: self.rows] + forKey: AngbandTerminalRowsDefaultsKey]; NSMutableArray *mutableTerminals = [[NSMutableArray alloc] initWithArray: terminals]; [mutableTerminals replaceObjectAtIndex: termIndex withObject: mutableTerm]; [[NSUserDefaults standardUserDefaults] setValue: mutableTerminals forKey: AngbandTerminalsDefaultsKey]; - [mutableTerminals release]; - [mutableTerm release]; } [[NSUserDefaults standardUserDefaults] synchronize]; } term *old = Term; Term_activate( self->terminal ); - Term_resize( (int)newColumns, (int)newRows); + Term_resize( self.cols, self.rows ); Term_redraw(); Term_activate( old ); } @@ -1465,9 +2171,9 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) minsize.height = 1; } minsize.width = - minsize.width * self->tileSize.width + self->borderSize.width * 2.0; + minsize.width * self.tileSize.width + self.borderSize.width * 2.0; minsize.height = - minsize.height * self->tileSize.height + self->borderSize.height * 2.0; + minsize.height * self.tileSize.height + self.borderSize.height * 2.0; [[self makePrimaryWindow] setContentMinSize:minsize]; } @@ -1486,8 +2192,6 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) [mutableTerminals replaceObjectAtIndex: termIndex withObject: mutableTerm]; [[NSUserDefaults standardUserDefaults] setValue: mutableTerminals forKey: AngbandTerminalsDefaultsKey]; - [mutableTerminals release]; - [mutableTerm release]; } } @@ -1528,7 +2232,7 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) { NSWindow *window = [notification object]; NSRect contentRect = [window contentRectForFrameRect: [window frame]]; - [self resizeTerminalWithContentRect: contentRect saveToDefaults: !(self->in_fullscreen_transition)]; + [self resizeTerminalWithContentRect: contentRect saveToDefaults: !(self->inFullscreenTransition)]; } /*- (NSSize)windowWillResize: (NSWindow *)sender toSize: (NSSize)frameSize @@ -1537,27 +2241,27 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) - (void)windowWillEnterFullScreen: (NSNotification *)notification { - self->in_fullscreen_transition = YES; + self->inFullscreenTransition = YES; } - (void)windowDidEnterFullScreen: (NSNotification *)notification { NSWindow *window = [notification object]; NSRect contentRect = [window contentRectForFrameRect: [window frame]]; - self->in_fullscreen_transition = NO; + self->inFullscreenTransition = NO; [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO]; } - (void)windowWillExitFullScreen: (NSNotification *)notification { - self->in_fullscreen_transition = YES; + self->inFullscreenTransition = YES; } - (void)windowDidExitFullScreen: (NSNotification *)notification { NSWindow *window = [notification object]; NSRect contentRect = [window contentRectForFrameRect: [window frame]]; - self->in_fullscreen_transition = NO; + self->inFullscreenTransition = NO; [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO]; } @@ -1565,7 +2269,7 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) { NSWindow *window = [notification object]; - if( window != self->primaryWindow ) + if( window != self.primaryWindow ) { return; } @@ -1576,7 +2280,8 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) if( [[NSFontPanel sharedFontPanel] isVisible] ) { - [[NSFontPanel sharedFontPanel] setPanelFont: [self selectionFont] isMultiple: NO]; + [[NSFontPanel sharedFontPanel] setPanelFont:self.angbandViewFont + isMultiple: NO]; } } @@ -1584,7 +2289,7 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n) { NSWindow *window = [notification object]; - if( window != self->primaryWindow ) + if( window != self.primaryWindow ) { return; } @@ -1712,8 +2417,7 @@ static wchar_t convert_two_byte_eucjp_to_utf16_native(const char *cp) NSString* str = [[NSString alloc] initWithBytes:cp length:2 encoding:NSJapaneseEUCStringEncoding]; wchar_t result = [str characterAtIndex:0]; - - [str release]; + str = nil; return result; } #endif /* JP */ @@ -1730,190 +2434,221 @@ static wchar_t convert_two_byte_eucjp_to_utf16_native(const char *cp) */ static void Term_init_cocoa(term *t) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - AngbandContext *context = [[AngbandContext alloc] init]; - - /* Give the term a hard retain on context (for GC) */ - t->data = (void *)CFRetain(context); - [context release]; - - /* Handle graphics */ - t->higher_pict = !! use_graphics; - t->always_pict = FALSE; - - NSDisableScreenUpdates(); - - /* Figure out the frame autosave name based on the index of this term */ - NSString *autosaveName = nil; - int termIdx; - for (termIdx = 0; termIdx < ANGBAND_TERM_MAX; termIdx++) - { - if (angband_term[termIdx] == t) - { - autosaveName = [NSString stringWithFormat:@"AngbandTerm-%d", termIdx]; - break; - } - } + @autoreleasepool { + AngbandContext *context = [[AngbandContext alloc] init]; - /* Set its font. */ - NSString *fontName = [[NSUserDefaults angbandDefaults] stringForKey:[NSString stringWithFormat:@"FontName-%d", termIdx]]; - if (! fontName) fontName = [default_font fontName]; + /* Give the term ownership of the context */ + t->data = (void *)CFBridgingRetain(context); - /* Use a smaller default font for the other windows, but only if the font - * hasn't been explicitly set */ - float fontSize = (termIdx > 0) ? 10.0 : [default_font pointSize]; - NSNumber *fontSizeNumber = [[NSUserDefaults angbandDefaults] valueForKey: [NSString stringWithFormat: @"FontSize-%d", termIdx]]; + /* Handle graphics */ + t->higher_pict = !! use_graphics; + t->always_pict = FALSE; - if( fontSizeNumber != nil ) - { - fontSize = [fontSizeNumber floatValue]; - } + NSDisableScreenUpdates(); - [context setSelectionFont:[NSFont fontWithName:fontName size:fontSize] adjustTerminal: NO]; + /* + * Figure out the frame autosave name based on the index of this term + */ + NSString *autosaveName = nil; + int termIdx; + for (termIdx = 0; termIdx < ANGBAND_TERM_MAX; termIdx++) + { + if (angband_term[termIdx] == t) + { + autosaveName = + [NSString stringWithFormat:@"AngbandTerm-%d", termIdx]; + break; + } + } - NSArray *terminalDefaults = [[NSUserDefaults standardUserDefaults] valueForKey: AngbandTerminalsDefaultsKey]; - NSInteger rows = 24; - NSInteger columns = 80; + /* Set its font. */ + NSString *fontName = + [[NSUserDefaults angbandDefaults] + stringForKey:[NSString stringWithFormat:@"FontName-%d", termIdx]]; + if (! fontName) fontName = [[AngbandContext defaultFont] fontName]; - if( termIdx < (int)[terminalDefaults count] ) - { - NSDictionary *term = [terminalDefaults objectAtIndex: termIdx]; - NSInteger defaultRows = [[term valueForKey: AngbandTerminalRowsDefaultsKey] integerValue]; - NSInteger defaultColumns = [[term valueForKey: AngbandTerminalColumnsDefaultsKey] integerValue]; + /* + * Use a smaller default font for the other windows, but only if the + * font hasn't been explicitly set. + */ + float fontSize = + (termIdx > 0) ? 10.0 : [[AngbandContext defaultFont] pointSize]; + NSNumber *fontSizeNumber = + [[NSUserDefaults angbandDefaults] + valueForKey: [NSString stringWithFormat: @"FontSize-%d", termIdx]]; - if (defaultRows > 0) rows = defaultRows; - if (defaultColumns > 0) columns = defaultColumns; - } + if( fontSizeNumber != nil ) + { + fontSize = [fontSizeNumber floatValue]; + } - context->cols = columns; - context->rows = rows; + [context setSelectionFont:[NSFont fontWithName:fontName size:fontSize] + adjustTerminal: NO]; - if (resize_pending_changes(context->changes, context->rows) != 0) { - destroy_pending_changes(context->changes); - context->changes = 0; - NSLog(@"initializing terminal %d: out of memory for pending changes", - termIdx); - } + NSArray *terminalDefaults = + [[NSUserDefaults standardUserDefaults] + valueForKey: AngbandTerminalsDefaultsKey]; + NSInteger rows = 24; + NSInteger columns = 80; - /* Get the window */ - NSWindow *window = [context makePrimaryWindow]; + if( termIdx < (int)[terminalDefaults count] ) + { + NSDictionary *term = [terminalDefaults objectAtIndex: termIdx]; + NSInteger defaultRows = + [[term valueForKey: AngbandTerminalRowsDefaultsKey] + integerValue]; + NSInteger defaultColumns = + [[term valueForKey: AngbandTerminalColumnsDefaultsKey] + integerValue]; + + if (defaultRows > 0) rows = defaultRows; + if (defaultColumns > 0) columns = defaultColumns; + } + + context.cols = columns; + context.rows = rows; + [context.changes resize:columns rows:rows]; + + /* Get the window */ + NSWindow *window = [context makePrimaryWindow]; - /* Set its title and, for auxiliary terms, tentative size */ - NSString *title = [NSString stringWithCString:angband_term_name[termIdx] + /* Set its title and, for auxiliary terms, tentative size */ + NSString *title = + [NSString stringWithCString:angband_term_name[termIdx] #ifdef JP - encoding:NSJapaneseEUCStringEncoding + encoding:NSJapaneseEUCStringEncoding #else - encoding:NSMacOSRomanStringEncoding + encoding:NSMacOSRomanStringEncoding #endif - ]; - [window setTitle:title]; - [context setMinimumWindowSize:termIdx]; - - /* If this is the first term, and we support full screen (Mac OS X Lion or - * later), then allow it to go full screen (sweet). Allow other terms to be - * FullScreenAuxilliary, so they can at least show up. Unfortunately in - * Lion they don't get brought to the full screen space; but they would - * only make sense on multiple displays anyways so it's not a big loss. */ - if ([window respondsToSelector:@selector(toggleFullScreen:)]) - { - NSWindowCollectionBehavior behavior = [window collectionBehavior]; - behavior |= (termIdx == 0 ? Angband_NSWindowCollectionBehaviorFullScreenPrimary : Angband_NSWindowCollectionBehaviorFullScreenAuxiliary); - [window setCollectionBehavior:behavior]; - } - - /* No Resume support yet, though it would not be hard to add */ - if ([window respondsToSelector:@selector(setRestorable:)]) - { - [window setRestorable:NO]; - } + ]; + [window setTitle:title]; + [context setMinimumWindowSize:termIdx]; + + /* + * If this is the first term, and we support full screen (Mac OS X Lion + * or later), then allow it to go full screen (sweet). Allow other + * terms to be FullScreenAuxilliary, so they can at least show up. + * Unfortunately in Lion they don't get brought to the full screen + * space; but they would only make sense on multiple displays anyways + * so it's not a big loss. + */ + if ([window respondsToSelector:@selector(toggleFullScreen:)]) + { + NSWindowCollectionBehavior behavior = [window collectionBehavior]; + behavior |= + (termIdx == 0 ? + Angband_NSWindowCollectionBehaviorFullScreenPrimary : + Angband_NSWindowCollectionBehaviorFullScreenAuxiliary); + [window setCollectionBehavior:behavior]; + } + + /* No Resume support yet, though it would not be hard to add */ + if ([window respondsToSelector:@selector(setRestorable:)]) + { + [window setRestorable:NO]; + } /* default window placement */ { - static NSRect overallBoundingRect; + static NSRect overallBoundingRect; - if( termIdx == 0 ) - { - /* This is a bit of a trick to allow us to display multiple windows - * in the "standard default" window position in OS X: the upper - * center of the screen. - * The term sizes set in load_prefs() are based on a 5-wide by - * 3-high grid, with the main term being 4/5 wide by 2/3 high - * (hence the scaling to find */ - - /* What the containing rect would be). */ - NSRect originalMainTermFrame = [window frame]; - NSRect scaledFrame = originalMainTermFrame; - scaledFrame.size.width *= 5.0 / 4.0; - scaledFrame.size.height *= 3.0 / 2.0; - scaledFrame.size.width += 1.0; /* spacing between window columns */ - scaledFrame.size.height += 1.0; /* spacing between window rows */ - [window setFrame: scaledFrame display: NO]; - [window center]; - overallBoundingRect = [window frame]; - [window setFrame: originalMainTermFrame display: NO]; - } + if( termIdx == 0 ) + { + /* + * This is a bit of a trick to allow us to display multiple + * windows in the "standard default" window position in OS X: + * the upper center of the screen. The term sizes set in + * AngbandAppDelegate's loadPrefs() are based on a 5-wide by + * 3-high grid, with the main term being 4/5 wide by 2/3 high + * (hence the scaling to find what the containing rect would + * be). + */ + NSRect originalMainTermFrame = [window frame]; + NSRect scaledFrame = originalMainTermFrame; + scaledFrame.size.width *= 5.0 / 4.0; + scaledFrame.size.height *= 3.0 / 2.0; + scaledFrame.size.width += 1.0; /* spacing between window columns */ + scaledFrame.size.height += 1.0; /* spacing between window rows */ + [window setFrame: scaledFrame display: NO]; + [window center]; + overallBoundingRect = [window frame]; + [window setFrame: originalMainTermFrame display: NO]; + } - static NSRect mainTermBaseRect; - NSRect windowFrame = [window frame]; + static NSRect mainTermBaseRect; + NSRect windowFrame = [window frame]; - if( termIdx == 0 ) - { - /* The height and width adjustments were determined experimentally, - * so that the rest of the windows line up nicely without - * overlapping */ - windowFrame.size.width += 7.0; - windowFrame.size.height += 9.0; - windowFrame.origin.x = NSMinX( overallBoundingRect ); - windowFrame.origin.y = NSMaxY( overallBoundingRect ) - NSHeight( windowFrame ); - mainTermBaseRect = windowFrame; - } - else if( termIdx == 1 ) - { - windowFrame.origin.x = NSMinX( mainTermBaseRect ); - windowFrame.origin.y = NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0; - } - else if( termIdx == 2 ) - { - windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0; - windowFrame.origin.y = NSMaxY( mainTermBaseRect ) - NSHeight( windowFrame ); - } - else if( termIdx == 3 ) - { - windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0; - windowFrame.origin.y = NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0; - } - else if( termIdx == 4 ) - { - windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0; - windowFrame.origin.y = NSMinY( mainTermBaseRect ); - } - else if( termIdx == 5 ) - { - windowFrame.origin.x = NSMinX( mainTermBaseRect ) + NSWidth( windowFrame ) + 1.0; - windowFrame.origin.y = NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0; - } + if( termIdx == 0 ) + { + /* + * The height and width adjustments were determined + * experimentally, so that the rest of the windows line up + * nicely without overlapping. + */ + windowFrame.size.width += 7.0; + windowFrame.size.height += 9.0; + windowFrame.origin.x = NSMinX( overallBoundingRect ); + windowFrame.origin.y = + NSMaxY( overallBoundingRect ) - NSHeight( windowFrame ); + mainTermBaseRect = windowFrame; + } + else if( termIdx == 1 ) + { + windowFrame.origin.x = NSMinX( mainTermBaseRect ); + windowFrame.origin.y = + NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0; + } + else if( termIdx == 2 ) + { + windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0; + windowFrame.origin.y = + NSMaxY( mainTermBaseRect ) - NSHeight( windowFrame ); + } + else if( termIdx == 3 ) + { + windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0; + windowFrame.origin.y = + NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0; + } + else if( termIdx == 4 ) + { + windowFrame.origin.x = NSMaxX( mainTermBaseRect ) + 1.0; + windowFrame.origin.y = NSMinY( mainTermBaseRect ); + } + else if( termIdx == 5 ) + { + windowFrame.origin.x = + NSMinX( mainTermBaseRect ) + NSWidth( windowFrame ) + 1.0; + windowFrame.origin.y = + NSMinY( mainTermBaseRect ) - NSHeight( windowFrame ) - 1.0; + } - [window setFrame: windowFrame display: NO]; + [window setFrame: windowFrame display: NO]; } /* Override the default frame above if the user has adjusted windows in * the past */ if (autosaveName) [window setFrameAutosaveName:autosaveName]; - /* Tell it about its term. Do this after we've sized it so that the sizing - * doesn't trigger redrawing and such. */ - [context setTerm:t]; - - /* Only order front if it's the first term. Other terms will be ordered + /* + * Tell it about its term. Do this after we've sized it so that the + * sizing doesn't trigger redrawing and such. + */ + [context setTerm:t]; + + /* + * Only order front if it's the first term. Other terms will be ordered * front from AngbandUpdateWindowVisibility(). This is to work around a * problem where Angband aggressively tells us to initialize terms that - * don't do anything! */ - if (t == angband_term[0]) [context->primaryWindow makeKeyAndOrderFront: nil]; - - NSEnableScreenUpdates(); - - /* Set "mapped" flag */ - t->mapped_flag = true; - [pool drain]; + * don't do anything! + */ + if (t == angband_term[0]) + [context.primaryWindow makeKeyAndOrderFront: nil]; + + NSEnableScreenUpdates(); + + /* Set "mapped" flag */ + t->mapped_flag = true; + } } @@ -1923,22 +2658,20 @@ static void Term_init_cocoa(term *t) */ static void Term_nuke_cocoa(term *t) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - AngbandContext *context = t->data; - if (context) - { - /* Tell the context to get rid of its windows, etc. */ - [context dispose]; - - /* Balance our CFRetain from when we created it */ - CFRelease(context); - - /* Done with it */ - t->data = NULL; + @autoreleasepool { + AngbandContext *context = (__bridge AngbandContext*) (t->data); + if (context) + { + /* Tell the context to get rid of its windows, etc. */ + [context dispose]; + + /* Balance our CFBridgingRetain from when we created it */ + CFRelease(t->data); + + /* Done with it */ + t->data = NULL; + } } - - [pool drain]; } /** @@ -1964,8 +2697,6 @@ static CGImageRef create_angband_image(NSString *path) decodedImage = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)options); CFRelease(source); } - [options release]; - [url release]; } } @@ -2017,102 +2748,108 @@ static errr Term_xtra_cocoa_react(void) /* Don't actually switch graphics until the game is running */ if (!initialized || !game_in_progress) return (-1); - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - AngbandContext *angbandContext = Term->data; + @autoreleasepool { + AngbandContext *angbandContext = + (__bridge AngbandContext*) (Term->data); - /* Handle graphics */ - int expected_graf_mode = (current_graphics_mode) ? - current_graphics_mode->grafID : GRAPHICS_NONE; - if (graf_mode_req != expected_graf_mode) - { - graphics_mode *new_mode; - if (graf_mode_req != GRAPHICS_NONE) { - new_mode = get_graphics_mode(graf_mode_req); - } else { - new_mode = NULL; - } - - /* Get rid of the old image. CGImageRelease is NULL-safe. */ - CGImageRelease(pict_image); - pict_image = NULL; - - /* Try creating the image if we want one */ - if (new_mode != NULL) - { - NSString *img_path = [NSString stringWithFormat:@"%s/%s", new_mode->path, new_mode->file]; - pict_image = create_angband_image(img_path); - - /* If we failed to create the image, revert to ASCII. */ - if (! pict_image) { - new_mode = NULL; - if (use_bigtile) { - arg_bigtile = FALSE; + /* Handle graphics */ + int expected_graf_mode = (current_graphics_mode) ? + current_graphics_mode->grafID : GRAPHICS_NONE; + if (graf_mode_req != expected_graf_mode) + { + graphics_mode *new_mode; + if (graf_mode_req != GRAPHICS_NONE) { + new_mode = get_graphics_mode(graf_mode_req); + } else { + new_mode = NULL; + } + + /* Get rid of the old image. CGImageRelease is NULL-safe. */ + CGImageRelease(pict_image); + pict_image = NULL; + + /* Try creating the image if we want one */ + if (new_mode != NULL) + { + NSString *img_path = + [NSString stringWithFormat:@"%s/%s", new_mode->path, new_mode->file]; + pict_image = create_angband_image(img_path); + + /* If we failed to create the image, revert to ASCII. */ + if (! pict_image) { + new_mode = NULL; + if (use_bigtile) { + arg_bigtile = FALSE; + } + [[NSUserDefaults angbandDefaults] + setInteger:GRAPHICS_NONE + forKey:AngbandGraphicsDefaultsKey]; + [[NSUserDefaults angbandDefaults] synchronize]; + + NSString *msg = NSLocalizedStringWithDefaultValue( + @"Error.TileSetLoadFailed", + AngbandMessageCatalog, + [NSBundle mainBundle], + @"Failed to Load Tile Set", + @"Alert text for failed tile set load"); + NSString *info = NSLocalizedStringWithDefaultValue( + @"Error.TileSetRevertToASCII", + AngbandMessageCatalog, + [NSBundle mainBundle], + @"Could not load the tile set. Switched back to ASCII.", + @"Alert informative message for failed tile set load"); + NSAlert *alert = [[NSAlert alloc] init]; + + alert.messageText = msg; + alert.informativeText = info; + NSModalResponse result = [alert runModal]; } - [[NSUserDefaults angbandDefaults] - setInteger:GRAPHICS_NONE - forKey:AngbandGraphicsDefaultsKey]; - [[NSUserDefaults angbandDefaults] synchronize]; - - NSString *msg = NSLocalizedStringWithDefaultValue( - @"Error.TileSetLoadFailed", - AngbandMessageCatalog, - [NSBundle mainBundle], - @"Failed to Load Tile Set", - @"Alert text for failed tile set load"); - NSString *info = NSLocalizedStringWithDefaultValue( - @"Error.TileSetRevertToASCII", - AngbandMessageCatalog, - [NSBundle mainBundle], - @"Could not load the tile set. Switched back to ASCII.", - @"Alert informative message for failed tile set load"); - NSAlert *alert = [[NSAlert alloc] init]; - - alert.messageText = msg; - alert.informativeText = info; - NSModalResponse result = [alert runModal]; - [alert release]; } - } - - /* Record what we did */ - use_graphics = new_mode ? new_mode->grafID : 0; - ANGBAND_GRAF = (new_mode ? new_mode->graf : "ascii"); - current_graphics_mode = new_mode; - - /* Enable or disable higher picts. Note: this should be done for all - * terms. */ - angbandContext->terminal->higher_pict = !! use_graphics; - - if (pict_image && current_graphics_mode) - { - /* Compute the row and column count via the image height and width. - */ - pict_rows = (int)(CGImageGetHeight(pict_image) / current_graphics_mode->cell_height); - pict_cols = (int)(CGImageGetWidth(pict_image) / current_graphics_mode->cell_width); - } - else - { - pict_rows = 0; - pict_cols = 0; - } - - /* Reset visuals */ - if (arg_bigtile == use_bigtile) - { - reset_visuals(); - } - } - if (arg_bigtile != use_bigtile) { - /* Reset visuals */ - reset_visuals(); + /* Record what we did */ + use_graphics = new_mode ? new_mode->grafID : 0; + ANGBAND_GRAF = (new_mode ? new_mode->graf : "ascii"); + current_graphics_mode = new_mode; + + /* + * Enable or disable higher picts. Note: this should be done for + * all terms. + */ + angbandContext->terminal->higher_pict = !! use_graphics; + + if (pict_image && current_graphics_mode) + { + /* + * Compute the row and column count via the image height and + * width. + */ + pict_rows = (int)(CGImageGetHeight(pict_image) / + current_graphics_mode->cell_height); + pict_cols = (int)(CGImageGetWidth(pict_image) / + current_graphics_mode->cell_width); + } + else + { + pict_rows = 0; + pict_cols = 0; + } + + /* Reset visuals */ + if (arg_bigtile == use_bigtile) + { + reset_visuals(); + } + } + + if (arg_bigtile != use_bigtile) { + /* Reset visuals */ + reset_visuals(); - Term_activate(angband_term[0]); - Term_resize(angband_term[0]->wid, angband_term[0]->hgt); + Term_activate(angband_term[0]); + Term_resize(angband_term[0]->wid, angband_term[0]->hgt); + } } - [pool drain]; - /* Success */ return (0); } @@ -2154,7 +2891,7 @@ static void draw_image_tile( * extended. */ static void query_before_text( - struct PendingRowChange* prc, int iy, int npre, int* pclip, int* prend) + PendingTermChanges *tc, int iy, int npre, int* pclip, int* prend) { int start = *prend; int i = start - 1; @@ -2163,15 +2900,16 @@ static void query_before_text( if (i < 0 || i < start - npre) { break; } + enum PendingCellChangeType ctype = [tc getCellChangeType:i row:iy]; - if (prc->cell_changes[i].change_type == CELL_CHANGE_PICT) { + if (ctype == CELL_CHANGE_TILE) { /* * The cell has been rendered with a tile. Do not want to modify * its contents so the clipping and rendering region can not be * extended. */ break; - } else if (prc->cell_changes[i].change_type == CELL_CHANGE_NONE) { + } else if (ctype == CELL_CHANGE_NONE) { /* * It has not changed (or using big tile mode and it is within * a changed tile but is not the left cell for that tile) so @@ -2209,34 +2947,27 @@ static void query_before_text( if (i > 0) { Term_what(i - 1, iy, a, c); if (iskanji(c)) { - prc->cell_changes[i - 1].c.w = - convert_two_byte_eucjp_to_utf16_native(c); - prc->cell_changes[i - 1].a = a[0]; - prc->cell_changes[i - 1].tcol = 1; - prc->cell_changes[i].c.w = 0; - prc->cell_changes[i].a = a[0]; - prc->cell_changes[i].tcol = 0; + [tc markTextChange:i-1 row:iy + glyph:convert_two_byte_eucjp_to_utf16_native(c) + color:a[0] isDoubleWidth:YES]; *pclip = i - 1; *prend = i - 1; --i; } else { - prc->cell_changes[i].c.w = c[1]; - prc->cell_changes[i].a = a[1]; - prc->cell_changes[i].tcol = 0; + [tc markTextChange:i row:iy + glyph:c[1] color:a[1] isDoubleWidth:NO]; *pclip = i; *prend = i; } } else { - prc->cell_changes[i].c.w = c[1]; - prc->cell_changes[i].a = a[1]; - prc->cell_changes[i].tcol = 0; + [tc markTextChange:i row:iy + glyph:c[1] color:a[1] isDoubleWidth:NO]; *pclip = i; *prend = i; } #else - prc->cell_changes[i].c.w = c[1]; - prc->cell_changes[i].a = a[1]; - prc->cell_changes[i].tcol = 0; + [tc markTextChange:i row:iy + glyph:c[1] color:a[1] isDoubleWidth:NO]; *pclip = i; *prend = i; #endif @@ -2259,39 +2990,39 @@ static void query_before_text( * extended. */ static void query_after_text( - struct PendingRowChange* prc, - int iy, - int ncol, - int npost, - int* pclip, - int* prend) + PendingTermChanges *tc, int iy, int npost, int* pclip, int* prend) { int end = *prend; int i = end + 1; + int ncol = tc.columnCount; while (1) { + if (i >= ncol) { + break; + } + + enum PendingCellChangeType ctype = [tc getCellChangeType:i row:iy]; + /* * Be willing to consolidate this block with the one after it. This * logic should be sufficient to avoid redraws of the region between - * changed blocks of text if angbandContext->ncol_pre is zero or one. - * For larger values of ncol_pre, would need to do something more to + * changed blocks of text if angbandContext.nColPre is zero or one. + * For larger values of nColPre, would need to do something more to * avoid extra redraws. */ - if (i >= ncol || - (i > end + npost && - prc->cell_changes[i].change_type != CELL_CHANGE_TEXT && - prc->cell_changes[i].change_type != CELL_CHANGE_WIPE)) { + if (i > end + npost && ctype != CELL_CHANGE_TEXT && + ctype != CELL_CHANGE_WIPE) { break; } - if (prc->cell_changes[i].change_type == CELL_CHANGE_PICT) { + if (ctype == CELL_CHANGE_TILE) { /* * The cell has been rendered with a tile. Do not want to modify * its contents so the clipping and rendering region can not be * extended. */ break; - } else if (prc->cell_changes[i].change_type == CELL_CHANGE_NONE) { + } else if (ctype == CELL_CHANGE_NONE) { /* It has not changed so inquire what it is. */ TERM_COLOR a[2]; char c[2]; @@ -2314,34 +3045,27 @@ static void query_after_text( if (i < ncol - 1) { Term_what(i + 1, iy, a + 1, c + 1); if (iskanji(c)) { - prc->cell_changes[i].c.w = - convert_two_byte_eucjp_to_utf16_native(c); - prc->cell_changes[i].a = a[0]; - prc->cell_changes[i].tcol = 1; - prc->cell_changes[i + 1].c.w = 0; - prc->cell_changes[i + 1].a = a[0]; - prc->cell_changes[i + 1].tcol = 0; + [tc markTextChange:i row:iy + glyph:convert_two_byte_eucjp_to_utf16_native(c) + color:a[0] isDoubleWidth:YES]; *pclip = i + 1; *prend = i + 1; ++i; } else { - prc->cell_changes[i].c.w = c[0]; - prc->cell_changes[i].a = a[0]; - prc->cell_changes[i].tcol = 0; + [tc markTextChange:i row:iy + glyph:c[0] color:a[0] isDoubleWidth:NO]; *pclip = i; *prend = i; } } else { - prc->cell_changes[i].c.w = c[0]; - prc->cell_changes[i].a = a[0]; - prc->cell_changes[i].tcol = 0; + [tc markTextChange:i row:iy + glyph:c[0] color:a[0] isDoubleWidth:NO]; *pclip = i; *prend = i; } #else - prc->cell_changes[i].c.w = c[0]; - prc->cell_changes[i].a = a[0]; - prc->cell_changes[i].tcol = 0; + [tc markTextChange:i row:iy + glyph:c[0] color:a[0] isDoubleWidth:NO]; *pclip = i; *prend = i; #endif @@ -2367,7 +3091,7 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext) { int graf_width, graf_height, alphablend; - if (angbandContext->changes->has_pict) { + if (angbandContext.changes.hasTileChanges) { CGImageAlphaInfo ainfo = CGImageGetAlphaInfo(pict_image); graf_width = current_graphics_mode->cell_width; @@ -2392,38 +3116,36 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext) CGContextRef ctx = [angbandContext lockFocus]; - if (angbandContext->changes->has_text || - angbandContext->changes->has_wipe) { - NSFont *selectionFont = [[angbandContext selectionFont] screenFont]; + if (angbandContext.changes.hasTextChanges || + angbandContext.changes.hasWipeChanges) { + NSFont *selectionFont = [angbandContext.angbandViewFont screenFont]; [selectionFont set]; } - int iy; - for (iy = angbandContext->changes->ymin; - iy <= angbandContext->changes->ymax; + for (int iy = angbandContext.changes.firstChangedRow; + iy <= angbandContext.changes.lastChangedRow; ++iy) { - struct PendingRowChange* prc = angbandContext->changes->rows[iy]; - int ix; - /* Skip untouched rows. */ - if (prc == 0) { + if ([angbandContext.changes getFirstChangedColumnInRow:iy] > + [angbandContext.changes getLastChangedColumnInRow:iy]) { continue; } + int ix = [angbandContext.changes getFirstChangedColumnInRow:iy]; + int ixmax = [angbandContext.changes getLastChangedColumnInRow:iy]; - ix = prc->xmin; while (1) { int jx; - if (ix > prc->xmax) { + if (ix > ixmax) { break; } - switch (prc->cell_changes[ix].change_type) { + switch ([angbandContext.changes getCellChangeType:ix row:iy]) { case CELL_CHANGE_NONE: ++ix; break; - case CELL_CHANGE_PICT: + case CELL_CHANGE_TILE: { /* * Because changes are made to the compositing mode, save @@ -2435,24 +3157,24 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext) int step = (use_bigtile) ? 2 : 1; jx = ix; - while (jx <= prc->xmax && - prc->cell_changes[jx].change_type - == CELL_CHANGE_PICT) { + while (jx <= ixmax && + [angbandContext.changes getCellChangeType:jx row:iy] + == CELL_CHANGE_TILE) { NSRect destinationRect = [angbandContext rectInImageForTileAtX:jx Y:iy]; + struct PendingTileChange tileIndices = + [angbandContext.changes + getCellTileChange:jx row:iy]; NSRect sourceRect, terrainRect; destinationRect.size.width *= step; - sourceRect.origin.x = graf_width * - prc->cell_changes[jx].c.c; - sourceRect.origin.y = graf_height * - prc->cell_changes[jx].a; + sourceRect.origin.x = graf_width * tileIndices.fgdCol; + sourceRect.origin.y = graf_height * tileIndices.fgdRow; sourceRect.size.width = graf_width; sourceRect.size.height = graf_height; - terrainRect.origin.x = graf_width * - prc->cell_changes[jx].tcol; + terrainRect.origin.x = graf_width * tileIndices.bckCol; terrainRect.origin.y = graf_height * - prc->cell_changes[jx].trow; + tileIndices.bckRow; terrainRect.size.width = graf_width; terrainRect.size.height = graf_height; if (alphablend) { @@ -2494,7 +3216,7 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext) NSRect rect = [angbandContext rectInImageForTileAtX:ix Y:iy]; rect.size.width = - angbandContext->tileSize.width * (jx - ix); + angbandContext.tileSize.width * (jx - ix); [angbandContext setNeedsDisplayInBaseRect:rect]; } ix = jx; @@ -2508,11 +3230,16 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext) * neighboring unchanged text). */ jx = ix + 1; - while (jx < angbandContext->cols && - (prc->cell_changes[jx].change_type - == CELL_CHANGE_TEXT - || prc->cell_changes[jx].change_type - == CELL_CHANGE_WIPE)) { + while (1) { + if (jx >= angbandContext.cols) { + break; + } + enum PendingCellChangeType ctype = + [angbandContext.changes getCellChangeType:jx row:iy]; + if (ctype != CELL_CHANGE_TEXT && + ctype != CELL_CHANGE_WIPE) { + break; + } ++jx; } { @@ -2526,12 +3253,15 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext) int k; query_before_text( - prc, iy, angbandContext->ncol_pre, &isclip, &isrend); + angbandContext.changes, + iy, + angbandContext.nColPre, + &isclip, + &isrend); query_after_text( - prc, + angbandContext.changes, iy, - angbandContext->cols, - angbandContext->ncol_post, + angbandContext.nColPost, &ieclip, &ierend ); @@ -2542,7 +3272,7 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext) /* Clear the area where rendering will be done. */ r = [angbandContext rectInImageForTileAtX:isrend Y:iy]; - r.size.width = angbandContext->tileSize.width * + r.size.width = angbandContext.tileSize.width * (ierend - isrend + 1); [[NSColor blackColor] set]; NSRectFill(r); @@ -2557,37 +3287,39 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext) */ CGContextBeginPath(ctx); r = [angbandContext rectInImageForTileAtX:isclip Y:iy]; - r.size.width = angbandContext->tileSize.width * + r.size.width = angbandContext.tileSize.width * (ieclip - isclip + 1); CGContextClipToRect(ctx, r); /* Render. */ k = isrend; while (k <= ierend) { - NSRect rectToDraw; - - if (prc->cell_changes[k].change_type + if ([angbandContext.changes getCellChangeType:k row:iy] == CELL_CHANGE_WIPE) { /* Skip over since no rendering is necessary. */ ++k; continue; } - if (set_color || alast != prc->cell_changes[k].a) { + struct PendingTextChange textChange = + [angbandContext.changes getCellTextChange:k + row:iy]; + int anew = textChange.color % MAX_COLORS; + if (set_color || alast != anew) { set_color = 0; - alast = prc->cell_changes[k].a; - set_color_for_index(alast % MAX_COLORS); + alast = anew; + set_color_for_index(anew); } - rectToDraw = + NSRect rectToDraw = [angbandContext rectInImageForTileAtX:k Y:iy]; - if (prc->cell_changes[k].tcol) { + if (textChange.doubleWidth) { rectToDraw.size.width *= 2.0; - [angbandContext drawWChar:prc->cell_changes[k].c.w + [angbandContext drawWChar:textChange.glyph inRect:rectToDraw context:ctx]; k += 2; } else { - [angbandContext drawWChar:prc->cell_changes[k].c.w + [angbandContext drawWChar:textChange.glyph inRect:rectToDraw context:ctx]; ++k; } @@ -2606,15 +3338,14 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext) } } - if (angbandContext->changes->xcurs >= 0 && - angbandContext->changes->ycurs >= 0) { + if (angbandContext.changes.cursorColumn >= 0 && + angbandContext.changes.cursorRow >= 0) { NSRect rect = [angbandContext - rectInImageForTileAtX:angbandContext->changes->xcurs - Y:angbandContext->changes->ycurs]; + rectInImageForTileAtX:angbandContext.changes.cursorColumn + Y:angbandContext.changes.cursorRow]; - if (angbandContext->changes->bigcurs) { - rect.size.width += angbandContext->tileSize.width; - } + rect.size.width *= angbandContext.changes.cursorWidth; + rect.size.height *= angbandContext.changes.cursorHeight; [[NSColor yellowColor] set]; NSFrameRectWithWidth(rect, 1); /* Invalidate that rect */ @@ -2630,147 +3361,114 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext) */ static errr Term_xtra_cocoa(int n, int v) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - AngbandContext* angbandContext = Term->data; - errr result = 0; - - /* Analyze */ - switch (n) - { - /* Make a noise */ + @autoreleasepool { + AngbandContext* angbandContext = + (__bridge AngbandContext*) (Term->data); + + /* Analyze */ + switch (n) { + /* Make a noise */ case TERM_XTRA_NOISE: - { - NSBeep(); - - /* Success */ - break; - } + NSBeep(); + break; - /* Make a sound */ + /* Make a sound */ case TERM_XTRA_SOUND: play_sound(v); break; - /* Process random events */ + /* Process random events */ case TERM_XTRA_BORED: - { - /* Show or hide cocoa windows based on the subwindow flags set by - * the user */ - AngbandUpdateWindowVisibility(); + /* + * Show or hide cocoa windows based on the subwindow flags set by + * the user. + */ + AngbandUpdateWindowVisibility(); + /* Process an event */ + (void)check_events(CHECK_EVENTS_NO_WAIT); + break; - /* Process an event */ - (void)check_events(CHECK_EVENTS_NO_WAIT); - - /* Success */ - break; - } - - /* Process pending events */ + /* Process pending events */ case TERM_XTRA_EVENT: - { - /* Process an event */ - (void)check_events(v); - - /* Success */ - break; - } - - /* Flush all pending events (if any) */ + /* Process an event */ + (void)check_events(v); + break; + + /* Flush all pending events (if any) */ case TERM_XTRA_FLUSH: - { - /* Hack -- flush all events */ - while (check_events(CHECK_EVENTS_DRAIN)) /* loop */; - - /* Success */ - break; - } - - /* Hack -- Change the "soft level" */ + /* Hack -- flush all events */ + while (check_events(CHECK_EVENTS_DRAIN)) /* loop */; + + break; + + /* Hack -- Change the "soft level" */ case TERM_XTRA_LEVEL: - { - /* Here we could activate (if requested), but I don't think Angband - * should be telling us our window order (the user should decide - * that), so do nothing. */ - break; - } - - /* Clear the screen */ + /* + * Here we could activate (if requested), but I don't think + * Angband should be telling us our window order (the user + * should decide that), so do nothing. + */ + break; + + /* Clear the screen */ case TERM_XTRA_CLEAR: - { - [angbandContext lockFocus]; - [[NSColor blackColor] set]; - NSRect imageRect = {NSZeroPoint, [angbandContext imageSize]}; - NSRectFillUsingOperation(imageRect, NSCompositeCopy); - [angbandContext unlockFocus]; - [angbandContext setNeedsDisplay:YES]; - /* Success */ - break; - } - - /* React to changes */ + { + [angbandContext lockFocus]; + [[NSColor blackColor] set]; + NSRect imageRect = {NSZeroPoint, [angbandContext imageSize]}; + NSRectFillUsingOperation(imageRect, NSCompositeCopy); + [angbandContext unlockFocus]; + [angbandContext setNeedsDisplay:YES]; + /* Success */ + break; + } + + /* React to changes */ case TERM_XTRA_REACT: - { - /* React to changes */ - return (Term_xtra_cocoa_react()); - } - - /* Delay (milliseconds) */ + result = Term_xtra_cocoa_react(); + break; + + /* Delay (milliseconds) */ case TERM_XTRA_DELAY: - { - /* If needed */ - if (v > 0) - { - - double seconds = v / 1000.; - NSDate* date = [NSDate dateWithTimeIntervalSinceNow:seconds]; - do - { - NSEvent* event; - do - { - event = [NSApp nextEventMatchingMask:-1 untilDate:date inMode:NSDefaultRunLoopMode dequeue:YES]; - if (event) send_event(event); - } while (event); - } while ([date timeIntervalSinceNow] >= 0); - - } - - /* Success */ - break; - } - - case TERM_XTRA_FRESH: - /* Draw the pending changes. */ - if (angbandContext->changes != 0) { - Term_xtra_cocoa_fresh(angbandContext); - clear_pending_changes(angbandContext->changes); + /* If needed */ + if (v > 0) { + double seconds = v / 1000.; + NSDate* date = [NSDate dateWithTimeIntervalSinceNow:seconds]; + do { + NSEvent* event; + do { + event = [NSApp nextEventMatchingMask:-1 + untilDate:date + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) send_event(event); + } while (event); + } while ([date timeIntervalSinceNow] >= 0); } + break; + + /* Draw the pending changes. */ + case TERM_XTRA_FRESH: + Term_xtra_cocoa_fresh(angbandContext); + [angbandContext.changes clear]; break; - + default: /* Oops */ result = 1; break; + } } - - [pool drain]; - - /* Oops */ + return result; } static errr Term_curs_cocoa(TERM_LEN x, TERM_LEN y) { - AngbandContext *angbandContext = Term->data; + AngbandContext *angbandContext = (__bridge AngbandContext*) (Term->data); - if (angbandContext->changes == 0) { - /* Bail out; there was an earlier memory allocation failure. */ - return 1; - } - angbandContext->changes->xcurs = x; - angbandContext->changes->ycurs = y; - angbandContext->changes->bigcurs = 0; + [angbandContext.changes markCursor:x row:y]; /* Success */ return 0; @@ -2783,15 +3481,9 @@ static errr Term_curs_cocoa(TERM_LEN x, TERM_LEN y) */ static errr Term_bigcurs_cocoa(TERM_LEN x, TERM_LEN y) { - AngbandContext *angbandContext = Term->data; + AngbandContext *angbandContext = (__bridge AngbandContext*) (Term->data); - if (angbandContext->changes == 0) { - /* Bail out; there was an earlier memory allocation failure. */ - return 1; - } - angbandContext->changes->xcurs = x; - angbandContext->changes->ycurs = y; - angbandContext->changes->bigcurs = 1; + [angbandContext.changes markBigCursor:x row:y cellsWide:2 cellsHigh:1]; /* Success */ return 0; @@ -2803,110 +3495,45 @@ static errr Term_bigcurs_cocoa(TERM_LEN x, TERM_LEN y) * Erase "n" characters starting at (x,y) */ static errr Term_wipe_cocoa(TERM_LEN x, TERM_LEN y, int n) -{ - AngbandContext *angbandContext = Term->data; - struct PendingCellChange *pc; - - if (angbandContext->changes == 0) { - /* Bail out; there was an earlier memory allocation failure. */ - return 1; - } - if (angbandContext->changes->rows[y] == 0) { - angbandContext->changes->rows[y] = - create_row_change(angbandContext->cols); - if (angbandContext->changes->rows[y] == 0) { - NSLog(@"failed to allocate changes for row %d", y); - return 1; - } - if (angbandContext->changes->ymin > y) { - angbandContext->changes->ymin = y; - } - if (angbandContext->changes->ymax < y) { - angbandContext->changes->ymax = y; - } - } - - angbandContext->changes->has_wipe = 1; - if (angbandContext->changes->rows[y]->xmin > x) { - angbandContext->changes->rows[y]->xmin = x; - } - if (angbandContext->changes->rows[y]->xmax < x + n - 1) { - angbandContext->changes->rows[y]->xmax = x + n - 1; - } - for (pc = angbandContext->changes->rows[y]->cell_changes + x; - pc != angbandContext->changes->rows[y]->cell_changes + x + n; - ++pc) { - pc->change_type = CELL_CHANGE_WIPE; - } - +{ + AngbandContext *angbandContext = (__bridge AngbandContext*) (Term->data); + + [angbandContext.changes markWipeRange:x row:y n:n]; + /* Success */ - return (0); + return 0; } static errr Term_pict_cocoa(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR *ap, concptr cp, const TERM_COLOR *tap, concptr tcp) { - /* Paranoia: Bail if we don't have a current graphics mode */ if (! current_graphics_mode) return -1; - - AngbandContext* angbandContext = Term->data; - int any_change = 0; + + AngbandContext* angbandContext = (__bridge AngbandContext*) (Term->data); int step = (use_bigtile) ? 2 : 1; - struct PendingCellChange *pc; - if (angbandContext->changes == 0) { - /* Bail out; there was an earlier memory allocation failure. */ - return 1; - } /* * In bigtile mode, it is sufficient that the bounds for the modified * region only encompass the left cell for the region affected by the * tile and that only that cell has to have the details of the changes. */ - if (angbandContext->changes->rows[y] == 0) { - angbandContext->changes->rows[y] = - create_row_change(angbandContext->cols); - if (angbandContext->changes->rows[y] == 0) { - NSLog(@"failed to allocate changes for row %d", y); - return 1; - } - if (angbandContext->changes->ymin > y) { - angbandContext->changes->ymin = y; - } - if (angbandContext->changes->ymax < y) { - angbandContext->changes->ymax = y; - } - } - - if (angbandContext->changes->rows[y]->xmin > x) { - angbandContext->changes->rows[y]->xmin = x; - } - if (angbandContext->changes->rows[y]->xmax < x + step * (n - 1)) { - angbandContext->changes->rows[y]->xmax = x + step * (n - 1); - } - for (pc = angbandContext->changes->rows[y]->cell_changes + x; - pc != angbandContext->changes->rows[y]->cell_changes + x + step * n; - pc += step) { + for (int i = x; i < x + n * step; i += step) { TERM_COLOR a = *ap++; char c = *cp++; TERM_COLOR ta = *tap++; char tc = *tcp++; if (use_graphics && (a & 0x80) && (c & 0x80)) { - pc->c.c = ((byte)c & 0x7F) % pict_cols; - pc->a = ((byte)a & 0x7F) % pict_rows; - pc->tcol = ((byte)tc & 0x7F) % pict_cols; - pc->trow = ((byte)ta & 0x7F) % pict_rows; - pc->change_type = CELL_CHANGE_PICT; - any_change = 1; + [angbandContext.changes markTileChange:i row:y + foregroundCol:((byte)c & 0x7F) % pict_cols + foregroundRow:((byte)a & 0x7F) % pict_rows + backgroundCol:((byte)tc & 0x7F) % pict_cols + backgroundRow:((byte)ta & 0x7F) % pict_rows]; } } - if (any_change) { - angbandContext->changes->has_pict = 1; - } - + /* Success */ return (0); } @@ -2919,484 +3546,54 @@ static errr Term_pict_cocoa(TERM_LEN x, TERM_LEN y, int n, static errr Term_text_cocoa( TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr cp) { - AngbandContext* angbandContext = Term->data; - struct PendingCellChange *pc; - - if (angbandContext->changes == 0) { - /* Bail out; there was an earlier memory allocation failure. */ - return 1; - } - if (angbandContext->changes->rows[y] == 0) { - angbandContext->changes->rows[y] = - create_row_change(angbandContext->cols); - if (angbandContext->changes->rows[y] == 0) { - NSLog(@"failed to allocate changes for row %d", y); - return 1; - } - if (angbandContext->changes->ymin > y) { - angbandContext->changes->ymin = y; - } - if (angbandContext->changes->ymax < y) { - angbandContext->changes->ymax = y; - } - } + AngbandContext* angbandContext = (__bridge AngbandContext*) (Term->data); + int i = 0; - angbandContext->changes->has_text = 1; - if (angbandContext->changes->rows[y]->xmin > x) { - angbandContext->changes->rows[y]->xmin = x; - } - if (angbandContext->changes->rows[y]->xmax < x + n - 1) { - angbandContext->changes->rows[y]->xmax = x + n - 1; - } - pc = angbandContext->changes->rows[y]->cell_changes + x; - while (pc != angbandContext->changes->rows[y]->cell_changes + x + n) { + while (i < n) { #ifdef JP if (iskanji(*cp)) { - if (pc + 1 == - angbandContext->changes->rows[y]->cell_changes + x + n) { + if (i == n - 1) { /* * The second byte of the character is past the end. Ignore * the character. */ break; } else { - pc->c.w = convert_two_byte_eucjp_to_utf16_native(cp); - pc->a = a; - pc->tcol = 1; - pc->change_type = CELL_CHANGE_TEXT; - ++pc; - /* - * Fill in a dummy value since the previous character will take - * up two columns. - */ - pc->c.w = 0; - pc->a = a; - pc->tcol = 0; - pc->change_type = CELL_CHANGE_TEXT; - ++pc; + [angbandContext.changes markTextChange:i+x row:y + glyph:convert_two_byte_eucjp_to_utf16_native(cp) + color:a isDoubleWidth:YES]; cp += 2; + i += 2; } } else { - pc->c.w = *cp; - pc->a = a; - pc->tcol = 0; - pc->change_type = CELL_CHANGE_TEXT; - ++pc; + [angbandContext.changes markTextChange:i+x row:y + glyph:*cp color:a isDoubleWidth:NO]; ++cp; + ++i; } #else - pc->c.w = *cp; - pc->a = a; - pc->tcol = 0; - pc->change_type = CELL_CHANGE_TEXT; - ++pc; + [angbandContext.changes markTextChange:i+x row:y + glyph:*cp color:a isDoubleWidth:NO]; ++cp; + ++i; #endif } - - /* Success */ - return (0); -} - -/** - * Post a nonsense event so that our event loop wakes up - */ -static void wakeup_event_loop(void) -{ - /* Big hack - send a nonsense event to make us update */ - NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:NULL subtype:AngbandEventWakeup data1:0 data2:0]; - [NSApp postEvent:event atStart:NO]; -} - - -/** - * Create and initialize window number "i" - */ -static term *term_data_link(int i) -{ - NSArray *terminalDefaults = [[NSUserDefaults standardUserDefaults] valueForKey: AngbandTerminalsDefaultsKey]; - NSInteger rows = 24; - NSInteger columns = 80; - - if( i < (int)[terminalDefaults count] ) - { - NSDictionary *term = [terminalDefaults objectAtIndex: i]; - rows = [[term valueForKey: AngbandTerminalRowsDefaultsKey] integerValue]; - columns = [[term valueForKey: AngbandTerminalColumnsDefaultsKey] integerValue]; - } - - /* Allocate */ - term *newterm = ZNEW(term); - - /* Initialize the term */ - term_init(newterm, columns, rows, 256 /* keypresses, for some reason? */); - - /* Differentiate between BS/^h, Tab/^i, etc. */ - /* newterm->complex_input = TRUE; */ - - /* Use a "software" cursor */ - newterm->soft_cursor = TRUE; - - /* Disable the per-row flush notifications since they are not used. */ - newterm->never_frosh = TRUE; - - /* Erase with "white space" */ - newterm->attr_blank = TERM_WHITE; - newterm->char_blank = ' '; - - /* Prepare the init/nuke hooks */ - newterm->init_hook = Term_init_cocoa; - newterm->nuke_hook = Term_nuke_cocoa; - - /* Prepare the function hooks */ - newterm->xtra_hook = Term_xtra_cocoa; - newterm->wipe_hook = Term_wipe_cocoa; - newterm->curs_hook = Term_curs_cocoa; - newterm->bigcurs_hook = Term_bigcurs_cocoa; - newterm->text_hook = Term_text_cocoa; - newterm->pict_hook = Term_pict_cocoa; - /* newterm->mbcs_hook = Term_mbcs_cocoa; */ - - /* Global pointer */ - angband_term[i] = newterm; - - return newterm; -} - -/** - * Load preferences from preferences file for current host+current user+ - * current application. - */ -static void load_prefs() -{ - NSUserDefaults *defs = [NSUserDefaults angbandDefaults]; - - /* Make some default defaults */ - NSMutableArray *defaultTerms = [[NSMutableArray alloc] init]; - - /* The following default rows/cols were determined experimentally by first - * finding the ideal window/font size combinations. But because of awful - * temporal coupling in Term_init_cocoa(), it's impossible to set up the - * defaults there, so we do it this way. */ - for( NSUInteger i = 0; i < ANGBAND_TERM_MAX; i++ ) - { - int columns, rows; - BOOL visible = YES; - - switch( i ) - { - case 0: - columns = 129; - rows = 32; - break; - case 1: - columns = 84; - rows = 20; - break; - case 2: - columns = 42; - rows = 24; - break; - case 3: - columns = 42; - rows = 20; - break; - case 4: - columns = 42; - rows = 16; - break; - case 5: - columns = 84; - rows = 20; - break; - default: - columns = 80; - rows = 24; - visible = NO; - break; - } - - NSDictionary *standardTerm = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInt: rows], AngbandTerminalRowsDefaultsKey, - [NSNumber numberWithInt: columns], AngbandTerminalColumnsDefaultsKey, - [NSNumber numberWithBool: visible], AngbandTerminalVisibleDefaultsKey, - nil]; - [defaultTerms addObject: standardTerm]; - } - - NSDictionary *defaults = [[NSDictionary alloc] initWithObjectsAndKeys: -#ifdef JP - @"Osaka", @"FontName", -#else - @"Menlo", @"FontName", -#endif - [NSNumber numberWithFloat:13.f], @"FontSize", - [NSNumber numberWithInt:60], AngbandFrameRateDefaultsKey, - [NSNumber numberWithBool:YES], AngbandSoundDefaultsKey, - [NSNumber numberWithInt:GRAPHICS_NONE], AngbandGraphicsDefaultsKey, - [NSNumber numberWithBool:YES], AngbandBigTileDefaultsKey, - defaultTerms, AngbandTerminalsDefaultsKey, - nil]; - [defs registerDefaults:defaults]; - [defaults release]; - [defaultTerms release]; - - /* Preferred graphics mode */ - graf_mode_req = [defs integerForKey:AngbandGraphicsDefaultsKey]; - if (graf_mode_req != GRAPHICS_NONE && - get_graphics_mode(graf_mode_req)->grafID != GRAPHICS_NONE && - [defs boolForKey:AngbandBigTileDefaultsKey] == YES) { - use_bigtile = TRUE; - arg_bigtile = TRUE; - } else { - use_bigtile = FALSE; - arg_bigtile = FALSE; - } - - /* Use sounds; set the Angband global */ - use_sound = ([defs boolForKey:AngbandSoundDefaultsKey] == YES) ? TRUE : FALSE; - - /* fps */ - frames_per_second = [defs integerForKey:AngbandFrameRateDefaultsKey]; - - /* Font */ - default_font = [[NSFont fontWithName:[defs valueForKey:@"FontName-0"] size:[defs floatForKey:@"FontSize-0"]] retain]; - if (! default_font) default_font = [[NSFont fontWithName:@"Menlo" size:13.] retain]; -} - -/** - * Arbitary limit on number of possible samples per event - */ -#define MAX_SAMPLES 16 - -/** - * Struct representing all data for a set of event samples - */ -typedef struct -{ - int num; /* Number of available samples for this event */ - NSSound *sound[MAX_SAMPLES]; -} sound_sample_list; - -/** - * Array of event sound structs - */ -static sound_sample_list samples[MSG_MAX]; - - -/** - * Load sound effects based on sound.cfg within the xtra/sound directory; - * bridge to Cocoa to use NSSound for simple loading and playback, avoiding - * I/O latency by cacheing all sounds at the start. Inherits full sound - * format support from Quicktime base/plugins. - * pelpel favoured a plist-based parser for the future but .cfg support - * improves cross-platform compatibility. - */ -static void load_sounds(void) -{ - char sound_dir[1024]; - char path[1024]; - char buffer[2048]; - FILE *fff; - - /* Build the "sound" path */ - path_build(sound_dir, sizeof(sound_dir), ANGBAND_DIR_XTRA, "sound"); - - /* Find and open the config file */ - path_build(path, sizeof(path), sound_dir, "sound.cfg"); - fff = my_fopen(path, "r"); - - /* Handle errors */ - if (!fff) - { - NSLog(@"The sound configuration file could not be opened."); - return; - } - - /* Instantiate an autorelease pool for use by NSSound */ - NSAutoreleasePool *autorelease_pool; - autorelease_pool = [[NSAutoreleasePool alloc] init]; - - /* Use a dictionary to unique sounds, so we can share NSSounds across - * multiple events */ - NSMutableDictionary *sound_dict = [NSMutableDictionary dictionary]; - - /* - * This loop may take a while depending on the count and size of samples - * to load. - */ - - /* Parse the file */ - /* Lines are always of the form "name = sample [sample ...]" */ - while (my_fgets(fff, buffer, sizeof(buffer)) == 0) - { - char *msg_name; - char *cfg_sample_list; - char *search; - char *cur_token; - char *next_token; - int event; - - /* Skip anything not beginning with an alphabetic character */ - if (!buffer[0] || !isalpha((unsigned char)buffer[0])) continue; - - /* Split the line into two: message name, and the rest */ - search = strchr(buffer, ' '); - cfg_sample_list = strchr(search + 1, ' '); - if (!search) continue; - if (!cfg_sample_list) continue; - - /* Set the message name, and terminate at first space */ - msg_name = buffer; - search[0] = '\0'; - - /* Make sure this is a valid event name */ - for (event = MSG_MAX - 1; event >= 0; event--) - { - if (strcmp(msg_name, angband_sound_name[event]) == 0) - break; - } - if (event < 0) continue; - - /* Advance the sample list pointer so it's at the beginning of text */ - cfg_sample_list++; - if (!cfg_sample_list[0]) continue; - - /* Terminate the current token */ - cur_token = cfg_sample_list; - search = strchr(cur_token, ' '); - if (search) - { - search[0] = '\0'; - next_token = search + 1; - } - else - { - next_token = NULL; - } - - /* - * Now we find all the sample names and add them one by one - */ - while (cur_token) - { - int num = samples[event].num; - - /* Don't allow too many samples */ - if (num >= MAX_SAMPLES) break; - - NSString *token_string = [NSString stringWithUTF8String:cur_token]; - NSSound *sound = [sound_dict objectForKey:token_string]; - - if (! sound) - { - struct stat stb; - /* We have to load the sound. Build the path to the sample */ - path_build(path, sizeof(path), sound_dir, cur_token); - if (stat(path, &stb) == 0) - { - - /* Load the sound into memory */ - sound = [[[NSSound alloc] initWithContentsOfFile:[NSString stringWithUTF8String:path] byReference:YES] autorelease]; - if (sound) [sound_dict setObject:sound forKey:token_string]; - } - } - - /* Store it if we loaded it */ - if (sound) - { - samples[event].sound[num] = [sound retain]; - - /* Imcrement the sample count */ - samples[event].num++; - } - - - /* Figure out next token */ - cur_token = next_token; - if (next_token) - { - /* Try to find a space */ - search = strchr(cur_token, ' '); - - /* If we can find one, terminate, and set new "next" */ - if (search) - { - search[0] = '\0'; - next_token = search + 1; - } - else - { - /* Otherwise prevent infinite looping */ - next_token = NULL; - } - } - } - } - - /* Release the autorelease pool */ - [autorelease_pool release]; - - /* Close the file */ - my_fclose(fff); + /* Success */ + return 0; } /** - * Play sound effects asynchronously. Select a sound from any available - * for the required event, and bridge to Cocoa to play it. - */ -static void play_sound(int event) -{ - /* Paranoia */ - if (event < 0 || event >= MSG_MAX) return; - - /* Load sounds just-in-time (once) */ - static BOOL loaded = NO; - if (!loaded) { - loaded = YES; - load_sounds(); - } - - /* Check there are samples for this event */ - if (!samples[event].num) return; - - /* Instantiate an autorelease pool for use by NSSound */ - NSAutoreleasePool *autorelease_pool; - autorelease_pool = [[NSAutoreleasePool alloc] init]; - - /* Choose a random event */ - int s = randint0(samples[event].num); - - /* Stop the sound if it's currently playing */ - if ([samples[event].sound[s] isPlaying]) - [samples[event].sound[s] stop]; - - /* Play the sound */ - [samples[event].sound[s] play]; - - /* Release the autorelease pool */ - [autorelease_pool drain]; -} - -/* - * + * Post a nonsense event so that our event loop wakes up */ -static void init_windows(void) +static void wakeup_event_loop(void) { - /* Create the main window */ - term *primary = term_data_link(0); - - /* Prepare to create any additional windows */ - int i; - for (i=1; i < ANGBAND_TERM_MAX; i++) { - term_data_link(i); - } - - /* Activate the primary term */ - Term_activate(primary); + /* Big hack - send a nonsense event to make us update */ + NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:NULL subtype:AngbandEventWakeup data1:0 data2:0]; + [NSApp postEvent:event atStart:NO]; } + /** * Handle the "open_when_ready" flag */ @@ -3467,14 +3664,17 @@ static void AngbandHandleEventMouseDown( NSEvent *event ) { #if 0 AngbandContext *angbandContext = [[[event window] contentView] angbandContext]; - AngbandContext *mainAngbandContext = angband_term[0]->data; + AngbandContext *mainAngbandContext = + (__bridge AngbandContext*) (angband_term[0]->data); - if (mainAngbandContext->primaryWindow && [[event window] windowNumber] == [mainAngbandContext->primaryWindow windowNumber]) + if (mainAngbandContext.primaryWindow && + [[event window] windowNumber] == + [mainAngbandContext.primaryWindow windowNumber]) { int cols, rows, x, y; Term_get_size(&cols, &rows); - NSSize tileSize = angbandContext->tileSize; - NSSize border = angbandContext->borderSize; + NSSize tileSize = angbandContext.tileSize; + NSSize border = angbandContext.borderSize; NSPoint windowPoint = [event locationInWindow]; /* Adjust for border; add border height because window origin is at @@ -3515,8 +3715,9 @@ static void AngbandHandleEventMouseDown( NSEvent *event ) } } #endif - /* Pass click through to permit focus change, resize, etc. */ - [NSApp sendEvent:event]; + + /* Pass click through to permit focus change, resize, etc. */ + [NSApp sendEvent:event]; } @@ -3656,42 +3857,39 @@ static BOOL send_event(NSEvent *event) * Check for Events, return TRUE if we process any */ static BOOL check_events(int wait) -{ - - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - /* Handles the quit_when_ready flag */ - if (quit_when_ready) quit_calmly(); - - NSDate* endDate; - if (wait == CHECK_EVENTS_WAIT) endDate = [NSDate distantFuture]; - else endDate = [NSDate distantPast]; - - NSEvent* event; - for (;;) { - if (quit_when_ready) - { - /* send escape events until we quit */ - Term_keypress(0x1B); - [pool drain]; - return false; - } - else { - event = [NSApp nextEventMatchingMask:-1 untilDate:endDate inMode:NSDefaultRunLoopMode dequeue:YES]; - if (! event) - { - [pool drain]; - return FALSE; - } - if (send_event(event)) break; - } +{ + BOOL result = YES; + + @autoreleasepool { + /* Handles the quit_when_ready flag */ + if (quit_when_ready) quit_calmly(); + + NSDate* endDate; + if (wait == CHECK_EVENTS_WAIT) endDate = [NSDate distantFuture]; + else endDate = [NSDate distantPast]; + + NSEvent* event; + for (;;) { + if (quit_when_ready) + { + /* send escape events until we quit */ + Term_keypress(0x1B); + result = NO; + break; + } + else { + event = [NSApp nextEventMatchingMask:-1 untilDate:endDate + inMode:NSDefaultRunLoopMode dequeue:YES]; + if (! event) { + result = NO; + break; + } + if (send_event(event)) break; + } + } } - - [pool drain]; - - /* Something happened */ - return YES; - + + return result; } /** @@ -3716,7 +3914,6 @@ static void hook_plog(const char * str) alert.messageText = msg; alert.informativeText = info; NSModalResponse result = [alert runModal]; - [alert release]; } } @@ -3771,7 +3968,6 @@ static NSString* get_lib_directory(void) alert.informativeText = info; [alert addButtonWithTitle:quit_label]; NSModalResponse result = [alert runModal]; - [alert release]; exit( 0 ); } @@ -3836,6 +4032,15 @@ static void prepare_paths_and_directories(void) } /** + * Play sound effects asynchronously. Select a sound from any available + * for the required event, and bridge to Cocoa to play it. + */ +static void play_sound(int event) +{ + [[AngbandSoundCatalog sharedSounds] playSound:event]; +} + +/** * ------------------------------------------------------------------------ * Main program * ------------------------------------------------------------------------ */ @@ -3856,16 +4061,18 @@ static void prepare_paths_and_directories(void) - (IBAction)editFont:sender { NSFontPanel *panel = [NSFontPanel sharedFontPanel]; - NSFont *termFont = default_font; + NSFont *termFont = [AngbandContext defaultFont]; int i; for (i=0; i < ANGBAND_TERM_MAX; i++) { - if ([(id)angband_term[i]->data isMainWindow]) { - termFont = [(id)angband_term[i]->data selectionFont]; + AngbandContext *context = + (__bridge AngbandContext*) (angband_term[i]->data); + if ([context isMainWindow]) { + termFont = [context angbandViewFont]; break; } } - + [panel setPanelFont:termFont isMultiple:NO]; [panel orderFront:self]; } @@ -3880,25 +4087,25 @@ static void prepare_paths_and_directories(void) { int mainTerm; for (mainTerm=0; mainTerm < ANGBAND_TERM_MAX; mainTerm++) { - if ([(id)angband_term[mainTerm]->data isMainWindow]) { + AngbandContext *context = + (__bridge AngbandContext*) (angband_term[mainTerm]->data); + if ([context isMainWindow]) { break; } } /* Bug #1709: Only change font for angband windows */ if (mainTerm == ANGBAND_TERM_MAX) return; - - NSFont *oldFont = default_font; + + NSFont *oldFont = [AngbandContext defaultFont]; NSFont *newFont = [sender convertFont:oldFont]; if (! newFont) return; /*paranoia */ - + /* Store as the default font if we changed the first term */ if (mainTerm == 0) { - [newFont retain]; - [default_font release]; - default_font = newFont; + [AngbandContext setDefaultFont:newFont]; } - + /* Record it in the preferences */ NSUserDefaults *defs = [NSUserDefaults angbandDefaults]; [defs setValue:[newFont fontName] @@ -3906,13 +4113,14 @@ static void prepare_paths_and_directories(void) [defs setFloat:[newFont pointSize] forKey:[NSString stringWithFormat:@"FontSize-%d", mainTerm]]; [defs synchronize]; - + NSDisableScreenUpdates(); - + /* Update window */ - AngbandContext *angbandContext = angband_term[mainTerm]->data; + AngbandContext *angbandContext = + (__bridge AngbandContext*) (angband_term[mainTerm]->data); [(id)angbandContext setSelectionFont:newFont adjustTerminal: YES]; - + NSEnableScreenUpdates(); if (mainTerm == 0 && game_in_progress) { @@ -3926,49 +4134,55 @@ static void prepare_paths_and_directories(void) - (IBAction)openGame:sender { - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - BOOL selectedSomething = NO; - int panelResult; - - /* Get where we think the save files are */ - NSURL *startingDirectoryURL = [NSURL fileURLWithPath:[NSString stringWithCString:ANGBAND_DIR_SAVE encoding:NSASCIIStringEncoding] isDirectory:YES]; - - /* Set up an open panel */ - NSOpenPanel* panel = [NSOpenPanel openPanel]; - [panel setCanChooseFiles:YES]; - [panel setCanChooseDirectories:NO]; - [panel setResolvesAliases:YES]; - [panel setAllowsMultipleSelection:NO]; - [panel setTreatsFilePackagesAsDirectories:YES]; - [panel setDirectoryURL:startingDirectoryURL]; - - /* Run it */ - panelResult = [panel runModal]; - if (panelResult == NSOKButton) - { - NSArray* fileURLs = [panel URLs]; - if ([fileURLs count] > 0 && [[fileURLs objectAtIndex:0] isFileURL]) - { - NSURL* savefileURL = (NSURL *)[fileURLs objectAtIndex:0]; - /* The path property doesn't do the right thing except for - * URLs with the file scheme. We had getFileSystemRepresentation - * here before, but that wasn't introduced until OS X 10.9. */ - selectedSomething = [[savefileURL path] getCString:savefile - maxLength:sizeof savefile encoding:NSMacOSRomanStringEncoding]; - } - } - - if (selectedSomething) - { - /* Remember this so we can select it by default next time */ - record_current_savefile(); - - /* Game is in progress */ - game_in_progress = TRUE; - new_game = FALSE; + @autoreleasepool { + BOOL selectedSomething = NO; + int panelResult; + + /* Get where we think the save files are */ + NSURL *startingDirectoryURL = + [NSURL fileURLWithPath:[NSString stringWithCString:ANGBAND_DIR_SAVE encoding:NSASCIIStringEncoding] + isDirectory:YES]; + + /* Set up an open panel */ + NSOpenPanel* panel = [NSOpenPanel openPanel]; + [panel setCanChooseFiles:YES]; + [panel setCanChooseDirectories:NO]; + [panel setResolvesAliases:YES]; + [panel setAllowsMultipleSelection:NO]; + [panel setTreatsFilePackagesAsDirectories:YES]; + [panel setDirectoryURL:startingDirectoryURL]; + + /* Run it */ + panelResult = [panel runModal]; + if (panelResult == NSOKButton) + { + NSArray* fileURLs = [panel URLs]; + if ([fileURLs count] > 0 && [[fileURLs objectAtIndex:0] isFileURL]) + { + NSURL* savefileURL = (NSURL *)[fileURLs objectAtIndex:0]; + /* + * The path property doesn't do the right thing except for + * URLs with the file scheme. We had + * getFileSystemRepresentation here before, but that wasn't + * introduced until OS X 10.9. + */ + selectedSomething = [[savefileURL path] + getCString:savefile + maxLength:sizeof savefile + encoding:NSMacOSRomanStringEncoding]; + } + } + + if (selectedSomething) + { + /* Remember this so we can select it by default next time */ + record_current_savefile(); + + /* Game is in progress */ + game_in_progress = TRUE; + new_game = FALSE; + } } - - [pool drain]; } - (IBAction)saveGame:sender @@ -3986,73 +4200,247 @@ static void prepare_paths_and_directories(void) } /** - * Entry point for initializing Angband + * Create and initialize Angband terminal number "termIndex". */ -- (void)beginGame +- (void)linkTermData:(int)termIndex { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSArray *terminalDefaults = [[NSUserDefaults standardUserDefaults] + valueForKey: AngbandTerminalsDefaultsKey]; + NSInteger rows = 24; + NSInteger columns = 80; + + if (termIndex < (int)[terminalDefaults count]) { + NSDictionary *term = [terminalDefaults objectAtIndex:termIndex]; + rows = [[term valueForKey: AngbandTerminalRowsDefaultsKey] + integerValue]; + columns = [[term valueForKey: AngbandTerminalColumnsDefaultsKey] + integerValue]; + } + + /* Allocate */ + term *newterm = ZNEW(term); + + /* Initialize the term */ + term_init(newterm, columns, rows, 256 /* keypresses, for some reason? */); - /* Hooks in some "z-util.c" hooks */ - plog_aux = hook_plog; - quit_aux = hook_quit; + /* Differentiate between BS/^h, Tab/^i, etc. */ + /* newterm->complex_input = TRUE; */ - /* Initialize file paths */ - prepare_paths_and_directories(); + /* Use a "software" cursor */ + newterm->soft_cursor = TRUE; - /* Note the "system" */ - ANGBAND_SYS = "coc"; + /* Disable the per-row flush notifications since they are not used. */ + newterm->never_frosh = TRUE; - /* Load possible graphics modes */ - init_graphics_modes(); + /* Erase with "white space" */ + newterm->attr_blank = TERM_WHITE; + newterm->char_blank = ' '; - /* Load preferences */ - load_prefs(); + /* Prepare the init/nuke hooks */ + newterm->init_hook = Term_init_cocoa; + newterm->nuke_hook = Term_nuke_cocoa; - /* Prepare the windows */ - init_windows(); + /* Prepare the function hooks */ + newterm->xtra_hook = Term_xtra_cocoa; + newterm->wipe_hook = Term_wipe_cocoa; + newterm->curs_hook = Term_curs_cocoa; + newterm->bigcurs_hook = Term_bigcurs_cocoa; + newterm->text_hook = Term_text_cocoa; + newterm->pict_hook = Term_pict_cocoa; + /* newterm->mbcs_hook = Term_mbcs_cocoa; */ - /* Set up game event handlers */ - /* init_display(); */ + /* Global pointer */ + angband_term[termIndex] = newterm; +} - /* Register the sound hook */ - /* sound_hook = play_sound; */ +/** + * Allocate the primary Angband terminal and activate it. Allocate the other + * Angband terminals. + */ +- (void)initWindows { + for (int i = 0; i < ANGBAND_TERM_MAX; i++) { + [self linkTermData:i]; + } - /* Initialise game */ - init_angband(); + Term_activate(angband_term[0]); +} - /* Initialize some save file stuff */ - player_egid = getegid(); +/** + * Load preferences from preferences file for current host+current user+ + * current application. + */ +- (void)loadPrefs +{ + NSUserDefaults *defs = [NSUserDefaults angbandDefaults]; - /* We are now initialized */ - initialized = TRUE; + /* Make some default defaults */ + NSMutableArray *defaultTerms = [[NSMutableArray alloc] init]; - /* Handle "open_when_ready" */ - handle_open_when_ready(); + /* + * The following default rows/cols were determined experimentally by first + * finding the ideal window/font size combinations. But because of awful + * temporal coupling in Term_init_cocoa(), it's impossible to set up the + * defaults there, so we do it this way. + */ + for (NSUInteger i = 0; i < ANGBAND_TERM_MAX; i++) { + int columns, rows; + BOOL visible = YES; + + switch (i) { + case 0: + columns = 129; + rows = 32; + break; + case 1: + columns = 84; + rows = 20; + break; + case 2: + columns = 42; + rows = 24; + break; + case 3: + columns = 42; + rows = 20; + break; + case 4: + columns = 42; + rows = 16; + break; + case 5: + columns = 84; + rows = 20; + break; + default: + columns = 80; + rows = 24; + visible = NO; + break; + } - /* Handle pending events (most notably update) and flush input */ - Term_flush(); + NSDictionary *standardTerm = + [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt: rows], AngbandTerminalRowsDefaultsKey, + [NSNumber numberWithInt: columns], AngbandTerminalColumnsDefaultsKey, + [NSNumber numberWithBool: visible], AngbandTerminalVisibleDefaultsKey, + nil]; + [defaultTerms addObject: standardTerm]; + } - /* Prompt the user. */ - int message_row = (Term->hgt - 23) / 5 + 23; - Term_erase(0, message_row, 255); - put_str( + NSDictionary *defaults = [[NSDictionary alloc] initWithObjectsAndKeys: #ifdef JP - "['ファイル' メニューから '新' または '開く' を選択します]", - message_row, (Term->wid - 57) / 2 + @"Osaka", @"FontName", #else - "[Choose 'New' or 'Open' from the 'File' menu]", - message_row, (Term->wid - 45) / 2 + @"Menlo", @"FontName", #endif - ); - Term_fresh(); + [NSNumber numberWithFloat:13.f], @"FontSize", + [NSNumber numberWithInt:60], AngbandFrameRateDefaultsKey, + [NSNumber numberWithBool:YES], AngbandSoundDefaultsKey, + [NSNumber numberWithInt:GRAPHICS_NONE], AngbandGraphicsDefaultsKey, + [NSNumber numberWithBool:YES], AngbandBigTileDefaultsKey, + defaultTerms, AngbandTerminalsDefaultsKey, + nil]; + [defs registerDefaults:defaults]; + + /* Preferred graphics mode */ + graf_mode_req = [defs integerForKey:AngbandGraphicsDefaultsKey]; + if (graf_mode_req != GRAPHICS_NONE && + get_graphics_mode(graf_mode_req)->grafID != GRAPHICS_NONE && + [defs boolForKey:AngbandBigTileDefaultsKey] == YES) { + use_bigtile = TRUE; + arg_bigtile = TRUE; + } else { + use_bigtile = FALSE; + arg_bigtile = FALSE; + } + + /* Use sounds; set the Angband global */ + if ([defs boolForKey:AngbandSoundDefaultsKey] == YES) { + use_sound = TRUE; + [AngbandSoundCatalog sharedSounds].enabled = YES; + } else { + use_sound = FALSE; + [AngbandSoundCatalog sharedSounds].enabled = NO; + } + + /* fps */ + frames_per_second = [defs integerForKey:AngbandFrameRateDefaultsKey]; + + /* Font */ + [AngbandContext + setDefaultFont:[NSFont fontWithName:[defs valueForKey:@"FontName-0"] + size:[defs floatForKey:@"FontSize-0"]]]; + if (! [AngbandContext defaultFont]) + [AngbandContext + setDefaultFont:[NSFont fontWithName:@"Menlo" size:13.]]; +} + +/** + * Entry point for initializing Angband + */ +- (void)beginGame +{ + @autoreleasepool { + /* Hooks in some "z-util.c" hooks */ + plog_aux = hook_plog; + quit_aux = hook_quit; + + /* Initialize file paths */ + prepare_paths_and_directories(); + + /* Note the "system" */ + ANGBAND_SYS = "coc"; + + /* Load possible graphics modes */ + init_graphics_modes(); + + /* Load preferences */ + [self loadPrefs]; + + /* Prepare the windows */ + [self initWindows]; + + /* Set up game event handlers */ + /* init_display(); */ + + /* Register the sound hook */ + /* sound_hook = play_sound; */ + + /* Initialise game */ + init_angband(); + + /* Initialize some save file stuff */ + player_egid = getegid(); - [pool drain]; + /* We are now initialized */ + initialized = TRUE; + + /* Handle "open_when_ready" */ + handle_open_when_ready(); + + /* Handle pending events (most notably update) and flush input */ + Term_flush(); + + /* Prompt the user */ + int message_row = (Term->hgt - 23) / 5 + 23; + Term_erase(0, message_row, 255); + put_str( +#ifdef JP + "['ファイル' メニューから '新' または '開く' を選択します]", + message_row, (Term->wid - 57) / 2 +#else + "[Choose 'New' or 'Open' from the 'File' menu]", + message_row, (Term->wid - 45) / 2 +#endif + ); + Term_fresh(); + } while (!game_in_progress) { - NSAutoreleasePool *splashScreenPool = [[NSAutoreleasePool alloc] init]; - NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES]; - if (event) [NSApp sendEvent:event]; - [splashScreenPool drain]; + @autoreleasepool { + NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES]; + if (event) [NSApp sendEvent:event]; + } } /* @@ -4152,53 +4540,6 @@ static void prepare_paths_and_directories(void) [[NSUserDefaults angbandDefaults] setInteger:frames_per_second forKey:AngbandFrameRateDefaultsKey]; } -- (void)selectWindow: (id)sender -{ - NSInteger subwindowNumber = [(NSMenuItem *)sender tag] - AngbandWindowMenuItemTagBase; - AngbandContext *context = angband_term[subwindowNumber]->data; - [context->primaryWindow makeKeyAndOrderFront: self]; - [context saveWindowVisibleToDefaults: YES]; -} - -- (void)prepareWindowsMenu -{ - /* Get the window menu with default items and add a separator and item for - * the main window */ - NSMenu *windowsMenu = [[NSApplication sharedApplication] windowsMenu]; - [windowsMenu addItem: [NSMenuItem separatorItem]]; - - NSString *title1 = [NSString stringWithCString:angband_term_name[0] -#ifdef JP - encoding:NSJapaneseEUCStringEncoding -#else - encoding:NSMacOSRomanStringEncoding -#endif - ]; - NSMenuItem *angbandItem = [[NSMenuItem alloc] initWithTitle:title1 action: @selector(selectWindow:) keyEquivalent: @"0"]; - [angbandItem setTarget: self]; - [angbandItem setTag: AngbandWindowMenuItemTagBase]; - [windowsMenu addItem: angbandItem]; - [angbandItem release]; - - /* Add items for the additional term windows */ - for( NSInteger i = 1; i < ANGBAND_TERM_MAX; i++ ) - { - NSString *title = [NSString stringWithCString:angband_term_name[i] -#ifdef JP - encoding:NSJapaneseEUCStringEncoding -#else - encoding:NSMacOSRomanStringEncoding -#endif - ]; - NSString *keyEquivalent = [NSString stringWithFormat: @"%ld", (long)i]; - NSMenuItem *windowItem = [[NSMenuItem alloc] initWithTitle: title action: @selector(selectWindow:) keyEquivalent: keyEquivalent]; - [windowItem setTarget: self]; - [windowItem setTag: AngbandWindowMenuItemTagBase + i]; - [windowsMenu addItem: windowItem]; - [windowItem release]; - } -} - - (void)setGraphicsMode:(NSMenuItem *)sender { /* We stashed the graphics mode ID in the menu item's tag */ @@ -4207,7 +4548,7 @@ static void prepare_paths_and_directories(void) /* Stash it in UserDefaults */ [[NSUserDefaults angbandDefaults] setInteger:graf_mode_req forKey:AngbandGraphicsDefaultsKey]; [[NSUserDefaults angbandDefaults] synchronize]; - + if (graf_mode_req == GRAPHICS_NONE || get_graphics_mode(graf_mode_req) == GRAPHICS_NONE) { if (use_bigtile) { @@ -4227,19 +4568,36 @@ static void prepare_paths_and_directories(void) /* Hack -- Force redraw */ do_cmd_redraw(); - + /* Wake up the event loop so it notices the change */ wakeup_event_loop(); } } +- (void)selectWindow: (id)sender +{ + NSInteger subwindowNumber = + [(NSMenuItem *)sender tag] - AngbandWindowMenuItemTagBase; + AngbandContext *context = + (__bridge AngbandContext*) (angband_term[subwindowNumber]->data); + [context.primaryWindow makeKeyAndOrderFront: self]; + [context saveWindowVisibleToDefaults: YES]; +} + - (IBAction) toggleSound: (NSMenuItem *) sender { BOOL is_on = (sender.state == NSOnState); /* Toggle the state and update the Angband global and preferences. */ - sender.state = (is_on) ? NSOffState : NSOnState; - use_sound = (is_on) ? FALSE : TRUE; + if (is_on) { + sender.state = NSOffState; + use_sound = FALSE; + [AngbandSoundCatalog sharedSounds].enabled = NO; + } else { + sender.state = NSOnState; + use_sound = TRUE; + [AngbandSoundCatalog sharedSounds].enabled = YES; + } [[NSUserDefaults angbandDefaults] setBool:(! is_on) forKey:AngbandSoundDefaultsKey]; } @@ -4265,6 +4623,51 @@ static void prepare_paths_and_directories(void) } } +- (void)prepareWindowsMenu +{ + @autoreleasepool { + /* + * Get the window menu with default items and add a separator and + * item for the main window. + */ + NSMenu *windowsMenu = [[NSApplication sharedApplication] windowsMenu]; + [windowsMenu addItem: [NSMenuItem separatorItem]]; + + NSString *title1 = [NSString stringWithCString:angband_term_name[0] +#ifdef JP + encoding:NSJapaneseEUCStringEncoding +#else + encoding:NSMacOSRomanStringEncoding +#endif + ]; + NSMenuItem *angbandItem = [[NSMenuItem alloc] initWithTitle:title1 action: @selector(selectWindow:) keyEquivalent: @"0"]; + [angbandItem setTarget: self]; + [angbandItem setTag: AngbandWindowMenuItemTagBase]; + [windowsMenu addItem: angbandItem]; + + /* Add items for the additional term windows */ + for( NSInteger i = 1; i < ANGBAND_TERM_MAX; i++ ) + { + NSString *title = [NSString stringWithCString:angband_term_name[i] +#ifdef JP + encoding:NSJapaneseEUCStringEncoding +#else + encoding:NSMacOSRomanStringEncoding +#endif + ]; + NSString *keyEquivalent = + [NSString stringWithFormat: @"%ld", (long)i]; + NSMenuItem *windowItem = + [[NSMenuItem alloc] initWithTitle: title + action: @selector(selectWindow:) + keyEquivalent: keyEquivalent]; + [windowItem setTarget: self]; + [windowItem setTag: AngbandWindowMenuItemTagBase + i]; + [windowsMenu addItem: windowItem]; + } + } +} + /** * Send a command to Angband via a menu item. This places the appropriate key * down events into the queue so that it seems like the user pressed them @@ -4274,7 +4677,9 @@ static void prepare_paths_and_directories(void) { NSMenuItem *menuItem = (NSMenuItem *)sender; NSString *command = [self.commandMenuTagMap objectForKey: [NSNumber numberWithInteger: [menuItem tag]]]; - NSInteger windowNumber = [((AngbandContext *)angband_term[0]->data)->primaryWindow windowNumber]; + AngbandContext* context = + (__bridge AngbandContext*) (angband_term[0]->data); + NSInteger windowNumber = [context.primaryWindow windowNumber]; /* Send a \ to bypass keymaps */ NSEvent *escape = [NSEvent keyEventWithType: NSKeyDown @@ -4308,42 +4713,49 @@ static void prepare_paths_and_directories(void) */ - (void)prepareCommandMenu { - NSString *commandMenuPath = [[NSBundle mainBundle] pathForResource: @"CommandMenu" ofType: @"plist"]; - NSArray *commandMenuItems = [[NSArray alloc] initWithContentsOfFile: commandMenuPath]; - NSMutableDictionary *angbandCommands = [[NSMutableDictionary alloc] init]; - NSString *tblname = @"CommandMenu"; - NSInteger tagOffset = 0; - - for( NSDictionary *item in commandMenuItems ) - { - BOOL useShiftModifier = [[item valueForKey: @"ShiftModifier"] boolValue]; - BOOL useOptionModifier = [[item valueForKey: @"OptionModifier"] boolValue]; - NSUInteger keyModifiers = NSCommandKeyMask; - keyModifiers |= (useShiftModifier) ? NSShiftKeyMask : 0; - keyModifiers |= (useOptionModifier) ? NSAlternateKeyMask : 0; - - NSString *lookup = [item valueForKey: @"Title"]; - NSString *title = NSLocalizedStringWithDefaultValue( - lookup, tblname, [NSBundle mainBundle], lookup, @""); - NSString *key = [item valueForKey: @"KeyEquivalent"]; - NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle: title action: @selector(sendAngbandCommand:) keyEquivalent: key]; - [menuItem setTarget: self]; - [menuItem setKeyEquivalentModifierMask: keyModifiers]; - [menuItem setTag: AngbandCommandMenuItemTagBase + tagOffset]; - [self.commandMenu addItem: menuItem]; - [menuItem release]; + @autoreleasepool { + NSString *commandMenuPath = + [[NSBundle mainBundle] pathForResource: @"CommandMenu" + ofType: @"plist"]; + NSArray *commandMenuItems = + [[NSArray alloc] initWithContentsOfFile: commandMenuPath]; + NSMutableDictionary *angbandCommands = + [[NSMutableDictionary alloc] init]; + NSString *tblname = @"CommandMenu"; + NSInteger tagOffset = 0; + + for( NSDictionary *item in commandMenuItems ) + { + BOOL useShiftModifier = + [[item valueForKey: @"ShiftModifier"] boolValue]; + BOOL useOptionModifier = + [[item valueForKey: @"OptionModifier"] boolValue]; + NSUInteger keyModifiers = NSCommandKeyMask; + keyModifiers |= (useShiftModifier) ? NSShiftKeyMask : 0; + keyModifiers |= (useOptionModifier) ? NSAlternateKeyMask : 0; + + NSString *lookup = [item valueForKey: @"Title"]; + NSString *title = NSLocalizedStringWithDefaultValue( + lookup, tblname, [NSBundle mainBundle], lookup, @""); + NSString *key = [item valueForKey: @"KeyEquivalent"]; + NSMenuItem *menuItem = + [[NSMenuItem alloc] initWithTitle: title + action: @selector(sendAngbandCommand:) + keyEquivalent: key]; + [menuItem setTarget: self]; + [menuItem setKeyEquivalentModifierMask: keyModifiers]; + [menuItem setTag: AngbandCommandMenuItemTagBase + tagOffset]; + [self.commandMenu addItem: menuItem]; + + NSString *angbandCommand = [item valueForKey: @"AngbandCommand"]; + [angbandCommands setObject: angbandCommand + forKey: [NSNumber numberWithInteger: [menuItem tag]]]; + tagOffset++; + } - NSString *angbandCommand = [item valueForKey: @"AngbandCommand"]; - [angbandCommands setObject: angbandCommand forKey: [NSNumber numberWithInteger: [menuItem tag]]]; - tagOffset++; + self.commandMenuTagMap = [[NSDictionary alloc] + initWithDictionary: angbandCommands]; } - - [commandMenuItems release]; - - NSDictionary *safeCommands = [[NSDictionary alloc] initWithDictionary: angbandCommands]; - self.commandMenuTagMap = safeCommands; - [safeCommands release]; - [angbandCommands release]; } - (void)awakeFromNib @@ -4433,10 +4845,9 @@ static void prepare_paths_and_directories(void) key = [[NSString alloc] initWithUTF8String:graf->menuname]; title = NSLocalizedStringWithDefaultValue( key, tblname, [NSBundle mainBundle], key, @""); - + /* Make the item */ NSMenuItem *item = [menu addItemWithTitle:title action:action keyEquivalent:@""]; - [key release]; [item setTag:graf->grafID]; } } -- 2.11.0