From 6aa48afdd57d03314fdf4be6c9da911c32277c84 Mon Sep 17 00:00:00 2001 From: Bardur Arantsson Date: Fri, 8 Jan 2010 20:28:34 +0100 Subject: Import tome-2.3.5. --- src/main-crb.c | 6450 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 6450 insertions(+) create mode 100644 src/main-crb.c (limited to 'src/main-crb.c') diff --git a/src/main-crb.c b/src/main-crb.c new file mode 100644 index 00000000..7c8d11b7 --- /dev/null +++ b/src/main-crb.c @@ -0,0 +1,6450 @@ +/* File: main-crb.c */ + +/* + * Copyright (c) 1997 Ben Harrison, Keith Randall, Peter Ammon, Ron Anderson + * and others + * + * This software may be copied and distributed for educational, research, + * and not for profit purposes provided that this copyright and statement + * are included in all such copies. + */ + + +/* + * This file helps Angband work with Macintosh computers running OS X, + * or OS 8/9 with CarbonLib system extention. + * + * To use this file, use an appropriate "Makefile" or "Project File", which + * should define "MACINTOSH". + * + * The official compilation uses the CodeWarrior Pro compiler. + * + * If you are never going to use "graphics" (especially if you are not + * compiling support for graphics anyway) then you can delete the "pict" + * resources with id "1001", "1002", "1003" and "1004" with no dangerous + * side effects. + * + * + * This file assumes that you will be using a PPC Mac running OS X + * or OS 8/9 (8.6 or greater) with CarbonLib system extention enabled. + * In fact, the game will refuse to run unless these features are available. + * + * MACH_O_CARBON code pushes the system requirement a bit further, and + * I don't think it works on System 8, even with CarbonLib, because it uses + * the Bundle services, but I may be wrong. + * + * Note that the "preference" file is now a simple XML text file + * called ".plist" in case of PEF Carbon, and ".plist" for Mach-O Carbon, which contains + * key-value paris, so it no longer has to check version stamp to validate + * its contents. + * + * + * Note that "init1.c", "init2.c", "load1.c", "load2.c", and "birth.c" + * should probably be "unloaded" as soon as they are no longer needed, + * to save space, but I do not know how to do this. XXX XXX XXX + * + * Stange bug -- The first "ClipRect()" call crashes if the user closes + * all the windows, switches to another application, switches back, and + * re-opens the main window, for example, using "command-a". XXX XXX XXX + * + * + * Initial framework (and most code) by Ben Harrison (benh@phial.com). + * + * Some code adapted from "MacAngband 2.6.1" by Keith Randall + * + * Initial PowerMac port by Maarten Hazewinkel (mmhazewi@cs.ruu.nl). + * + * Most Apple Event code provided by Steve Linberg (slinberg@crocker.com). + * + * Most of the graphics code is adapted from an extremely minimal subset of + * the "Sprite World II" package, an amazing (and free) animation package. + * + * Carbon code adapted from works by Peter Ammon and Ron Anderson. + * + * (List of changes made by "pelpel" follow) + * Some API calls are updated to OS 8.x-- ones. + * + * Pixmap locking code in Term_pict_map() follows Carbon Porting Guide + * by Apple. + * + * The idle loop in TERM_XTRA_DELAY is rewritten to sleep on WaitNextEvent + * for a couple of reasons. + * + * CheckEvent now really blocks whenever asked to wait. + * + * The unused buffer GWorld is completely removed. It has long been pure waste + * of memory. + * + * The default font-size combination was changed because the old one, Monaco + * at 12 points causes the redraw artefact problem on OS X. + * + * Characters in the ASCII mode are clipped by their bounding rects to reduce + * redraw artefacts that were quite annoying in certain font-point combos. + * + * Transparency effect now avoids double bitblts whenever possible. + * + * Old tiles were drawn in a wrong fashion by the USE_TRANSPARENCY code. + * + * ASCII and the two graphics modes are now controlled by single graf_mode + * variable. arg_* and use_* variables are set when requested mode is + * successfully initialised. + * + * Most of the menus are now loaded from resources. + * + * Moved TileWidth and TileHeight menus into Special. There were too many menus. + * + * Added support for 32x32 tiles, now for [V] only. + * + * Related to the above, globe_init no longer loads tile images twice if + * a tileset doesn't have corresponding masks. + * + * Added support for POSIX-style pathnames, for Mach-O Carbon (gcc, CW >= 7). + * We can finally live without Pascal strings to handle files this way. + * + * (Mach-O Carbon) Graphics tiles are moved out of the resource fork into + * bundle-based data fork files. + * + * Changed size-related menu code, because they no longer function because + * some APIs have been changed to return Unicode in some cases. + * + * Changed the transparency code again, this time using Ron Anderson's code, + * which makes more sound assumption about background colour and is more + * efficient. + * + * The old asynchronous sound player could try to lock the same handle more + * than once, load same sound resource already in use, or unlock and release + * currently playing sound. + * + * hook_quit() now releases memory-related resources dynamically allocated by + * the graphics and sound code. + * + * Important Resources in the resource file: + * + * FREF 130 = ANGBAND_CREATOR / 'APPL' (application) + * FREF 129 = ANGBAND_CREATOR / 'SAVE' (save file) + * FREF 130 = ANGBAND_CREATOR / 'TEXT' (bone file, generic text file) + * FREF 131 = ANGBAND_CREATOR / 'DATA' (binary image file, score file) + * + * DLOG 128 = "About Angband..." + * + * ALRT 128 = unused (?) + * ALRT 129 = "Warning..." + * + * DITL 128 = body for DLOG 128 + * DITL 129 = body for ALRT 129 + * DITL 130 = body for ALRT 130 + * + * ICON 128 = "warning" icon + * + * MBAR 128 = array of MENU id's (128, 129, 130, 131, 132, 133, 134) + * MENU 128 = apple (about, -, ...) + * MENU 129 = File (new, open, close, save, -, score, quit) + * (If SAVEFILE_SCREEN is defined) + * MENU 129 = File (close, save, -, score, quit) + * MENU 130 = Edit (undo, -, cut, copy, paste, clear) + * MENU 131 = Font (bold, wide, -) + * MENU 132 = Size () + * MENU 133 = Windows () + * MENU 134 = Special (Sound, Graphics, TileWidth, TileHeight, -, Fiddle, + * Wizard) + * Graphics have following submenu attached: + * MENU 144 = Graphics (None, 8x8, 16x16, 32x32, enlarge tiles) + * TileWidth and TileHeight submenus are filled in by this program. + * MENU 145 = TileWidth () + * MENU 146 = TileHeight () + * + * On CFM(PEF) Carbon only: + * PICT 1001 = Graphics tile set (8x8) + * PICT 1002 = Graphics tile set (16x16 images) + * PICT 1004 = Graphics tile set (32x32) + * + * Mach-O Carbon now uses data fork resources: + * 8x8.png = Graphics tile set (8x8) + * 16x16.png = Graphics tile set (16x16 images) + * 32x32.png = Graphics tile set (32x32) + * These files should go into the Resources subdirectory of an application + * bundle. + * + * STR# 128 = "Please select the "lib" folder" + * + * plst 0 can be empty, but required for single binary Carbon apps on OS X + * Isn't necessary for Mach-O Carbon. + * + * + * File name patterns: + * all 'APEX' files have a filename of the form "*:apex:*" (?) + * all 'BONE' files have a filename of the form "*:bone:*" (?) + * all 'DATA' files have a filename of the form "*:data:*" + * all 'SAVE' files have a filename of the form "*:save:*" + * all 'USER' files have a filename of the form "*:user:*" (?) + * + * Perhaps we should attempt to set the "_ftype" flag inside this file, + * to avoid nasty file type information being spread all through the + * rest of the code. (?) This might require adding hooks into the + * "fd_open()" and "my_fopen()" functions in "util.c". XXX XXX XXX + * + * + * Reasons for each header file: + * + * angband.h = Angband header file + * + * Types.h = (included anyway) + * Gestalt.h = gestalt code + * QuickDraw.h = (included anyway) + * OSUtils.h = (included anyway) + * Files.h = file code + * Fonts.h = font code + * Menus.h = menu code + * Dialogs.h = dialog code + * Windows.h = (included anyway) + * Palettes.h = palette code + * ToolUtils.h = HiWord() / LoWord() + * Events.h = event code + * Resources.h = resource code + * Controls.h = button code + * SegLoad.h = ExitToShell(), AppFile, etc + * Memory.h = NewPtr(), etc + * QDOffscreen.h = GWorld code + * Sound.h = Sound code + * Navigation.h = save file / lib locating dialogues + * CFPreferences.h = Preferences + * CFNumber.h = read/write short values from/to preferences + */ + +/* + * Yet another main-xxx.c for Carbon (pelpel) - revision 11d + * + * Since I'm using CodeWarrior, the traditional header files are + * #include'd below. + * + * I also compiled Angband 3.0.2 successfully with OS X's gcc. + * Please follow these instructions if you are interested. + * + * ---(developer CD gcc + makefile porting notes, for Angband 3.0.2)------- + * 1. Compiling the binary + * + * If you try this on OS X + gcc, please use makefile.std, replacing + * main.c and main.o with main-crb.c and main-crb.o, removing all main-xxx.c + * and main-xxx.o from SRCS and OBJS, and, and use these settings: + * + * COPTS = -Wall -O1 -g -fpascal-strings + * INCLUDES = + * DEFINES = -DMACH_O_CARBON -DANGBAND30X + * LIBS = -framework CoreFoundation -framework QuickTime -framework Carbon + * + * -DANGBAND30X only affects main-crb.c. This is because I'm also compiling + * a couple of variants, and this arrangement makes my life easier. + * + * Never, ever #define MACINTOSH. It'll wreck havoc in system interface + * (mostly because of totally different pathname convention). + * + * You might wish to disable some SET_UID features for various reasons: + * to have user folder within the lib folder, savefile names etc. + * + * For the best compatibility with the Classic ports and my PEF Carbon + * ports, my_fopen, fd_make and fd_open [in util.c] should call + * (void)fsetfileinfo(buf, _fcreator, _ftype); + * when a file is successfully opened. Or you'll see odd icons for some files + * in the lib folder. In order to do so, extern.h should contain these lines, + * within #ifdef MACH_O_CARBON: + * extern int fsetfileinfo(char *path, u32b fcreator, u32b ftype); + * extern u32b _fcreator; + * extern u32b _ftype; + * And enable the four FILE_TYPE macros in h-config.h for defined(MACH_O_CARBON) + * in addition to defined(MACINTOSH) && !defined(applec), i.e. + * #if defined(MACINTOSH) && !defined(applec) || defined(MACH_O_CARBON) + * + * This is a very good way to spot bugs in use of these macros, btw. + * + * 2. Installation + * + * The "angband" binary must be arranged this way for it to work: + * + * lib/ <- the lib folder + * Angband (OS X).app/ + * Contents/ + * MacOS/ + * angband <- the binary you've just compiled + * Info.plist <- to be explained below + * Resources/ + * Angband.icns + * Data.icns + * Edit.icns + * Save.icns + * 8x8.png <- 8x8 tiles + * 16x16.png <- 16x16 tiles + * angband.rsrc <- see below + * + * 3. Preparing Info.plist + * + * Info.plist is an XML file describing some attributes of an application, + * and this is appropriate for Angband: + * + * + * + * + * CFBundleNameAngband + * CFBundleDisplayNameAngband (OS X) + * CFBundleExecutableangband + * CFBundlePackageTypeAPPL + * CFBundleSignatureA271 + * CFBundleVersion3.0.2 + * CFBundleShortVersionString3.0.2 + * CFBundleIconFileAngband + * CFBundleIdentifiernet.thangorodrim.Angband + * CFBundleInfoDictionaryVersion6.0 + * CFBundleDocumentTypes + * + * + * CFBundleTypeExtentions* + * CFBundleTypeIconFileSave + * CFBundleTypeNameAngband saved game + * CFBundleTypeOSTypesSAVE + * CFBundleTypeRoleEditor + * + * + * CFBundleTypeExtentions* + * CFBundleTypeIconFileEdit + * CFBundleTypeNameAngband game data + * CFBundleTypeOSTypesTEXT + * CFBundleTypeRoleEditor + * + * + * CFBundleTypeExtentionsraw + * CFBundleTypeIconFileData + * CFBundleTypeNameAngband game data + * CFBundleTypeOSTypesDATA + * CFBundleTypeRoleEditor + * + * + * + * + * + * 4. Menu, diaglogue and gfx resources + * + * The binary assumes angband.rsrc should be in the traditional resource + * mangager format. Please run this command to create it from its textual + * description: + * + * Rez -i /Developer/Headers/FlatCarbon -d MACH_O -o angband.rsrc Angband.r + * + * The command is in /Developer/Tools. You might wish to include it in your + * PATH. + * + * It's better to comment out the definitions of BNDL and plst resources + * before you do that. I think you can DeRez the resulting tome.rsrc and + * feed it to the Interface Builder to produce a set of compatible .nib files, + * but this file also needs to be updated to understand .nib... On the other + * hand, I really don't like to hardcode UI definitions in C. + * + * Graphics resources are moved out of the resource fork and become ordinary + * PNG files. Make sure to set its resolution to 72 dpi (<- VERY important) + * while keeping vertical and horizontal scaling factor to 100% (<- VERY + * important), when you convert tiles in any formats to PNG. This means + * that the real size of an image must shrink or grow when you change it's dpi. + * + * Sound resources are a bit more complicated. + * The easiest way is: + * 1) Grab recent Mac Angband binary. + * 2) Run this command: + * DeRez -only 'snd ' (Angband binary) > sound.r + * 3) And specify sound.r files in addition to Angband.r when you run Rez. + * + * ---(end of OS X + gcc porting note)-------------------------------------- + * + * Code adapted from Peter Ammon's work on 2.8.3 and some modifications + * are made when Apple's Carbon Porting Guide says they are absolutely + * necessary. Other arbirary changes are mostly because of my hatred + * of deep nestings and indentations. The code for controlling graphics modes + * have been thoroughly revised simply because I didn't like it (^ ^;). + * A bonus of this is that graphics settings can be loaded from Preferences + * quite easily. + * + * I also took Ron Anderson's (minimising the use of local-global coordinate + * conversions). Some might say his QuickTime multimedia is the most + * significant achievement... Play your favourite CD instead, if you really + * miss that (^ ^;) I might consider incorporating it if it makes use of + * event notification. + * + * I replaced some old API calls with new (OS 8.x--) ones, especially + * when I felt Apple is strongly against their continued usage. + * + * Similarly, USE_SFL_CODE should be always active, so I removed ifdef's + * just to prevent accidents, as well as to make the code a bit cleaner. + * + * On the contrary, I deliberately left traditional resource interfaces. + * Whatever Apple might say, I abhor file name extentions. And keeping two + * different sets of resources for Classic and Carbon is just too much for + * a personal project XXX + * + * Because Carbon forbids the use of 68K code, ANGBAND_LITE_MAC sections + * are removed. + * + * Because the default font-size combination causes redraw artefact problem + * (some characters, even in monospace fonts, have negative left bearings), + * I introduced rather crude hack to clip all character drawings within + * their bounding rects. If you don't like this, please comment out the line + * #define CLIP_HACK + * below. + * + * The check for return values of AEProcessAppleEvent is removed, + * because it results in annoying dialogues on OS X, also because + * Apple says it isn't usually necessary. + * + * Because the always_pict code is *so* slow, I changed the graphics + * mode selection a bit to use higher_pict when a user chooses a fixed + * width font and doesn't changes tile width / height. + * + * Added support for David Gervais' 32x32 tiles. + * + * Replaced transparency effect code by Ron Anderson's. + * + * Added support for gcc & make compilation. They come free with OS X + * (on the developer CD). This means that it can be compiled as a bundle. + * + * For Mach-O Carbon binary, moved graphics tiles out of the + * resource fork and made them plain PNG files, to be stored in the application + * bundle's "Resources" subdirectory. + * + * For Mach-O Carbon binary, provided a compile-time option (USE_QT_SOUND) to + * move sound effect samples out of the resource fork and use *.wav files in + * the bundle's "Resources" subdirectory. The "*" part must match the names in + * angband_sound_name (variable.c) exactly. This doesn't hurt performance + * a lot in [V] (it's got ~25 sound events), but problematic in [Z]-based + * ones (they have somewhere around 70 sound events). + * + * You still can use the resource file that comes with the ext-mac archive + * on the Angband FTP server, with these additions: + * - MENUs 131--134 and 144--146, as described above + * - MBAR 128: just a array of 128 through 134 + * - plst 0 : can be empty, although Apple recommends us to fill it in. + * - STR# 128 : something like "Please select your lib folder" + * + * Since this involves considerable amount of work, I attached + * a plain text resource definition (= Rez format) . + * I heavily commented on the file, hoping it could be easily adapted + * to future versions of Angband as well as variants. + * I omitted sound effects and graphic tiles to make it reasonably small. + * Please copy them from any recent Mac binaries - you can use, say ZAngband + * ones for Vanilla or [V]-based variants quite safely. T.o.M.E. uses fairly + * extended 16x16 tileset and it is maintained actively. IIRC Thangorodrim + * compile page has an intruction explaining how to convert tiles for + * use on the Mac... It can be tricky, depending on your choice of + * graphics utility. Remember setting resolution to 72 pixels per inch, + * while keeping vertical/horizontal scale factor to 100% and dump the + * result as a PICT from resource. + * + * To build Carbonised Angband with CodeWarrior, copy your PPC project + * and + * - replace main-mac.c in the project with this file (in the link order tab) + * - remove InterfaceLib and MathLib + * - add CarbonLib (found in Carbon SDK or CW's UniversalInterfaces) -- + * if you have compiler/linker errors, you'll need Carbon SDK 1.1 or greater + * - replace MSL C.PPC.Lib with MSL C.Carbon.Lib (both found in + * MSL:MSL_C:MSL_MacOS:Lib:PPC) + * - leave MSL RuntimePPC.Lib as it is + * - don't forget to update resource file, as described above + * - as in Classic targets, you may have to include and + * . The most convinient place for them is the first + * #ifdef MACINTOSH in h-system.h + * - check variant dependent ifdef's explained below, and add + * appropriate one(s) in your A-mac-h.pch. + */ + + +/* + * Force Carbon-compatible APIs + */ +#ifndef MACH_O_CARBON + +/* Can be CodeWarrior or MPW */ +# define TARGET_API_MAC_CARBON 1 + +#else + +/* +* Must be Mach-O Carbon target with OS X gcc. +* No need to set TARGET_API_MAC_CARBON to 1 here, but I assume it should +* be able to make efficient use of BSD functions, hence: +*/ +# define USE_MALLOC +/* Not yet */ +/* # define USE_NIB */ + +#endif /* !MACH_O_CARBON */ + + +#include "angband.h" + +#if defined(MACINTOSH) || defined(MACH_O_CARBON) + +#ifdef PRIVATE_USER_PATH + +/* + * Check and create if needed the directory dirpath + */ +bool private_check_user_directory(cptr dirpath) +{ + /* Is this used anywhere else in *bands? */ + struct stat stat_buf; + + int ret; + + /* See if it already exists */ + ret = stat(dirpath, &stat_buf); + + /* It does */ + if (ret == 0) + { + /* Now we see if it's a directory */ + if ((stat_buf.st_mode & S_IFMT) == S_IFDIR) return (TRUE); + + /* + * Something prevents us from create a directory with + * the same pathname + */ + return (FALSE); + } + + /* No - this maybe the first time. Try to create a directory */ + else + { + /* Create the ~/.ToME directory */ + ret = mkdir(dirpath, 0700); + + /* An error occured */ + if (ret == -1) return (FALSE); + + /* Success */ + return (TRUE); + } +} + +/* + * Check existence of ".ToME/" directory in the user's + * home directory or try to create it if it doesn't exist. + * Returns FALSE if all the attempts fail. + */ +static bool check_create_user_dir(void) +{ + char dirpath[1024]; + char versionpath[1024]; + char savepath[1024]; +#ifdef PRIVATE_USER_PATH_DATA + char datapath[1024]; +#endif +#ifdef PRIVATE_USER_PATH_APEX + char apexpath[1024]; +#endif + + /* Get an absolute path from the filename */ + path_parse(dirpath, 1024, PRIVATE_USER_PATH); + strcpy(versionpath, dirpath); + strcat(versionpath, USER_PATH_VERSION); + strcpy(savepath, versionpath); + strcat(savepath, "/save"); +#ifdef PRIVATE_USER_PATH_DATA + strcpy(datapath, versionpath); + strcat(datapath, "/data"); +#endif +#ifdef PRIVATE_USER_PATH_APEX + strcpy(apexpath, versionpath); + strcat(apexpath, "/apex"); +#endif + + return /* don't forget, the dirpath muts come first */ + private_check_user_directory(dirpath) && + private_check_user_directory(versionpath) && +#ifdef PRIVATE_USER_PATH_DATA + private_check_user_directory(datapath) && +#endif +#ifdef PRIVATE_USER_PATH_APEX + private_check_user_directory(apexpath) && +#endif + private_check_user_directory(savepath); +} + +#endif /* PRIVATE_USER_PATH */ + + +/* + * Variant-dependent features: + * + * #define ALLOW_BIG_SCREEN (V, Ey, O, T.o.M.E., and Z. Dr's big screen needs + * more work. New S one is too idiosyncratic...) + * #define ANG281_RESET_VISUALS (Cth, Gum, T.o.M.E., Z) + * #define SAVEFILE_SCREEN (T.o.M.E.) + * #define ZANG_AUTO_SAVE (O and Z) + * #define HAS_SCORE_MENU (V and T.o.M.E.) + * #define ANGBAND_CREATOR four letter code for your variant, if any. + * or use the default one. + * + * For [Z], you also have to say -- #define inkey_flag (p_ptr->inkey_flag) + * but before that, please, please consider using main-mac-carbon.c in [Z], + * that has some interesting features. + */ + +/* Some porting examples */ +#ifdef ANGBAND30X +# define USE_DOUBLE_TILES +# define ALLOW_BIG_SCREEN +# define HAS_SCORE_MENU +# define NEW_ZVIRT_HOOKS +/* I can't ditch this, yet, because there are many variants */ +# define USE_TRANSPARENCY +#endif /* ANGBAND30X */ + +# define USE_DOUBLE_TILES +# define SAVEFILE_SCREEN +# define ANG281_RESET_VISUALS +# define ALLOW_BIG_SCREEN +# define HAS_SCORE_MENU +# define ANGBAND_CREATOR 'PrnA' + +/* Default creator signature */ +#ifndef ANGBAND_CREATOR +# define ANGBAND_CREATOR 'A271' +#endif + + +/* + * Use rewritten asynchronous sound player + */ +#define USE_ASYNC_SOUND + + +/* + * A rather crude fix to reduce amount of redraw artefacts. + * Some fixed width fonts (i.e. Monaco) has characters with negative + * left bearings, so Term_wipe_mac or overwriting cannot completely + * erase them. This could be introduced to Classic Mac OS ports too, + * but since I've never heard any complaints and I don't like to + * make 68K ports even slower, I won't do so there. + */ +#define CLIP_HACK /* */ + +/* + * To cope with pref file related problems. It no longer has to be acculate, + * because preferences are stored in plist. + */ +#define PREF_VER_MAJOR VERSION_MAJOR +#define PREF_VER_MINOR VERSION_MINOR +#define PREF_VER_PATCH VERSION_PATCH +#define PREF_VER_EXTRA VERSION_EXTRA + + +/* + * In OS X + gcc, use , and + * for ALL of these, including the Apple + * Event ones. is used by the tile loading code. + */ +#ifdef MACH_O_CARBON + +#include +#include +#include +#include + +#else /* MACH_O_CARBON */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif /* MACH_O_CARBON */ + +/* MacOSX == Unix == Good */ +#ifdef USE_MACOSX +#include +#include +#include +#include +#endif + + +/* + * Use "malloc()" instead of "NewPtr()" + */ +/* #define USE_MALLOC */ + + +/* + * Information about each of the 256 available colors + */ +static RGBColor color_info[256]; + + +#ifdef MACH_O_CARBON + +/* + * Creator signature and file type - Didn't I say that I abhor file name + * extentions? Names and metadata are entirely different set of notions. + */ +OSType _fcreator; +OSType _ftype; + +#endif /* MACH_O_CARBON */ + + +/* + * Forward declare + */ +typedef struct term_data term_data; + +/* + * Extra "term" data + */ +struct term_data +{ + term *t; + + Rect r; + + WindowPtr w; + + + short padding; + + short pixelDepth; + + GWorldPtr theGWorld; /* not used ... */ + + GDHandle theGDH; + + GDHandle mainSWGDH; /* not used ... */ + + Str15 title; + + s16b oops; + + s16b keys; + + s16b last; + + s16b mapped; + + s16b rows; + s16b cols; + + s16b font_id; + s16b font_size; + s16b font_face; + s16b font_mono; + + s16b font_o_x; + s16b font_o_y; + s16b font_wid; + s16b font_hgt; + + s16b tile_o_x; + s16b tile_o_y; + s16b tile_wid; + s16b tile_hgt; + + s16b size_wid; + s16b size_hgt; + + s16b size_ow1; + s16b size_oh1; + s16b size_ow2; + s16b size_oh2; +}; + + + + +/* + * Forward declare -- see below + */ +static bool CheckEvents(bool wait); + + +#ifndef MACH_O_CARBON + +/* + * Hack -- location of the main directory + */ +static short app_vol; +static long app_dir; + +#endif /* !MACH_O_CARBON */ + + +/* + * Delay handling of double-clicked savefiles + */ +Boolean open_when_ready = FALSE; + +/* + * Delay handling of pre-emptive "quit" event + */ +Boolean quit_when_ready = FALSE; + + +/* + * Aqua automatically supplies the Quit menu. + */ +static Boolean is_aqua = FALSE; + +/* + * Version of Mac OS - for version specific bug workarounds (; ;) + */ +static long mac_os_version; + + +/* + * Hack -- game in progress + */ +static int game_in_progress = 0; + + +/* + * Only do "SetPort()" when needed + */ +static WindowPtr active = NULL; + + +/* + * Maximum number of terms + */ +#define MAX_TERM_DATA 8 + + +/* + * An array of term_data's + */ +static term_data data[MAX_TERM_DATA]; + + +/* + * Note when "open"/"new" become valid + */ +static bool initialized = FALSE; + + + +/* + * Convert a C string to a pascal string in place + * + * This function may be defined elsewhere, but since it is so + * small, it is not worth finding the proper function name for + * all the different platforms. + */ +static void ctopstr(StringPtr src) +{ + int i; + byte len; + + /* Hack -- pointer */ + char *s = (char*)(src); + + len = strlen(s); + + /* Hack -- convert the string */ + for (i = len; i > 1; i--) s[i] = s[i - 1]; + + /* Hack -- terminate the string */ + s[0] = len; +} + + +#ifdef MACH_O_CARBON + +/* Carbon File Manager utilities by pelpel */ + +/* + * (Carbon) + * Convert a pathname to a corresponding FSSpec. + * Returns noErr on success. + */ +static OSErr path_to_spec(const char *path, FSSpec *spec) +{ + OSErr err; + FSRef ref; + + /* Convert pathname to FSRef ... */ + err = FSPathMakeRef(path, &ref, NULL); + if (err != noErr) return (err); + + /* ... then FSRef to FSSpec */ + err = FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, spec, NULL); + + /* Inform caller of success or failure */ + return (err); +} + + +/* + * (Carbon) + * Convert a FSSpec to a corresponding pathname. + * Returns noErr on success. + */ +static OSErr spec_to_path(const FSSpec *spec, char *buf, size_t size) +{ + OSErr err; + FSRef ref; + + /* Convert FSSpec to FSRef ... */ + err = FSpMakeFSRef(spec, &ref); + if (err != noErr) return (err); + + /* ... then FSRef to pathname */ + err = FSRefMakePath(&ref, buf, size); + + /* Inform caller of success or failure */ + return (err); +} + + +/* + * (Carbon) [via path_to_spec] + * Set creator and filetype of a file specified by POSIX-style pathname. + * Returns 0 on success, -1 in case of errors. + */ +int fsetfileinfo(char *pathname, OSType fcreator, OSType ftype) +{ + OSErr err; + FSSpec spec; + FInfo info; + + /* Convert pathname to FSSpec */ + if (path_to_spec(pathname, &spec) != noErr) return ( -1); + + /* Obtain current finder info of the file */ + if (FSpGetFInfo(&spec, &info) != noErr) return ( -1); + + /* Overwrite creator and type */ + info.fdCreator = fcreator; + info.fdType = ftype; + err = FSpSetFInfo(&spec, &info); + + /* Inform caller of success or failure */ + return ((err == noErr) ? 0 : -1); +} + + +#else /* MACH_O_CARBON */ + +/* +* Convert refnum+vrefnum+fname into a full file name +* Store this filename in 'buf' (make sure it is long enough) +* Note that 'fname' looks to be a "pascal" string +*/ +static void refnum_to_name(char *buf, long refnum, short vrefnum, char *fname) +{ + DirInfo pb; + Str255 name; + int err; + int i, j; + + char res[1000]; + + i = 999; + + res[i] = 0; + i--; + for (j = 1; j <= fname[0]; j++) + { + res[i - fname[0] + j] = fname[j]; + } + i -= fname[0]; + + pb.ioCompletion = NULL; + pb.ioNamePtr = name; + pb.ioVRefNum = vrefnum; + pb.ioDrParID = refnum; + pb.ioFDirIndex = -1; + + while (1) + { + pb.ioDrDirID = pb.ioDrParID; + err = PBGetCatInfoSync((CInfoPBPtr) & pb); + res[i] = ':'; + i--; + for (j = 1; j <= name[0]; j++) + { + res[i - name[0] + j] = name[j]; + } + i -= name[0]; + + if (pb.ioDrDirID == fsRtDirID) break; + } + + /* Extract the result */ + for (j = 0, i++; res[i]; j++, i++) buf[j] = res[i]; + buf[j] = 0; +} + + +/* +* Convert a pascal string in place +* +* This function may be defined elsewhere, but since it is so +* small, it is not worth finding the proper function name for +* all the different platforms. +*/ +static void ptocstr(StringPtr src) +{ + int i; + + /* Hack -- pointer */ + char *s = (char*)(src); + + /* Hack -- convert the string */ + for (i = s[0]; i; i--, s++) s[0] = s[1]; + + /* Hack -- terminate the string */ + s[0] = '\0'; +} + + +/* +* Utility routines by Steve Linberg +* +* The following three routines (pstrcat, pstrinsert, and PathNameFromDirID) +* were taken from the Think Reference section called "Getting a Full Pathname" +* (under the File Manager section). We need PathNameFromDirID to get the +* full pathname of the opened savefile, making no assumptions about where it +* is. +* +* I had to hack PathNameFromDirID a little for MetroWerks, but it's awfully +* nice. +*/ +static void pstrcat(StringPtr dst, StringPtr src) +{ + /* copy string in */ + BlockMove(src + 1, dst + *dst + 1, *src); + + /* adjust length byte */ + *dst += *src; +} + + +/* +* pstrinsert - insert string 'src' at beginning of string 'dst' +*/ +static void pstrinsert(StringPtr dst, StringPtr src) +{ + /* make room for new string */ + BlockMove(dst + 1, dst + *src + 1, *dst); + + /* copy new string in */ + BlockMove(src + 1, dst + 1, *src); + + /* adjust length byte */ + *dst += *src; +} + + +static void PathNameFromDirID(long dirID, short vRefNum, StringPtr fullPathName) +{ + CInfoPBRec block; + Str255 directoryName; + OSErr err; + + fullPathName[0] = '\0'; + + block.dirInfo.ioDrParID = dirID; + block.dirInfo.ioNamePtr = directoryName; + + while (1) + { + block.dirInfo.ioVRefNum = vRefNum; + block.dirInfo.ioFDirIndex = -1; + block.dirInfo.ioDrDirID = block.dirInfo.ioDrParID; + err = PBGetCatInfoSync(&block); + pstrcat(directoryName, (StringPtr)"\p:"); + pstrinsert(fullPathName, directoryName); + if (block.dirInfo.ioDrDirID == 2) break; + } +} + +#endif /* MACH_O_CARBON */ + + + + +/* + * Center a rectangle inside another rectangle + * + * Consider using RepositionWindow() whenever possible + */ +static void center_rect(Rect *r, Rect *s) +{ + int centerx = (s->left + s->right) / 2; + int centery = (2 * s->top + s->bottom) / 3; + int dx = centerx - (r->right - r->left) / 2 - r->left; + int dy = centery - (r->bottom - r->top) / 2 - r->top; + r->left += dx; + r->right += dx; + r->top += dy; + r->bottom += dy; +} + + +/* + * Activate a given window, if necessary + */ +static void activate(WindowPtr w) +{ + /* Activate */ + if (active != w) + { + /* Activate */ + if (w) SetPort(GetWindowPort(w)); + + /* Remember */ + active = w; + } +} + + +/* + * Display a warning message + */ +static void mac_warning(cptr warning) +{ + Str255 text; + int len, i; + + /* Limit of 250 chars */ + len = strlen(warning); + if (len > 250) len = 250; + + /* Make a "Pascal" string */ + text[0] = len; + for (i = 0; i < len; i++) text[i + 1] = warning[i]; + + /* Prepare the dialog box values */ + ParamText(text, "\p", "\p", "\p"); + + /* Display the Alert, wait for Okay */ + Alert(129, 0L); +} + + + +/*** Some generic functions ***/ + +/* + * Hack -- activate a color (0 to 255) + */ +static void term_data_color(term_data *td, int a) +{ + /* Activate the color */ + if (td->last != a) + { + /* Activate the color */ + RGBForeColor(&color_info[a]); + + /* Memorize color */ + td->last = a; + } +} + + +/* + * Hack -- Apply and Verify the "font" info + * + * This should usually be followed by "term_data_check_size()" + * + * XXX XXX To force (re)initialisation of td->tile_wid and td->tile_hgt + * you have to reset them to zero before this function is called. + * XXX XXX This is automatic when the program starts because the term_data + * array is WIPE'd by term_data_hack, but isn't in the other cases, i.e. + * font, font style and size changes. + */ +static void term_data_check_font(term_data *td) +{ + int i; + + FontInfo info; + + WindowPtr old = active; + + + /* Activate */ + activate(td->w); + + /* Instantiate font */ + TextFont(td->font_id); + TextSize(td->font_size); + TextFace(td->font_face); + + /* Extract the font info */ + GetFontInfo(&info); + + /* Assume monospaced */ + td->font_mono = TRUE; + + /* Extract the font sizing values XXX XXX XXX */ + td->font_wid = CharWidth('@'); /* info.widMax; */ + td->font_hgt = info.ascent + info.descent; + td->font_o_x = 0; + td->font_o_y = info.ascent; + + /* Check important characters */ + for (i = 33; i < 127; i++) + { + /* Hack -- notice non-mono-space */ + if (td->font_wid != CharWidth(i)) td->font_mono = FALSE; + + /* Hack -- collect largest width */ + if (td->font_wid < CharWidth(i)) td->font_wid = CharWidth(i); + } + + /* Set default offsets */ + td->tile_o_x = td->font_o_x; + td->tile_o_y = td->font_o_y; + + /* Set default tile size */ + if (td->tile_wid == 0) td->tile_wid = td->font_wid; + if (td->tile_hgt == 0) td->tile_hgt = td->font_hgt; + + /* Re-activate the old window */ + activate(old); +} + + +/* + * Hack -- Apply and Verify the "size" info + */ +static void term_data_check_size(term_data *td) +{ + if (td == &data[0]) + { +#ifndef ALLOW_BIG_SCREEN + + /* Forbid resizing of the Angband window */ + td->cols = 80; + td->rows = 24; + +#else + + /* Enforce minimal size */ + if (td->cols < 80) td->cols = 80; + if (td->rows < 24) td->rows = 24; + +#endif /* !ALLOW_BIG_SCREEN */ + } + + /* Information windows can be much smaller */ + else + { + if (td->cols < 1) td->cols = 1; + if (td->rows < 1) td->rows = 1; + } + + /* Enforce maximal sizes */ + if (td->cols > 255) td->cols = 255; + if (td->rows > 255) td->rows = 255; + + /* Minimal tile size */ + if (td->tile_wid < td->font_wid) td->tile_wid = td->font_wid; + if (td->tile_hgt < td->font_hgt) td->tile_hgt = td->font_hgt; + + /* Default tile offsets */ + td->tile_o_x = (td->tile_wid - td->font_wid) / 2; + td->tile_o_y = (td->tile_hgt - td->font_hgt) / 2; + + /* Minimal tile offsets */ + if (td->tile_o_x < 0) td->tile_o_x = 0; + if (td->tile_o_y < 0) td->tile_o_y = 0; + + /* Apply font offsets */ + td->tile_o_x += td->font_o_x; + td->tile_o_y += td->font_o_y; + + /* Calculate full window size */ + td->size_wid = td->cols * td->tile_wid + td->size_ow1 + td->size_ow2; + td->size_hgt = td->rows * td->tile_hgt + td->size_oh1 + td->size_oh2; + + { + BitMap tScreen; + + /* Get current screen */ + (void)GetQDGlobalsScreenBits(&tScreen); + + /* Verify the top */ + if (td->r.top > tScreen.bounds.bottom - td->size_hgt) + { + td->r.top = tScreen.bounds.bottom - td->size_hgt; + } + + /* Verify the top */ + if (td->r.top < tScreen.bounds.top + GetMBarHeight()) + { + td->r.top = tScreen.bounds.top + GetMBarHeight(); + } + + /* Verify the left */ + if (td->r.left > tScreen.bounds.right - td->size_wid) + { + td->r.left = tScreen.bounds.right - td->size_wid; + } + + /* Verify the left */ + if (td->r.left < tScreen.bounds.left) + { + td->r.left = tScreen.bounds.left; + } + } + + /* Calculate bottom right corner */ + td->r.right = td->r.left + td->size_wid; + td->r.bottom = td->r.top + td->size_hgt; + + /* Assume no graphics */ + td->t->higher_pict = FALSE; + td->t->always_pict = FALSE; + + + /* Handle graphics */ + if (use_graphics) + { + /* Use higher pict whenever possible */ + if (td->font_mono) td->t->higher_pict = TRUE; + + /* Use always_pict only when necessary */ + else td->t->always_pict = TRUE; + } + + /* Fake mono-space */ + if (!td->font_mono || + (td->font_wid != td->tile_wid) || + (td->font_hgt != td->tile_hgt)) + { + /* + * Handle fake monospace + * + * pelpel: This is SLOW. Couldn't we use CharExtra + * and SpaceExtra for monospaced fonts? + */ + if (td->t->higher_pict) td->t->higher_pict = FALSE; + td->t->always_pict = TRUE; + } +} + + +/* + * Hack -- resize a term_data + * + * This should normally be followed by "term_data_redraw()" + */ +static void term_data_resize(term_data *td) +{ + /* + * Actually resize the window + * + * ResizeWindow is the preferred API call, but it cannot + * be used here. + */ + SizeWindow(td->w, td->size_wid, td->size_hgt, 0); +} + + + +/* + * Hack -- redraw a term_data + * + * Note that "Term_redraw()" calls "TERM_XTRA_CLEAR" + */ +static void term_data_redraw(term_data *td) +{ + term *old = Term; + Rect tRect; + + /* Activate the term */ + Term_activate(td->t); + + /* Redraw the contents */ + Term_redraw(); + + /* Flush the output */ + Term_fresh(); + + /* Restore the old term */ + Term_activate(old); + + /* No need to redraw */ + ValidWindowRect(td->w, GetPortBounds(GetWindowPort(td->w), &tRect)); +} + + +/* + * Graphics support + */ + +/* Set by Term_xtra_mac_react */ +#ifdef MACH_O_CARBON +static CFStringRef pict_id; /* PICT id of image tiles */ +#else +static int pict_id; /* PICT id of image tiles */ +#endif /* MACH_O_CARBON */ + +static int graf_width; /* Width of a tile in pixels */ +static int graf_height; /* Height of a tile in pixels */ + +/* Calculated by PICT loading code */ +static int pict_cols; /* Number of columns in tiles */ +static int pict_rows; /* Number of rows in tiles */ + +/* Available graphics modes */ +#define GRAF_MODE_NONE 0 /* plain ASCII */ +#define GRAF_MODE_8X8 1 /* 8x8 tiles */ +#define GRAF_MODE_16X16 2 /* 16x16 tiles */ +#define GRAF_MODE_32X32 3 /* 32x32 tiles */ + +static int graf_mode = GRAF_MODE_NONE; /* current graphics mode */ +static int graf_mode_req = GRAF_MODE_NONE; /* requested graphics mode */ + +#define TR_NONE 0 /* No transparency */ +#define TR_OVER 1 /* Overwriting with transparent black pixels */ +static int transparency_mode = TR_NONE; /* types of transparency effect */ + + +/* + * Forward Declare + */ +typedef struct FrameRec FrameRec; + +/* + * Frame + * + * - GWorld for the frame image + * - Handle to pix map (saved for unlocking/locking) + * - Pointer to color pix map (valid only while locked) + */ +struct FrameRec +{ + GWorldPtr framePort; + PixMapHandle framePixHndl; + PixMapPtr framePix; +}; + + +/* + * The global picture data + */ +static FrameRec *frameP = NULL; + + +/* + * Lock a frame + */ +static void BenSWLockFrame(FrameRec *srcFrameP) +{ + PixMapHandle pixMapH; + + pixMapH = GetGWorldPixMap(srcFrameP->framePort); + (void)LockPixels(pixMapH); + HLockHi((Handle)pixMapH); + srcFrameP->framePixHndl = pixMapH; + srcFrameP->framePix = (PixMapPtr)(*(Handle)pixMapH); +} + + +/* + * Unlock a frame + */ +static void BenSWUnlockFrame(FrameRec *srcFrameP) +{ + if (srcFrameP->framePort != NULL) + { + HUnlock((Handle)srcFrameP->framePixHndl); + UnlockPixels(srcFrameP->framePixHndl); + } + + srcFrameP->framePix = NULL; +} + + + +#ifdef MACH_O_CARBON + +/* Moving graphics resources into data fork -- pelpel */ + +/* + * (Carbon, Bundle) + * Given base and type names of a resource, find a file in the + * current application bundle and return its FSSpec in the third argument. + * Returns true on success, false otherwise. + * e.g. get_resource_spec(CFSTR("8x8"), CFSTR("png"), &spec); + */ +static Boolean get_resource_spec( + CFStringRef base_name, CFStringRef type_name, FSSpec *spec) +{ + CFURLRef res_url; + FSRef ref; + + /* Find the tile resource specified in the current bundle */ + res_url = CFBundleCopyResourceURL( + CFBundleGetMainBundle(), base_name, type_name, NULL); + + /* Oops */ + if (res_url == NULL) return (false); + + /* Convert CFURL to FSRef */ + (void)CFURLGetFSRef(res_url, &ref); + + /* Convert FSRef to FSSpec */ + (void)FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, spec, NULL); + + /* Free allocated CF data */ + CFRelease(res_url); + + /* Success */ + return (true); +} + + +/* + * (QuickTime) + * Create a off-screen GWorld from contents of a file specified by a FSSpec. + * + * Globals referenced: data[0], graf_height, graf_width + * Globals updated: pict_rows, pict_cols. + */ +static OSErr create_gworld_from_spec( + GWorldPtr *tile_gw, FSSpec *tile_spec) +{ + OSErr err; + GraphicsImportComponent gi; + GWorldPtr gw, tmp_gw; + GDHandle gdh, tmp_gdh; + Rect r; + SInt16 depth; + + /* See if QuickTime understands the file format */ + err = GetGraphicsImporterForFile(tile_spec, &gi); + + /* Oops */ + if (err != noErr) return (err); + + /* Get depth */ + depth = data[0].pixelDepth; + + /* Get GDH */ + gdh = data[0].theGDH; + + /* Retrieve the rect of the image */ + err = GraphicsImportGetNaturalBounds(gi, &r); + + /* Adjust it, so that the upper left corner becomes (0, 0) */ + OffsetRect(&r, -r.left, -r.top); + + /* Calculate and set numbers of rows and columns */ + pict_rows = r.bottom / graf_height; + pict_cols = r.right / graf_width; + + /* Create a GWorld */ + err = NewGWorld(&gw, depth, &r, NULL, gdh, noNewDevice); + + /* Oops */ + if (err != noErr) return (err); + + /* Save the pointer to the GWorld */ + *tile_gw = gw; + + /* Save the current GWorld */ + GetGWorld(&tmp_gw, &tmp_gdh); + + /* Activate the newly created GWorld */ + (void)GraphicsImportSetGWorld(gi, gw, NULL); + + /* Prevent pixmap from moving while drawing */ + (void)LockPixels(GetGWorldPixMap(gw)); + + /* Clear the pixels */ + EraseRect(&r); + + /* Draw the image into it */ + (void)GraphicsImportDraw(gi); + + /* Release the lock*/ + UnlockPixels(GetGWorldPixMap(gw)); + + /* Restore GWorld */ + SetGWorld(tmp_gw, tmp_gdh); + + /* Close the image importer */ + CloseComponent(gi); + + /* Success */ + return (noErr); +} + +#else /* MACH_O_CARBON */ + +static OSErr BenSWCreateGWorldFromPict( + GWorldPtr *pictGWorld, PicHandle pictH) +{ + OSErr err; + GWorldPtr saveGWorld; + GDHandle saveGDevice; + GWorldPtr tempGWorld; + Rect pictRect; + short depth; + GDHandle theGDH; + + tempGWorld = NULL; + + /* Reset */ + *pictGWorld = NULL; + + /* Get depth */ + depth = data[0].pixelDepth; + + /* Get GDH */ + theGDH = data[0].theGDH; + + /* Obtain size rectangle */ + pictRect = (**pictH).picFrame; + OffsetRect(&pictRect, -pictRect.left, -pictRect.top); + + /* Calculate and set numbers of rows and columns */ + pict_rows = pictRect.bottom / graf_height; + pict_cols = pictRect.right / graf_width; + + /* Create a GWorld */ + err = NewGWorld(&tempGWorld, depth, &pictRect, nil, theGDH, noNewDevice); + + /* Oops */ + if (err != noErr) return (err); + + /* Save pointer */ + *pictGWorld = tempGWorld; + + /* Save GWorld */ + GetGWorld(&saveGWorld, &saveGDevice); + + /* Activate */ + SetGWorld(tempGWorld, nil); + + /* Dump the pict into the GWorld */ + (void)LockPixels(GetGWorldPixMap(tempGWorld)); + EraseRect(&pictRect); + DrawPicture(pictH, &pictRect); + UnlockPixels(GetGWorldPixMap(tempGWorld)); + + /* Restore GWorld */ + SetGWorld(saveGWorld, saveGDevice); + + /* Success */ + return (0); +} + +#endif /* MACH_O_CARBON */ + + +/* + * Init the global "frameP" + */ +static errr globe_init(void) +{ + OSErr err; + + GWorldPtr tempPictGWorldP; + +#ifdef MACH_O_CARBON + FSSpec pict_spec; +#else + PicHandle newPictH; +#endif /* MACH_O_CARBON */ + + + /* Use window XXX XXX XXX */ + SetPort(GetWindowPort(data[0].w)); + + +#ifdef MACH_O_CARBON + + /* Get the tile resources */ + if (!get_resource_spec(pict_id, CFSTR("png"), &pict_spec)) return ( -1); + + /* Create GWorld */ + err = create_gworld_from_spec(&tempPictGWorldP, &pict_spec); + +#else /* MACH_O_CARBON */ + + /* Get the pict resource */ + if ((newPictH = GetPicture(pict_id)) == 0) return ( -1); + + /* Create GWorld */ + err = BenSWCreateGWorldFromPict(&tempPictGWorldP, newPictH); + + /* Release resource */ + ReleaseResource((Handle)newPictH); + +#endif /* MACH_O_CARBON */ + + /* Error */ + if (err != noErr) return (err); + + /* Create the frame */ + frameP = (FrameRec*)NewPtrClear((Size)sizeof(FrameRec)); + + /* Analyze result */ + if (frameP == NULL) return ( -1); + + /* Save GWorld */ + frameP->framePort = tempPictGWorldP; + + /* Lock it */ + BenSWLockFrame(frameP); + + /* Success */ + return (noErr); +} + + +/* + * Nuke the global "frameP" + */ +static errr globe_nuke(void) +{ + /* Dispose */ + if (frameP) + { + /* Unlock */ + BenSWUnlockFrame(frameP); + + /* Dispose of the GWorld */ + DisposeGWorld(frameP->framePort); + + /* Dispose of the memory */ + DisposePtr((Ptr)frameP); + + /* Forget */ + frameP = NULL; + } + + /* Flush events */ + FlushEvents(everyEvent, 0); + + /* Success */ + return (0); +} + + +#ifdef USE_ASYNC_SOUND + +/* + * Asynchronous sound player - completely revised (beta) + */ +#if defined(USE_QT_SOUND) && !defined(MACH_O_CARBON) +# undef USE_QT_SOUND +#endif /* USE_QT_SOUND && !MACH_O_CARBON */ + +/* + * How many sound channels will be pooled + * + * Was: 20, but I don't think we need 20 sound effects playing + * simultaneously :) -- pelpel + */ +#define MAX_CHANNELS 8 + +/* + * A pool of sound channels + */ +static SndChannelPtr channels[MAX_CHANNELS]; + +/* + * Status of the channel pool + */ +static Boolean channel_initialised = FALSE; + +/* + * Data handles containing sound samples + */ +static SndListHandle samples[SOUND_MAX]; + +/* + * Reference counts of sound samples + */ +static SInt16 sample_refs[SOUND_MAX]; + +#define SOUND_VOLUME_MIN 0 /* Default minimum sound volume */ +#define SOUND_VOLUME_MAX 255 /* Default maximum sound volume */ +#define VOLUME_MIN 0 /* Minimum sound volume in % */ +#define VOLUME_MAX 100 /* Maximum sound volume in % */ +#define VOLUME_INC 5 /* Increment sound volume in % */ + +/* + * I'm just too lazy to write a panel for this XXX XXX + */ +static int sound_volume = SOUND_VOLUME_MAX; + + +#ifdef USE_QT_SOUND + +/* + * QuickTime sound, by Ron Anderson + * + * I didn't choose to use Windows-style .ini files (Ron wrote a parser + * for it, but...), nor did I use lib/xtra directory, hoping someone + * would code plist-based configuration code in the future -- pelpel + */ + +/* + * (QuickTime) + * Load sound effects from data-fork resources. They are wav files + * with the same names as angband_sound_name[] (variable.c) + * + * Globals referenced: angband_sound_name[] + * Globals updated: samples[] (they can be *huge*) + */ +static void load_sounds(void) +{ + OSErr err; + int i; + + /* Start QuickTime */ + err = EnterMovies(); + + /* Error */ + if (err != noErr) return; + + /* + * This loop may take a while depending on the count and size of samples + * to load. + * + * We should use a progress dialog for this. + */ + for (i = 1; i < SOUND_MAX; i++) + { + /* Apple APIs always give me headacke :( */ + CFStringRef name; + FSSpec spec; + SInt16 file_id; + SInt16 res_id; + Str255 movie_name; + Movie movie; + Track track; + Handle h; + Boolean res; + + /* Allocate CFString with the name of sound event to be processed */ + name = CFStringCreateWithCString(NULL, angband_sound_name[i], + kTextEncodingUS_ASCII); + + /* Error */ + if (name == NULL) continue; + + /* Find sound sample resource with the same name */ + res = get_resource_spec(name, CFSTR("wav"), &spec); + + /* Free the reference to CFString */ + CFRelease(name); + + /* Error */ + if (!res) continue; + + /* Open the sound file */ + err = OpenMovieFile(&spec, &file_id, fsRdPerm); + + /* Error */ + if (err != noErr) continue; + + /* Create Movie from the file */ + err = NewMovieFromFile(&movie, file_id, &res_id, movie_name, + newMovieActive, NULL); + + /* Error */ + if (err != noErr) goto close_file; + + /* Get the first track of the movie */ + track = GetMovieIndTrackType(movie, 1, AudioMediaCharacteristic, + movieTrackCharacteristic | movieTrackEnabledOnly ); + + /* Error */ + if (track == NULL) goto close_movie; + + /* Allocate a handle to store sample */ + h = NewHandle(0); + + /* Error */ + if (h == NULL) goto close_track; + + /* Dump the sample into the handle */ + err = PutMovieIntoTypedHandle(movie, track, soundListRsrc, h, 0, + GetTrackDuration(track), 0L, NULL); + + /* Success */ + if (err == noErr) + { + /* Store the handle in the sample list */ + samples[i] = (SndListHandle)h; + } + + /* Failure */ + else + { + /* Free unused handle */ + DisposeHandle(h); + } + + /* Free the track */ +close_track: + DisposeMovieTrack(track); + + /* Free the movie */ +close_movie: + DisposeMovie(movie); + + /* Close the movie file */ +close_file: + CloseMovieFile(file_id); + } + + /* Stop QuickTime */ + ExitMovies(); +} + +#else /* USE_QT_SOUND */ + +/* +* Return a handle of 'snd ' resource given Angband sound event number, +* or NULL if it isn't found. +* +* Globals referenced: angband_sound_name[] (variable.c) +*/ +static SndListHandle find_sound(int num) +{ +Str255 sound; + +/* Get the proper sound name */ +strnfmt((char*)sound + 1, 255, "%.16s.wav", angband_sound_name[num]); +sound[0] = strlen((char*)sound + 1); + +/* Obtain resource XXX XXX XXX */ +return ((SndListHandle)GetNamedResource('snd ', sound)); +} + +#endif /* USE_QT_SOUND */ + + +/* + * Clean up sound support - to be called when the game exits. + * + * Globals referenced: channels[], samples[], sample_refs[]. + */ +static void cleanup_sound(void) +{ + int i; + + /* No need to clean it up */ + if (!channel_initialised) return; + + /* Dispose channels */ + for (i = 0; i < MAX_CHANNELS; i++) + { + /* Drain sound commands and free the channel */ + SndDisposeChannel(channels[i], TRUE); + } + + /* Free sound data */ + for (i = 1; i < SOUND_MAX; i++) + { + /* Still locked */ + if ((sample_refs[i] > 0) && (samples[i] != NULL)) + { + /* Unlock it */ + HUnlock((Handle)samples[i]); + } + +#ifndef USE_QT_SOUND + + /* Release it */ + if (samples[i]) ReleaseResource((Handle)samples[i]); + +#else +/* Free handle */ + if (samples[i]) DisposeHandle((Handle)samples[i]); + +#endif /* !USE_QT_SOUND */ + } +} + + +/* + * Play sound effects asynchronously -- pelpel + * + * I don't believe those who first started using the previous implementations + * imagined this is *much* more complicated as it may seem. Anyway, + * introduced round-robin scheduling of channels and made it much more + * paranoid about HLock/HUnlock. + * + * XXX XXX de-refcounting, HUnlock and ReleaseResource should be done + * using channel's callback procedures, which set global flags, and + * a procedure hooked into CheckEvents does housekeeping. On the other + * hand, this lazy reclaiming strategy keeps things simple (no interrupt + * time code) and provides a sort of cache for sound data. + * + * Globals referenced: channel_initialised, channels[], samples[], + * sample_refs[]. + * Globals updated: channel_initialised, channels[], sample_refs[]. + * Only in !USE_QT_SOUND, samples[]. + */ +static void play_sound(int num, int vol) +{ + OSErr err; + int i; + int prev_num; + SndListHandle h; + SndChannelPtr chan; + SCStatus status; + + static int next_chan; + static SInt16 channel_occupants[MAX_CHANNELS]; + static SndCommand volume_cmd, quiet_cmd; + + + /* Initialise sound channels */ + if (!channel_initialised) + { + for (i = 0; i < MAX_CHANNELS; i++) + { + /* Paranoia - Clear occupant table */ + /* channel_occupants[i] = 0; */ + + /* Create sound channel for all sounds to play from */ + err = SndNewChannel(&channels[i], sampledSynth, initMono, NULL); + + /* Error */ + if (err != noErr) + { + /* Free channels */ + while (--i >= 0) + { + SndDisposeChannel(channels[i], TRUE); + } + + /* Notify error */ + plog("Cannot initialise sound channels!"); + + /* Cancel request */ + use_sound = arg_sound = FALSE; + + /* Failure */ + return; + } + } + + /* First channel to use */ + next_chan = 0; + + /* Prepare volume command */ + volume_cmd.cmd = volumeCmd; + volume_cmd.param1 = 0; + volume_cmd.param2 = 0; + + /* Prepare quiet command */ + quiet_cmd.cmd = quietCmd; + quiet_cmd.param1 = 0; + quiet_cmd.param2 = 0; + + /* Initialisation complete */ + channel_initialised = TRUE; + } + + /* Paranoia */ + if ((num <= 0) || (num >= SOUND_MAX)) return; + + /* Prepare volume command */ + volume_cmd.param2 = (SInt16)((vol << 4) | vol); + + /* Channel to use (round robin) */ + chan = channels[next_chan]; + + /* See if the resource is already in use */ + if (sample_refs[num] > 0) + { + /* Resource in use */ + h = samples[num]; + + /* Increase the refcount */ + sample_refs[num]++; + } + + /* Sound is not currently in use */ + else + { + /* Get handle for the sound */ +#ifdef USE_QT_SOUND + h = samples[num]; +#else + h = find_sound(num); +#endif /* USE_QT_SOUND */ + + /* Sample not available */ + if (h == NULL) return; + +#ifndef USE_QT_SOUND + + /* Load resource */ + LoadResource((Handle)h); + + /* Remember it */ + samples[num] = h; + +#endif /* !USE_QT_SOUND */ + + /* Lock the handle */ + HLock((Handle)h); + + /* Initialise refcount */ + sample_refs[num] = 1; + } + + /* Poll the channel */ + err = SndChannelStatus(chan, sizeof(SCStatus), &status); + + /* It isn't available */ + if ((err != noErr) || status.scChannelBusy) + { + /* Shut it down */ + SndDoImmediate(chan, &quiet_cmd); + } + + /* Previously played sound on this channel */ + prev_num = channel_occupants[next_chan]; + + /* Process previously played sound */ + if (prev_num != 0) + { + /* Decrease refcount */ + sample_refs[prev_num]--; + + /* We can free it now */ + if (sample_refs[prev_num] <= 0) + { + /* Unlock */ + HUnlock((Handle)samples[prev_num]); + +#ifndef USE_QT_SOUND + + /* Release */ + ReleaseResource((Handle)samples[prev_num]); + + /* Forget handle */ + samples[prev_num] = NULL; + +#endif /* !USE_QT_SOUND */ + + /* Paranoia */ + sample_refs[prev_num] = 0; + } + } + + /* Remember this sound as the current occupant of the channel */ + channel_occupants[next_chan] = num; + + /* Set up volume for channel */ + SndDoImmediate(chan, &volume_cmd); + + /* Play new sound asynchronously */ + SndPlay(chan, h, TRUE); + + /* Schedule next channel (round robin) */ + next_chan++; + if (next_chan >= MAX_CHANNELS) next_chan = 0; +} + +#endif /* USE_ASYNC_SOUND */ + + + + +/*** Support for the "z-term.c" package ***/ + + +/* + * Initialize a new Term + * + * Note also the "window type" called "noGrowDocProc", which might be more + * appropriate for the main "screen" window. + * + * Note the use of "srcCopy" mode for optimized screen writes. + */ +static void Term_init_mac(term *t) +{ + term_data *td = (term_data*)(t->data); + WindowAttributes wattrs; + OSStatus err; + + static RGBColor black = {0x0000, 0x0000, 0x0000}; + static RGBColor white = {0xFFFF, 0xFFFF, 0xFFFF}; + +#ifndef ALLOW_BIG_SCREEN + + /* Every window has close and collapse boxes */ + wattrs = kWindowCloseBoxAttribute | kWindowCollapseBoxAttribute; + + /* Information windows are resizable */ + if (td != &data[0]) wattrs |= kWindowResizableAttribute; + +#else + + /* Big screen - every window has close, collapse and resize boxes */ + wattrs = kWindowCloseBoxAttribute | + kWindowCollapseBoxAttribute | + kWindowResizableAttribute; + +#endif /* !ALLOW_BIG_SCREEN */ + + /* Make the window */ + err = CreateNewWindow( + kDocumentWindowClass, + wattrs, + &td->r, + &td->w); + + /* + * XXX XXX Although the original main-mac.c doesn't perform error + * checking, it should be done here. + */ + + /* Set window title */ + SetWTitle(td->w, td->title); + + /* Activate the window */ + activate(td->w); + + /* Erase behind words */ + TextMode(srcCopy); + + /* Apply and Verify */ + term_data_check_font(td); + term_data_check_size(td); + + /* Resize the window */ + term_data_resize(td); + + + /* Prepare the colors (real colors) */ + RGBBackColor(&black); + RGBForeColor(&white); + + /* Block */ + { + Rect globalRect; + GDHandle mainGDH; + GDHandle currentGDH; + GWorldPtr windowGWorld; + PixMapHandle basePixMap; + + /* Obtain the global rect */ + GetWindowBounds((WindowRef)td->w, kWindowContentRgn, &globalRect); + + /* Obtain the proper GDH */ + mainGDH = GetMaxDevice(&globalRect); + + /* Extract GWorld and GDH */ + GetGWorld(&windowGWorld, ¤tGDH); + + /* Obtain base pixmap */ + basePixMap = (**mainGDH).gdPMap; + + /* Save pixel depth */ + td->pixelDepth = (**basePixMap).pixelSize; + + /* Save Window GWorld - unused */ + td->theGWorld = windowGWorld; + + /* Save Window GDH */ + td->theGDH = currentGDH; + + /* Save main GDH - unused */ + td->mainSWGDH = mainGDH; + } + + { + Rect portRect; + + /* Get current Rect */ + GetPortBounds(GetWindowPort(td->w), &portRect); + + /* Clip to the window */ + ClipRect(&portRect); + + /* Erase the window */ + EraseRect(&portRect); + + /* Invalidate the window */ + InvalWindowRect(td->w, &portRect); + } + + /* + * A certain release of OS X fails to display windows at proper + * locations (_ _#) + */ + if ((mac_os_version >= 0x1000) && (mac_os_version < 0x1010)) + { + /* Hack - Make sure the window is displayed at (r.left,r.top) */ + MoveWindow(td->w, td->r.left, td->r.top, 1); + } + + /* Display the window if needed */ + if (td->mapped) + { + TransitionWindow(td->w, + kWindowZoomTransitionEffect, kWindowShowTransitionAction, NULL); + } + + /* Hack -- set "mapped" flag */ + t->mapped_flag = td->mapped; + + /* Forget color */ + td->last = -1; +} + + + +/* + * Nuke an old Term + */ +static void Term_nuke_mac(term *t) +{ + /* XXX */ +} + + + +/* + * Unused + */ +static errr Term_user_mac(int n) +{ + /* Success */ + return (0); +} + + + +/* + * React to changes + */ +static errr Term_xtra_mac_react(void) +{ + term_data *td = (term_data*)(Term->data); + + int i; + + + /* Reset color */ + td->last = -1; + + /* Update colors */ + for (i = 0; i < 256; i++) + { + u16b rv, gv, bv; + + /* Extract the R,G,B data */ + rv = angband_color_table[i][1]; + gv = angband_color_table[i][2]; + bv = angband_color_table[i][3]; + + /* Save the actual color */ + color_info[i].red = (rv | (rv << 8)); + color_info[i].green = (gv | (gv << 8)); + color_info[i].blue = (bv | (bv << 8)); + } + + + /* Handle sound */ + if (use_sound != arg_sound) + { + /* Apply request */ + use_sound = arg_sound; + } + + + /* Handle graphics */ + if (graf_mode_req != graf_mode) + { + /* dispose old GWorld's if present */ + globe_nuke(); + + /* + * Setup parameters according to request + * + * In [Z], you have to set use_graphics and arg_graphics to + * GRAPHICS_NONE, GRAPHICS_ORIGINAL or GRAPHICS_ADAM_BOLT, and + * comment ANGBAND_GRAF out. + */ + switch (graf_mode_req) + { + /* ASCII - no graphics whatsoever */ + case GRAF_MODE_NONE: + { + use_graphics = arg_graphics = FALSE; + transparency_mode = TR_NONE; + break; + } + + /* + * 8x8 tiles (PICT id 1001) + * no transparency effect + * "old" graphics definitions + */ + case GRAF_MODE_8X8: + { + use_graphics = arg_graphics = TRUE; + ANGBAND_GRAF = "old"; + transparency_mode = TR_NONE; +#ifdef MACH_O_CARBON + pict_id = CFSTR("8x8"); +#else + pict_id = 1001; +#endif /* MACH_O_CARBON */ + graf_width = graf_height = 8; + break; + } + + /* + * 16x16 tiles (images: PICT id 1002, masks: PICT id 1003) + * with transparency effect + * "new" graphics definitions + */ + case GRAF_MODE_16X16: + { + use_graphics = arg_graphics = TRUE; + ANGBAND_GRAF = "new"; + transparency_mode = TR_OVER; +#ifdef MACH_O_CARBON + pict_id = CFSTR("16x16"); +#else + pict_id = 1002; +#endif /* MACH_O_CARBON */ + graf_width = graf_height = 16; + break; + } + + /* + * 32x32 tiles (images: PICT id 1004) + * with transparency effect + * "david" graphics definitions + * Vanilla-specific + */ + case GRAF_MODE_32X32: + { + use_graphics = arg_graphics = TRUE; + ANGBAND_GRAF = "david"; + transparency_mode = TR_OVER; +#ifdef MACH_O_CARBON + pict_id = CFSTR("32x32"); +#else + pict_id = 1004; +#endif /* MACH_O_CARBON */ + graf_width = graf_height = 32; + break; + } + } + + /* load tiles and setup GWorlds if tiles are requested */ + if ((graf_mode_req != GRAF_MODE_NONE) && (globe_init() != 0)) + { + /* Oops */ + plog("Cannot initialize graphics!"); + + /* reject request */ + graf_mode_req = GRAF_MODE_NONE; + + /* reset graphics flags */ + use_graphics = arg_graphics = FALSE; + + /* reset transparency mode */ + transparency_mode = TR_NONE; + } + + /* update current graphics mode */ + graf_mode = graf_mode_req; + + /* Apply and Verify */ + term_data_check_size(td); + + /* Resize the window */ + term_data_resize(td); + + /* Reset visuals */ +#ifndef ANG281_RESET_VISUALS + reset_visuals(TRUE); +#else + reset_visuals(); +#endif /* !ANG281_RESET_VISUALS */ + } + + /* Success */ + return (0); +} + + +/* + * Do a "special thing" + */ +static errr Term_xtra_mac(int n, int v) +{ + term_data *td = (term_data*)(Term->data); + + Rect r; + + /* Analyze */ + switch (n) + { + /* Make a noise */ + case TERM_XTRA_NOISE: + { + /* Make a noise */ + SysBeep(1); + + /* Success */ + return (0); + } + + /* Make a sound */ + case TERM_XTRA_SOUND: + { +#ifndef USE_ASYNC_SOUND + + /* + * This may not be your choice, but much safer and much less + * resource hungry. Existing implementations can quite easily + * crash, by starting asynchronous playing and immediately + * unlocking and releasing the sound data just started playing... + * -- pelpel + */ + Handle handle; + Str255 sound; + + /* Get the proper sound name */ + strnfmt((char*)sound + 1, 255, "%.16s.wav", angband_sound_name[v]); + sound[0] = strlen((char*)sound + 1); + + /* Obtain resource XXX XXX XXX */ + handle = GetNamedResource('snd ', sound); + + /* Oops -- it is a failure, but we return 0 anyway */ + if (handle == NULL) return (0); + + /* Load and Lock */ + LoadResource(handle); + HLock(handle); + + /* Play sound (wait for completion) */ + SndPlay(NULL, (SndListHandle)handle, FALSE); + + /* Unlock and release */ + HUnlock(handle); + ReleaseResource(handle); + +#else /* !USE_ASYNC_SOUND */ + + /* Play sound */ + play_sound(v, sound_volume); + +#endif /* !USE_ASYNC_SOUND */ + + /* Success */ + return (0); + } + + /* Process random events */ + case TERM_XTRA_BORED: + { + /* Process an event */ + (void)CheckEvents(FALSE); + + /* Success */ + return (0); + } + + /* Process pending events */ + case TERM_XTRA_EVENT: + { + /* Process an event */ + (void)CheckEvents(v); + + /* Success */ + return (0); + } + + /* Flush all pending events (if any) */ + case TERM_XTRA_FLUSH: + { + /* Hack -- flush all events */ + while (CheckEvents(FALSE)) /* loop */; + + /* Success */ + return (0); + } + + /* Hack -- Change the "soft level" */ + case TERM_XTRA_LEVEL: + { + /* Activate if requested */ + if (v) activate(td->w); + + /* Success */ + return (0); + } + + /* Clear the screen */ + case TERM_XTRA_CLEAR: + { + Rect portRect; + + /* Get current Rect */ + GetPortBounds(GetWindowPort(td->w), &portRect); + + /* No clipping XXX XXX XXX */ + ClipRect(&portRect); + + /* Erase the window */ + EraseRect(&portRect); + + /* Set the color */ + term_data_color(td, TERM_WHITE); + + /* Frame the window in white */ + MoveTo(0, 0); + LineTo(0, td->size_hgt - 1); + LineTo(td->size_wid - 1, td->size_hgt - 1); + LineTo(td->size_wid - 1, 0); + + /* Clip to the new size */ + r.left = portRect.left + td->size_ow1; + r.top = portRect.top + td->size_oh1; + r.right = portRect.right - td->size_ow2; + r.bottom = portRect.bottom - td->size_oh2; + ClipRect(&r); + + /* Success */ + return (0); + } + + /* React to changes */ + case TERM_XTRA_REACT: + { + /* React to changes */ + return (Term_xtra_mac_react()); + } + + /* Delay (milliseconds) */ + case TERM_XTRA_DELAY: + { + /* + * WaitNextEvent relinquishes CPU as well as + * induces a screen refresh on OS X + */ + + /* If needed */ + if (v > 0) + { + EventRecord tmp; + UInt32 ticks; + + /* Convert millisecs to ticks */ + ticks = (v * 60L) / 1000; + + /* + * Hack? - Put the programme into sleep. + * No events match ~everyEvent, so nothing + * should be lost in Angband's event queue. + * Even if ticks are 0, it's worth calling for + * the above mentioned reasons. + */ + WaitNextEvent((EventMask)~everyEvent, &tmp, ticks, nil); + } + + /* Success */ + return (0); + } + + /* Rename main window */ + case TERM_XTRA_RENAME_MAIN_WIN: + { + char *s = strdup(angband_term_name[0]); + + ctopstr((StringPtr)s); + SetWTitle(data[0].w, (StringPtr)s); + + free(s); + return (0); + } + +/* MacOSX == Unix == Good */ +#ifdef USE_MACOSX + /* Get Delay of some milliseconds */ + case TERM_XTRA_GET_DELAY: + { + int ret; + struct timeval tv; + + ret = gettimeofday(&tv, NULL); + Term_xtra_long = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); + + return ret; + } + + /* Subdirectory scan */ + case TERM_XTRA_SCANSUBDIR: + { + DIR *directory; + struct dirent *entry; + + scansubdir_max = 0; + + directory = opendir(scansubdir_dir); + if (!directory) + return 1; + + while ((entry = readdir(directory))) + { + char file[PATH_MAX + NAME_MAX + 2]; + struct stat filedata; + + file[PATH_MAX + NAME_MAX] = 0; + strncpy(file, scansubdir_dir, PATH_MAX); + strncat(file, "/", 2); + strncat(file, entry->d_name, NAME_MAX); + if (!stat(file, &filedata) && S_ISDIR((filedata.st_mode))) + { + string_free(scansubdir_result[scansubdir_max]); + scansubdir_result[scansubdir_max] = string_make(entry->d_name); + ++scansubdir_max; + } + } + + closedir(directory); + return 0; + } +#endif + } + + /* Oops */ + return (1); +} + + + +/* + * Low level graphics (Assumes valid input). + * Draw a "cursor" at (x,y), using a "yellow box". + * We are allowed to use "Term_what()" to determine + * the current screen contents (for inverting, etc). + */ +static errr Term_curs_mac(int x, int y) +{ + Rect r; + + term_data *td = (term_data*)(Term->data); + + /* Set the color */ + term_data_color(td, TERM_YELLOW); + + /* Frame the grid */ + r.left = x * td->tile_wid + td->size_ow1; + r.right = r.left + td->tile_wid; + r.top = y * td->tile_hgt + td->size_oh1; + r.bottom = r.top + td->tile_hgt; + +#ifdef USE_DOUBLE_TILES + + /* Mogami's bigtile patch */ + + /* Adjust it if double width tiles are requested */ + if (use_bigtile && + (x + 1 < Term->wid) && + (Term->old->a[y][x + 1] == 255)) + { + r.right += td->tile_wid; + } + +#endif /* USE_DOUBLE_TILES */ + + FrameRect(&r); + + /* Success */ + return (0); +} + + +/* + * Low level graphics (Assumes valid input) + * + * Erase "n" characters starting at (x,y) + */ +static errr Term_wipe_mac(int x, int y, int n) +{ + Rect r; + + term_data *td = (term_data*)(Term->data); + + + /* Erase the block of characters */ + r.left = x * td->tile_wid + td->size_ow1; + r.right = r.left + n * td->tile_wid; + r.top = y * td->tile_hgt + td->size_oh1; + r.bottom = r.top + td->tile_hgt; + EraseRect(&r); + + /* Success */ + return (0); +} + + +/* + * Low level graphics. Assumes valid input. + * + * Draw several ("n") chars, with an attr, at a given location. + */ +static errr Term_text_mac(int x, int y, int n, byte a, const char *cp) +{ + int xp, yp; + +#ifdef CLIP_HACK + Rect r; +#endif /* CLIP_HACK */ + + term_data *td = (term_data*)(Term->data); + + + /* Set the color */ + term_data_color(td, a); + +#ifdef CLIP_HACK + /* Hack - only draw within the bounding rect */ + r.left = x * td->tile_wid + td->size_ow1; + r.right = r.left + n * td->tile_wid; + r.top = y * td->tile_hgt + td->size_oh1; + r.bottom = r.top + td->tile_hgt; + ClipRect(&r); + + /* Hack - clear the content of the bounding rect */ + EraseRect(&r); +#endif /* CLIP_HACK */ + + /* Starting pixel */ + xp = x * td->tile_wid + td->tile_o_x + td->size_ow1; + yp = y * td->tile_hgt + td->tile_o_y + td->size_oh1; + + /* Move to the correct location */ + MoveTo(xp, yp); + + /* Draw the character */ + if (n == 1) DrawChar(*cp); + + /* Draw the string */ + else DrawText(cp, 0, n); + +#ifdef CLIP_HACK + /* Obtain current window's rect */ + GetPortBounds(GetWindowPort(td->w), &r); + + /* Clip to the window again */ + ClipRect(&r); +#endif /* CLIP_HACK */ + + /* Success */ + return (0); +} + + +/* + * Low level graphics (Assumes valid input) + * + * Erase "n" characters starting at (x,y) + */ +#ifdef USE_TRANSPARENCY +# ifdef USE_EGO_GRAPHICS +static errr Term_pict_mac(int x, int y, int n, const byte *ap, const char *cp, + const byte *tap, const char *tcp, + const byte *eap, const char *ecp) +# else /* USE_EGO_GRAPHICS */ +static errr Term_pict_mac(int x, int y, int n, const byte *ap, const char *cp, + const byte *tap, const char *tcp) +# endif /* USE_EGO_GRAPHICS */ +#else /* USE_TRANSPARENCY */ +static errr Term_pict_mac(int x, int y, int n, const byte *ap, const char *cp) +#endif /* USE_TRANSPARENCY */ +{ + int i; + Rect dst_r; + GrafPtr port; + PixMapHandle pixmap_h; + +#ifdef CLIP_HACK + Rect portRect; +#endif /* CLIP_HACK */ + + term_data *td = (term_data*)(Term->data); + + static RGBColor black = {0x0000, 0x0000, 0x0000}; + static RGBColor white = {0xFFFF, 0xFFFF, 0xFFFF}; + + +#ifdef CLIP_HACK + /* Remember current window's rect */ + GetPortBounds(GetWindowPort(td->w), &portRect); +#endif /* CLIP_HACK */ + + /* Destination rectangle */ + dst_r.left = x * td->tile_wid + td->size_ow1; +#ifndef USE_DOUBLE_TILES + dst_r.right = dst_r.left + td->tile_wid; +#endif /* !USE_DOUBLE_TILES */ + dst_r.top = y * td->tile_hgt + td->size_oh1; + dst_r.bottom = dst_r.top + td->tile_hgt; + + /* Scan the input */ + for (i = 0; i < n; i++) + { + bool done = FALSE; + + byte a = *ap++; + char c = *cp++; + +#ifdef USE_TRANSPARENCY + byte ta = *tap++; + char tc = *tcp++; +# ifdef USE_EGO_GRAPHICS + byte ea = *eap++; + char ec = *ecp++; + bool has_overlay = (ea && ec); +# endif /* USE_EGO_GRAPHICS */ +#endif + + +#ifdef USE_DOUBLE_TILES + + /* Hack -- a filler for double-width tile */ + if (use_bigtile && (a == 255)) + { + /* Advance */ + dst_r.left += td->tile_wid; + + /* Ignore */ + continue; + } + + /* Prepare right side of rectagle now */ + dst_r.right = dst_r.left + td->tile_wid; + +#endif /* USE_DOUBLE_TILES */ + + /* Graphics -- if Available and Needed */ + if (use_graphics && ((byte)a & 0x80) && ((byte)c & 0x80)) + { + int col, row; + Rect src_r; +#ifdef USE_TRANSPARENCY + int t_col, t_row; + Rect terrain_r; +# ifdef USE_EGO_GRAPHICS + int e_col, e_row; + Rect ego_r; +# endif /* USE_EGO_GRAPHICS */ +#endif /* USE_TRANSPARENCY */ + + /* Row and Col */ + row = ((byte)a & 0x7F) % pict_rows; + col = ((byte)c & 0x7F) % pict_cols; + + /* Source rectangle */ + src_r.left = col * graf_width; + src_r.top = row * graf_height; + src_r.right = src_r.left + graf_width; + src_r.bottom = src_r.top + graf_height; + +#ifdef USE_TRANSPARENCY + /* Row and Col */ + t_row = ((byte)ta & 0x7F) % pict_rows; + t_col = ((byte)tc & 0x7F) % pict_cols; + + /* Source rectangle */ + terrain_r.left = t_col * graf_width; + terrain_r.top = t_row * graf_height; + terrain_r.right = terrain_r.left + graf_width; + terrain_r.bottom = terrain_r.top + graf_height; + +# ifdef USE_EGO_GRAPHICS + + /* If there's an overlay */ + if (has_overlay) + { + /* Row and Col */ + e_row = ((byte)ea & 0x7F) % pict_rows; + e_col = ((byte)ec & 0x7F) % pict_cols; + + /* Source rectangle */ + ego_r.left = e_col * graf_width; + ego_r.top = e_row * graf_height; + ego_r.right = ego_r.left + graf_width; + ego_r.bottom = ego_r.top + graf_height; + } + +# endif /* USE_EGO_GRAPHICS */ + +#endif /* USE_TRANSPARENCY */ + + /* Hardwire CopyBits */ + RGBBackColor(&white); + RGBForeColor(&black); + +#ifdef USE_DOUBLE_TILES + + /* Double width tiles */ + if (use_bigtile) dst_r.right += td->tile_wid; + +#endif /* USE_DOUBLE_TILES */ + + /* + * OS X requires locking and unlocking of window port + * when we draw directly to its pixmap. + * The Lock/Unlock protocol is described in the Carbon + * Porting Guide. + */ + + /* Obtain current window's graphic port */ + port = GetWindowPort(td->w); + + /* Lock pixels, so we can use handle safely */ + LockPortBits(port); + + /* Get Pixmap handle */ + pixmap_h = GetPortPixMap(port); + +#ifdef USE_TRANSPARENCY + + /* Transparency effect */ + switch (transparency_mode) + { + /* No transparency effects */ + case TR_NONE: + default: + { + /* Draw the picture */ + CopyBits((BitMap*)frameP->framePix, + (BitMap*)*pixmap_h, + &src_r, &dst_r, srcCopy, NULL); + + break; + } + + /* Overwriting with transparent black pixels */ + case TR_OVER: + { + /* Draw the terrain */ + CopyBits((BitMap*)frameP->framePix, + (BitMap*)*pixmap_h, + &terrain_r, &dst_r, srcCopy, NULL); + + /* Make black pixels transparent */ + RGBBackColor(&black); + + /* Draw mon/obj if there's one */ + if ((row != t_row) || (col != t_col)) + CopyBits((BitMap*)frameP->framePix, + (BitMap*)*pixmap_h, + &src_r, &dst_r, transparent, NULL); + +# ifdef USE_EGO_GRAPHICS + + /* Draw overlay if there's one */ + if (has_overlay) + { + CopyBits((BitMap*)frameP->framePix, + (BitMap*)*pixmap_h, + &ego_r, &dst_r, transparent, NULL); + } + +# endif /* USE_EGO_GRAPHICS */ + + break; + } + } + +#else /* USE_TRANSPARENCY */ + + /* Draw the picture */ + CopyBits((BitMap*)frameP->framePix, + (BitMap*)*pixmap_h, + &src_r, &dst_r, srcCopy, NULL); + +#endif /* USE_TRANSPARENCY */ + + /* Release the lock and dispose the PixMap handle */ + UnlockPortBits(port); + + /* Restore colors */ + RGBBackColor(&black); + RGBForeColor(&white); + + /* Forget color */ + td->last = -1; + + /* Done */ + done = TRUE; + } + + /* Normal */ + if (!done) + { + int xp, yp; + +#ifdef CLIP_HACK + /* Hack - avoid writing outside of dst_r */ + ClipRect(&dst_r); + /* Some characters do not match dst_r, therefore we have to... */ +#endif /* CLIP_HACK */ + + /* Erase */ + EraseRect(&dst_r); + + /* Set the color */ + term_data_color(td, a); + + /* Starting pixel */ + xp = dst_r.left + td->tile_o_x; + yp = dst_r.top + td->tile_o_y; + + /* Move to the correct location */ + MoveTo(xp, yp); + + /* Draw the character */ + DrawChar(c); + +#ifdef CLIP_HACK + /* Clip to the window - inefficient (; ;) XXX XXX */ + ClipRect(&portRect); +#endif /* CLIP_HACK */ + } + + /* Advance */ + dst_r.left += td->tile_wid; +#ifndef USE_DOUBLE_TILES + dst_r.right += td->tile_wid; +#endif /* !USE_DOUBLE_TILES */ + } + + /* Success */ + return (0); +} + + + + + +/* + * Create and initialize window number "i" + */ +static void term_data_link(int i) +{ + term *old = Term; + + term_data *td = &data[i]; + + /* Only once */ + if (td->t) return; + + /* Require mapped */ + if (!td->mapped) return; + + /* Allocate */ + MAKE(td->t, term); + + /* Initialize the term */ + term_init(td->t, td->cols, td->rows, td->keys); + + /* Use a "software" cursor */ + td->t->soft_cursor = TRUE; + + /* Erase with "white space" */ + td->t->attr_blank = TERM_WHITE; + td->t->char_blank = ' '; + + /* Prepare the init/nuke hooks */ + td->t->init_hook = Term_init_mac; + td->t->nuke_hook = Term_nuke_mac; + + /* Prepare the function hooks */ + td->t->user_hook = Term_user_mac; + td->t->xtra_hook = Term_xtra_mac; + td->t->wipe_hook = Term_wipe_mac; + td->t->curs_hook = Term_curs_mac; + td->t->text_hook = Term_text_mac; + td->t->pict_hook = Term_pict_mac; + +#if 0 + + /* Doesn't make big difference? */ + td->t->never_bored = TRUE; + +#endif + + /* Link the local structure */ + td->t->data = (void *)(td); + + /* Activate it */ + Term_activate(td->t); + + /* Global pointer */ + angband_term[i] = td->t; + + /* Activate old */ + Term_activate(old); +} + + + + +#ifdef MACH_O_CARBON + +/* + * (Carbon, Bundle) + * Return a POSIX pathname of the lib directory, or NULL if it can't be + * located. Caller must supply a buffer along with its size in bytes, + * where returned pathname will be stored. + * I prefer use of goto's to several nested if's, if they involve error + * handling. Sorry if you are offended by their presence. Modern + * languages have neater constructs for this kind of jobs -- pelpel + */ +static char *locate_lib(char *buf, size_t size) +{ + CFURLRef main_url = NULL; + CFStringRef main_str = NULL; + char *p; + char *res = NULL; + + /* Obtain the URL of the main bundle */ + main_url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + + /* Oops */ + if (main_url == NULL) goto ret; + + /* Convert it to POSIX pathname */ + main_str = CFURLCopyFileSystemPath(main_url, kCFURLPOSIXPathStyle); + + /* Oops */ + if (main_str == NULL) goto ret; + + /* Convert it again from darn unisomething encoding to ASCII */ + if (CFStringGetCString(main_str, buf, size, kTextEncodingUS_ASCII) == FALSE) + goto ret; + + /* + * Paranoia - bounds check + */ + if (strlen(buf) + 25 + 1 > size) goto ret; + + /* Location of the data */ + strcat(buf, "/Contents/Resources/"); + + /* Set result */ + res = buf; + +ret: + + /* Release objects allocated and implicitly retained by the program */ + if (main_str) CFRelease(main_str); + if (main_url) CFRelease(main_url); + + /* pathname of the lib folder or NULL */ + return (res); +} + + +#else /* MACH_O_CARBON */ + +/* +* Set the "current working directory" (also known as the "default" +* volume/directory) to the location of the current application. +* +* Original code by: Maarten Hazewinkel (mmhazewi@cs.ruu.nl) +* +* Completely rewritten to use Carbon Process Manager. It retrieves the +* volume and direcotry of the current application and simply stores it +* in the (static) global variables app_vol and app_dir, but doesn't +* mess with the "current working directory", because it has long been +* an obsolete (and arcane!) feature. +*/ +static void SetupAppDir(void) +{ + OSErr err; + ProcessSerialNumber curPSN; + ProcessInfoRec procInfo; + FSSpec cwdSpec; + + /* Initialise PSN info for the current process */ + curPSN.highLongOfPSN = 0; + curPSN.lowLongOfPSN = kCurrentProcess; + + /* Fill in mandatory fields */ + procInfo.processInfoLength = sizeof(ProcessInfoRec); + procInfo.processName = nil; + procInfo.processAppSpec = &cwdSpec; + + /* Obtain current process information */ + err = GetProcessInformation(&curPSN, &procInfo); + + /* Oops */ + if (err != noErr) + { + mac_warning("Unable to get process information"); + + /* Quit without writing anything */ + ExitToShell(); + } + + /* Extract and save the Vol and Dir */ + app_vol = cwdSpec.vRefNum; + app_dir = cwdSpec.parID; +} + +#endif /* MACH_O_CARBON */ + + + + +/* + * Using Core Foundation's Preferences services -- pelpel + * + * Requires OS 8.6 or greater with CarbonLib 1.1 or greater. Or OS X, + * of course. + * + * Without this, we can support older versions of OS 8 as well + * (with CarbonLib 1.0.4). + * + * Frequent allocation/deallocation of small chunks of data is + * far from my liking, but since this is only called at the + * beginning and the end of a session, I hope this hardly matters. + */ + + +/* + * Store "value" as the value for preferences item name + * pointed by key + */ +static void save_pref_short(const char *key, short value) +{ + CFStringRef cf_key; + CFNumberRef cf_value; + + /* allocate and initialise the key */ + cf_key = CFStringCreateWithCString(NULL, key, kTextEncodingUS_ASCII); + + /* allocate and initialise the value */ + cf_value = CFNumberCreate(NULL, kCFNumberShortType, &value); + + if ((cf_key != NULL) && (cf_value != NULL)) + { + /* Store the key-value pair in the applications preferences */ + CFPreferencesSetAppValue( + cf_key, + cf_value, + kCFPreferencesCurrentApplication); + } + + /* + * Free CF data - the reverse order is a vain attempt to + * minimise memory fragmentation. + */ + if (cf_value) CFRelease(cf_value); + if (cf_key) CFRelease(cf_key); +} + + +/* + * Load preference value for key, returns TRUE if it succeeds with + * vptr updated appropriately, FALSE otherwise. + */ +static bool query_load_pref_short(const char *key, short *vptr) +{ + CFStringRef cf_key; + CFNumberRef cf_value; + + /* allocate and initialise the key */ + cf_key = CFStringCreateWithCString(NULL, key, kTextEncodingUS_ASCII); + + /* Oops */ + if (cf_key == NULL) return (FALSE); + + /* Retrieve value for the key */ + cf_value = CFPreferencesCopyAppValue( + cf_key, + kCFPreferencesCurrentApplication); + + /* Value not found */ + if (cf_value == NULL) + { + CFRelease(cf_key); + return (FALSE); + } + + /* Convert the value to short */ + CFNumberGetValue( + cf_value, + kCFNumberShortType, + vptr); + + /* Free CF data */ + CFRelease(cf_value); + CFRelease(cf_key); + + /* Success */ + return (TRUE); +} + + +/* + * Update short data pointed by vptr only if preferences + * value for key is located. + */ +static void load_pref_short(const char *key, short *vptr) +{ + short tmp; + + if (query_load_pref_short(key, &tmp)) *vptr = tmp; + return; +} + + +/* + * Save preferences to preferences file for current host+current user+ + * current application. + */ +static void cf_save_prefs() +{ + int i; + + /* Version stamp */ + save_pref_short("version.major", PREF_VER_MAJOR); + save_pref_short("version.minor", PREF_VER_MINOR); + save_pref_short("version.patch", PREF_VER_PATCH); + save_pref_short("version.extra", PREF_VER_EXTRA); + + /* Gfx settings */ + save_pref_short("arg.arg_sound", arg_sound); + save_pref_short("arg.graf_mode", graf_mode); +#ifdef USE_DOUBLE_TILES + save_pref_short("arg.big_tile", use_bigtile); +#endif /* USE_DOUBLE_TILES */ + + /* Windows */ + for (i = 0; i < MAX_TERM_DATA; i++) + { + term_data *td = &data[i]; + + save_pref_short(format("term%d.font_mono", i), td->font_mono); + save_pref_short(format("term%d.font_o_x", i), td->font_o_x); + save_pref_short(format("term%d.font_o_y", i), td->font_o_y); + save_pref_short(format("term%d.font_wid", i), td->font_wid); + save_pref_short(format("term%d.font_hgt", i), td->font_hgt); + save_pref_short(format("term%d.tile_o_x", i), td->tile_o_x); + save_pref_short(format("term%d.tile_o_y", i), td->tile_o_y); + save_pref_short(format("term%d.right", i), td->r.right); + save_pref_short(format("term%d.bottom", i), td->r.bottom); + save_pref_short(format("term%d.ow1", i), td->size_ow1); + save_pref_short(format("term%d.oh1", i), td->size_oh1); + save_pref_short(format("term%d.ow2", i), td->size_ow2); + save_pref_short(format("term%d.oh2", i), td->size_oh2); + + save_pref_short(format("term%d.mapped", i), td->mapped); + + save_pref_short(format("term%d.font_id", i), td->font_id); + save_pref_short(format("term%d.font_size", i), td->font_size); + save_pref_short(format("term%d.font_face", i), td->font_face); + + save_pref_short(format("term%d.tile_wid", i), td->tile_wid); + save_pref_short(format("term%d.tile_hgt", i), td->tile_hgt); + + save_pref_short(format("term%d.cols", i), td->cols); + save_pref_short(format("term%d.rows", i), td->rows); + save_pref_short(format("term%d.left", i), td->r.left); + save_pref_short(format("term%d.top", i), td->r.top); + } + + /* + * Make sure preferences are persistent + */ + CFPreferencesAppSynchronize( + kCFPreferencesCurrentApplication); +} + + +/* + * Load preferences from preferences file for current host+current user+ + * current application. + */ +static void cf_load_prefs() +{ + bool ok; + short pref_major, pref_minor, pref_patch, pref_extra; + int i; + + /* Assume nothing is wrong, yet */ + ok = TRUE; + + /* Load version information */ + ok &= query_load_pref_short("version.major", &pref_major); + ok &= query_load_pref_short("version.minor", &pref_minor); + ok &= query_load_pref_short("version.patch", &pref_patch); + ok &= query_load_pref_short("version.extra", &pref_extra); + + /* Any of the above failed */ + if (!ok) + { + /* This may be the first run */ + mac_warning("Preferences are not found."); + + /* Ignore the rest */ + return; + } + +#if 0 + + /* Check version */ + if ((pref_major != PREF_VER_MAJOR) || + (pref_minor != PREF_VER_MINOR) || + (pref_patch != PREF_VER_PATCH) || + (pref_extra != PREF_VER_EXTRA)) + { + /* Message */ + mac_warning( + format("Ignoring %d.%d.%d.%d preferences.", + pref_major, pref_minor, pref_patch, pref_extra)); + + /* Ignore */ + return; + } + +#endif + + /* Gfx settings */ + { + short pref_tmp; + + /* sound */ + if (query_load_pref_short("arg.arg_sound", &pref_tmp)) + arg_sound = pref_tmp; + + /* graphics */ + if (query_load_pref_short("arg.graf_mode", &pref_tmp)) + graf_mode_req = pref_tmp; + +#ifdef USE_DOUBLE_TILES + + /* double-width tiles */ + if (query_load_pref_short("arg.big_tile", &pref_tmp)) + { + use_bigtile = pref_tmp; + } + +#endif /* USE_DOUBLE_TILES */ + + } + + /* Windows */ + for (i = 0; i < MAX_TERM_DATA; i++) + { + term_data *td = &data[i]; + + load_pref_short(format("term%d.mapped", i), &td->mapped); + + load_pref_short(format("term%d.font_id", i), &td->font_id); + load_pref_short(format("term%d.font_size", i), &td->font_size); + load_pref_short(format("term%d.font_face", i), &td->font_face); + + load_pref_short(format("term%d.tile_wid", i), &td->tile_wid); + load_pref_short(format("term%d.tile_hgt", i), &td->tile_hgt); + + load_pref_short(format("term%d.cols", i), &td->cols); + load_pref_short(format("term%d.rows", i), &td->rows); + load_pref_short(format("term%d.left", i), &td->r.left); + load_pref_short(format("term%d.top", i), &td->r.top); + + load_pref_short(format("term%d.font_mono", i), &td->font_mono); + load_pref_short(format("term%d.font_o_x", i), &td->font_o_x); + load_pref_short(format("term%d.font_o_y", i), &td->font_o_y); + load_pref_short(format("term%d.font_wid", i), &td->font_wid); + load_pref_short(format("term%d.font_hgt", i), &td->font_hgt); + load_pref_short(format("term%d.tile_o_x", i), &td->tile_o_x); + load_pref_short(format("term%d.tile_o_y", i), &td->tile_o_y); + load_pref_short(format("term%d.right", i), &td->r.right); + load_pref_short(format("term%d.bottom", i), &td->r.bottom); + load_pref_short(format("term%d.ow1", i), &td->size_ow1); + load_pref_short(format("term%d.oh1", i), &td->size_oh1); + load_pref_short(format("term%d.ow2", i), &td->size_ow2); + load_pref_short(format("term%d.oh2", i), &td->size_oh2); + } +} + + + + +/* + * Hack -- default data for a window + */ +static void term_data_hack(term_data *td) +{ + short fid; + + /* Default to Monaco font */ + GetFNum("\pmonaco", &fid); + + /* Wipe it */ + WIPE(td, term_data); + + /* No color */ + td->last = -1; + + /* Default borders */ + td->size_ow1 = 2; + td->size_ow2 = 2; + td->size_oh1 = 2; + td->size_oh2 = 2; + + /* Start hidden */ + td->mapped = FALSE; + + /* Default font */ + td->font_id = fid; + + /* Default font size - was 12 */ + td->font_size = 14; + + /* Default font face */ + td->font_face = 0; + + /* Default size */ + td->rows = 24; + td->cols = 80; + + /* Default position */ + td->r.left = 10; + td->r.top = 40; + + /* Minimal keys */ + td->keys = 16; +} + + +/* + * Read the preference file, Create the windows. + * + * We attempt to use "FindFolder()" to track down the preference file. + */ +static void init_windows(void) +{ + int i, b = 0; + + term_data *td; + + + /*** Default values ***/ + + /* Initialize (backwards) */ + for (i = MAX_TERM_DATA; i-- > 0; ) + { + int n; + + cptr s; + + /* Obtain */ + td = &data[i]; + + /* Defaults */ + term_data_hack(td); + + /* Obtain title */ + s = angband_term_name[i]; + + /* Get length */ + n = strlen(s); + + /* Maximal length */ + if (n > 15) n = 15; + + /* Copy the title */ + strncpy((char*)(td->title) + 1, s, n); + + /* Save the length */ + td->title[0] = n; + + /* Tile the windows */ + td->r.left += (b * 30); + td->r.top += (b * 30); + + /* Tile */ + b++; + } + + + /*** Load preferences ***/ + + cf_load_prefs(); + + + /*** Instantiate ***/ + + /* Main window */ + td = &data[0]; + + /* Many keys */ + td->keys = 1024; + + /* Start visible */ + td->mapped = TRUE; + + /* Link (backwards, for stacking order) */ + for (i = MAX_TERM_DATA; i-- > 0; ) + { + term_data_link(i); + } + + /* Main window */ + td = &data[0]; + + /* Main window */ + Term_activate(td->t); +} + + +/* + * Save preferences + */ +static void save_pref_file(void) +{ + cf_save_prefs(); +} + + + + +#ifndef SAVEFILE_SCREEN + +/* + * Prepare savefile dialogue and set the variable + * savefile accordingly. Returns true if it succeeds, false (or + * aborts) otherwise. If all is false, only allow files whose type + * is 'SAVE'. + * Originally written by Peter Ammon + */ +static bool select_savefile(bool all) +{ + OSErr err; + FSSpec theFolderSpec; + FSSpec savedGameSpec; + NavDialogOptions dialogOptions; + NavReplyRecord reply; + /* Used only when 'all' is true */ + NavTypeList types = {ANGBAND_CREATOR, 1, 1, {'SAVE'}}; + NavTypeListHandle myTypeList; + AEDesc defaultLocation; + +#ifdef MACH_O_CARBON + + /* Find the save folder */ + err = path_to_spec(ANGBAND_DIR_SAVE, &theFolderSpec); + +#else + + /* Find :lib:save: folder */ + err = FSMakeFSSpec( + app_vol, + app_dir, + "\p:lib:save:", + &theFolderSpec); + +#endif + + /* Oops */ + if (err != noErr) quit("Unable to find the folder :lib:save:"); + + /* Get default Navigator dialog options */ + err = NavGetDefaultDialogOptions(&dialogOptions); + + /* Clear preview option */ + dialogOptions.dialogOptionFlags &= ~kNavAllowPreviews; + + /* Disable multiple file selection */ + dialogOptions.dialogOptionFlags &= ~kNavAllowMultipleFiles; + + /* Make descriptor for default location */ + err = AECreateDesc( + typeFSS, + &theFolderSpec, + sizeof(FSSpec), + &defaultLocation); + + /* Oops */ + if (err != noErr) quit("Unable to allocate descriptor"); + + /* We are indifferent to signature and file types */ + if (all) + { + myTypeList = (NavTypeListHandle)nil; + } + + /* Set up type handle */ + else + { + err = PtrToHand(&types, (Handle *) & myTypeList, sizeof(NavTypeList)); + + /* Oops */ + if (err != noErr) quit("Error in PtrToHand. Try enlarging heap"); + + } + + /* Call NavGetFile() with the types list */ + err = NavChooseFile( + &defaultLocation, + &reply, + &dialogOptions, + nil, + nil, + nil, + myTypeList, + nil); + + /* Free type list */ + DisposeHandle((Handle)myTypeList); + + /* Invalid response -- allow the user to cancel */ + if (!reply.validRecord) return (FALSE); + + /* Retrieve FSSpec from the reply */ + if (err == noErr) + { + AEKeyword theKeyword; + DescType actualType; + Size actualSize; + + /* Get a pointer to selected file */ + (void)AEGetNthPtr( + &reply.selection, + 1, + typeFSS, + &theKeyword, + &actualType, + &savedGameSpec, + sizeof(FSSpec), + &actualSize); + + /* Dispose NavReplyRecord, resources and descriptors */ + (void)NavDisposeReply(&reply); + } + + /* Dispose location info */ + AEDisposeDesc(&defaultLocation); + +#ifdef MACH_O_CARBON + + /* Convert FSSpec to pathname and store it in variable savefile */ + (void)spec_to_path(&savedGameSpec, savefile, sizeof(savefile)); + +#else + + /* Convert FSSpec to pathname and store it in variable savefile */ + refnum_to_name( + savefile, + savedGameSpec.parID, + savedGameSpec.vRefNum, + (char *)savedGameSpec.name); + +#endif + + /* Success */ + return (TRUE); +} + + +/* + * Handle menu: "File" + "New" + */ +static void do_menu_file_new(void) +{ + /* Hack */ + HiliteMenu(0); + + /* Game is in progress */ + game_in_progress = 1; + + /* Flush input */ + Term_flush(); + + /* Play a game */ + play_game(TRUE); + + /* Hack -- quit */ + quit(NULL); +} + + +/* + * Handle menu: "File" + "Open" / "Import" + */ +static void do_menu_file_open(bool all) +{ + /* Let the player to choose savefile */ + if (!select_savefile(all)) return; + + /* Hack */ + HiliteMenu(0); + + /* Game is in progress */ + game_in_progress = 1; + + /* Flush input */ + flush(); + + /* Play a game */ + play_game(FALSE); + + /* Hack -- quit */ + quit(NULL); +} + +#endif /* !SAVEFILE_SCREEN */ + + +/* + * Handle the "open_when_ready" flag + */ +static void handle_open_when_ready(void) +{ + /* Check the flag XXX XXX XXX make a function for this */ + if (open_when_ready && initialized && !game_in_progress) + { + /* Forget */ + open_when_ready = FALSE; + + /* Game is in progress */ + game_in_progress = 1; + + /* Wait for it */ + pause_line(23); + + /* Flush input */ + flush(); + +#ifdef SAVEFILE_SCREEN + + /* User double-clicked savefile; no savefile screen */ + no_begin_screen = TRUE; + +#endif /* SAVEFILE_SCREEN */ + + /* Play a game */ + play_game(FALSE); + + /* Quit */ + quit(NULL); + } +} + + + + +/* + * Menus + * + * The standard menus are: + * + * Apple (128) = { About, -, ... } + * File (129) = { New,Open,Import,Close,Save,-,Score,Quit } + * (If SAVEFILE_SCREEN is defined, this becomes) + * File (129) = { Close,Save,-,Score,Quit } + * Edit (130) = { Cut, Copy, Paste, Clear } (?) + * Font (131) = { Bold, Extend, -, Monaco, ..., -, ... } + * Size (132) = { ... } + * Window (133) = { Angband, Term-1/Mirror, Term-2/Recall, Term-3/Choice, + * Term-4, Term-5, Term-6, Term-7 } + * Special (134) = { Sound, Graphics, TileWidth, TileHeight, -, + * Fiddle, Wizard } + */ + +/* Apple menu */ +#define MENU_APPLE 128 +#define ITEM_ABOUT 1 + +/* File menu */ +#define MENU_FILE 129 +#ifndef SAVEFILE_SCREEN +# define ITEM_NEW 1 +# define ITEM_OPEN 2 +# define ITEM_IMPORT 3 +# define ITEM_CLOSE 4 +# define ITEM_SAVE 5 +# ifdef HAS_SCORE_MENU +# define ITEM_SCORE 7 +# define ITEM_QUIT 8 +# else +# define ITEM_QUIT 7 +# endif /* HAS_SCORE_MENU */ +#else /* !SAVEFILE_SCREEN - in-game savefile menu */ +# define ITEM_CLOSE 1 +# define ITEM_SAVE 2 +# ifdef HAS_SCORE_MENU +# define ITEM_SCORE 4 +# define ITEM_QUIT 5 +# else +# define ITEM_QUIT 4 +# endif /* HAS_SCORE_MENU */ +#endif /* !SAVEFILE_SCREEN */ + +/* Edit menu */ +#define MENU_EDIT 130 +#define ITEM_UNDO 1 +#define ITEM_CUT 3 +#define ITEM_COPY 4 +#define ITEM_PASTE 5 +#define ITEM_CLEAR 6 + +/* Font menu */ +#define MENU_FONT 131 +#define ITEM_BOLD 1 +#define ITEM_WIDE 2 + +/* Size menu */ +#define MENU_SIZE 132 + +/* Windows menu */ +#define MENU_WINDOWS 133 + +/* Special menu */ +#define MENU_SPECIAL 134 +#define ITEM_SOUND 1 +#define ITEM_GRAPH 2 +# define SUBMENU_GRAPH 144 +# define ITEM_NONE 1 +# define ITEM_8X8 2 +# define ITEM_16X16 3 +# define ITEM_32X32 4 +# define ITEM_BIGTILE 6 +#define ITEM_TILEWIDTH 3 +# define SUBMENU_TILEWIDTH 145 +#define ITEM_TILEHEIGHT 4 +# define SUBMENU_TILEHEIGHT 146 +#define ITEM_FIDDLE 6 +#define ITEM_WIZARD 7 + + +/* + * I HATE UNICODE! We've never wanted it. Some multi-national companies + * made it up as their internationalisation "solution". So I won't use + * any such API's -- pelpel + */ +#define NSIZES 32 +static byte menu_size_values[NSIZES]; +static byte menu_tilewidth_values[NSIZES]; +static byte menu_tileheight_values[NSIZES]; + +/* + * Initialize the menus + * + * Fixed top level menus are now loaded all at once by GetNewMBar(). + * Although this simplifies the function a bit, we have to make sure + * that resources have all the expected entries defined XXX XXX + */ +static void init_menubar(void) +{ + int i, n; + + Rect r; + + WindowPtr tmpw; + + MenuRef m; + +#ifdef USE_NIB + + /* The new way - loading main menu using Interface Builder services */ + { + IBNibRef nib; + OSStatus err; + + /* Create a nib reference to the main nib file */ + err = CreateNibReference(CFSTR("main"), &nib); + + /* Fatal error - missing Main.nib */ + if (err != noErr) quit("Cannot find Main.nib in the bundle!"); + + /* Unarchive the menu bar and make it ready to use */ + err = SetMenuBarFromNib(nib, CFSTR("MainMenu")); + + /* Fatal error - couldn't insert menu bar */ + if (err != noErr) quit("Cannot prepare menu bar!"); + + /* Dispose of the nib reference because we don't need it any longer */ + DisposeNibReference(nib); + } + +#else /* USE_NIB */ + + /* The old way - loading main menu from Resource Manager resource */ + { + Handle mbar; + + /* Load menubar from resources */ + mbar = GetNewMBar(128); + + /* Whoops! */ + if (mbar == nil) quit("Cannot find menubar('MBAR') id 128!"); + + /* Insert them into the current menu list */ + SetMenuBar(mbar); + + /* Free handle */ + DisposeHandle(mbar); + } + +#endif /* USE_NIB */ + + + /* Apple menu (id 128) - we don't have to do anything */ + +#ifndef USE_NIB + + /* File menu (id 129) - Aqua provides Quit menu for us */ + if (is_aqua) + { + /* Get a handle to the file menu */ + m = GetMenuHandle(MENU_FILE); + + /* Nuke the quit menu since Aqua does that for us */ + DeleteMenuItem(m, ITEM_QUIT); + +#ifndef HAS_SCORE_MENU + + /* Hack - because the above leaves a separator as the last item */ + DeleteMenuItem(m, ITEM_QUIT - 1); + +#endif /* !HAS_SCORE_MENU */ + } + +#endif /* !USE_NIB */ + + + /* Edit menu (id 130) - we don't have to do anything */ + + + /* + * Font menu (id 131) - append names of mono-spaced fonts + * followed by all available ones + */ + m = GetMenuHandle(MENU_FONT); + + /* Fake window */ + r.left = r.right = r.top = r.bottom = 0; + + /* Make the fake window so that we can retrieve font info */ + (void)CreateNewWindow( + kDocumentWindowClass, + kWindowNoAttributes, + &r, + &tmpw); + + /* Activate the "fake" window */ + SetPort(GetWindowPort(tmpw)); + + /* Default mode */ + TextMode(0); + + /* Default size */ + TextSize(12); + + /* Add the fonts to the menu */ + AppendResMenu(m, 'FONT'); + + /* Size of menu */ + n = CountMenuItems(m); + + /* Scan the menu */ + for (i = n; i >= 4; i--) + { + Str255 tmpName; + short fontNum; + + /* Acquire the font name */ + GetMenuItemText(m, i, tmpName); + + /* Acquire the font index */ + GetFNum(tmpName, &fontNum); + + /* Apply the font index */ + TextFont(fontNum); + + /* Remove non-mono-spaced fonts */ + if ((CharWidth('i') != CharWidth('W')) || (CharWidth('W') == 0)) + { + /* Delete the menu item */ + DeleteMenuItem(m, i); + } + } + + /* Destroy the fake window */ + DisposeWindow(tmpw); + + /* Add a separator */ + AppendMenu(m, "\p-"); + + /* Add the fonts to the menu */ + AppendResMenu(m, 'FONT'); + + +#ifndef USE_NIB + + /* Size menu (id 132) */ + m = GetMenuHandle(MENU_SIZE); + + /* Add some sizes (stagger choices) */ + for (i = 8, n = 1; i <= 32; i += ((i / 16) + 1), n++) + { + Str15 buf; + + /* Textual size */ + strnfmt((char*)buf + 1, 15, "%d", i); + buf[0] = strlen((char*)buf + 1); + + /* Add the item */ + AppendMenu(m, buf); + + /* Remember its value, for we can't be sure it's in ASCII */ + menu_size_values[n] = i; + } + +#endif /* !USE_NIB */ + + + /* Windows menu (id 133) */ + m = GetMenuHandle(MENU_WINDOWS); + + /* Default choices */ + for (i = 0; i < MAX_TERM_DATA; i++) + { + Str15 buf; + + /* Describe the item */ + strnfmt((char*)buf + 1, 15, "%.15s", angband_term_name[i]); + buf[0] = strlen((char*)buf + 1); + + /* Add the item */ + AppendMenu(m, buf); + + /* Command-Key shortcuts */ + if (i < 8) SetItemCmd(m, i + 1, I2D(i)); + } + + +#ifndef USE_NIB + + /* Special menu (id 134) */ + m = GetMenuHandle(MENU_SPECIAL); + + /* Insert Graphics submenu (id 144) */ + { + MenuHandle submenu; + + /* Get the submenu */ + submenu = GetMenu(SUBMENU_GRAPH); + + /* Insert it */ + SetMenuItemHierarchicalMenu(m, ITEM_GRAPH, submenu); + } + + /* Insert TileWidth submenu (id 145) */ + { + MenuHandle submenu; + + /* Get the submenu */ + submenu = GetMenu(SUBMENU_TILEWIDTH); + + /* Add some sizes */ + for (i = 4, n = 1; i <= 32; i++, n++) + { + Str15 buf; + + /* Textual size */ + strnfmt((char*)buf + 1, 15, "%d", i); + buf[0] = strlen((char*)buf + 1); + + /* Append item */ + AppendMenu(submenu, buf); + + /* Remember its value, for we can't be sure it's in ASCII */ + menu_tilewidth_values[n] = i; + } + + /* Insert it */ + SetMenuItemHierarchicalMenu(m, ITEM_TILEWIDTH, submenu); + } + + /* Insert TileHeight submenu (id 146) */ + { + MenuHandle submenu; + + /* Get the submenu */ + submenu = GetMenu(SUBMENU_TILEHEIGHT); + + + /* Add some sizes */ + for (i = 4, n = 1; i <= 32; i++, n++) + { + Str15 buf; + + /* Textual size */ + strnfmt((char*)buf + 1, 15, "%d", i); + buf[0] = strlen((char*)buf + 1); + + /* Append item */ + AppendMenu(submenu, buf); + + /* Remember its value, for we can't be sure it's in ASCII */ + menu_tileheight_values[n] = i; + } + + /* Insert it */ + SetMenuItemHierarchicalMenu(m, ITEM_TILEHEIGHT, submenu); + } + +#endif /* !USE_NIB */ + + /* Update the menu bar */ + DrawMenuBar(); +} + + +/* + * Prepare the menus + * + * It is very important that the player not be allowed to "save" the game + * unless the "inkey_flag" variable is set, indicating that the game is + * waiting for a new command. XXX XXX XXX + */ + +static void setup_menus(void) +{ + int i, n; + + short value; + + Str255 s; + + MenuHandle m; + + term_data *td = NULL; + + + /* Relevant "term_data" */ + for (i = 0; i < MAX_TERM_DATA; i++) + { + /* Unused */ + if (!data[i].t) continue; + + /* Notice the matching window */ + if (data[i].w == FrontWindow()) td = &data[i]; + } + + + /* File menu */ + m = GetMenuHandle(MENU_FILE); + + /* Get menu size */ + n = CountMenuItems(m); + + /* Reset menu */ + for (i = 1; i <= n; i++) + { + /* Reset */ + DisableMenuItem(m, i); + CheckMenuItem(m, i, FALSE); + } + +#ifndef SAVEFILE_SCREEN + + /* Enable "new"/"open..."/"import..." */ + if (initialized && !game_in_progress) + { + EnableMenuItem(m, ITEM_NEW); + EnableMenuItem(m, ITEM_OPEN); + EnableMenuItem(m, ITEM_IMPORT); + } + +#endif /* !SAVEFILE_SCREEN */ + + /* Enable "close" */ + if (initialized) + { + EnableMenuItem(m, ITEM_CLOSE); + } + + /* Enable "save" */ + if (initialized && character_generated && inkey_flag) + { + EnableMenuItem(m, ITEM_SAVE); + } + +#ifdef HAS_SCORE_MENU + + /* Enable "score" */ + if (initialized && character_generated && !character_icky) + { + EnableMenuItem(m, ITEM_SCORE); + } + +#endif /* HAS_SCORE_MENU */ + + /* Enable "quit" */ + if (!is_aqua) + { + if (!initialized || !character_generated || inkey_flag) + { + EnableMenuItem(m, ITEM_QUIT); + } + } + + + /* Edit menu */ + m = GetMenuHandle(MENU_EDIT); + + /* Get menu size */ + n = CountMenuItems(m); + + /* Reset menu */ + for (i = 1; i <= n; i++) + { + /* Reset */ + DisableMenuItem(m, i); + CheckMenuItem(m, i, FALSE); + } + + /* Enable "edit" options if "needed" */ + if (!td) + { + EnableMenuItem(m, ITEM_UNDO); + EnableMenuItem(m, ITEM_CUT); + EnableMenuItem(m, ITEM_COPY); + EnableMenuItem(m, ITEM_PASTE); + EnableMenuItem(m, ITEM_CLEAR); + } + + + /* Font menu */ + m = GetMenuHandle(MENU_FONT); + + /* Get menu size */ + n = CountMenuItems(m); + + /* Reset menu */ + for (i = 1; i <= n; i++) + { + /* Reset */ + DisableMenuItem(m, i); + CheckMenuItem(m, i, FALSE); + } + + /* Hack -- look cute XXX XXX */ + /* SetItemStyle(m, ITEM_BOLD, bold); */ + + /* Hack -- look cute XXX XXX */ + /* SetItemStyle(m, ITEM_WIDE, extend); */ + + /* Active window */ + if (initialized && td) + { + /* Enable "bold" */ + EnableMenuItem(m, ITEM_BOLD); + + /* Enable "extend" */ + EnableMenuItem(m, ITEM_WIDE); + + /* Check the appropriate "bold-ness" */ + if (td->font_face & bold) CheckMenuItem(m, ITEM_BOLD, TRUE); + + /* Check the appropriate "wide-ness" */ + if (td->font_face & extend) CheckMenuItem(m, ITEM_WIDE, TRUE); + + /* Analyze fonts */ + for (i = 4; i <= n; i++) + { + /* Enable it */ + EnableMenuItem(m, i); + + /* Analyze font */ + GetMenuItemText(m, i, s); + GetFNum(s, &value); + + /* Check active font */ + if (td->font_id == value) CheckMenuItem(m, i, TRUE); + } + } + + + /* Size menu */ + m = GetMenuHandle(MENU_SIZE); + + /* Get menu size */ + n = CountMenuItems(m); + + /* Reset menu */ + for (i = 1; i <= n; i++) + { + /* Reset */ + DisableMenuItem(m, i); + CheckMenuItem(m, i, FALSE); + } + + /* Active window */ + if (initialized && td) + { + /* Analyze sizes */ + for (i = 1; i <= n; i++) + { + /* Analyze size */ + value = menu_size_values[i]; + + /* Enable the "real" sizes */ + if (RealFont(td->font_id, value)) EnableMenuItem(m, i); + + /* Check the current size */ + if (td->font_size == value) CheckMenuItem(m, i, TRUE); + } + } + + + /* Windows menu */ + m = GetMenuHandle(MENU_WINDOWS); + + /* Get menu size */ + n = CountMenuItems(m); + + /* Check active windows */ + for (i = 1; i <= n; i++) + { + /* Check if needed */ + CheckMenuItem(m, i, data[i - 1].mapped); + } + + + /* Special menu */ + m = GetMenuHandle(MENU_SPECIAL); + + /* Get menu size */ + n = CountMenuItems(m); + + /* Reset menu */ + for (i = 1; i <= n; i++) + { + /* Reset */ + DisableMenuItem(m, i); + CheckMenuItem(m, i, FALSE); + } + + /* Item "arg_sound" */ + EnableMenuItem(m, ITEM_SOUND); + CheckMenuItem(m, ITEM_SOUND, arg_sound); + + /* Item "Graphics" */ + EnableMenuItem(m, ITEM_GRAPH); + { + MenuRef submenu; + + /* Graphics submenu */ + (void)GetMenuItemHierarchicalMenu(m, ITEM_GRAPH, &submenu); + + /* Get menu size */ + n = CountMenuItems(submenu); + + /* Reset menu */ + for (i = 1; i <= n; i++) + { + /* Reset */ + DisableMenuItem(submenu, i); + CheckMenuItem(submenu, i, FALSE); + } + + /* Item "None" */ + EnableMenuItem(submenu, ITEM_NONE); + CheckMenuItem(submenu, ITEM_NONE, (graf_mode == GRAF_MODE_NONE)); + + /* Item "8x8" */ + EnableMenuItem(submenu, ITEM_8X8); + CheckMenuItem(submenu, ITEM_8X8, (graf_mode == GRAF_MODE_8X8)); + + /* Item "16x16" */ + EnableMenuItem(submenu, ITEM_16X16); + CheckMenuItem(submenu, ITEM_16X16, (graf_mode == GRAF_MODE_16X16)); + + /* Item "32x32" */ + /*EnableMenuItem(submenu, ITEM_32X32); + CheckMenuItem(submenu, ITEM_32X32, (graf_mode == GRAF_MODE_32X32));*/ + +#ifdef USE_DOUBLE_TILES + + /* Item "Big tiles" */ + if (inkey_flag) EnableMenuItem(submenu, ITEM_BIGTILE); + CheckMenuItem(submenu, ITEM_BIGTILE, use_bigtile); + +#endif /* USE_DOUBLE_TILES */ + + } + + /* Item "TileWidth" */ + EnableMenuItem(m, ITEM_TILEWIDTH); + { + MenuRef submenu; + + /* TileWidth submenu */ + (void)GetMenuItemHierarchicalMenu(m, ITEM_TILEWIDTH, &submenu); + + /* Get menu size */ + n = CountMenuItems(submenu); + + /* Reset menu */ + for (i = 1; i <= n; i++) + { + /* Reset */ + DisableMenuItem(submenu, i); + CheckMenuItem(submenu, i, FALSE); + } + + /* Active window */ + if (initialized && td) + { + /* Analyze sizes */ + for (i = 1; i <= n; i++) + { + /* Analyze size */ + value = menu_tilewidth_values[i]; + + /* Enable */ + if (value >= td->font_wid) EnableMenuItem(submenu, i); + + /* Check the current size */ + if (td->tile_wid == value) CheckMenuItem(submenu, i, TRUE); + } + } + } + + /* Item "TileHeight" */ + EnableMenuItem(m, ITEM_TILEHEIGHT); + { + MenuRef submenu; + + /* TileWidth submenu */ + (void)GetMenuItemHierarchicalMenu(m, ITEM_TILEHEIGHT, &submenu); + + /* Get menu size */ + n = CountMenuItems(submenu); + + /* Reset menu */ + for (i = 1; i <= n; i++) + { + /* Reset */ + DisableMenuItem(submenu, i); + CheckMenuItem(submenu, i, FALSE); + } + + /* Active window */ + if (initialized && td) + { + /* Analyze sizes */ + for (i = 1; i <= n; i++) + { + /* Analyze size */ + value = menu_tileheight_values[i]; + + /* Enable */ + if (value >= td->font_hgt) EnableMenuItem(submenu, i); + + /* Check the current size */ + if (td->tile_hgt == value) CheckMenuItem(submenu, i, TRUE); + } + } + } + + /* Item "arg_fiddle" */ + EnableMenuItem(m, ITEM_FIDDLE); + CheckMenuItem(m, ITEM_FIDDLE, arg_fiddle); + + /* Item "arg_wizard" */ + EnableMenuItem(m, ITEM_WIZARD); + CheckMenuItem(m, ITEM_WIZARD, arg_wizard); + + + /* TileHeight menu */ + m = GetMenuHandle(SUBMENU_TILEHEIGHT); + + /* Get menu size */ + n = CountMenuItems(m); + + /* Reset menu */ + for (i = 1; i <= n; i++) + { + /* Reset */ + DisableMenuItem(m, i); + CheckMenuItem(m, i, FALSE); + } +} + + +/* + * Process a menu selection (see above) + * + * Hack -- assume that invalid menu selections are disabled above, + * which I have been informed may not be reliable. XXX XXX XXX + */ +static void menu(long mc) +{ + int i; + + int menuid, selection; + + static unsigned char s[1000]; + + short fid; + + term_data *td = NULL; + + WindowPtr old_win; + + + /* Analyze the menu command */ + menuid = HiWord(mc); + selection = LoWord(mc); + + + /* Find the window */ + for (i = 0; i < MAX_TERM_DATA; i++) + { + /* Skip dead windows */ + if (!data[i].t) continue; + + /* Notice matches */ + if (data[i].w == FrontWindow()) td = &data[i]; + } + + + /* Branch on the menu */ + switch (menuid) + { + /* Apple Menu */ + case MENU_APPLE: + { + /* About Angband... */ + if (selection == ITEM_ABOUT) + { + DialogPtr dialog; + short item_hit; + + /* Get the about dialogue */ + dialog = GetNewDialog(128, 0, (WindowPtr) - 1); + + /* Move it to the middle of the screen */ + RepositionWindow( + GetDialogWindow(dialog), + NULL, + kWindowCenterOnMainScreen); + + /* Show the dialog */ + TransitionWindow(GetDialogWindow(dialog), + kWindowZoomTransitionEffect, + kWindowShowTransitionAction, + NULL); + + /* Wait for user to click on it */ + ModalDialog(0, &item_hit); + + /* Free the dialogue */ + DisposeDialog(dialog); + break; + } + + break; + } + + /* File Menu */ + case MENU_FILE: + { + switch (selection) + { +#ifndef SAVEFILE_SCREEN + + /* New */ + case ITEM_NEW: + { + do_menu_file_new(); + break; + } + + /* Open... */ + case ITEM_OPEN: + { + do_menu_file_open(FALSE); + break; + } + + /* Import... */ + case ITEM_IMPORT: + { + do_menu_file_open(TRUE); + break; + } + +#endif /* !SAVEFILE_SCREEN */ + + /* Close */ + case ITEM_CLOSE: + { + /* No window */ + if (!td) break; + + /* Not Mapped */ + td->mapped = FALSE; + + /* Not Mapped */ + td->t->mapped_flag = FALSE; + + /* Hide the window */ + TransitionWindow(td->w, + kWindowZoomTransitionEffect, + kWindowHideTransitionAction, + NULL); + + break; + } + + /* Save */ + case ITEM_SAVE: + { + /* Hack -- Forget messages */ + msg_flag = FALSE; + + /* Hack -- Save the game */ +#ifndef ZANG_AUTO_SAVE + do_cmd_save_game(); +#else + do_cmd_save_game(FALSE); +#endif /* !ZANG_AUTO_SAVE */ + + break; + } + +#ifdef HAS_SCORE_MENU + + /* Show score */ + case ITEM_SCORE: + { + char buf[1024]; + + /* Paranoia */ + if (!initialized || character_icky || + !game_in_progress || !character_generated) + { + /* Can't happen but just in case */ + plog("You may not do that right now."); + + break; + } + + /* Build the pathname of the score file */ + path_build(buf, sizeof(buf), ANGBAND_DIR_APEX, + "scores.raw"); + + /* Hack - open the score file for reading */ + highscore_fd = fd_open(buf, O_RDONLY); + + /* Paranoia - No score file */ + if (highscore_fd < 0) + { + msg_print("Score file is not available."); + + break; + } + + /* Mega-Hack - prevent various functions XXX XXX XXX */ + initialized = FALSE; + + /* Save screen */ + screen_save(); + + /* Clear screen */ + Term_clear(); + + /* Prepare scores */ + if (game_in_progress && character_generated) + { + predict_score(); + } + +#if 0 /* I don't like this - pelpel */ + + /* Mega-Hack - No current player XXX XXX XXX XXX */ + else + { + display_scores_aux(0, MAX_HISCORES, -1, NULL); + } + +#endif + + /* Close the high score file */ + (void)fd_close(highscore_fd); + + /* Forget the fd */ + highscore_fd = -1; + + /* Restore screen */ + screen_load(); + + /* Hack - Flush it */ + Term_fresh(); + + /* Mega-Hack - We are ready again */ + initialized = TRUE; + + /* Done */ + break; + } + +#endif /* HAS_SCORE_MENU */ + + /* Quit (with save) */ + case ITEM_QUIT: + { + /* Save the game (if necessary) */ + if (game_in_progress && character_generated) + { + /* Hack -- Forget messages */ + msg_flag = FALSE; + + /* Save the game */ +#ifndef ZANG_AUTO_SAVE + do_cmd_save_game(); +#else + do_cmd_save_game(FALSE); +#endif /* !ZANG_AUTO_SAVE */ + } + + /* Quit */ + quit(NULL); + break; + } + } + break; + } + + /* Edit menu */ + case MENU_EDIT: + { + /* Unused */ + break; + } + + /* Font menu */ + case MENU_FONT: + { + /* Require a window */ + if (!td) break; + + /* Memorize old */ + old_win = active; + + /* Activate */ + activate(td->w); + + /* Toggle the "bold" setting */ + if (selection == ITEM_BOLD) + { + /* Toggle the setting */ + if (td->font_face & bold) + { + td->font_face &= ~bold; + } + else + { + td->font_face |= bold; + } + + /* Hack - clear tile size info XXX XXX */ + td->tile_wid = td->tile_hgt = 0; + + /* Apply and Verify */ + term_data_check_font(td); + term_data_check_size(td); + + /* Resize and Redraw */ + term_data_resize(td); + term_data_redraw(td); + + break; + } + + /* Toggle the "wide" setting */ + if (selection == ITEM_WIDE) + { + /* Toggle the setting */ + if (td->font_face & extend) + { + td->font_face &= ~extend; + } + else + { + td->font_face |= extend; + } + + /* Hack - clear tile size info XXX XXX */ + td->tile_wid = td->tile_hgt = 0; + + /* Apply and Verify */ + term_data_check_font(td); + term_data_check_size(td); + + /* Resize and Redraw */ + term_data_resize(td); + term_data_redraw(td); + + break; + } + + /* Get a new font name */ + GetMenuItemText(GetMenuHandle(MENU_FONT), selection, s); + GetFNum(s, &fid); + + /* Save the new font id */ + td->font_id = fid; + + /* Current size is bad for new font */ + if (!RealFont(td->font_id, td->font_size)) + { + /* Find similar size */ + for (i = 1; i <= 32; i++) + { + /* Adjust smaller */ + if (td->font_size - i >= 8) + { + if (RealFont(td->font_id, td->font_size - i)) + { + td->font_size -= i; + break; + } + } + + /* Adjust larger */ + if (td->font_size + i <= 128) + { + if (RealFont(td->font_id, td->font_size + i)) + { + td->font_size += i; + break; + } + } + } + } + + /* Hack - clear tile size info XXX XXX */ + td->tile_wid = td->tile_hgt = 0; + + /* Apply and Verify */ + term_data_check_font(td); + term_data_check_size(td); + + /* Resize and Redraw */ + term_data_resize(td); + term_data_redraw(td); + + /* Restore the window */ + activate(old_win); + + break; + } + + /* Size menu */ + case MENU_SIZE: + { + if (!td) break; + + /* Save old */ + old_win = active; + + /* Activate */ + activate(td->w); + + td->font_size = menu_size_values[selection]; + + /* Hack - clear tile size info XXX XXX */ + td->tile_wid = td->tile_hgt = 0; + + /* Apply and Verify */ + term_data_check_font(td); + term_data_check_size(td); + + /* Resize and Redraw */ + term_data_resize(td); + term_data_redraw(td); + + /* Restore */ + activate(old_win); + + break; + } + + /* Window menu */ + case MENU_WINDOWS: + { + /* Parse */ + i = selection - 1; + + /* Check legality of choice */ + if ((i < 0) || (i >= MAX_TERM_DATA)) break; + + /* Obtain the window */ + td = &data[i]; + + /* Mapped */ + td->mapped = TRUE; + + /* Link */ + term_data_link(i); + + /* Mapped (?) */ + td->t->mapped_flag = TRUE; + + /* Show the window */ + TransitionWindow(td->w, + kWindowZoomTransitionEffect, + kWindowShowTransitionAction, + NULL); + + /* Bring to the front */ + SelectWindow(td->w); + + break; + } + + /* Special menu */ + case MENU_SPECIAL: + { + switch (selection) + { + case ITEM_SOUND: + { + /* Toggle arg_sound */ + arg_sound = !arg_sound; + + /* React to changes */ + Term_xtra(TERM_XTRA_REACT, 0); + + break; + } + + case ITEM_FIDDLE: + { + arg_fiddle = !arg_fiddle; + + break; + } + + case ITEM_WIZARD: + { + arg_wizard = !arg_wizard; + + break; + } + } + + break; + } + + /* Graphics submenu */ + case SUBMENU_GRAPH: + { + switch (selection) + { + case ITEM_NONE: + { + graf_mode_req = GRAF_MODE_NONE; + + break; + } + + case ITEM_8X8: + { + graf_mode_req = GRAF_MODE_8X8; + + break; + } + + case ITEM_16X16: + { + graf_mode_req = GRAF_MODE_16X16; + + break; + } + + case ITEM_32X32: + { + graf_mode_req = GRAF_MODE_32X32; + + break; + } + +#ifdef USE_DOUBLE_TILES + + case ITEM_BIGTILE: + { + term *old = Term; + term_data *td = &data[0]; + + /* Toggle "use_bigtile" */ + use_bigtile = !use_bigtile; + arg_bigtile = use_bigtile; + + /* Activate */ + Term_activate(td->t); + + /* Resize the term */ + Term_resize(td->cols, td->rows); + + /* Activate old */ + Term_activate(old); + + break; + } + +#endif /* USE_DOUBLE_TILES */ + + } + + /* Hack -- Force redraw */ + Term_key_push(KTRL('R')); + + break; + } + + /* TileWidth menu */ + case SUBMENU_TILEWIDTH: + { + if (!td) break; + + /* Save old */ + old_win = active; + + /* Activate */ + activate(td->w); + + /* Analyse value */ + td->tile_wid = menu_tilewidth_values[selection]; + + /* Apply and Verify */ + term_data_check_size(td); + + /* Resize and Redraw */ + term_data_resize(td); + term_data_redraw(td); + + /* Restore */ + activate(old_win); + + break; + } + + /* TileHeight menu */ + case SUBMENU_TILEHEIGHT: + { + if (!td) break; + + /* Save old */ + old_win = active; + + /* Activate */ + activate(td->w); + + /* Analyse value */ + td->tile_hgt = menu_tileheight_values[selection]; + + /* Apply and Verify */ + term_data_check_size(td); + + /* Resize and Redraw */ + term_data_resize(td); + term_data_redraw(td); + + /* Restore */ + activate(old_win); + + break; + } + } + + + /* Clean the menu */ + HiliteMenu(0); +} + + +/* + * Check for extra required parameters -- From "Maarten Hazewinkel" + */ +static OSErr CheckRequiredAEParams(const AppleEvent *theAppleEvent) +{ + OSErr aeError; + DescType returnedType; + Size actualSize; + + aeError = AEGetAttributePtr( + theAppleEvent, keyMissedKeywordAttr, typeWildCard, + &returnedType, NULL, 0, &actualSize); + + if (aeError == errAEDescNotFound) return (noErr); + + if (aeError == noErr) return (errAEParamMissed); + + return (aeError); +} + + +/* + * Apple Event Handler -- Open Application + */ +static OSErr AEH_Start(const AppleEvent *theAppleEvent, AppleEvent *reply, + SInt32 handlerRefCon) +{ + return (CheckRequiredAEParams(theAppleEvent)); +} + + +/* + * Apple Event Handler -- Quit Application + */ +static OSErr AEH_Quit(const AppleEvent *theAppleEvent, AppleEvent *reply, + SInt32 handlerRefCon) +{ + /* Quit later */ + quit_when_ready = TRUE; + + /* Check arguments */ + return (CheckRequiredAEParams(theAppleEvent)); +} + + +/* + * Apple Event Handler -- Print Documents + */ +static OSErr AEH_Print(const AppleEvent *theAppleEvent, AppleEvent *reply, + SInt32 handlerRefCon) +{ + return (errAEEventNotHandled); +} + + +/* + * Apple Event Handler by Steve Linberg (slinberg@crocker.com). + * + * The old method of opening savefiles from the finder does not work + * on the Power Macintosh, because CountAppFiles and GetAppFiles, + * used to return information about the selected document files when + * an application is launched, are part of the Segment Loader, which + * is not present in the RISC OS due to the new memory architecture. + * + * The "correct" way to do this is with AppleEvents. The following + * code is modeled on the "Getting Files Selected from the Finder" + * snippet from Think Reference 2.0. (The prior sentence could read + * "shamelessly swiped & hacked") + */ +static OSErr AEH_Open(const AppleEvent *theAppleEvent, AppleEvent* reply, + SInt32 handlerRefCon) +{ + FSSpec myFSS; + AEDescList docList; + OSErr err; + Size actualSize; + AEKeyword keywd; + DescType returnedType; + char msg[128]; + FInfo myFileInfo; + + /* Put the direct parameter (a descriptor list) into a docList */ + err = AEGetParamDesc( + theAppleEvent, keyDirectObject, typeAEList, &docList); + if (err) return err; + + /* + * We ignore the validity check, because we trust the FInder, and we only + * allow one savefile to be opened, so we ignore the depth of the list. + */ + err = AEGetNthPtr( + &docList, 1L, typeFSS, &keywd, &returnedType, + (Ptr) & myFSS, sizeof(myFSS), &actualSize); + if (err) return err; + + /* Only needed to check savefile type below */ + err = FSpGetFInfo(&myFSS, &myFileInfo); + if (err) + { + strnfmt(msg, 128, "Argh! FSpGetFInfo failed with code %d", err); + mac_warning(msg); + return err; + } + + /* Ignore non 'SAVE' files */ + if (myFileInfo.fdType != 'SAVE') return noErr; + +#ifdef MACH_O_CARBON + + /* Extract a file name */ + (void)spec_to_path(&myFSS, savefile, sizeof(savefile)); + +#else + + /* XXX XXX XXX Extract a file name */ + PathNameFromDirID(myFSS.parID, myFSS.vRefNum, (StringPtr)savefile); + pstrcat((StringPtr)savefile, (StringPtr)&myFSS.name); + + /* Convert the string */ + ptocstr((StringPtr)savefile); + +#endif /* MACH_O_CARBON */ + + /* Delay actual open */ + open_when_ready = TRUE; + + /* Dispose */ + err = AEDisposeDesc(&docList); + + /* Success */ + return noErr; +} + + +/* + * Handle quit_when_ready, by Peter Ammon, + * slightly modified to check inkey_flag. + */ +static void quit_calmly(void) +{ + /* Quit immediately if game's not started */ + if (!game_in_progress || !character_generated) quit(NULL); + + /* Save the game and Quit (if it's safe) */ + if (inkey_flag) + { + /* Hack -- Forget messages */ + msg_flag = FALSE; + + /* Save the game */ +#ifndef ZANG_AUTO_SAVE + do_cmd_save_game(); +#else + do_cmd_save_game(FALSE); +#endif /* !ZANG_AUTO_SAVE */ + + /* Quit */ + quit(NULL); + } + + /* Wait until inkey_flag is set */ +} + + +/* + * Macintosh modifiers (event.modifier & ccc): + * cmdKey, optionKey, shiftKey, alphaLock, controlKey + * + * + * Macintosh Keycodes (0-63 normal, 64-95 keypad, 96-127 extra): + * + * Return:36 + * Delete:51 + * + * Period:65 + * Star:67 + * Plus:69 + * Clear:71 + * Slash:75 + * Enter:76 + * Minus:78 + * Equal:81 + * 0-7:82-89 + * 8-9:91-92 + * + * backslash/vertical bar (Japanese keyboard):93 + * + * F5: 96 + * F6: 97 + * F7: 98 + * F3:99 + * F8:100 + * F10:101 + * F11:103 + * F13:105 + * F14:107 + * F9:109 + * F12:111 + * F15:113 + * Help:114 + * Home:115 + * PgUp:116 + * Del:117 + * F4: 118 + * End:119 + * F2:120 + * PgDn:121 + * F1:122 + * Lt:123 + * Rt:124 + * Dn:125 + * Up:126 + */ + + +/* + * Check for Events, return TRUE if we process any + * + * Now it really waits for events if wait set to true, to prevent + * undesirable monopoly of CPU. The side-effect is that you cannot do + * while (CheckEvents(TRUE)); without discretion. + */ +static bool CheckEvents(bool wait) +{ + EventRecord event; + + WindowPtr w; + + Rect r; + + UInt32 sleep_ticks; + + int ch, ck; + + int mc, ms, mo, mx; + + int i; + + term_data *td = NULL; + + + /* + * With the wait mode blocking for available event / timeout, + * the non-wait mode should actually call WaitNextEvent, + * because of those event draining loops. Or we had to + * implement yet another mode. + */ + + /* Handles the quit_when_ready flag */ + if (quit_when_ready) quit_calmly(); + + /* Blocking call to WaitNextEvent - should use MAX_INT XXX XXX */ + if (wait) sleep_ticks = 0x7FFFFFFFL; + + /* Non-blocking */ + else sleep_ticks = 0L; + + /* Get an event (or null) */ + WaitNextEvent(everyEvent, &event, sleep_ticks, nil); + + /* Hack -- Nothing is ready yet */ + if (event.what == nullEvent) return (FALSE); + + + /* Analyze the event */ + switch (event.what) + { + +#if 0 + + case activateEvt: + { + w = (WindowPtr)event.message; + + activate(w); + + break; + } + +#endif + + case updateEvt: + { + /* Extract the window */ + w = (WindowPtr)event.message; + + /* Find the window */ + for (i = 0; i < MAX_TERM_DATA; i++) + { + /* Skip dead windows */ + if (!data[i].t) continue; + + /* Notice matches */ + if (data[i].w == w) td = &data[i]; + } + + /* Hack XXX XXX XXX */ + BeginUpdate(w); + EndUpdate(w); + + /* Redraw the window */ + if (td) term_data_redraw(td); + + break; + } + + case keyDown: + case autoKey: + { + /* Extract some modifiers */ + mc = (event.modifiers & controlKey) ? TRUE : FALSE; + ms = (event.modifiers & shiftKey) ? TRUE : FALSE; + mo = (event.modifiers & optionKey) ? TRUE : FALSE; + mx = (event.modifiers & cmdKey) ? TRUE : FALSE; + + /* Keypress: (only "valid" if ck < 96) */ + ch = (event.message & charCodeMask) & 255; + + /* Keycode: see table above */ + ck = ((event.message & keyCodeMask) >> 8) & 255; + + /* Command + "normal key" -> menu action */ + if (mx && (ck < 64)) + { +#ifdef MENU_SHORTCUTS + /* Hack -- Prepare the menus */ + setup_menus(); + + /* Run the Menu-Handler */ + menu(MenuKey(ch)); + + /* Turn off the menus */ + HiliteMenu(0); + + /* Done */ + break; +#else + /* Begin special trigger */ + Term_keypress(31); + + /* Send some modifier keys */ + if (mc) Term_keypress('C'); + if (ms) Term_keypress('S'); + if (mo) Term_keypress('O'); + if (mx) Term_keypress('X'); + + /* Enqueue the keypress */ + Term_keypress(ch); + + /* Terminate the trigger */ + Term_keypress(13); +#endif + } + + /* Hide the mouse pointer */ + ObscureCursor(); + + /* Normal key -> simple keypress */ + if ((ck < 64) || (ck == 93)) + { + /* Enqueue the keypress */ + Term_keypress(ch); + } + + /* Keypad keys -> trigger plus simple keypress */ + else if (!mc && !ms && !mo && !mx && (ck < 96)) + { + /* Hack -- "enter" is confused */ + if (ck == 76) ch = '\n'; + + /* Begin special trigger */ + Term_keypress(31); + + /* Send the "keypad" modifier */ + Term_keypress('K'); + + /* Send the "ascii" keypress */ + Term_keypress(ch); + + /* Terminate the trigger */ + Term_keypress(13); + } + + /* Bizarre key -> encoded keypress */ + else if (ck <= 127) + { + /* Begin special trigger */ + Term_keypress(31); + + /* Send some modifier keys */ + if (mc) Term_keypress('C'); + if (ms) Term_keypress('S'); + if (mo) Term_keypress('O'); + if (mx) Term_keypress('X'); + + /* Downshift and encode the keycode */ + Term_keypress(I2D((ck - 64) / 10)); + Term_keypress(I2D((ck - 64) % 10)); + + /* Terminate the trigger */ + Term_keypress(13); + } + + break; + } + + case mouseDown: + { + int code; + + /* Analyze click location */ + code = FindWindow(event.where, &w); + + /* Find the window */ + for (i = 0; i < MAX_TERM_DATA; i++) + { + /* Skip dead windows */ + if (!data[i].t) continue; + + /* Notice matches */ + if (data[i].w == w) td = &data[i]; + } + + /* Analyze */ + switch (code) + { + case inMenuBar: + { + setup_menus(); + menu(MenuSelect(event.where)); + HiliteMenu(0); + break; + } + + case inDrag: + { + WindowPtr old_win; + BitMap tBitMap; + Rect pRect; + + r = GetQDGlobalsScreenBits(&tBitMap)->bounds; + r.top += 20; /* GetMBarHeight() XXX XXX XXX */ + InsetRect(&r, 4, 4); + DragWindow(w, event.where, &r); + + /* Oops */ + if (!td) break; + + /* Save */ + old_win = active; + + /* Activate */ + activate(td->w); + + /* Analyze */ + GetWindowBounds( + (WindowRef)td->w, + kWindowContentRgn, + &pRect); + td->r.left = pRect.left; + td->r.top = pRect.top; + + /* Apply and Verify */ + term_data_check_size(td); + + /* Restore */ + activate(old_win); + + break; + } + + case inGoAway: + { + /* Oops */ + if (!td) break; + + /* Track the go-away box */ + if (TrackGoAway(w, event.where)) + { + /* Not Mapped */ + td->mapped = FALSE; + + /* Not Mapped */ + td->t->mapped_flag = FALSE; + + /* Hide the window */ + TransitionWindow(td->w, + kWindowZoomTransitionEffect, + kWindowHideTransitionAction, + NULL); + } + + break; + } + + case inGrow: + { + int x, y; + + Rect nr; + + term *old = Term; + + /* Oops */ + if (!td) break; + +#ifndef ALLOW_BIG_SCREEN + + /* Minimum and maximum sizes */ + r.left = 20 * td->tile_wid + td->size_ow1; + r.right = 80 * td->tile_wid + td->size_ow1 + td->size_ow2 + 1; + r.top = 1 * td->tile_hgt + td->size_oh1; + r.bottom = 24 * td->tile_hgt + td->size_oh1 + td->size_oh2 + 1; + + /* Grow the rectangle */ + if (!ResizeWindow(w, event.where, &r, NULL)) break; +#else + + /* Grow the rectangle */ + if (!ResizeWindow(w, event.where, NULL, NULL)) break; + +#endif /* !ALLOW_BIG_SCREEN */ + + + /* Obtain geometry of resized window */ + GetWindowBounds(w, kWindowContentRgn, &nr); + + /* Extract the new size in pixels */ + y = nr.bottom - nr.top - td->size_oh1 - td->size_oh2; + x = nr.right - nr.left - td->size_ow1 - td->size_ow2; + + /* Extract a "close" approximation */ + td->rows = y / td->tile_hgt; + td->cols = x / td->tile_wid; + + /* Apply and Verify */ + term_data_check_size(td); + + /* Activate */ + Term_activate(td->t); + + /* Hack -- Resize the term */ + Term_resize(td->cols, td->rows); + + /* Resize and Redraw */ + term_data_resize(td); + term_data_redraw(td); + + /* Restore */ + Term_activate(old); + + break; + } + + case inContent: + { + SelectWindow(w); + + break; + } + } + + break; + } + + /* OS Event -- From "Maarten Hazewinkel" */ + case osEvt: + { + switch ((event.message >> 24) & 0x000000FF) + { + case suspendResumeMessage: + + /* Resuming: activate the front window */ + if (event.message & resumeFlag) + { + Cursor tempCursor; + SetPort(GetWindowPort(FrontWindow())); + SetCursor(GetQDGlobalsArrow(&tempCursor)); + } + + /* Suspend: deactivate the front window */ + else + { + /* Nothing */ + } + + break; + } + + break; + } + + /* From "Steve Linberg" and "Maarten Hazewinkel" */ + case kHighLevelEvent: + { + /* Process apple events */ + (void)AEProcessAppleEvent(&event); + + /* Handle "quit_when_ready" */ + if (quit_when_ready) + { +#if 0 /* Doesn't work with Aqua well */ + /* Forget */ + quit_when_ready = FALSE; + + /* Do the menu key */ + menu(MenuKey('q')); +#endif + /* Turn off the menus */ + HiliteMenu(0); + } + + /* Handle "open_when_ready" */ + else if (open_when_ready) + { + handle_open_when_ready(); + } + + break; + } + + } + + + /* Something happened */ + return (TRUE); +} + + +/*** Some Hooks for various routines ***/ + + +/* + * Mega-Hack -- emergency lifeboat + */ +static void *lifeboat = NULL; + + +/* + * Hook to "release" memory + */ +#ifdef NEW_ZVIRT_HOOKS /* [V] removed the unused 'size' argument. */ +static void *hook_rnfree(void *v) +#else +static void *hook_rnfree(void *v, size_t size) +#endif /* NEW_ZVIRT_HOOKS */ +{ + +#ifdef USE_MALLOC + + /* Alternative method */ + free(v); + +#else + + /* Dispose */ + DisposePtr(v); + +#endif + + /* Success */ + return (NULL); +} + +/* + * Hook to "allocate" memory + */ +static void *hook_ralloc(size_t size) +{ + +#ifdef USE_MALLOC + + /* Make a new pointer */ + return (malloc(size)); + +#else + + /* Make a new pointer */ + return (NewPtr(size)); + +#endif + +} + +/* + * Hook to handle "out of memory" errors + */ +static void *hook_rpanic(size_t size) +{ + /* void *mem = NULL; */ + + /* Free the lifeboat */ + if (lifeboat) + { + /* Free the lifeboat */ + DisposePtr(lifeboat); + + /* Forget the lifeboat */ + lifeboat = NULL; + + /* Mega-Hack -- Warning */ + mac_warning("Running out of Memory!\rAbort this process now!"); + + /* Mega-Hack -- Never leave this function */ + while (TRUE) CheckEvents(TRUE); + } + + /* Mega-Hack -- Crash */ + return (NULL); +} + + +/* + * Hook to tell the user something important + */ +static void hook_plog(cptr str) +{ + /* Warning message */ + mac_warning(str); +} + + +/* + * Hook to tell the user something, and then quit + */ +static void hook_quit(cptr str) +{ + /* Warning if needed */ + if (str) mac_warning(str); + +#ifdef USE_ASYNC_SOUND + + /* Clean up sound support */ + cleanup_sound(); + +#endif /* USE_ASYNC_SOUND */ + + /* Dispose of graphic tiles */ + if (frameP) + { + /* Unlock */ + BenSWUnlockFrame(frameP); + + /* Dispose of the GWorld */ + DisposeGWorld(frameP->framePort); + + /* Dispose of the memory */ + DisposePtr((Ptr)frameP); + } + + /* Write a preference file */ + save_pref_file(); + + /* All done */ + ExitToShell(); +} + + +/* + * Hook to tell the user something, and then crash + */ +static void hook_core(cptr str) +{ + /* XXX Use the debugger */ + /* DebugStr(str); */ + + /* Warning */ + if (str) mac_warning(str); + + /* Warn, then save player */ + mac_warning("Fatal error.\rI will now attempt to save and quit."); + + /* Attempt to save */ + if (!save_player()) mac_warning("Warning -- save failed!"); + + /* Quit */ + quit(NULL); +} + + + +/*** Main program ***/ + + +/* + * Init some stuff + * + * XXX XXX XXX Hack -- This function attempts to "fix" the nasty + * "Macintosh Save Bug" by using "absolute" path names, since on + * System 7 machines anyway, the "current working directory" often + * "changes" due to background processes, invalidating any "relative" + * path names. Note that the Macintosh is limited to 255 character + * path names, so be careful about deeply embedded directories... + * + * XXX XXX XXX Hack -- This function attempts to "fix" the nasty + * "missing lib folder bug" by allowing the user to help find the + * "lib" folder by hand if the "application folder" code fails... + * + * + * The problem description above no longer applies, but I left it here, + * modified for Carbon, to allow the game proceeds when a user doesn't + * placed the Angband binary and the lib folder in the same place for + * whatever reasons. -- pelpel + */ +static void init_stuff(void) +{ + Rect r; + BitMap tBitMap; + Rect screenRect; + Point topleft; + + char path[1024]; + + OSErr err = noErr; + NavDialogOptions dialogOptions; + FSSpec theFolderSpec; + NavReplyRecord theReply; + + + /* Fake rectangle */ + r.left = 0; + r.top = 0; + r.right = 344; + r.bottom = 188; + + /* Center it */ + screenRect = GetQDGlobalsScreenBits(&tBitMap)->bounds; + center_rect(&r, &screenRect); + + /* Extract corner */ + topleft.v = r.top; + topleft.h = r.left; + + /* Default to the "lib" folder with the application */ +#ifdef MACH_O_CARBON + if (locate_lib(path, sizeof(path)) == NULL) quit(NULL); +#else + refnum_to_name(path, app_dir, app_vol, (char*)("\plib:")); +#endif + + + /* Check until done */ + while (1) + { + /* Prepare the paths */ + init_file_paths(path); + + /* Build the filename */ + path_build(path, 1024, ANGBAND_DIR_FILE, "news.txt"); + + /* Attempt to open and close that file */ + if (0 == fd_close(fd_open(path, O_RDONLY))) break; + + /* Warning */ + plog_fmt("Unable to open the '%s' file.", path); + + /* Warning */ + plog("The Angband 'lib' folder is probably missing or misplaced."); + + /* Ask the user to choose the lib folder */ + err = NavGetDefaultDialogOptions(&dialogOptions); + + /* Paranoia */ + if (err != noErr) quit(NULL); + + /* Set default location option */ + dialogOptions.dialogOptionFlags |= kNavSelectDefaultLocation; + + /* Clear preview option */ + dialogOptions.dialogOptionFlags &= ~(kNavAllowPreviews); + + /* Forbit selection of multiple files */ + dialogOptions.dialogOptionFlags &= ~(kNavAllowMultipleFiles); + + /* Display location */ + dialogOptions.location = topleft; + +#if 0 + + /* Load the message for the missing folder from the resource fork */ + /* GetIndString(dialogOptions.message, 128, 1); */ + +#else + + /* Set the message for the missing folder XXX XXX */ + strcpy(dialogOptions.message + 1, "Please select the \"lib\" folder"); + dialogOptions.message[0] = strlen(dialogOptions.message + 1); + +#endif + + /* Wait for the user to choose a folder */ + err = NavChooseFolder( + nil, &theReply, &dialogOptions, nil, nil, nil); + + /* Assume the player doesn't want to go on */ + if ((err != noErr) || !theReply.validRecord) quit(NULL); + + /* Retrieve FSSpec from the reply */ + { + AEKeyword theKeyword; + DescType actualType; + Size actualSize; + + /* Get a pointer to selected folder */ + err = AEGetNthPtr( + &(theReply.selection), 1, typeFSS, &theKeyword, + &actualType, &theFolderSpec, sizeof(FSSpec), &actualSize); + + /* Paranoia */ + if (err != noErr) quit(NULL); + } + + /* Free navitagor reply */ + err = NavDisposeReply(&theReply); + + /* Paranoia */ + if (err != noErr) quit(NULL); + + /* Extract textual file name for given file */ +#ifdef MACH_O_CARBON + if (spec_to_path(&theFolderSpec, path, sizeof(path)) != noErr) + { + quit(NULL); + } +#else /* MACH_O_CARBON */ +refnum_to_name( + path, + theFolderSpec.parID, + theFolderSpec.vRefNum, + (char *)theFolderSpec.name); +#endif /* MACH_O_CARBON */ + } +} + + +/* + * Macintosh Main loop + */ +int main(void) +{ + int i; + long response; + OSStatus err; + EventRecord tempEvent; + UInt32 numberOfMasters = 10; + + /* Get more Masters -- it is not recommended by Apple, should go away */ + MoreMasterPointers(numberOfMasters); + + /* Check for existence of Carbon */ + err = Gestalt(gestaltCarbonVersion, &response); + + if (err != noErr) quit("This program requires Carbon API"); + + /* See if we are running on Aqua */ + err = Gestalt(gestaltMenuMgrAttr, &response); + + /* Cache the result */ + if ((err == noErr) && + (response & gestaltMenuMgrAquaLayoutMask)) is_aqua = TRUE; + + /* + * Remember Mac OS version, in case we have to cope with version-specific + * problems + */ + (void)Gestalt(gestaltSystemVersion, &mac_os_version); + + + /* Set up the Macintosh */ + InitCursor(); + + /* Flush events */ + FlushEvents(everyEvent, 0); + + /* Flush events some more (?) */ + if (EventAvail(everyEvent, &tempEvent)) FlushEvents(everyEvent, 0); + + + /* Install the start event hook (ignore error codes) */ + AEInstallEventHandler( + kCoreEventClass, + kAEOpenApplication, + NewAEEventHandlerUPP(AEH_Start), + 0L, + FALSE); + + /* Install the quit event hook (ignore error codes) */ + AEInstallEventHandler( + kCoreEventClass, + kAEQuitApplication, + NewAEEventHandlerUPP(AEH_Quit), + 0L, + FALSE); + + /* Install the print event hook (ignore error codes) */ + AEInstallEventHandler( + kCoreEventClass, + kAEPrintDocuments, + NewAEEventHandlerUPP(AEH_Print), + 0L, + FALSE); + + /* Install the open event hook (ignore error codes) */ + AEInstallEventHandler( + kCoreEventClass, + kAEOpenDocuments, + NewAEEventHandlerUPP(AEH_Open), + 0L, + FALSE); + + +#ifndef MACH_O_CARBON + + /* Find the current application */ + SetupAppDir(); + +#endif /* !MACH_O_CARBON */ + + /* Mark ourself as the file creator */ + _fcreator = ANGBAND_CREATOR; + + /* Default to saving a "text" file */ + _ftype = 'TEXT'; + + + /* Hook in some "z-virt.c" hooks */ + rnfree_aux = hook_rnfree; + ralloc_aux = hook_ralloc; + rpanic_aux = hook_rpanic; + + /* Hooks in some "z-util.c" hooks */ + plog_aux = hook_plog; + quit_aux = hook_quit; + core_aux = hook_core; + + + /* Initialize colors */ + for (i = 0; i < 256; i++) + { + u16b rv, gv, bv; + + /* Extract the R,G,B data */ + rv = angband_color_table[i][1]; + gv = angband_color_table[i][2]; + bv = angband_color_table[i][3]; + + /* Save the actual color */ + color_info[i].red = (rv | (rv << 8)); + color_info[i].green = (gv | (gv << 8)); + color_info[i].blue = (bv | (bv << 8)); + } + + + /* Show the "watch" cursor */ + SetCursor(*(GetCursor(watchCursor))); + + /* Prepare the menubar */ + init_menubar(); + + /* Prepare the windows */ + init_windows(); + + /* Hack -- process all events */ + while (CheckEvents(FALSE)) /* loop */; + + /* Reset the cursor */ + { + Cursor tempCursor; + + SetCursor(GetQDGlobalsArrow(&tempCursor)); + } + + /* Mega-Hack -- Allocate a "lifeboat" */ + lifeboat = NewPtr(16384); + +#ifdef USE_QT_SOUND + + /* Load sound effect resources */ + load_sounds(); + +#endif /* USE_QT_SOUND */ + + /* Note the "system" */ + ANGBAND_SYS = "mac"; + +#ifdef PRIVATE_USER_PATH + if (check_create_user_dir() == FALSE) + quit("Cannot create directory " PRIVATE_USER_PATH); +#endif + + /* Initialize */ + init_stuff(); + + /* Initialize */ + init_angband(); + + + /* Hack -- process all events */ + while (CheckEvents(FALSE)) /* loop */; + + + /* We are now initialized */ + initialized = TRUE; + + + /* Handle "open_when_ready" */ + handle_open_when_ready(); + +#ifndef SAVEFILE_SCREEN + + /* Prompt the user - You may have to change this for some variants */ + prt("[Choose 'New' or 'Open' from the 'File' menu]", 23, 15); + + /* Flush the prompt */ + Term_fresh(); + + /* Hack -- Process Events Forever */ + while (TRUE) CheckEvents(TRUE); + +#else + + /* Game is in progress */ + game_in_progress = 1; + + /* Wait for keypress */ + pause_line(23); + + /* flush input - Warning: without this, _system_ would hang */ + flush(); + + /* Play the game - note the value of the argument */ + play_game(FALSE); + + /* Quit */ + quit(NULL); + + /* Since it's a int function */ + return (0); +#endif /* !SAVEFILE_SCREEN */ +} + +#endif /* MACINTOSH || MACH_O_CARBON */ + -- cgit v1.2.3