#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;
/* 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
*/
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);
/*
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
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];
[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,
}
#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;
-}
-
- (void)addAngbandView:(AngbandView *)view
{
if (! [angbandViews containsObject: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)
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];
}
/* 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 */
*/
break;
} else if (prc->cell_changes[i].change_type == CELL_CHANGE_NONE) {
- /* It has not changed so inquire what it is. */
+ /*
+ * 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];
*/
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.
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;
/*
- * 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.
+ * 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 = current_graphics_mode->alphablend */
- alphablend = (graf_width > 8 || graf_height > 8);
+ alphablend = (ainfo & (kCGImageAlphaPremultipliedFirst |
+ kCGImageAlphaPremultipliedLast)) ? 1 : 0;
} else {
graf_width = 0;
graf_height = 0;
alphablend = 0;
}
-
+
CGContextRef ctx = [angbandContext lockFocus];
if (angbandContext->changes->has_text ||
NSGraphicsContext *nsContext =
[NSGraphicsContext currentContext];
NSCompositingOperation op = nsContext.compositingOperation;
+ int step = (use_bigtile) ? 2 : 1;
jx = ix;
while (jx <= prc->xmax &&
[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 *
terrainRect,
destinationRect,
NSCompositeCopy);
- draw_image_tile(
- nsContext,
- ctx,
- pict_image,
- sourceRect,
- destinationRect,
- NSCompositeSourceOver);
+ /*
+ * 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,
destinationRect,
NSCompositeCopy);
}
- ++jx;
+ jx += step;
}
[nsContext setCompositingOperation:op];
/* Save the state since the clipping will be modified. */
CGContextSaveGState(ctx);
- /* Clear the area that where rendering will be done. */
+ /* Clear the area where rendering will be done. */
r = [angbandContext rectInImageForTileAtX:isrend Y:iy];
r.size.width = angbandContext->tileSize.width *
(ierend - isrend + 1);
return result;
}
-static errr Term_curs_cocoa(int x, int y)
+static errr Term_curs_cocoa(TERM_LEN x, TERM_LEN y)
{
AngbandContext *angbandContext = Term->data;
* the cursor points at a kanji character, irregardless of whether operating
* in big tile mode.
*/
-static errr Term_bigcurs_cocoa(int x, int y)
+static errr Term_bigcurs_cocoa(TERM_LEN x, TERM_LEN y)
{
AngbandContext *angbandContext = Term->data;
*
* 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)
{
AngbandContext *angbandContext = Term->data;
struct PendingCellChange *pc;
return (0);
}
-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 */
AngbandContext* angbandContext = Term->data;
int any_change = 0;
+ 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]->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;
+ 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 + n;
- ++pc) {
+ pc != angbandContext->changes->rows[y]->cell_changes + x + step * n;
+ pc += step) {
TERM_COLOR a = *ap++;
char c = *cp++;
TERM_COLOR ta = *tap++;
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->tcol = ((byte)tc & 0x7F) % pict_cols;
+ pc->trow = ((byte)ta & 0x7F) % pict_rows;
pc->change_type = CELL_CHANGE_PICT;
any_change = 1;
}
*
* 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)
{
AngbandContext* angbandContext = Term->data;
struct PendingCellChange *pc;
[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;
/* 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 );
-@interface AngbandAppDelegate : NSObject {
- IBOutlet NSMenu *terminalsMenu;
- NSMenu *_graphicsMenu;
- NSMenu *_commandMenu;
- NSDictionary *_commandMenuTagMap;
+ 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;
}
-@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];
-- (IBAction)newGame:sender;
-- (IBAction)openGame:sender;
+#if defined(SAFE_DIRECTORY)
+ NSString *versionedDirectory = [NSString stringWithFormat: @"%@-%s", AngbandDirectoryNameBase, VERSION_STRING];
+ return [documents stringByAppendingPathComponent: versionedDirectory];
+#else
+ return [documents stringByAppendingPathComponent: AngbandDirectoryNameBase];
+#endif
+}
-- (IBAction)editFont:sender;
-- (IBAction)setGraphicsMode:(NSMenuItem *)sender;
-- (IBAction)toggleSound:(NSMenuItem *)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)setRefreshRate:(NSMenuItem *)menuItem;
-- (IBAction)selectWindow: (id)sender;
+ if (![originalPath hasSuffix: @"/"]) {
+ return [originalPath stringByAppendingString: @"/"];
+ }
-@end
+ return originalPath;
+}
+
+/**
+ * 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;
}
}
-- (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