#if defined(MACH_O_COCOA)
/* Mac headers */
-#include <Cocoa/Cocoa.h>
+#include <cocoa/AppDelegate.h>
//#include <Carbon/Carbon.h> /* For keycodes */
/* Hack - keycodes to enable compiling in macOS 10.14 */
#define kVK_Return 0x24
#define kVK_Escape 0x35
#define kVK_ANSI_KeypadEnter 0x4C
-static NSSize const AngbandScaleIdentity = {1.0, 1.0};
static NSString * const AngbandDirectoryNameLib = @"lib";
static NSString * const AngbandDirectoryNameBase = @"Hengband";
static NSString * const AngbandTerminalColumnsDefaultsKey = @"Columns";
static NSString * const AngbandTerminalVisibleDefaultsKey = @"Visible";
static NSString * const AngbandGraphicsDefaultsKey = @"GraphicsID";
+static NSString * const AngbandBigTileDefaultsKey = @"UseBigTiles";
static NSString * const AngbandFrameRateDefaultsKey = @"FramesPerSecond";
static NSString * const AngbandSoundDefaultsKey = @"AllowSound";
static NSInteger const AngbandWindowMenuItemTagBase = 1000;
@class AngbandView;
+/*
+ * To handle fonts where an individual glyph's bounding box can extend into
+ * neighboring columns, Term_curs_cocoa(), Term_pict_cocoa(),
+ * Term_text_cocoa(), and Term_wipe_cocoa() merely record what needs to be
+ * done with the actual drawing happening in response to the notification to
+ * flush all rows, the TERM_XTRA_FRESH case in Term_xtra_cocoa(). Can not use
+ * the TERM_XTRA_FROSH notification (the per-row flush), since with a software
+ * cursor, there are calls to Term_pict_cocoa(), Term_text_cocoa(), or
+ * Term_wipe_cocoa() to take care of the old cursor position which are not
+ * followed by a row flush.
+ */
+enum PendingCellChangeType {
+ CELL_CHANGE_NONE = 0,
+ CELL_CHANGE_WIPE,
+ CELL_CHANGE_TEXT,
+ CELL_CHANGE_PICT
+};
+struct PendingCellChange {
+ /*
+ * For text rendering, stores the character as a wchar_t; for tile
+ * rendering, stores the column in the tile set for the source tile.
+ */
+ union { wchar_t w; char c; } c;
+ /*
+ * For text rendering, stores the color; for tile rendering, stores the
+ * row in the tile set for the source tile.
+ */
+ TERM_COLOR a;
+ /*
+ * For text rendering, is one if wc is a character that takes up two
+ * columns (i.e. Japanese kanji); otherwise it is zero. For tile
+ * rendering, Stores the column in the tile set for the terrain tile. */
+ char tcol;
+ /*
+ * For tile rendering, stores the row in the tile set for the
+ * terrain tile.
+ */
+ TERM_COLOR trow;
+ enum PendingCellChangeType change_type;
+};
+
+struct PendingRowChange
+{
+ /*
+ * These are the first and last columns, inclusive, that have been
+ * modified. xmin is greater than xmax if no changes have been made.
+ */
+ int xmin, xmax;
+ /*
+ * This points to storage for a number of elements equal to the number
+ * of columns (implicitly gotten from the enclosing AngbandContext).
+ */
+ struct PendingCellChange* cell_changes;
+};
+
+static struct PendingRowChange* create_row_change(int ncol)
+{
+ struct PendingRowChange* prc =
+ (struct PendingRowChange*) malloc(sizeof(struct PendingRowChange));
+ struct PendingCellChange* pcc = (struct PendingCellChange*)
+ malloc(ncol * sizeof(struct PendingCellChange));
+ int i;
+
+ if (prc == 0 || pcc == 0) {
+ if (pcc != 0) {
+ free(pcc);
+ }
+ if (prc != 0) {
+ free(prc);
+ }
+ return 0;
+ }
+
+ prc->xmin = ncol;
+ prc->xmax = -1;
+ prc->cell_changes = pcc;
+ for (i = 0; i < ncol; ++i) {
+ pcc[i].change_type = CELL_CHANGE_NONE;
+ }
+ return prc;
+}
+
+
+static void destroy_row_change(struct PendingRowChange* prc)
+{
+ if (prc != 0) {
+ if (prc->cell_changes != 0) {
+ free(prc->cell_changes);
+ }
+ free(prc);
+ }
+}
+
+
+struct PendingChanges
+{
+ /* Hold the number of rows specified at creation. */
+ int nrow;
+ /*
+ * Hold the position set for the software cursor. Use negative indices
+ * to indicate that the cursor is not displayed.
+ */
+ int xcurs, ycurs;
+ /* Is nonzero if the cursor should be drawn at double the tile width. */
+ int bigcurs;
+ /* Record whether the changes include any text, picts, or wipes. */
+ int has_text, has_pict, has_wipe;
+ /*
+ * These are the first and last rows, inclusive, that have been
+ * modified. ymin is greater than ymax if no changes have been made.
+ */
+ int ymin, ymax;
+ /*
+ * This is an array of pointers to the changes. The number of elements
+ * is the number of rows. An element will be a NULL pointer if no
+ * modifications have been made to the row.
+ */
+ struct PendingRowChange** rows;
+};
+
+
+static struct PendingChanges* create_pending_changes(int ncol, int nrow)
+{
+ struct PendingChanges* pc =
+ (struct PendingChanges*) malloc(sizeof(struct PendingChanges));
+ struct PendingRowChange** pprc = (struct PendingRowChange**)
+ malloc(nrow * sizeof(struct PendingRowChange*));
+ int i;
+
+ if (pc == 0 || pprc == 0) {
+ if (pprc != 0) {
+ free(pprc);
+ }
+ if (pc != 0) {
+ free(pc);
+ }
+ return 0;
+ }
+
+ pc->nrow = nrow;
+ pc->xcurs = -1;
+ pc->ycurs = -1;
+ pc->bigcurs = 0;
+ pc->has_text = 0;
+ pc->has_pict = 0;
+ pc->has_wipe = 0;
+ pc->ymin = nrow;
+ pc->ymax = -1;
+ pc->rows = pprc;
+ for (i = 0; i < nrow; ++i) {
+ pprc[i] = 0;
+ }
+ return pc;
+}
+
+
+static void destroy_pending_changes(struct PendingChanges* pc)
+{
+ if (pc != 0) {
+ if (pc->rows != 0) {
+ int i;
+
+ for (i = 0; i < pc->nrow; ++i) {
+ if (pc->rows[i] != 0) {
+ destroy_row_change(pc->rows[i]);
+ }
+ }
+ free(pc->rows);
+ }
+ free(pc);
+ }
+}
+
+
+static void clear_pending_changes(struct PendingChanges* pc)
+{
+ pc->xcurs = -1;
+ pc->ycurs = -1;
+ pc->bigcurs = 0;
+ pc->has_text = 0;
+ pc->has_pict = 0;
+ pc->has_wipe = 0;
+ pc->ymin = pc->nrow;
+ pc->ymax = -1;
+ if (pc->rows != 0) {
+ int i;
+
+ for (i = 0; i < pc->nrow; ++i) {
+ if (pc->rows[i] != 0) {
+ destroy_row_change(pc->rows[i]);
+ pc->rows[i] = 0;
+ }
+ }
+ }
+}
+
+
+/* Return zero if successful; otherwise return a nonzero value. */
+static int resize_pending_changes(struct PendingChanges* pc, int nrow)
+{
+ struct PendingRowChange** pprc;
+ int i;
+
+ if (pc == 0) {
+ return 1;
+ }
+
+ pprc = (struct PendingRowChange**)
+ malloc(nrow * sizeof(struct PendingRowChange*));
+ if (pprc == 0) {
+ return 1;
+ }
+ for (i = 0; i < nrow; ++i) {
+ pprc[i] = 0;
+ }
+
+ if (pc->rows != 0) {
+ for (i = 0; i < pc->nrow; ++i) {
+ if (pc->rows[i] != 0) {
+ destroy_row_change(pc->rows[i]);
+ }
+ }
+ free(pc->rows);
+ }
+ pc->nrow = nrow;
+ pc->xcurs = -1;
+ pc->ycurs = -1;
+ pc->bigcurs = 0;
+ pc->has_text = 0;
+ pc->has_pict = 0;
+ pc->has_wipe = 0;
+ pc->ymin = nrow;
+ pc->ymax = -1;
+ pc->rows = pprc;
+ return 0;
+}
+
+
/* The max number of glyphs we support. Currently this only affects
- * updateGlyphInfo() for the calculation of the tile size (the glyphArray and
- * glyphWidths members of AngbandContext are only used in updateGlyphInfo()).
- * The rendering in drawWChar will work for glyphs not in updateGlyphInfo()'s
+ * updateGlyphInfo() for the calculation of the tile size, fontAscender,
+ * fontDescender, ncol_pre, and ncol_post (the glyphArray and glyphWidths
+ * members of AngbandContext are only used in updateGlyphInfo()). The
+ * rendering in drawWChar will work for glyphs not in updateGlyphInfo()'s
* set, and that is used for rendering Japanese characters.
*/
#define GLYPH_COUNT 256
/* Last time we drew, so we can throttle drawing */
CFAbsoluteTime lastRefreshTime;
-
+
+ struct PendingChanges* changes;
+ /*
+ * These are the number of columns before or after, respectively, a text
+ * change that may need to be redrawn.
+ */
+ int ncol_pre, ncol_post;
+
+ /* Flags whether or not a fullscreen transition is in progress. */
+ BOOL in_fullscreen_transition;
+
@private
BOOL _hasSubwindowFlags;
* defaults */
- (void)resizeTerminalWithContentRect: (NSRect)contentRect saveToDefaults: (BOOL)saveToDefaults;
+/*
+ * Change the minimum size for the window associated with the context.
+ * termIdx is the index for the terminal: pass it so this function can be
+ * used when self->terminal has not yet been set.
+ */
+- (void)setMinimumWindowSize:(int)termIdx;
+
/* Called from the view to indicate that it is starting or ending live resize */
- (void)viewWillStartLiveResize:(AngbandView *)view;
- (void)viewDidEndLiveResize:(AngbandView *)view;
- (void)saveWindowVisibleToDefaults: (BOOL)windowVisible;
- (BOOL)windowVisibleUsingDefaults;
-/* Class methods */
-
-/* Begins an Angband game. This is the entry point for starting off. */
-+ (void)beginGame;
-
-/* Ends an Angband game. */
-+ (void)endGame;
-
/* Internal method */
- (AngbandView *)activeView;
static void wakeup_event_loop(void);
static void hook_plog(const char *str);
static void hook_quit(const char * str);
+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);
/* The NSView subclass that draws our Angband image */
@interface AngbandView : NSView
{
- IBOutlet AngbandContext *angbandContext;
+ AngbandContext *angbandContext;
}
- (void)setAngbandContext:(AngbandContext *)context;
}
/*
- * Record the ascender and descender. Adjust both by 0.5 pixel to leave
- * space for antialiasing/subpixel positioning.
+ * Record the ascender and descender. Some fonts, for instance DIN
+ * Condensed and Rockwell in 10.14, the ascent on '@' exceeds that
+ * reported by [screenFont ascender]. Get the overall bounding box
+ * for the glyphs and use that instead of the ascender and descender
+ * values if the bounding box result extends farther from the baseline.
*/
- fontAscender = [screenFont ascender] + 0.5;
- fontDescender = [screenFont descender] - 0.5;
+ 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;
+ }
+ fontDescender = [screenFont descender];
+ if (fontDescender > bounds.origin.y) {
+ fontDescender = bounds.origin.y;
+ }
/*
- * Record the tile size. Add one to the median advance to leave space
- * for antialiasing/subpixel positioning. Round both values up to
- * have tile boundaries match pixel boundaries.
+ * Record the tile size. Round both values up to have tile boundaries
+ * match pixel boundaries.
*/
- tileSize.width = ceil(medianAdvance + 1.);
+ tileSize.width = ceil(medianAdvance);
tileSize.height = ceil(fontAscender - fontDescender);
+
+ /*
+ * Determine whether neighboring columns need to redrawn when a character
+ * changes.
+ */
+ CGRect boxes[GLYPH_COUNT] = {};
+ CGFloat beyond_right = 0.;
+ CGFloat beyond_left = 0.;
+ CTFontGetBoundingRectsForGlyphs(
+ (CTFontRef)screenFont,
+ kCTFontHorizontalOrientation,
+ glyphArray,
+ boxes,
+ GLYPH_COUNT);
+ for (i = 0; i < GLYPH_COUNT; i++) {
+ /* Account for the compression and offset used by drawWChar(). */
+ CGFloat compression, offset;
+ CGFloat v;
+
+ if (glyphWidths[i] <= tileSize.width) {
+ compression = 1.;
+ offset = 0.5 * (tileSize.width - glyphWidths[i]);
+ } else {
+ compression = tileSize.width / glyphWidths[i];
+ offset = 0.;
+ }
+ v = (offset + boxes[i].origin.x) * compression;
+ if (beyond_left > v) {
+ beyond_left = v;
+ }
+ v = (offset + boxes[i].origin.x + boxes[i].size.width) * compression;
+ if (beyond_right < v) {
+ beyond_right = v;
+ }
+ }
+ ncol_pre = ceil(-beyond_left / tileSize.width);
+ if (beyond_right > tileSize.width) {
+ ncol_post = ceil((beyond_right - tileSize.width) / tileSize.width);
+ } else {
+ ncol_post = 0;
+ }
}
- (void)updateImage
textMatrix = CGAffineTransformScale( textMatrix, 1.0, -1.0 );
CGContextSetTextMatrix(ctx, textMatrix);
- CGContextShowGlyphsWithAdvances(ctx, &glyph, &CGSizeZero, 1);
+ CGContextShowGlyphsAtPositions(ctx, &glyph, &CGPointZero, 1);
/* Restore the text matrix if we messed with the compression ratio */
if (compressionRatio != 1.)
/* Adjust terminal to fit window with new font; save the new columns
* and rows since they could be changed */
NSRect contentRect = [self->primaryWindow contentRectForFrameRect: [self->primaryWindow frame]];
+
+ [self setMinimumWindowSize:[self terminalIndex]];
+ NSSize size = self->primaryWindow.contentMinSize;
+ BOOL windowNeedsResizing = NO;
+ if (contentRect.size.width < size.width) {
+ contentRect.size.width = size.width;
+ windowNeedsResizing = YES;
+ }
+ if (contentRect.size.height < size.height) {
+ contentRect.size.height = size.height;
+ windowNeedsResizing = YES;
+ }
+ if (windowNeedsResizing) {
+ size.width = contentRect.size.width;
+ size.height = contentRect.size.height;
+ [self->primaryWindow setContentSize:size];
+ }
[self resizeTerminalWithContentRect: contentRect saveToDefaults: YES];
}
/* Update our image */
[self updateImage];
-
- /* Get redrawn */
- [self requestRedraw];
}
- (id)init
/* Allocate our array of views */
angbandViews = [[NSMutableArray alloc] init];
+ self->changes = create_pending_changes(self->cols, self->rows);
+ if (self->changes == 0) {
+ NSLog(@"AngbandContext init: out of memory for pending changes");
+ }
+ self->ncol_pre = 0;
+ self->ncol_post = 0;
+
+ self->in_fullscreen_transition = NO;
+
/* Make the image. Since we have no views, it'll just be a puny 1x1 image. */
[self updateImage];
[primaryWindow close];
[primaryWindow release];
primaryWindow = nil;
+
+ /* Pending changes */
+ destroy_pending_changes(self->changes);
+ self->changes = 0;
}
/* Usual Cocoa fare */
[super dealloc];
}
-
-
-#pragma mark -
-#pragma mark Directories and Paths Setup
-
-/**
- * Return the path for Angband's lib directory and bail if it isn't found. The
- * lib directory should be in the bundle's resources directory, since it's
- * copied when built.
- */
-+ (NSString *)libDirectoryPath
-{
- NSString *bundleLibPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: AngbandDirectoryNameLib];
- BOOL isDirectory = NO;
- BOOL libExists = [[NSFileManager defaultManager] fileExistsAtPath: bundleLibPath isDirectory: &isDirectory];
-
- if( !libExists || !isDirectory )
- {
- NSLog( @"[%@ %@]: can't find %@/ in bundle: isDirectory: %d libExists: %d", NSStringFromClass( [self class] ), NSStringFromSelector( _cmd ), AngbandDirectoryNameLib, isDirectory, libExists );
-
- NSString *msg = NSLocalizedStringWithDefaultValue(
- @"Error.MissingResources",
- AngbandMessageCatalog,
- [NSBundle mainBundle],
- @"Missing Resources",
- @"Alert text for missing resources");
- NSString *info = NSLocalizedStringWithDefaultValue(
- @"Error.MissingAngbandLib",
- AngbandMessageCatalog,
- [NSBundle mainBundle],
- @"Hengband was unable to find required resources and must quit. Please report a bug on the Angband forums.",
- @"Alert informative message for missing Angband lib/ folder");
- NSString *quit_label = NSLocalizedStringWithDefaultValue(
- @"Label.Quit", AngbandMessageCatalog, [NSBundle mainBundle],
- @"Quit", @"Quit");
- NSAlert *alert = [[NSAlert alloc] init];
-
- /*
- * Note that NSCriticalAlertStyle was deprecated in 10.10. The
- * replacement is NSAlertStyleCritical.
- */
- alert.alertStyle = NSCriticalAlertStyle;
- alert.messageText = msg;
- alert.informativeText = info;
- [alert addButtonWithTitle:quit_label];
- NSModalResponse result = [alert runModal];
- [alert release];
- exit( 0 );
- }
-
- return bundleLibPath;
-}
-
-/**
- * Return the path for the directory where Angband should look for its standard
- * user file tree.
- */
-+ (NSString *)angbandDocumentsPath
-{
- NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
-
-#if defined(SAFE_DIRECTORY)
- NSString *versionedDirectory = [NSString stringWithFormat: @"%@-%s", AngbandDirectoryNameBase, VERSION_STRING];
- return [documents stringByAppendingPathComponent: versionedDirectory];
-#else
- return [documents stringByAppendingPathComponent: AngbandDirectoryNameBase];
-#endif
-}
-
-/**
- * Adjust directory paths as needed to correct for any differences needed by
- * Angband. \c init_file_paths() currently requires that all paths provided have
- * a trailing slash and all other platforms honor this.
- *
- * \param originalPath The directory path to adjust.
- * \return A path suitable for Angband or nil if an error occurred.
- */
-static NSString *AngbandCorrectedDirectoryPath(NSString *originalPath)
-{
- if ([originalPath length] == 0) {
- return nil;
- }
-
- if (![originalPath hasSuffix: @"/"]) {
- return [originalPath stringByAppendingString: @"/"];
- }
-
- return originalPath;
-}
-
-/**
- * Give Angband the base paths that should be used for the various directories
- * it needs. It will create any needed directories.
- */
-+ (void)prepareFilePathsAndDirectories
-{
- char libpath[PATH_MAX + 1] = "\0";
- NSString *libDirectoryPath = AngbandCorrectedDirectoryPath([self libDirectoryPath]);
- [libDirectoryPath getFileSystemRepresentation: libpath maxLength: sizeof(libpath)];
-
- char basepath[PATH_MAX + 1] = "\0";
- NSString *angbandDocumentsPath = AngbandCorrectedDirectoryPath([self angbandDocumentsPath]);
- [angbandDocumentsPath getFileSystemRepresentation: basepath maxLength: sizeof(basepath)];
-
- init_file_paths(libpath, libpath, basepath);
- create_needed_dirs();
-}
-
-#pragma mark -
-
#if 0
/* From the Linux mbstowcs(3) man page:
* If dest is NULL, n is ignored, and the conversion proceeds as above,
}
count++;
}
- return count;
-}
-#endif
-
-/**
- * Entry point for initializing Angband
- */
-+ (void)beginGame
-{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
- /* Hooks in some "z-util.c" hooks */
- plog_aux = hook_plog;
- quit_aux = hook_quit;
-
- /* Initialize file paths */
- [self prepareFilePathsAndDirectories];
-
- /* Load preferences */
- load_prefs();
-
- /* Prepare the windows */
- init_windows();
-
- /* Set up game event handlers */
- /* init_display(); */
-
- /* Register the sound hook */
- /* sound_hook = play_sound; */
-
- /* Initialise game */
- init_angband();
-
- /* This is not incorporated into Hengband's init_angband() yet. */
- init_graphics_modes();
-
- /* Note the "system" */
- ANGBAND_SYS = "mac";
-
- /* Initialize some save file stuff */
- player_egid = getegid();
-
- /* 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();
-
- /*
- * Play a game -- "new_game" is set by "new", "open" or the open document
- * even handler as appropriate
- */
-
- [pool drain];
-
- 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];
- }
-
- Term_fresh();
- play_game(new_game);
-
- quit(NULL);
-}
-
-+ (void)endGame
-{
- /* Hack -- Forget messages */
- msg_flag = FALSE;
-
- p_ptr->playing = FALSE;
- p_ptr->leaving = TRUE;
- quit_when_ready = TRUE;
+ return count;
}
+#endif
- (void)addAngbandView:(AngbandView *)view
{
}
-static NSMenuItem *superitem(NSMenuItem *self)
-{
- NSMenu *supermenu = [[self menu] supermenu];
- int index = [supermenu indexOfItemWithSubmenu:[self menu]];
- if (index == -1) return nil;
- else return [supermenu itemAtIndex:index];
-}
-
-
-- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
-{
- int tag = [menuItem tag];
- SEL sel = [menuItem action];
- if (sel == @selector(setGraphicsMode:))
- {
- [menuItem setState: (tag == graf_mode_req)];
- return YES;
- }
- else
- {
- return YES;
- }
-}
-
- (NSWindow *)makePrimaryWindow
{
if (! primaryWindow)
self->cols = newColumns;
self->rows = newRows;
+ if (resize_pending_changes(self->changes, self->rows) != 0) {
+ destroy_pending_changes(self->changes);
+ self->changes = 0;
+ NSLog(@"out of memory for pending changes with resize of terminal %d",
+ [self terminalIndex]);
+ }
+
if( saveToDefaults )
{
int termIndex = [self terminalIndex];
Term_activate( old );
}
+- (void)setMinimumWindowSize:(int)termIdx
+{
+ NSSize minsize;
+
+ if (termIdx == 0) {
+ minsize.width = 80;
+ minsize.height = 24;
+ } else {
+ minsize.width = 1;
+ minsize.height = 1;
+ }
+ minsize.width =
+ minsize.width * self->tileSize.width + self->borderSize.width * 2.0;
+ minsize.height =
+ minsize.height * self->tileSize.height + self->borderSize.height * 2.0;
+ [[self makePrimaryWindow] setContentMinSize:minsize];
+}
+
- (void)saveWindowVisibleToDefaults: (BOOL)windowVisible
{
int termIndex = [self terminalIndex];
{
NSWindow *window = [notification object];
NSRect contentRect = [window contentRectForFrameRect: [window frame]];
- [self resizeTerminalWithContentRect: contentRect saveToDefaults: YES];
+ [self resizeTerminalWithContentRect: contentRect saveToDefaults: !(self->in_fullscreen_transition)];
}
/*- (NSSize)windowWillResize: (NSWindow *)sender toSize: (NSSize)frameSize
{
} */
+- (void)windowWillEnterFullScreen: (NSNotification *)notification
+{
+ self->in_fullscreen_transition = YES;
+}
+
- (void)windowDidEnterFullScreen: (NSNotification *)notification
{
NSWindow *window = [notification object];
NSRect contentRect = [window contentRectForFrameRect: [window frame]];
+ self->in_fullscreen_transition = NO;
[self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
}
+- (void)windowWillExitFullScreen: (NSNotification *)notification
+{
+ self->in_fullscreen_transition = YES;
+}
+
- (void)windowDidExitFullScreen: (NSNotification *)notification
{
NSWindow *window = [notification object];
NSRect contentRect = [window contentRectForFrameRect: [window frame]];
+ self->in_fullscreen_transition = NO;
[self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
}
{
NSUserDefaults *angbandDefs = [NSUserDefaults angbandDefaults];
[angbandDefs setObject:savefileString forKey:@"SaveFile"];
- [angbandDefs synchronize];
+ [angbandDefs synchronize];
}
}
context->cols = columns;
context->rows = rows;
+ if (resize_pending_changes(context->changes, context->rows) != 0) {
+ destroy_pending_changes(context->changes);
+ context->changes = 0;
+ NSLog(@"initializing terminal %d: out of memory for pending changes",
+ termIdx);
+ }
+
/* Get the window */
NSWindow *window = [context makePrimaryWindow];
- NSString *title;
/* Set its title and, for auxiliary terms, tentative size */
- if (termIdx == 0)
- {
- title = [NSString stringWithCString:angband_term_name[0]
+ NSString *title = [NSString stringWithCString:angband_term_name[termIdx]
#ifdef JP
- encoding:NSJapaneseEUCStringEncoding
+ encoding:NSJapaneseEUCStringEncoding
#else
- encoding:NSMacOSRomanStringEncoding
+ encoding:NSMacOSRomanStringEncoding
#endif
- ];
- [window setTitle:title];
+ ];
+ [window setTitle:title];
+ [context setMinimumWindowSize:termIdx];
- /* Set minimum size (80x24) */
- NSSize minsize;
- minsize.width = 80 * context->tileSize.width + context->borderSize.width * 2.0;
- minsize.height = 24 * context->tileSize.height + context->borderSize.height * 2.0;
- [window setContentMinSize:minsize];
- }
- else
- {
- title = [NSString stringWithCString:angband_term_name[termIdx]
-#ifdef JP
- encoding:NSJapaneseEUCStringEncoding
-#else
- encoding:NSMacOSRomanStringEncoding
-#endif
- ];
- [window setTitle:title];
- /* Set minimum size (1x1) */
- NSSize minsize;
- minsize.width = context->tileSize.width + context->borderSize.width * 2.0;
- minsize.height = context->tileSize.height + context->borderSize.height * 2.0;
- [window setContentMinSize:minsize];
- }
-
-
/* 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
/* Draw the source image flipped, since the view is flipped */
CGContextRef ctx = CGBitmapContextCreate(NULL, width, height, CGImageGetBitsPerComponent(decodedImage), CGImageGetBytesPerRow(decodedImage), CGImageGetColorSpace(decodedImage), contextBitmapInfo);
- CGContextSetBlendMode(ctx, kCGBlendModeCopy);
- CGContextTranslateCTM(ctx, 0.0, height);
- CGContextScaleCTM(ctx, 1.0, -1.0);
- CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), decodedImage);
- result = CGBitmapContextCreateImage(ctx);
-
- /* Done with these things */
- CFRelease(ctx);
+ if (ctx) {
+ CGContextSetBlendMode(ctx, kCGBlendModeCopy);
+ CGContextTranslateCTM(ctx, 0.0, height);
+ CGContextScaleCTM(ctx, 1.0, -1.0);
+ CGContextDrawImage(
+ ctx, CGRectMake(0, 0, width, height), decodedImage);
+ result = CGBitmapContextCreateImage(ctx);
+ CFRelease(ctx);
+ }
+
CGImageRelease(decodedImage);
}
return result;
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, set the new desired mode to
- * NULL */
- if (! pict_image)
+ /* 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];
+ [alert release];
+ }
}
/* Record what we did */
use_graphics = new_mode ? new_mode->grafID : 0;
-#if 0
- /* This global is not in Hengband. */
- use_transparency = (new_mode != NULL);
-#endif
ANGBAND_GRAF = (new_mode ? new_mode->graf : "ascii");
current_graphics_mode = new_mode;
}
/* Reset visuals */
- if (initialized && game_in_progress)
+ 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);
+ }
+
[pool drain];
/* Success */
/**
+ * Draws one tile as a helper function for Term_xtra_cocoa_fresh().
+ */
+static void draw_image_tile(
+ NSGraphicsContext* nsContext,
+ CGContextRef cgContext,
+ CGImageRef image,
+ NSRect srcRect,
+ NSRect dstRect,
+ NSCompositingOperation op)
+{
+ /* Flip the source rect since the source image is flipped */
+ CGAffineTransform flip = CGAffineTransformIdentity;
+ flip = CGAffineTransformTranslate(flip, 0.0, CGImageGetHeight(image));
+ flip = CGAffineTransformScale(flip, 1.0, -1.0);
+ CGRect flippedSourceRect =
+ CGRectApplyAffineTransform(NSRectToCGRect(srcRect), flip);
+
+ /*
+ * When we use high-quality resampling to draw a tile, pixels from outside
+ * the tile may bleed in, causing graphics artifacts. Work around that.
+ */
+ CGImageRef subimage =
+ CGImageCreateWithImageInRect(image, flippedSourceRect);
+ [nsContext setCompositingOperation:op];
+ CGContextDrawImage(cgContext, NSRectToCGRect(dstRect), subimage);
+ CGImageRelease(subimage);
+}
+
+
+/**
+ * This is a helper function for Term_xtra_cocoa_fresh(): look before a block
+ * of text on a row to see if the bounds for rendering and clipping need to be
+ * extended.
+ */
+static void query_before_text(
+ struct PendingRowChange* prc, int iy, int npre, int* pclip, int* prend)
+{
+ int start = *prend;
+ int i = start - 1;
+
+ while (1) {
+ if (i < 0 || i < start - npre) {
+ break;
+ }
+
+ if (prc->cell_changes[i].change_type == CELL_CHANGE_PICT) {
+ /*
+ * The cell has been rendered with a tile. Do not want to modify
+ * its contents so the clipping and rendering region can not be
+ * extended.
+ */
+ break;
+ } else if (prc->cell_changes[i].change_type == CELL_CHANGE_NONE) {
+ /*
+ * It has not changed (or using big tile mode and it is within
+ * a changed tile but is not the left cell for that tile) so
+ * inquire what it is.
+ */
+ TERM_COLOR a[2];
+ char c[2];
+
+ Term_what(i, iy, a + 1, c + 1);
+ if (use_graphics && (a[1] & 0x80) && (c[1] & 0x80)) {
+ /*
+ * It is an unchanged location rendered with a tile. Do not
+ * want to modify its contents so the clipping and rendering
+ * region can not be extended.
+ */
+ break;
+ }
+ if (use_bigtile && i > 0) {
+ Term_what(i - 1, iy, a, c);
+ if (use_graphics && (a[0] & 0x80) && (c[0] & 0x80)) {
+ /*
+ * It is the right cell of a location rendered with a tile.
+ * Do not want to modify its contents so the clipping and
+ * rendering region can not be exteded.
+ */
+ break;
+ }
+ }
+ /*
+ * It is unchanged text. A character from the changed region
+ * may have extended into it so render it to clear that.
+ */
+#ifdef JP
+ /* Check to see if it is the second part of a kanji character. */
+ if (i > 0) {
+ Term_what(i - 1, iy, a, c);
+ if (iskanji(c)) {
+ prc->cell_changes[i - 1].c.w =
+ convert_two_byte_eucjp_to_utf16_native(c);
+ prc->cell_changes[i - 1].a = a[0];
+ prc->cell_changes[i - 1].tcol = 1;
+ prc->cell_changes[i].c.w = 0;
+ prc->cell_changes[i].a = a[0];
+ prc->cell_changes[i].tcol = 0;
+ *pclip = i - 1;
+ *prend = i - 1;
+ --i;
+ } else {
+ prc->cell_changes[i].c.w = c[1];
+ prc->cell_changes[i].a = a[1];
+ prc->cell_changes[i].tcol = 0;
+ *pclip = i;
+ *prend = i;
+ }
+ } else {
+ prc->cell_changes[i].c.w = c[1];
+ prc->cell_changes[i].a = a[1];
+ prc->cell_changes[i].tcol = 0;
+ *pclip = i;
+ *prend = i;
+ }
+#else
+ prc->cell_changes[i].c.w = c[1];
+ prc->cell_changes[i].a = a[1];
+ prc->cell_changes[i].tcol = 0;
+ *pclip = i;
+ *prend = i;
+#endif
+ --i;
+ } else {
+ /*
+ * The cell has been wiped or had changed text rendered. Do
+ * not need to render. Can extend the clipping rectangle into it.
+ */
+ *pclip = i;
+ --i;
+ }
+ }
+}
+
+
+/**
+ * This is a helper function for Term_xtra_cocoa_fresh(): look after a block
+ * of text on a row to see if the bounds for rendering and clipping need to be
+ * extended.
+ */
+static void query_after_text(
+ struct PendingRowChange* prc,
+ int iy,
+ int ncol,
+ int npost,
+ int* pclip,
+ int* prend)
+{
+ int end = *prend;
+ int i = end + 1;
+
+ while (1) {
+ /*
+ * Be willing to consolidate this block with the one after it. This
+ * logic should be sufficient to avoid redraws of the region between
+ * changed blocks of text if angbandContext->ncol_pre is zero or one.
+ * For larger values of ncol_pre, would need to do something more to
+ * avoid extra redraws.
+ */
+ if (i >= ncol ||
+ (i > end + npost &&
+ prc->cell_changes[i].change_type != CELL_CHANGE_TEXT &&
+ prc->cell_changes[i].change_type != CELL_CHANGE_WIPE)) {
+ break;
+ }
+
+ if (prc->cell_changes[i].change_type == CELL_CHANGE_PICT) {
+ /*
+ * The cell has been rendered with a tile. Do not want to modify
+ * its contents so the clipping and rendering region can not be
+ * extended.
+ */
+ break;
+ } else if (prc->cell_changes[i].change_type == CELL_CHANGE_NONE) {
+ /* It has not changed so inquire what it is. */
+ TERM_COLOR a[2];
+ char c[2];
+
+ Term_what(i, iy, a, c);
+ if (use_graphics && (a[0] & 0x80) && (c[0] & 0x80)) {
+ /*
+ * It is an unchanged location rendered with a tile. Do not
+ * want to modify its contents so the clipping and rendering
+ * region can not be extended.
+ */
+ break;
+ }
+ /*
+ * It is unchanged text. A character from the changed region
+ * may have extended into it so render it to clear that.
+ */
+#ifdef JP
+ /* Check to see if it is the first part of a kanji character. */
+ if (i < ncol - 1) {
+ Term_what(i + 1, iy, a + 1, c + 1);
+ if (iskanji(c)) {
+ prc->cell_changes[i].c.w =
+ convert_two_byte_eucjp_to_utf16_native(c);
+ prc->cell_changes[i].a = a[0];
+ prc->cell_changes[i].tcol = 1;
+ prc->cell_changes[i + 1].c.w = 0;
+ prc->cell_changes[i + 1].a = a[0];
+ prc->cell_changes[i + 1].tcol = 0;
+ *pclip = i + 1;
+ *prend = i + 1;
+ ++i;
+ } else {
+ prc->cell_changes[i].c.w = c[0];
+ prc->cell_changes[i].a = a[0];
+ prc->cell_changes[i].tcol = 0;
+ *pclip = i;
+ *prend = i;
+ }
+ } else {
+ prc->cell_changes[i].c.w = c[0];
+ prc->cell_changes[i].a = a[0];
+ prc->cell_changes[i].tcol = 0;
+ *pclip = i;
+ *prend = i;
+ }
+#else
+ prc->cell_changes[i].c.w = c[0];
+ prc->cell_changes[i].a = a[0];
+ prc->cell_changes[i].tcol = 0;
+ *pclip = i;
+ *prend = i;
+#endif
+ ++i;
+ } else {
+ /*
+ * Have come to another region of changed text or another region
+ * to wipe. Combine the regions to minimize redraws.
+ */
+ *pclip = i;
+ *prend = i;
+ end = i;
+ ++i;
+ }
+ }
+}
+
+
+/**
+ * Draw the pending changes saved in angbandContext->changes.
+ */
+static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext)
+{
+ int graf_width, graf_height, alphablend;
+
+ if (angbandContext->changes->has_pict) {
+ CGImageAlphaInfo ainfo = CGImageGetAlphaInfo(pict_image);
+
+ graf_width = current_graphics_mode->cell_width;
+ graf_height = current_graphics_mode->cell_height;
+ /*
+ * As of this writing, a value of zero for
+ * current_graphics_mode->alphablend can mean either that the tile set
+ * doesn't have an alpha channel or it does but it only takes on values
+ * of 0 or 255. For main-cocoa.m's purposes, the latter is rendered
+ * using the same procedure as if alphablend was nonzero. The former
+ * is handled differently, but alphablend doesn't distinguish it from
+ * the latter. So ignore alphablend and directly test whether an
+ * alpha channel is present.
+ */
+ alphablend = (ainfo & (kCGImageAlphaPremultipliedFirst |
+ kCGImageAlphaPremultipliedLast)) ? 1 : 0;
+ } else {
+ graf_width = 0;
+ graf_height = 0;
+ alphablend = 0;
+ }
+
+ CGContextRef ctx = [angbandContext lockFocus];
+
+ if (angbandContext->changes->has_text ||
+ angbandContext->changes->has_wipe) {
+ NSFont *selectionFont = [[angbandContext selectionFont] screenFont];
+ [selectionFont set];
+ }
+
+ int iy;
+ for (iy = angbandContext->changes->ymin;
+ iy <= angbandContext->changes->ymax;
+ ++iy) {
+ struct PendingRowChange* prc = angbandContext->changes->rows[iy];
+ int ix;
+
+ /* Skip untouched rows. */
+ if (prc == 0) {
+ continue;
+ }
+
+ ix = prc->xmin;
+ while (1) {
+ int jx;
+
+ if (ix > prc->xmax) {
+ break;
+ }
+
+ switch (prc->cell_changes[ix].change_type) {
+ case CELL_CHANGE_NONE:
+ ++ix;
+ break;
+
+ case CELL_CHANGE_PICT:
+ {
+ /*
+ * Because changes are made to the compositing mode, save
+ * the incoming value.
+ */
+ NSGraphicsContext *nsContext =
+ [NSGraphicsContext currentContext];
+ NSCompositingOperation op = nsContext.compositingOperation;
+ int step = (use_bigtile) ? 2 : 1;
+
+ jx = ix;
+ while (jx <= prc->xmax &&
+ prc->cell_changes[jx].change_type
+ == CELL_CHANGE_PICT) {
+ NSRect destinationRect =
+ [angbandContext rectInImageForTileAtX:jx Y:iy];
+ NSRect sourceRect, terrainRect;
+
+ 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.size.width = graf_width;
+ sourceRect.size.height = graf_height;
+ terrainRect.origin.x = graf_width *
+ prc->cell_changes[jx].tcol;
+ terrainRect.origin.y = graf_height *
+ prc->cell_changes[jx].trow;
+ terrainRect.size.width = graf_width;
+ terrainRect.size.height = graf_height;
+ if (alphablend) {
+ draw_image_tile(
+ nsContext,
+ ctx,
+ pict_image,
+ terrainRect,
+ destinationRect,
+ NSCompositeCopy);
+ /*
+ * Skip drawing the foreground if it is the same
+ * as the background.
+ */
+ if (sourceRect.origin.x != terrainRect.origin.x ||
+ sourceRect.origin.y != terrainRect.origin.y) {
+ draw_image_tile(
+ nsContext,
+ ctx,
+ pict_image,
+ sourceRect,
+ destinationRect,
+ NSCompositeSourceOver);
+ }
+ } else {
+ draw_image_tile(
+ nsContext,
+ ctx,
+ pict_image,
+ sourceRect,
+ destinationRect,
+ NSCompositeCopy);
+ }
+ jx += step;
+ }
+
+ [nsContext setCompositingOperation:op];
+
+ NSRect rect =
+ [angbandContext rectInImageForTileAtX:ix Y:iy];
+ rect.size.width =
+ angbandContext->tileSize.width * (jx - ix);
+ [angbandContext setNeedsDisplayInBaseRect:rect];
+ }
+ ix = jx;
+ break;
+
+ case CELL_CHANGE_WIPE:
+ case CELL_CHANGE_TEXT:
+ /*
+ * For a wiped region, treat it as if it had text (the only
+ * loss if it was not is some extra work rendering
+ * neighboring unchanged text).
+ */
+ jx = ix + 1;
+ while (jx < angbandContext->cols &&
+ (prc->cell_changes[jx].change_type
+ == CELL_CHANGE_TEXT
+ || prc->cell_changes[jx].change_type
+ == CELL_CHANGE_WIPE)) {
+ ++jx;
+ }
+ {
+ int isclip = ix;
+ int ieclip = jx - 1;
+ int isrend = ix;
+ int ierend = jx - 1;
+ int set_color = 1;
+ TERM_COLOR alast = 0;
+ NSRect r;
+ int k;
+
+ query_before_text(
+ prc, iy, angbandContext->ncol_pre, &isclip, &isrend);
+ query_after_text(
+ prc,
+ iy,
+ angbandContext->cols,
+ angbandContext->ncol_post,
+ &ieclip,
+ &ierend
+ );
+ ix = ierend + 1;
+
+ /* Save the state since the clipping will be modified. */
+ CGContextSaveGState(ctx);
+
+ /* Clear the area where rendering will be done. */
+ r = [angbandContext rectInImageForTileAtX:isrend Y:iy];
+ r.size.width = angbandContext->tileSize.width *
+ (ierend - isrend + 1);
+ [[NSColor blackColor] set];
+ NSRectFill(r);
+
+ /*
+ * Clear the current path so it does not affect clipping.
+ * Then set the clipping rectangle. Using
+ * CGContextSetTextDrawingMode() to include clipping does
+ * not appear to be necessary on 10.14 and is actually
+ * detrimental: when displaying more than one character,
+ * only the first is visible.
+ */
+ CGContextBeginPath(ctx);
+ r = [angbandContext rectInImageForTileAtX:isclip Y:iy];
+ r.size.width = angbandContext->tileSize.width *
+ (ieclip - isclip + 1);
+ CGContextClipToRect(ctx, r);
+
+ /* Render. */
+ k = isrend;
+ while (k <= ierend) {
+ NSRect rectToDraw;
+
+ if (prc->cell_changes[k].change_type
+ == CELL_CHANGE_WIPE) {
+ /* Skip over since no rendering is necessary. */
+ ++k;
+ continue;
+ }
+
+ if (set_color || alast != prc->cell_changes[k].a) {
+ set_color = 0;
+ alast = prc->cell_changes[k].a;
+ set_color_for_index(alast % MAX_COLORS);
+ }
+
+ rectToDraw =
+ [angbandContext rectInImageForTileAtX:k Y:iy];
+ if (prc->cell_changes[k].tcol) {
+ rectToDraw.size.width *= 2.0;
+ [angbandContext drawWChar:prc->cell_changes[k].c.w
+ inRect:rectToDraw context:ctx];
+ k += 2;
+ } else {
+ [angbandContext drawWChar:prc->cell_changes[k].c.w
+ inRect:rectToDraw context:ctx];
+ ++k;
+ }
+ }
+
+ /*
+ * Inform the context that the area in the clipping
+ * rectangle needs to be redisplayed.
+ */
+ [angbandContext setNeedsDisplayInBaseRect:r];
+
+ CGContextRestoreGState(ctx);
+ }
+ break;
+ }
+ }
+ }
+
+ if (angbandContext->changes->xcurs >= 0 &&
+ angbandContext->changes->ycurs >= 0) {
+ NSRect rect = [angbandContext
+ rectInImageForTileAtX:angbandContext->changes->xcurs
+ Y:angbandContext->changes->ycurs];
+
+ if (angbandContext->changes->bigcurs) {
+ rect.size.width += angbandContext->tileSize.width;
+ }
+ [[NSColor yellowColor] set];
+ NSFrameRectWithWidth(rect, 1);
+ /* Invalidate that rect */
+ [angbandContext setNeedsDisplayInBaseRect:rect];
+ }
+
+ [angbandContext unlockFocus];
+}
+
+
+/**
* Do a "special thing"
*/
static errr Term_xtra_cocoa(int n, int v)
{
[angbandContext lockFocus];
[[NSColor blackColor] set];
- NSRect imageRect = {NSZeroPoint, [angbandContext imageSize]};
+ NSRect imageRect = {NSZeroPoint, [angbandContext imageSize]};
NSRectFillUsingOperation(imageRect, NSCompositeCopy);
[angbandContext unlockFocus];
[angbandContext setNeedsDisplay:YES];
}
case TERM_XTRA_FRESH:
- {
- /* No-op -- see #1669
- * [angbandContext displayIfNeeded]; */
+ /* Draw the pending changes. */
+ if (angbandContext->changes != 0) {
+ Term_xtra_cocoa_fresh(angbandContext);
+ clear_pending_changes(angbandContext->changes);
+ }
break;
- }
default:
/* Oops */
return result;
}
-static errr Term_curs_cocoa(int x, int y)
+static errr Term_curs_cocoa(TERM_LEN x, TERM_LEN y)
{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
AngbandContext *angbandContext = Term->data;
-
- /* Get the tile */
- NSRect rect = [angbandContext rectInImageForTileAtX:x Y:y];
-
- /* Lock focus and draw it */
- [angbandContext lockFocus];
- [[NSColor yellowColor] set];
- NSFrameRectWithWidth(rect, 1);
- [angbandContext unlockFocus];
-
- /* Invalidate that rect */
- [angbandContext setNeedsDisplayInBaseRect:rect];
-
+
+ if (angbandContext->changes == 0) {
+ /* Bail out; there was an earlier memory allocation failure. */
+ return 1;
+ }
+ angbandContext->changes->xcurs = x;
+ angbandContext->changes->ycurs = y;
+ angbandContext->changes->bigcurs = 0;
+
+ /* Success */
+ return 0;
+}
+
+/**
+ * Draw a cursor that's two tiles wide. For Japanese, that's used when
+ * the cursor points at a kanji character, irregardless of whether operating
+ * in big tile mode.
+ */
+static errr Term_bigcurs_cocoa(TERM_LEN x, TERM_LEN y)
+{
+ AngbandContext *angbandContext = Term->data;
+
+ if (angbandContext->changes == 0) {
+ /* Bail out; there was an earlier memory allocation failure. */
+ return 1;
+ }
+ angbandContext->changes->xcurs = x;
+ angbandContext->changes->ycurs = y;
+ angbandContext->changes->bigcurs = 1;
+
/* Success */
- [pool drain];
return 0;
}
*
* Erase "n" characters starting at (x,y)
*/
-static errr Term_wipe_cocoa(int x, int y, int n)
+static errr Term_wipe_cocoa(TERM_LEN x, TERM_LEN y, int n)
{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
AngbandContext *angbandContext = Term->data;
- int i;
+ struct PendingCellChange *pc;
- /*
- * Erase the block of characters. Mimic the geometry calculations for
- * the cleared rectangle in Term_text_cocoa().
- */
- NSRect rect = [angbandContext rectInImageForTileAtX:x Y:y];
- rect.size.width = angbandContext->tileSize.width * n;
-
- /* Lock focus and clear */
- [angbandContext lockFocus];
- [[NSColor blackColor] set];
- NSRectFill(rect);
- [angbandContext unlockFocus];
- [angbandContext setNeedsDisplayInBaseRect:rect];
-
- [pool drain];
+ if (angbandContext->changes == 0) {
+ /* Bail out; there was an earlier memory allocation failure. */
+ return 1;
+ }
+ if (angbandContext->changes->rows[y] == 0) {
+ angbandContext->changes->rows[y] =
+ create_row_change(angbandContext->cols);
+ if (angbandContext->changes->rows[y] == 0) {
+ NSLog(@"failed to allocate changes for row %d", y);
+ return 1;
+ }
+ if (angbandContext->changes->ymin > y) {
+ angbandContext->changes->ymin = y;
+ }
+ if (angbandContext->changes->ymax < y) {
+ angbandContext->changes->ymax = y;
+ }
+ }
+
+ angbandContext->changes->has_wipe = 1;
+ if (angbandContext->changes->rows[y]->xmin > x) {
+ angbandContext->changes->rows[y]->xmin = x;
+ }
+ if (angbandContext->changes->rows[y]->xmax < x + n - 1) {
+ angbandContext->changes->rows[y]->xmax = x + n - 1;
+ }
+ for (pc = angbandContext->changes->rows[y]->cell_changes + x;
+ pc != angbandContext->changes->rows[y]->cell_changes + x + n;
+ ++pc) {
+ pc->change_type = CELL_CHANGE_WIPE;
+ }
/* Success */
return (0);
}
-static void draw_image_tile(CGImageRef image, NSRect srcRect, NSRect dstRect, NSCompositingOperation op)
-{
- /* Flip the source rect since the source image is flipped */
- CGAffineTransform flip = CGAffineTransformIdentity;
- flip = CGAffineTransformTranslate(flip, 0.0, CGImageGetHeight(image));
- flip = CGAffineTransformScale(flip, 1.0, -1.0);
- CGRect flippedSourceRect = CGRectApplyAffineTransform(NSRectToCGRect(srcRect), flip);
-
- /* When we use high-quality resampling to draw a tile, pixels from outside
- * the tile may bleed in, causing graphics artifacts. Work around that. */
- CGImageRef subimage = CGImageCreateWithImageInRect(image, flippedSourceRect);
- NSGraphicsContext *context = [NSGraphicsContext currentContext];
- [context setCompositingOperation:op];
- CGContextDrawImage([context graphicsPort], NSRectToCGRect(dstRect), subimage);
- CGImageRelease(subimage);
-}
-
-static errr Term_pict_cocoa(int x, int y, int n, TERM_COLOR *ap,
- const char *cp, const TERM_COLOR *tap,
- const char *tcp)
+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;
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
AngbandContext* angbandContext = Term->data;
+ int any_change = 0;
+ int step = (use_bigtile) ? 2 : 1;
+ struct PendingCellChange *pc;
- /* Lock focus */
- [angbandContext lockFocus];
-
- NSRect redisplayRect = [angbandContext rectInImageForTileAtX:x Y:y];
- redisplayRect.size.width = angbandContext->tileSize.width * n;
-
- /* Scan the input */
- int i;
- int graf_width = current_graphics_mode->cell_width;
- int graf_height = current_graphics_mode->cell_height;
-
- for (i = 0; i < n; i++)
- {
- NSRect destinationRect =
- [angbandContext rectInImageForTileAtX:x+i Y:y];
- TERM_COLOR a = *ap++;
- char c = *cp++;
-
- TERM_COLOR ta = *tap++;
- char tc = *tcp++;
-
-
- /* Graphics -- if Available and Needed */
- if (use_graphics && (a & 0x80) && (c & 0x80))
- {
- int col, row;
- int t_col, t_row;
-
+ if (angbandContext->changes == 0) {
+ /* Bail out; there was an earlier memory allocation failure. */
+ return 1;
+ }
+ /*
+ * 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;
+ }
+ }
- /* Primary Row and Col */
- row = ((byte)a & 0x7F) % pict_rows;
- col = ((byte)c & 0x7F) % pict_cols;
-
- NSRect sourceRect;
- sourceRect.origin.x = col * graf_width;
- sourceRect.origin.y = row * graf_height;
- sourceRect.size.width = graf_width;
- sourceRect.size.height = graf_height;
-
- /* Terrain Row and Col */
- t_row = ((byte)ta & 0x7F) % pict_rows;
- t_col = ((byte)tc & 0x7F) % pict_cols;
-
- NSRect terrainRect;
- terrainRect.origin.x = t_col * graf_width;
- terrainRect.origin.y = t_row * graf_height;
- terrainRect.size.width = graf_width;
- terrainRect.size.height = graf_height;
-
- /* Transparency effect. We really want to check
- * current_graphics_mode->alphablend, but as of this writing that's
- * never set, so we do something lame. */
- /*if (current_graphics_mode->alphablend) */
- if (graf_width > 8 || graf_height > 8)
- {
- draw_image_tile(pict_image, terrainRect, destinationRect, NSCompositeCopy);
- draw_image_tile(pict_image, sourceRect, destinationRect, NSCompositeSourceOver);
- }
- else
- {
- draw_image_tile(pict_image, sourceRect, destinationRect, NSCompositeCopy);
- }
- }
+ if (angbandContext->changes->rows[y]->xmin > x) {
+ angbandContext->changes->rows[y]->xmin = x;
+ }
+ if (angbandContext->changes->rows[y]->xmax < x + 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) {
+ TERM_COLOR a = *ap++;
+ char c = *cp++;
+ TERM_COLOR ta = *tap++;
+ char tc = *tcp++;
+
+ if (use_graphics && (a & 0x80) && (c & 0x80)) {
+ pc->c.c = ((byte)c & 0x7F) % pict_cols;
+ pc->a = ((byte)a & 0x7F) % pict_rows;
+ pc->tcol = ((byte)tc & 0x7F) % pict_cols;
+ pc->trow = ((byte)ta & 0x7F) % pict_rows;
+ pc->change_type = CELL_CHANGE_PICT;
+ any_change = 1;
+ }
+ }
+ if (any_change) {
+ angbandContext->changes->has_pict = 1;
}
-
- [angbandContext unlockFocus];
- [angbandContext setNeedsDisplayInBaseRect:redisplayRect];
-
- [pool drain];
/* Success */
return (0);
*
* Draw several ("n") chars, with an attr, at a given location.
*/
-static errr Term_text_cocoa(int x, int y, int n, byte_hack a, concptr cp)
+static errr Term_text_cocoa(
+ TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr cp)
{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
AngbandContext* angbandContext = Term->data;
-
- /* Focus on our layer */
- CGContextRef ctx = [angbandContext lockFocus];
-
- /* Starting pixel */
- NSRect charRect = [angbandContext rectInImageForTileAtX:x Y:y];
-
- const CGFloat tileWidth = angbandContext->tileSize.width;
+ struct PendingCellChange *pc;
-#if 0
- switch (a / MAX_COLORS) {
- case BG_BLACK:
- [[NSColor blackColor] set];
- break;
- case BG_SAME:
- set_color_for_index(a % MAX_COLORS);
- break;
- case BG_DARK:
- set_color_for_index(TERM_SHADE);
- break;
+ if (angbandContext->changes == 0) {
+ /* Bail out; there was an earlier memory allocation failure. */
+ return 1;
+ }
+ 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;
+ }
}
-#endif
- NSRect rectToClear = charRect;
- rectToClear.size.width = tileWidth * n;
- NSRectFill(rectToClear);
-
- /* Clear the current path. so it does not affect clipping. */
- CGContextBeginPath(ctx);
-
- /*
- * Clip to the clearing rectangle so clutter is not left behind. Using
- * CGContextSetTextDrawingMode() to include clipping does not appear to
- * be necessary on 10.14 and is actually detrimental - when displaying
- * more than one character only the first is visible.
- */
- CGContextClipToRect(ctx, rectToClear);
- NSFont *selectionFont = [[angbandContext selectionFont] screenFont];
- [selectionFont set];
-
- /* Set the color */
- set_color_for_index(a % MAX_COLORS);
-
- /* Draw each */
- NSRect rectToDraw = charRect;
- int i = 0;
- while (i < n) {
+ angbandContext->changes->has_text = 1;
+ if (angbandContext->changes->rows[y]->xmin > x) {
+ angbandContext->changes->rows[y]->xmin = x;
+ }
+ if (angbandContext->changes->rows[y]->xmax < x + n - 1) {
+ angbandContext->changes->rows[y]->xmax = x + n - 1;
+ }
+ pc = angbandContext->changes->rows[y]->cell_changes + x;
+ while (pc != angbandContext->changes->rows[y]->cell_changes + x + n) {
#ifdef JP
- if (iskanji(cp[i])) {
- CGFloat w = rectToDraw.size.width;
- wchar_t uv = convert_two_byte_eucjp_to_utf16_native(cp + i);
-
- rectToDraw.size.width *= 2.0;
- [angbandContext drawWChar:uv inRect:rectToDraw context:ctx];
- rectToDraw.origin.x += tileWidth + tileWidth;
- rectToDraw.size.width = w;
- i += 2;
+ if (iskanji(*cp)) {
+ if (pc + 1 ==
+ angbandContext->changes->rows[y]->cell_changes + x + n) {
+ /*
+ * The second byte of the character is past the end. Ignore
+ * the character.
+ */
+ break;
+ } else {
+ pc->c.w = convert_two_byte_eucjp_to_utf16_native(cp);
+ pc->a = a;
+ pc->tcol = 1;
+ pc->change_type = CELL_CHANGE_TEXT;
+ ++pc;
+ /*
+ * Fill in a dummy value since the previous character will take
+ * up two columns.
+ */
+ pc->c.w = 0;
+ pc->a = a;
+ pc->tcol = 0;
+ pc->change_type = CELL_CHANGE_TEXT;
+ ++pc;
+ cp += 2;
+ }
} else {
- [angbandContext drawWChar:cp[i] inRect:rectToDraw context:ctx];
- rectToDraw.origin.x += tileWidth;
- ++i;
+ pc->c.w = *cp;
+ pc->a = a;
+ pc->tcol = 0;
+ pc->change_type = CELL_CHANGE_TEXT;
+ ++pc;
+ ++cp;
}
#else
- [angbandContext drawWChar:cp[i] inRect:rectToDraw context:ctx];
- ++i;
- rectToDraw.origin.x += tileWidth;
+ pc->c.w = *cp;
+ pc->a = a;
+ pc->tcol = 0;
+ pc->change_type = CELL_CHANGE_TEXT;
+ ++pc;
+ ++cp;
#endif
}
-
- [angbandContext unlockFocus];
- /* Invalidate what we just drew */
- [angbandContext setNeedsDisplayInBaseRect:rectToClear];
-
- [pool drain];
/* Success */
return (0);
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; */
[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 */
use_sound = ([defs boolForKey:AngbandSoundDefaultsKey] == YES) ? TRUE : FALSE;
game_in_progress = TRUE;
/* Wait for a keypress */
- pause_line(23);
+ pause_line(Term->hgt - 1);
}
}
/* Save the game */
do_cmd_save_game(FALSE);
record_current_savefile();
-
-
+
/* Quit */
quit(NULL);
}
/* Extract some modifiers */
-#if 0
- /* Caught above so don't do anything with it here. */
- int mx = !! (modifiers & NSCommandKeyMask);
-#endif
int mc = !! (modifiers & NSControlKeyMask);
int ms = !! (modifiers & NSShiftKeyMask);
int mo = !! (modifiers & NSAlternateKeyMask);
/* Get the Angband char corresponding to this unichar */
unichar c = [[event characters] characterAtIndex:0];
char ch;
- switch (c) {
+ /*
+ * Have anything from the numeric keypad generate a macro
+ * trigger so that shift or control modifiers can be passed.
+ */
+ if (c <= 0x7F && !kp)
+ {
+ ch = (char) c;
+ }
+ else {
/*
- * Convert some special keys to what would be the normal
- * alternative in the original keyset or, for things lke
- * Delete, Return, and Escape, what one might use from ASCII.
* The rest of Hengband uses Angband 2.7's or so key handling:
* so for the rest do something like the encoding that
* main-win.c does: send a macro trigger with the Unicode
- * value encoded into printable ASCII characters. Since
- * macro triggers appear to assume at most two keys plus the
- * modifiers, can only handle values of c below 4096 with
- * 64 values per key.
+ * value encoded into printable ASCII characters.
*/
- case NSUpArrowFunctionKey: ch = '8'; kp = 0; break;
- case NSDownArrowFunctionKey: ch = '2'; kp = 0; break;
- case NSLeftArrowFunctionKey: ch = '4'; kp = 0; break;
- case NSRightArrowFunctionKey: ch = '6'; kp = 0; break;
- case NSHelpFunctionKey: ch = '?'; break;
- case NSDeleteFunctionKey: ch = '\b'; break;
-
- default:
- if (c <= 0x7F)
- ch = (char)c;
- else
- ch = '\0';
- break;
+ ch = '\0';
}
/* override special keys */
/* Enqueue it */
if (ch != '\0')
{
-
- /* Enqueue the keypress */
-#if 0
- byte mods = 0;
- if (mo) mods |= KC_MOD_ALT;
- if (mx) mods |= KC_MOD_META;
- if (mc && MODS_INCLUDE_CONTROL(ch)) mods |= KC_MOD_CONTROL;
- if (ms && MODS_INCLUDE_SHIFT(ch)) mods |= KC_MOD_SHIFT;
- if (kp) mods |= KC_MOD_KEYPAD;
- Term_keypress(ch, mods);
-#else
Term_keypress(ch);
-#endif
- } else if (c < 4096 || (c >= 0xF700 && c <= 0xF77F)) {
- unichar part;
- char cenc;
+ }
+ else
+ {
+ /*
+ * Could use the hexsym global but some characters overlap with
+ * those used to indicate modifiers.
+ */
+ const char encoded[16] = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
+ 'c', 'd', 'e', 'f'
+ };
/* Begin the macro trigger. */
Term_keypress(31);
/* Send the modifiers. */
if (mc) Term_keypress('C');
if (ms) Term_keypress('S');
- if (mo) Term_keypress('A');
+ if (mo) Term_keypress('O');
if (kp) Term_keypress('K');
- /*
- * Put part of the range Apple reserves for special keys
- * into 0 - 127 since that range has been handled normally.
- */
- if (c >= 0xF700) {
- c -= 0xF700;
- }
-
- /* Encode the value as two printable characters. */
- part = (c >> 6) & 63;
- if (part > 38) {
- cenc = 'a' + (part - 38);
- } else if (part > 12) {
- cenc = 'A' + (part - 12);
- } else {
- cenc = '0' + part;
- }
- Term_keypress(cenc);
- part = c & 63;
- if (part > 38) {
- cenc = 'a' + (part - 38);
- } else if (part > 12) {
- cenc = 'A' + (part - 12);
- } else {
- cenc = '0' + part;
- }
- Term_keypress(cenc);
+ do {
+ Term_keypress(encoded[c & 0xF]);
+ c >>= 4;
+ } while (c > 0);
/* End the macro trigger. */
Term_keypress(13);
}
/**
- * ------------------------------------------------------------------------
- * Main program
- * ------------------------------------------------------------------------ */
+ * Return the path for Angband's lib directory and bail if it isn't found. The
+ * lib directory should be in the bundle's resources directory, since it's
+ * copied when built.
+ */
+static NSString* get_lib_directory(void)
+{
+ NSString *bundleLibPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: AngbandDirectoryNameLib];
+ BOOL isDirectory = NO;
+ BOOL libExists = [[NSFileManager defaultManager] fileExistsAtPath: bundleLibPath isDirectory: &isDirectory];
+
+ if( !libExists || !isDirectory )
+ {
+ NSLog( @"Hengband: can't find %@/ in bundle: isDirectory: %d libExists: %d", AngbandDirectoryNameLib, isDirectory, libExists );
+
+ NSString *msg = NSLocalizedStringWithDefaultValue(
+ @"Error.MissingResources",
+ AngbandMessageCatalog,
+ [NSBundle mainBundle],
+ @"Missing Resources",
+ @"Alert text for missing resources");
+ NSString *info = NSLocalizedStringWithDefaultValue(
+ @"Error.MissingAngbandLib",
+ AngbandMessageCatalog,
+ [NSBundle mainBundle],
+ @"Hengband was unable to find required resources and must quit. Please report a bug on the Angband forums.",
+ @"Alert informative message for missing Angband lib/ folder");
+ NSString *quit_label = NSLocalizedStringWithDefaultValue(
+ @"Label.Quit", AngbandMessageCatalog, [NSBundle mainBundle],
+ @"Quit", @"Quit");
+ NSAlert *alert = [[NSAlert alloc] init];
+
+ /*
+ * Note that NSCriticalAlertStyle was deprecated in 10.10. The
+ * replacement is NSAlertStyleCritical.
+ */
+ alert.alertStyle = NSCriticalAlertStyle;
+ alert.messageText = msg;
+ alert.informativeText = info;
+ [alert addButtonWithTitle:quit_label];
+ NSModalResponse result = [alert runModal];
+ [alert release];
+ exit( 0 );
+ }
-@interface AngbandAppDelegate : NSObject {
- IBOutlet NSMenu *terminalsMenu;
- NSMenu *_graphicsMenu;
- NSMenu *_commandMenu;
- NSDictionary *_commandMenuTagMap;
+ return bundleLibPath;
}
-@property (nonatomic, retain) IBOutlet NSMenu *graphicsMenu;
-@property (nonatomic, retain) IBOutlet NSMenu *commandMenu;
-@property (nonatomic, retain) NSDictionary *commandMenuTagMap;
+/**
+ * Return the path for the directory where Angband should look for its standard
+ * user file tree.
+ */
+static NSString* get_doc_directory(void)
+{
+ NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
+
+#if defined(SAFE_DIRECTORY)
+ NSString *versionedDirectory = [NSString stringWithFormat: @"%@-%s", AngbandDirectoryNameBase, VERSION_STRING];
+ return [documents stringByAppendingPathComponent: versionedDirectory];
+#else
+ return [documents stringByAppendingPathComponent: AngbandDirectoryNameBase];
+#endif
+}
-- (IBAction)newGame:sender;
-- (IBAction)openGame:sender;
+/**
+ * Adjust directory paths as needed to correct for any differences needed by
+ * Angband. init_file_paths() currently requires that all paths provided have
+ * a trailing slash and all other platforms honor this.
+ *
+ * \param originalPath The directory path to adjust.
+ * \return A path suitable for Angband or nil if an error occurred.
+ */
+static NSString* AngbandCorrectedDirectoryPath(NSString *originalPath)
+{
+ if ([originalPath length] == 0) {
+ return nil;
+ }
-- (IBAction)editFont:sender;
-- (IBAction)setGraphicsMode:(NSMenuItem *)sender;
-- (IBAction)toggleSound:(NSMenuItem *)sender;
+ if (![originalPath hasSuffix: @"/"]) {
+ return [originalPath stringByAppendingString: @"/"];
+ }
-- (IBAction)setRefreshRate:(NSMenuItem *)menuItem;
-- (IBAction)selectWindow: (id)sender;
+ return originalPath;
+}
-@end
+/**
+ * Give Angband the base paths that should be used for the various directories
+ * it needs. It will create any needed directories.
+ */
+static void prepare_paths_and_directories(void)
+{
+ char libpath[PATH_MAX + 1] = "\0";
+ NSString *libDirectoryPath =
+ AngbandCorrectedDirectoryPath(get_lib_directory());
+ [libDirectoryPath getFileSystemRepresentation: libpath maxLength: sizeof(libpath)];
+
+ char basepath[PATH_MAX + 1] = "\0";
+ NSString *angbandDocumentsPath =
+ AngbandCorrectedDirectoryPath(get_doc_directory());
+ [angbandDocumentsPath getFileSystemRepresentation: basepath maxLength: sizeof(basepath)];
+
+ init_file_paths(libpath, basepath);
+ create_needed_dirs();
+}
+
+/**
+ * ------------------------------------------------------------------------
+ * Main program
+ * ------------------------------------------------------------------------ */
@implementation AngbandAppDelegate
[(id)angbandContext setSelectionFont:newFont adjustTerminal: YES];
NSEnableScreenUpdates();
+
+ if (mainTerm == 0 && game_in_progress) {
+ /* Mimics the logic in setGraphicsMode(). */
+ do_cmd_redraw();
+ wakeup_event_loop();
+ } else {
+ [(id)angbandContext requestRedraw];
+ }
}
- (IBAction)openGame:sender
}
/**
+ * Entry point for initializing Angband
+ */
+- (void)beginGame
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ /* 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 */
+ load_prefs();
+
+ /* Prepare the windows */
+ init_windows();
+
+ /* 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();
+
+ /* 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();
+
+ [pool drain];
+
+ 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];
+ }
+
+ /*
+ * Play a game -- "new_game" is set by "new", "open" or the open document
+ * even handler as appropriate
+ */
+ Term_fresh();
+ play_game(new_game);
+
+ quit(NULL);
+}
+
+/**
* Implement NSObject's validateMenuItem() method to override enabling or
* disabling a menu item. Note that, as of 10.14, validateMenuItem() is
* deprecated in NSObject - it will be removed at some point and the
}
else
{
+ /*
+ * Another window is only usable after Term_init_cocoa() has
+ * been called for it. For Angband if window_flag[i] is nonzero
+ * then that has happened for window i. For Hengband, that is
+ * not the case so also test angband_term[i]->data.
+ */
NSInteger subwindowNumber = tag - AngbandWindowMenuItemTagBase;
- return (window_flag[subwindowNumber] > 0);
+ return (angband_term[subwindowNumber]->data != 0
+ && window_flag[subwindowNumber] > 0);
}
return NO;
{
return ! game_in_progress;
}
- else if (sel == @selector(setRefreshRate:) && [superitem(menuItem) tag] == 150)
+ else if (sel == @selector(setRefreshRate:) &&
+ [[menuItem parentItem] tag] == 150)
{
NSInteger fps = [[NSUserDefaults standardUserDefaults] integerForKey:AngbandFrameRateDefaultsKey];
[menuItem setState: ([menuItem tag] == fps)];
[menuItem setState: ((is_on) ? NSOnState : NSOffState)];
return YES;
}
- else if( sel == @selector(sendAngbandCommand:) )
+ else if( sel == @selector(sendAngbandCommand:) ||
+ sel == @selector(saveGame:) )
{
- /* we only want to be able to send commands during an active game */
- return !!game_in_progress;
+ /*
+ * we only want to be able to send commands during an active game
+ * after the birth screens
+ */
+ return !!game_in_progress && character_generated;
}
else return YES;
}
[[NSUserDefaults angbandDefaults] setInteger:frames_per_second forKey:AngbandFrameRateDefaultsKey];
}
-- (IBAction)selectWindow: (id)sender
+- (void)selectWindow: (id)sender
{
NSInteger subwindowNumber = [(NSMenuItem *)sender tag] - AngbandWindowMenuItemTagBase;
AngbandContext *context = angband_term[subwindowNumber]->data;
NSMenu *windowsMenu = [[NSApplication sharedApplication] windowsMenu];
[windowsMenu addItem: [NSMenuItem separatorItem]];
- NSMenuItem *angbandItem = [[NSMenuItem alloc] initWithTitle: @"Hengband" action: @selector(selectWindow:) keyEquivalent: @"0"];
+ NSString *title1 = [NSString stringWithCString:angband_term_name[0]
+#ifdef JP
+ encoding:NSJapaneseEUCStringEncoding
+#else
+ encoding:NSMacOSRomanStringEncoding
+#endif
+ ];
+ NSMenuItem *angbandItem = [[NSMenuItem alloc] initWithTitle:title1 action: @selector(selectWindow:) keyEquivalent: @"0"];
[angbandItem setTarget: self];
[angbandItem setTag: AngbandWindowMenuItemTagBase];
[windowsMenu addItem: angbandItem];
/* Add items for the additional term windows */
for( NSInteger i = 1; i < ANGBAND_TERM_MAX; i++ )
{
- NSString *title = [NSString stringWithFormat: @"Term %ld", (long)i];
+ NSString *title = [NSString stringWithCString:angband_term_name[i]
+#ifdef JP
+ encoding:NSJapaneseEUCStringEncoding
+#else
+ encoding:NSMacOSRomanStringEncoding
+#endif
+ ];
NSString *keyEquivalent = [NSString stringWithFormat: @"%ld", (long)i];
NSMenuItem *windowItem = [[NSMenuItem alloc] initWithTitle: title action: @selector(selectWindow:) keyEquivalent: keyEquivalent];
[windowItem setTarget: self];
}
}
-- (IBAction)setGraphicsMode:(NSMenuItem *)sender
+- (void)setGraphicsMode:(NSMenuItem *)sender
{
/* We stashed the graphics mode ID in the menu item's tag */
graf_mode_req = [sender tag];
[[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) {
+ arg_bigtile = FALSE;
+ }
+ } else if ([[NSUserDefaults angbandDefaults] boolForKey:AngbandBigTileDefaultsKey] == YES &&
+ ! use_bigtile) {
+ arg_bigtile = TRUE;
+ }
+
if (game_in_progress)
{
+ if (arg_bigtile != use_bigtile) {
+ Term_activate(angband_term[0]);
+ Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
+ }
+
/* Hack -- Force redraw */
do_cmd_redraw();
forKey:AngbandSoundDefaultsKey];
}
+- (IBAction)toggleWideTiles:(NSMenuItem *) sender
+{
+ BOOL is_on = (sender.state == NSOnState);
+
+ /* Toggle the state and update the Angband globals and preferences. */
+ sender.state = (is_on) ? NSOffState : NSOnState;
+ [[NSUserDefaults angbandDefaults] setBool:(! is_on)
+ forKey:AngbandBigTileDefaultsKey];
+ [[NSUserDefaults angbandDefaults] synchronize];
+ if (graphics_are_enabled()) {
+ arg_bigtile = (is_on) ? FALSE : TRUE;
+ /* Mimics the logic in setGraphicsMode(). */
+ if (game_in_progress && arg_bigtile != use_bigtile) {
+ Term_activate(angband_term[0]);
+ Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
+ do_cmd_redraw();
+ wakeup_event_loop();
+ }
+ }
+}
+
/**
* 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
- (void)applicationDidFinishLaunching:sender
{
- [AngbandContext beginGame];
+ [self beginGame];
/* Once beginGame finished, the game is over - that's how Angband works,
* and we should quit */
/**
* Delegate method that gets called if we're asked to open a file.
*/
-- (BOOL)application:(NSApplication *)sender openFiles:(NSArray *)filenames
+- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
{
/* Can't open a file once we've started */
- if (game_in_progress) return NO;
-
+ if (game_in_progress) {
+ [[NSApplication sharedApplication]
+ replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
+ return;
+ }
+
/* We can only open one file. Use the last one. */
NSString *file = [filenames lastObject];
- if (! file) return NO;
-
+ if (! file) {
+ [[NSApplication sharedApplication]
+ replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
+ return;
+ }
+
/* Put it in savefile */
- if (! [file getFileSystemRepresentation:savefile maxLength:sizeof savefile])
- return NO;
-
+ if (! [file getFileSystemRepresentation:savefile maxLength:sizeof savefile]) {
+ [[NSApplication sharedApplication]
+ replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
+ return;
+ }
+
game_in_progress = TRUE;
new_game = FALSE;
/* Wake us up in case this arrives while we're sitting at the Welcome
* screen! */
wakeup_event_loop();
-
- return YES;
+
+ [[NSApplication sharedApplication]
+ replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
}
@end
int main(int argc, char* argv[])
{
- NSApplicationMain(argc, (void*)argv);
+ NSApplicationMain(argc, (void*)argv);
return (0);
}