OSDN Git Service

upgrade to 3.6.2
[jnethack/source.git] / sys / mac / macmenu.c
1 /* NetHack 3.6  macmenu.c       $NHDT-Date: 1432512797 2015/05/25 00:13:17 $  $NHDT-Branch: master $:$NHDT-Revision: 1.13 $ */
2 /*      Copyright (c) Macintosh NetHack Port Team, 1993.          */
3 /* NetHack may be freely redistributed.  See license for details. */
4
5 /****************************************\
6  * Extended Macintosh menu support
7  *
8  * provides access to all keyboard commands from cmd.c
9  * provides control key functionality for classic keyboards
10  * provides key equivalent references and logical menu groups
11  * supports various menu highlighting modes
12 \****************************************/
13
14 /****************************************\
15  * Edit History:
16  *
17  * 930512       - More bug fixes and getting tty to work again, Jon W{tte
18  * 930508       - Bug fixes in-flight, Jon W{tte
19  * 04/29/93 - 1st Release Draft, David Hairston
20  * 04/11/93 - 1st Draft, David Hairston
21 \****************************************/
22
23 /******** Application Defines ********/
24 #include "hack.h"
25 #include "mactty.h"
26 #include "macwin.h"
27 #include "macpopup.h"
28 #include "patchlevel.h"
29
30 /******** Toolbox Defines ********/
31 #if !TARGET_API_MAC_CARBON
32 #include <Menus.h>
33 #include <Devices.h>
34 #include <Resources.h>
35 #include <TextUtils.h>
36 #include <ToolUtils.h>
37 #include <Sound.h>
38 #endif
39
40 /* Borrowed from the Mac tty port */
41 extern WindowPtr _mt_window;
42
43 /******** Local Defines ********/
44
45 /* 'MNU#' (menu list record) */
46 typedef union menuRefUnn {
47     short mresID;    /* MENU resource ID (before GetMenu) */
48     MenuHandle mhnd; /* MENU handle (after GetMenu) */
49 } menuRefUnn;
50
51 typedef struct menuListRec {
52     short firstMenuID;
53     short numMenus;
54     menuRefUnn mref[];
55 } menuListRec, *menuListPtr, **menuListHandle;
56
57 /* indices and resource IDs of the menu list data */
58 enum {
59     listMenubar,
60     listSubmenu,
61
62     menuBarListID = 128,
63     subMenuListID
64 };
65
66 /* the following mref[] indices are reserved */
67 enum {
68     /* menu bar */
69     menuApple,
70     menuFile,
71     menuEdit,
72
73     /* submenu */
74     menuWizard = 0
75 };
76
77 /* the following menu items are reserved */
78 enum {
79     /* apple */
80     menuAppleAboutBox = 1,
81     ____Apple__1,
82
83     /* File */
84     menuFileRedraw = 1,
85     menuFilePrevMsg,
86     menuFileCleanup,
87     ____File___1,
88     menuFilePlayMode,
89     menuFileEnterExplore,
90     ____File___2,
91     menuFileSave,
92     ____File___3,
93     menuFileQuit,
94
95     /* standard minimum Edit menu items */
96
97     /* Wizard */
98     menuWizardAttributes = 1
99 };
100
101 /*
102  * menuListRec data (preloaded and locked) specifies the number of menus in
103  * the menu bar, the number of hierarchal or submenus and the menu IDs of
104  * all of those menus.  menus that go into in the menu bar are specified by
105  * 'MNU#' 128 and submenus are specified by 'MNU#' 129.  the fields of the
106  * menuListRec are:
107  * firstMenuID - the menu ID (not resource ID) of the 1st menu.  subsequent
108  *     menus in the list are _forced_ to have consecutively incremented IDs.
109  * numMenus - the total count of menus in a given list (and the extent of
110  *     valid menu IDs).
111  * mref[] - initially the MENU resource ID is stored in the placeholder for
112  *     the resource handle.  after loading (GetResource), the menu handle
113  *     is stored and the menu ID, in memory, is set as noted above.
114  *
115  * NOTE: a ResEdit template editor is supplied to edit the 'MNU#' resources.
116  *
117  * NOTE: the resource IDs do not need to match the menu IDs in a menu list
118  * record although they have been originally set that way.
119  *
120  * NOTE: the menu ID's of menus in the submenu list record may be reset, as
121  * noted above.  it is the programmers responsibility to make sure that
122  * submenu references/IDs are valid.
123  *
124  * WARNING: the existence of the submenu list record is assumed even if the
125  * number of submenus is zero.  also, no error checking is done on the
126  * extents of the menu IDs.  this must be correctly setup by the programmer.
127  */
128
129 #define ID1_MBAR pMenuList[listMenubar]->firstMenuID
130 #define ID1_SUBM pMenuList[listSubmenu]->firstMenuID
131
132 #define NUM_MBAR pMenuList[listMenubar]->numMenus
133 #define NUM_SUBM pMenuList[listSubmenu]->numMenus
134
135 #define MHND_APPLE pMenuList[listMenubar]->mref[menuApple].mhnd
136 #define MHND_FILE pMenuList[listMenubar]->mref[menuFile].mhnd
137 #define MHND_EDIT pMenuList[listMenubar]->mref[menuEdit].mhnd
138
139 #define MBARHND(x) pMenuList[listMenubar]->mref[(x)].mhnd
140
141 #define MHND_WIZ pMenuList[listSubmenu]->mref[menuWizard].mhnd
142
143 /* mutually exclusive (and prioritized) menu bar states */
144 enum {
145     mbarDim,
146     mbarNoWindows,
147     mbarDA,
148     mbarNoMap,
149     mbarRegular,
150     mbarSpecial /* explore or debug mode */
151 };
152
153 #define WKND_MAP (WIN_BASE_KIND + NHW_MAP)
154
155 /* menu routine error numbers */
156 enum {
157     errGetMenuList,
158     errGetMenu,
159     errGetANDlogTemplate,
160     errGetANDlogItems,
161     errGetANDialog,
162     errANNewMenu,
163     err_Menu_total
164 };
165
166 /* menu 'STR#' comment char */
167 #define mstrEndChar 0xA5 /* '\245' or option-* or "bullet" */
168
169 /* 'ALRT' */
170 enum {
171     alrt_Menu_start = 5000,
172     alrtMenuNote = alrt_Menu_start,
173     alrtMenu_NY,
174     alrt_Menu_limit
175 };
176
177 #define beepMenuAlertErr 1 /* # of SysBeep()'s before exitting */
178 enum { bttnMenuAlertNo = 1, bttnMenuAlertYes };
179
180 /******** Globals ********/
181 static unsigned char *menuErrStr[err_Menu_total] = {
182     "\pAbort: Bad \'MNU#\' resource!", /* errGetMenuList */
183     "\pAbort: Bad \'MENU\' resource!", /* errGetMenu */
184     "\pAbort: Bad \'DLOG\' resource!", /* errGetANDlogTemplate */
185     "\pAbort: Bad \'DITL\' resource!", /* errGetANDlogItems */
186     "\pAbort: Bad Dialog Allocation!", /* errGetANDialog */
187     "\pAbort: Bad Menu Allocation!",   /* errANNewMenu */
188 };
189 static menuListPtr pMenuList[2];
190 static short theMenubar = mbarDA; /* force initial update */
191 static short kAdjustWizardMenu = 1;
192
193 /******** Prototypes ********/
194 #if !TARGET_API_MAC_CARBON
195 static void alignAD(Rect *, short);
196 #endif
197 static void mustGetMenuAlerts(void);
198 static void menuError(short);
199 static void aboutNetHack(void);
200 static void askSave(void);
201 static void askQuit(void);
202
203 /*** Askname dialog box ***/
204
205 #define RSRC_ASK 6000      /* Askname dialog and item list */
206 #define RSRC_ASK_PLAY 1    /*   Play button */
207 #define RSRC_ASK_QUIT 2    /*   Quit button */
208 #define RSRC_ASK_DEFAULT 3 /*   Default ring */
209 #define RSRC_ASK_ROLE 4    /*   Role popup menu */
210 #define RSRC_ASK_RACE 5    /*   Race popup menu */
211 #define RSRC_ASK_GEND 6    /*   Gender popup menu */
212 #define RSRC_ASK_ALIGN 7   /*   Alignment popup menu */
213 #define RSRC_ASK_MODE 8    /*   Mode popup menu */
214 #define RSRC_ASK_NAME 9    /*   Name text field */
215 #define RSRC_ASK_MAX 10    /*   Maximum enabled item */
216
217 #define KEY_MASK 0xff00
218 #define KEY_RETURN 0x2400
219 #define KEY_ENTER 0x4c00
220 #define KEY_ESCAPE 0x3500
221 #define CH_MASK 0x00ff
222 #define CH_RETURN 0x000d
223 #define CH_ENTER 0x0003
224 #define CH_ESCAPE 0x001b
225
226 static void ask_restring(const char *cstr, unsigned char *pstr);
227 static void ask_enable(DialogRef wind, short item, int enable);
228 static pascal void ask_redraw(DialogRef wind, DialogItemIndex item);
229 static pascal Boolean
230 ask_filter(DialogRef wind, EventRecord *event, DialogItemIndex *item);
231 #define noresource(t, n) \
232     {                    \
233         SysBeep(3);      \
234         ExitToShell();   \
235     }
236 #define fatal(s)       \
237     {                  \
238         SysBeep(3);    \
239         ExitToShell(); \
240     }
241
242 static MenuHandle askmenu[RSRC_ASK_MAX];
243 static int askselect[RSRC_ASK_MAX];
244 #define currrole askselect[RSRC_ASK_ROLE]
245 #define currrace askselect[RSRC_ASK_RACE]
246 #define currgend askselect[RSRC_ASK_GEND]
247 #define curralign askselect[RSRC_ASK_ALIGN]
248 #define currmode askselect[RSRC_ASK_MODE]
249
250 static RGBColor blackcolor = { 0x0000, 0x0000, 0x0000 },
251                 //      indentcolor = {0x4000, 0x4000, 0x4000},
252     darkcolor = { 0x8000, 0x8000, 0x8000 },
253                 backcolor = { 0xdddd, 0xdddd, 0xdddd },
254                 lightcolor = { 0xffff, 0xffff, 0xffff },
255                 whitecolor = { 0xffff, 0xffff, 0xffff };
256
257 /* Convert a mixed-case C string to a Capitalized Pascal string */
258 static void
259 ask_restring(const char *cstr, unsigned char *pstr)
260 {
261     int i;
262
263     for (i = 0; *cstr && (i < 255); i++)
264         pstr[i + 1] = *cstr++;
265     pstr[0] = i;
266     if ((pstr[1] >= 'a') && (pstr[1] <= 'z'))
267         pstr[1] += 'A' - 'a';
268     return;
269 }
270
271 /* Enable the dialog item with the given index */
272 static void
273 ask_enable(DialogRef wind, short item, int enable)
274 {
275     short type;
276     Handle handle;
277     Rect rect;
278
279     /* Enable or disable the appropriate item */
280     GetDialogItem(wind, item, &type, &handle, &rect);
281     if (enable)
282         type &= ~itemDisable;
283     else
284         type |= itemDisable;
285     HiliteControl((ControlHandle) handle, enable ? 0 : 255);
286     SetDialogItem(wind, item, type, handle, &rect);
287     return;
288 }
289
290 static pascal void
291 ask_redraw(DialogRef wind, DialogItemIndex item)
292 {
293     short type;
294     Handle handle;
295     Rect rect;
296     static char *modechar = "NED";
297
298     /* Which item shall we redraw? */
299     GetDialogItem(wind, item, &type, &handle, &rect);
300     switch (item) {
301     case RSRC_ASK_DEFAULT:
302         PenSize(3, 3);
303         FrameRoundRect(&rect, 16, 16);
304         break;
305
306     case RSRC_ASK_ROLE:
307     case RSRC_ASK_RACE:
308     case RSRC_ASK_GEND:
309     case RSRC_ASK_ALIGN:
310     case RSRC_ASK_MODE:
311         if (macFlags.color) {
312             RGBForeColor(&blackcolor);
313             RGBBackColor(&backcolor);
314         }
315         PenNormal();
316         TextMode(srcOr);
317         EraseRect(&rect);
318
319         /* Draw the frame and drop shadow */
320         rect.right--;
321         rect.bottom--;
322         FrameRect(&rect);
323         MoveTo(rect.right, rect.top + 1);
324         LineTo(rect.right, rect.bottom);
325         LineTo(rect.left + 1, rect.bottom);
326
327         /* Draw the menu character */
328         MoveTo(rect.left + 4, rect.top + 12);
329         switch (item) {
330         case RSRC_ASK_ROLE:
331             DrawText(roles[askselect[item]].filecode, 0, 3);
332             break;
333         case RSRC_ASK_RACE:
334             DrawText(races[askselect[item]].filecode, 0, 3);
335             break;
336         case RSRC_ASK_GEND:
337             DrawText(genders[askselect[item]].filecode, 0, 3);
338             break;
339         case RSRC_ASK_ALIGN:
340             DrawText(aligns[askselect[item]].filecode, 0, 3);
341             break;
342         case RSRC_ASK_MODE:
343             DrawChar(modechar[askselect[item]]);
344             break;
345         }
346
347         /* Draw the popup symbol */
348         MoveTo(rect.right - 16, rect.top + 5);
349         LineTo(rect.right - 6, rect.top + 5);
350         LineTo(rect.right - 11, rect.top + 10);
351         LineTo(rect.right - 15, rect.top + 6);
352         LineTo(rect.right - 8, rect.top + 6);
353         LineTo(rect.right - 11, rect.top + 9);
354         LineTo(rect.right - 13, rect.top + 7);
355         LineTo(rect.right - 10, rect.top + 7);
356         LineTo(rect.right - 11, rect.top + 8);
357
358         /* Draw the shadow */
359         InsetRect(&rect, 1, 1);
360         if (macFlags.color) {
361             RGBColor color;
362
363             /* Save the foreground color */
364             GetForeColor(&color);
365
366             /* Draw the top and left */
367             RGBForeColor(&lightcolor);
368             MoveTo(rect.left, rect.bottom - 1);
369             LineTo(rect.left, rect.top);
370             LineTo(rect.right - 1, rect.top);
371
372             /* Draw the bottom and right */
373             RGBForeColor(&darkcolor);
374             MoveTo(rect.right - 1, rect.top + 1);
375             LineTo(rect.right - 1, rect.bottom - 1);
376             LineTo(rect.left + 1, rect.bottom - 1);
377
378             /* Restore the foreground color */
379             RGBForeColor(&color);
380         }
381         break;
382
383     case RSRC_ASK_NAME:
384         PenNormal();
385         if (macFlags.color) {
386             RGBForeColor(&whitecolor);
387             RGBBackColor(&whitecolor);
388             TextMode(srcOr);
389         } else {
390             PenMode(notPatCopy);
391             TextMode(srcBic);
392         }
393         InsetRect(&rect, -1, -1);
394         FrameRect(&rect);
395         InsetRect(&rect, -1, -1);
396         FrameRect(&rect);
397         InsetRect(&rect, -2, -2);
398         if (macFlags.color) {
399             /* Draw the top and left */
400             RGBForeColor(&darkcolor);
401             MoveTo(rect.left, rect.bottom - 1);
402             LineTo(rect.left, rect.top);
403             LineTo(rect.right - 1, rect.top);
404
405             /* Draw the bottom and right */
406             RGBForeColor(&lightcolor);
407             MoveTo(rect.right - 1, rect.top + 1);
408             LineTo(rect.right - 1, rect.bottom - 1);
409             LineTo(rect.left + 1, rect.bottom - 1);
410
411             /* Restore the colors */
412             RGBForeColor(&blackcolor);
413             RGBBackColor(&backcolor);
414         }
415         break;
416     }
417     return;
418 }
419
420 static pascal Boolean
421 ask_filter(DialogRef wind, EventRecord *event, DialogItemIndex *item)
422 {
423     short ch, key;
424
425     switch (event->what) {
426     case keyDown:
427     case autoKey:
428         ch = event->message & CH_MASK;
429         key = event->message & KEY_MASK;
430         /* Handle equivalents for OK */
431         if ((ch == CH_RETURN) || (key == KEY_RETURN) || (ch == CH_ENTER)
432             || (key == KEY_ENTER)) {
433             if (GetDialogTextEditHandle(wind)[0]->teLength) {
434                 FlashButton(wind, RSRC_ASK_PLAY);
435                 *item = RSRC_ASK_PLAY;
436             } else
437                 *item = 0;
438             return (TRUE);
439         }
440         /* Handle equivalents for Normal/Explore/Debug */
441         if ((event->modifiers & cmdKey) && (ch == 'n')) {
442             currmode = 0;
443             ask_redraw(wind, RSRC_ASK_MODE);
444             *item = RSRC_ASK_MODE;
445             return (TRUE);
446         }
447         if ((event->modifiers & cmdKey) && (ch == 'e')) {
448             currmode = 1;
449             ask_redraw(wind, RSRC_ASK_MODE);
450             *item = RSRC_ASK_MODE;
451             return (TRUE);
452         }
453         if ((event->modifiers & cmdKey) && (ch == 'd')) {
454             currmode = 2;
455             ask_redraw(wind, RSRC_ASK_MODE);
456             *item = RSRC_ASK_MODE;
457             return (TRUE);
458         }
459         /* Handle equivalents for Cancel and Quit */
460         if ((ch == CH_ESCAPE) || (key == KEY_ESCAPE)
461             || ((event->modifiers & cmdKey) && (ch == 'q'))
462             || ((event->modifiers & cmdKey) && (ch == '.'))) {
463             FlashButton(wind, RSRC_ASK_QUIT);
464             *item = RSRC_ASK_QUIT;
465             return (TRUE);
466         }
467         return (FALSE);
468     case updateEvt:
469         ask_redraw(wind, RSRC_ASK_NAME);
470         return (FALSE);
471     default:
472         return (FALSE);
473     }
474 }
475
476 void
477 mac_askname()
478 {
479     GrafPtr oldport;
480     DialogRef askdialog;
481     short i, j, item, type;
482     Handle handle;
483     Rect rect;
484     Str255 str;
485     Point pt;
486     UserItemUPP redraw = NewUserItemUPP(ask_redraw);
487     ModalFilterUPP filter = NewModalFilterUPP(ask_filter);
488
489     /* Create the dialog */
490     if (!(askdialog = GetNewDialog(RSRC_ASK, NULL, (WindowRef) -1)))
491         noresource('DLOG', RSRC_ASK);
492     GetPort(&oldport);
493     SetPortDialogPort(askdialog);
494
495     /* Initialize the name text item */
496     ask_restring(plname, str);
497     if (plname[0]) {
498         GetDialogItem(askdialog, RSRC_ASK_NAME, &type, &handle, &rect);
499         SetDialogItemText(handle, str);
500     }
501 #if 0
502         {
503         Str32 pName;
504                 pName [0] = 0;
505                 if (plname && plname [0]) {
506                         strcpy ((char *) pName, plname);
507                         c2pstr ((char *) pName);
508                 } else {
509                         Handle h;
510                         h = GetResource ('STR ', -16096);
511                         if (((Handle) 0 != h) && (GetHandleSize (h) > 0)) {
512                                 DetachResource (h);
513                                 HLock (h);
514                                 if (**h > 31) {
515                                         **h = 31;
516                                 }
517                                 BlockMove (*h, pName, **h + 1);
518                                 DisposeHandle (h);
519                         }
520                 }
521                 if (pName [0]) {
522                         GetDialogItem(askdialog, RSRC_ASK_NAME, &type, &handle, &rect);
523                         SetDialogItemText(handle, pName);
524                         if (pName [0] > 2 && pName [pName [0] - 1] == '-') {
525                             short role = (*pANR).anMenu[anRole];
526                             char suffix = (char) pName[pName[0]],
527                                 *sfxindx = strchr(pl_classes, suffix);
528
529                             if (sfxindx)
530                                 role = (short) (sfxindx - pl_classes);
531                             else if (suffix == '@')
532                                 role = (short) rn2((int) strlen(pl_classes));
533                             (*pANR).anMenu[anRole] = role;
534                         }
535                 }
536         }
537 #endif
538     SelectDialogItemText(askdialog, RSRC_ASK_NAME, 0, 32767);
539
540     /* Initialize the role popup menu */
541     if (!(askmenu[RSRC_ASK_ROLE] = NewMenu(RSRC_ASK_ROLE, "\p")))
542         fatal("\pCannot create role menu");
543     for (i = 0; roles[i].name.m; i++) {
544         ask_restring(roles[i].name.m, str);
545         AppendMenu(askmenu[RSRC_ASK_ROLE], str);
546     }
547     InsertMenu(askmenu[RSRC_ASK_ROLE], hierMenu);
548     if (flags.initrole >= 0)
549         currrole = flags.initrole;
550     /* Check for backward compatibility */
551     else if ((currrole = str2role(pl_character)) < 0)
552         currrole = randrole(FALSE);
553
554     /* Initialize the race popup menu */
555     if (!(askmenu[RSRC_ASK_RACE] = NewMenu(RSRC_ASK_RACE, "\p")))
556         fatal("\pCannot create race menu");
557     for (i = 0; races[i].noun; i++) {
558         ask_restring(races[i].noun, str);
559         AppendMenu(askmenu[RSRC_ASK_RACE], str);
560     }
561     InsertMenu(askmenu[RSRC_ASK_RACE], hierMenu);
562     if (flags.initrace >= 0)
563         currrace = flags.initrace;
564     else
565         currrace = randrace(currrole);
566
567     /* Initialize the gender popup menu */
568     if (!(askmenu[RSRC_ASK_GEND] = NewMenu(RSRC_ASK_GEND, "\p")))
569         fatal("\pCannot create gender menu");
570     for (i = 0; i < ROLE_GENDERS; i++) {
571         ask_restring(genders[i].adj, str);
572         AppendMenu(askmenu[RSRC_ASK_GEND], str);
573     }
574     InsertMenu(askmenu[RSRC_ASK_GEND], hierMenu);
575     if (flags.initgend >= 0)
576         currgend = flags.initgend;
577     else if (flags.female)
578         currgend = 1;
579     else
580         currgend = randgend(currrole, currrace);
581
582     /* Initialize the alignment popup menu */
583     if (!(askmenu[RSRC_ASK_ALIGN] = NewMenu(RSRC_ASK_ALIGN, "\p")))
584         fatal("\pCannot create alignment menu");
585     for (i = 0; i < ROLE_ALIGNS; i++) {
586         ask_restring(aligns[i].adj, str);
587         AppendMenu(askmenu[RSRC_ASK_ALIGN], str);
588     }
589     InsertMenu(askmenu[RSRC_ASK_ALIGN], hierMenu);
590     if (flags.initalign >= 0)
591         curralign = flags.initalign;
592     else
593         curralign = randalign(currrole, currrace);
594
595     /* Initialize the mode popup menu */
596     if (!(askmenu[RSRC_ASK_MODE] = NewMenu(RSRC_ASK_MODE, "\p")))
597         fatal("\pCannot create mode menu");
598     AppendMenu(askmenu[RSRC_ASK_MODE], "\pNormal");
599     AppendMenu(askmenu[RSRC_ASK_MODE], "\pExplore");
600     AppendMenu(askmenu[RSRC_ASK_MODE], "\pDebug");
601     InsertMenu(askmenu[RSRC_ASK_MODE], hierMenu);
602     currmode = 0;
603
604     /* Set the redraw procedures */
605     for (item = RSRC_ASK_DEFAULT; item <= RSRC_ASK_MODE; item++) {
606         GetDialogItem(askdialog, item, &type, &handle, &rect);
607         SetDialogItem(askdialog, item, type, (Handle) redraw, &rect);
608     }
609
610     /* Handle dialog events */
611     do {
612         /* Adjust the Play button */
613         ask_enable(askdialog, RSRC_ASK_PLAY,
614                    GetDialogTextEditHandle(askdialog)[0]->teLength);
615
616         /* Adjust the race popup menu */
617         i = j = currrace;
618         do {
619             if (validrace(currrole, j)) {
620                 EnableMenuItem(askmenu[RSRC_ASK_RACE], j + 1);
621                 CheckMenuItem(askmenu[RSRC_ASK_RACE], j + 1, currrace == j);
622             } else {
623                 DisableMenuItem(askmenu[RSRC_ASK_RACE], j + 1);
624                 CheckMenuItem(askmenu[RSRC_ASK_RACE], j + 1, FALSE);
625                 if ((currrace == j) && !races[++currrace].noun)
626                     currrace = 0;
627             }
628             if (!races[++j].noun)
629                 j = 0;
630         } while (i != j);
631         if (currrace != i) {
632             GetDialogItem(askdialog, RSRC_ASK_RACE, &type, &handle, &rect);
633             InvalWindowRect(GetDialogWindow(askdialog), &rect);
634         }
635
636         /* Adjust the gender popup menu */
637         i = j = currgend;
638         do {
639             if (validgend(currrole, currrace, j)) {
640                 EnableMenuItem(askmenu[RSRC_ASK_GEND], j + 1);
641                 CheckMenuItem(askmenu[RSRC_ASK_GEND], j + 1, currgend == j);
642             } else {
643                 DisableMenuItem(askmenu[RSRC_ASK_GEND], j + 1);
644                 CheckMenuItem(askmenu[RSRC_ASK_GEND], j + 1, FALSE);
645                 if ((currgend == j) && (++currgend >= ROLE_GENDERS))
646                     currgend = 0;
647             }
648             if (++j >= ROLE_GENDERS)
649                 j = 0;
650         } while (i != j);
651         if (currgend != i) {
652             GetDialogItem(askdialog, RSRC_ASK_GEND, &type, &handle, &rect);
653             InvalWindowRect(GetDialogWindow(askdialog), &rect);
654         }
655
656         /* Adjust the alignment popup menu */
657         i = j = curralign;
658         do {
659             if (validalign(currrole, currrace, j)) {
660                 EnableMenuItem(askmenu[RSRC_ASK_ALIGN], j + 1);
661                 CheckMenuItem(askmenu[RSRC_ASK_ALIGN], j + 1, curralign == j);
662             } else {
663                 DisableMenuItem(askmenu[RSRC_ASK_ALIGN], j + 1);
664                 CheckMenuItem(askmenu[RSRC_ASK_ALIGN], j + 1, FALSE);
665                 if ((curralign == j) && (++curralign >= ROLE_ALIGNS))
666                     curralign = 0;
667             }
668             if (++j >= ROLE_ALIGNS)
669                 j = 0;
670         } while (i != j);
671         if (curralign != i) {
672             GetDialogItem(askdialog, RSRC_ASK_ALIGN, &type, &handle, &rect);
673             InvalWindowRect(GetDialogWindow(askdialog), &rect);
674         }
675
676         /* Adjust the role popup menu */
677         for (i = 0; roles[i].name.m; i++) {
678             ask_restring((currgend && roles[i].name.f) ? roles[i].name.f
679                                                        : roles[i].name.m,
680                          str);
681             SetMenuItemText(askmenu[RSRC_ASK_ROLE], i + 1, str);
682             CheckMenuItem(askmenu[RSRC_ASK_ROLE], i + 1, currrole == i);
683         }
684
685         /* Adjust the mode popup menu */
686         CheckMenuItem(askmenu[RSRC_ASK_MODE], 1, currmode == 0);
687         CheckMenuItem(askmenu[RSRC_ASK_MODE], 2, currmode == 1);
688         CheckMenuItem(askmenu[RSRC_ASK_MODE], 3, currmode == 2);
689
690         /* Wait for an action on an item */
691         ModalDialog(filter, &item);
692         switch (item) {
693         case RSRC_ASK_PLAY:
694             break;
695         case RSRC_ASK_QUIT:
696             currmode = -1;
697             break;
698         case RSRC_ASK_ROLE:
699         case RSRC_ASK_RACE:
700         case RSRC_ASK_ALIGN:
701         case RSRC_ASK_GEND:
702         case RSRC_ASK_MODE:
703             GetDialogItem(askdialog, item, &type, &handle, &rect);
704             pt = *(Point *) &rect;
705             LocalToGlobal(&pt);
706             if (!!(i = PopUpMenuSelect(askmenu[item], pt.v, pt.h,
707                                        askselect[item] + 1)))
708                 askselect[item] = LoWord(i) - 1;
709             InvalWindowRect(GetDialogWindow(askdialog), &rect);
710             break;
711         case RSRC_ASK_NAME:
712 #if 0
713             /* limit the data here to 25 chars */
714             {
715                 short beepTEDelete = 1;
716
717                 while ((**dRec.textH).teLength > 25)
718                 {
719                         if (beepTEDelete++ <= 3)
720                                 SysBeep(3);
721                         TEKey('\b', dRec.textH);
722                 }
723             }
724
725             /* special case filter (that doesn't plug all the holes!) */
726             if (((**dRec.textH).teLength == 1) && (**((**dRec.textH).hText) < 32))
727                 TEKey('\b', dRec.textH);
728 #endif
729             break;
730         }
731     } while ((item != RSRC_ASK_PLAY) && (item != RSRC_ASK_QUIT));
732
733     /* Process the name */
734     GetDialogItem(askdialog, RSRC_ASK_NAME, &type, &handle, &rect);
735     GetDialogItemText(handle, str);
736     if (str[0] > PL_NSIZ - 1)
737         str[0] = PL_NSIZ - 1;
738     BlockMove(&str[1], plname, str[0]);
739     plname[str[0]] = '\0';
740
741     /* Destroy the dialog */
742     for (i = RSRC_ASK_ROLE; i <= RSRC_ASK_MODE; i++) {
743         DeleteMenu(i);
744         DisposeMenu(askmenu[i]);
745     }
746     SetPort(oldport);
747     DisposeDialog(askdialog);
748     DisposeModalFilterUPP(filter);
749     DisposeUserItemUPP(redraw);
750
751     /* Process the mode */
752     wizard = discover = 0;
753     switch (currmode) {
754     case 0: /* Normal */
755         break;
756     case 1: /* Explore */
757         discover = 1;
758         break;
759     case 2: /* Debug */
760         wizard = 1;
761         strcpy(plname, WIZARD_NAME);
762         break;
763     default: /* Quit */
764         ExitToShell();
765     }
766
767     /* Process the role */
768     strcpy(pl_character, roles[currrole].name.m);
769     flags.initrole = currrole;
770
771     /* Process the race */
772     flags.initrace = currrace;
773
774     /* Process the gender */
775     flags.female = flags.initgend = currgend;
776
777     /* Process the alignment */
778     flags.initalign = curralign;
779
780     return;
781 }
782
783 /*** Menu bar routines ***/
784
785 #if !TARGET_API_MAC_CARBON
786 static void
787 alignAD(Rect *pRct, short vExempt)
788 {
789     BitMap qbitmap;
790
791     GetQDGlobalsScreenBits(&qbitmap);
792     (*pRct).right -= (*pRct).left; /* width */
793     (*pRct).bottom -= (*pRct).top; /* height */
794     (*pRct).left = (qbitmap.bounds.right - (*pRct).right) / 2;
795     (*pRct).top = (qbitmap.bounds.bottom - (*pRct).bottom - vExempt) / 2;
796     (*pRct).top += vExempt;
797     (*pRct).right += (*pRct).left;
798     (*pRct).bottom += (*pRct).top;
799 }
800 #endif
801
802 static void
803 mustGetMenuAlerts()
804 {
805     short i;
806     Rect **hRct;
807
808     for (i = alrt_Menu_start; i < alrt_Menu_limit; i++) {
809         if (!(hRct = (Rect **) GetResource('ALRT', i))) /* AlertTHndl */
810         {
811             for (i = 0; i < beepMenuAlertErr; i++)
812                 SysBeep(3);
813             ExitToShell();
814         }
815
816 #if !TARGET_API_MAC_CARBON
817         alignAD(*hRct, GetMBarHeight());
818 #endif
819     }
820 }
821
822 static void
823 menuError(short menuErr)
824 {
825     short i;
826
827     for (i = 0; i < beepMenuAlertErr; i++)
828         SysBeep(3);
829
830     ParamText(menuErrStr[menuErr], "\p", "\p", "\p");
831     (void) Alert(alrtMenuNote, (ModalFilterUPP) 0L);
832
833     ExitToShell();
834 }
835
836 void
837 InitMenuRes()
838 {
839     static Boolean was_inited = 0;
840     short i, j;
841     menuListHandle mlHnd;
842     MenuHandle menu;
843
844     if (was_inited)
845         return;
846     was_inited = 1;
847
848     mustGetMenuAlerts();
849
850     for (i = listMenubar; i <= listSubmenu; i++) {
851         if (!(mlHnd =
852                   (menuListHandle) GetResource('MNU#', (menuBarListID + i))))
853             menuError(errGetMenuList);
854
855         pMenuList[i] = (menuListPtr) NewPtr(GetHandleSize((Handle) mlHnd));
856         *pMenuList[i] = **mlHnd;
857
858         for (j = 0; j < pMenuList[i]->numMenus; j++) {
859             if (!(menu = (MenuHandle) GetMenu((**mlHnd).mref[j].mresID))) {
860                 Str31 d;
861                 NumToString((**mlHnd).mref[j].mresID, d);
862                 menuError(errGetMenu);
863             }
864
865             pMenuList[i]->mref[j].mhnd = menu;
866             SetMenuID(menu, j + (**mlHnd).firstMenuID); /* consecutive IDs */
867
868             /* expand apple menu */
869             if ((i == listMenubar) && (j == menuApple)) {
870                 AppendResMenu(menu, 'DRVR');
871             }
872
873             InsertMenu(menu, ((i == listSubmenu) ? hierMenu : 0));
874         }
875     }
876     DrawMenuBar();
877     return;
878 }
879
880 void
881 AdjustMenus(short dimMenubar)
882 {
883     short newMenubar = mbarRegular;
884     WindowRef win = FrontWindow();
885     short i;
886
887     /*
888      *  if (windowprocs != mac_procs) {
889      *          return;
890      *  }
891      */
892     /* determine the new menubar state */
893     if (dimMenubar)
894         newMenubar = mbarDim;
895     else if (!win)
896         newMenubar = mbarNoWindows;
897     else if (GetWindowKind(win) < 0)
898         newMenubar = mbarDA;
899     else if (!IsWindowVisible(_mt_window))
900         newMenubar = mbarNoMap;
901
902     if (newMenubar != mbarRegular)
903         ; /* we've already found its state */
904     else if (wizard) {
905         newMenubar = mbarSpecial;
906
907         if (kAdjustWizardMenu) {
908             kAdjustWizardMenu = 0;
909
910             SetMenuItemText(MHND_FILE, menuFilePlayMode, "\pDebug");
911         }
912     }
913
914     else if (discover) {
915         newMenubar = mbarSpecial;
916
917         if (kAdjustWizardMenu) {
918             kAdjustWizardMenu = 0;
919
920             SetMenuItemText(MHND_FILE, menuFilePlayMode, "\pExplore");
921
922             for (i = CountMenuItems(MHND_WIZ); i > menuWizardAttributes; i--)
923                 DeleteMenuItem(MHND_WIZ, i);
924         }
925     }
926
927     /* adjust the menubar, if there's a state change */
928     if (theMenubar != newMenubar) {
929         switch (theMenubar = newMenubar) {
930         case mbarDim:
931             /* disable all menus (except the apple menu) */
932             for (i = menuFile; i < NUM_MBAR; i++)
933                 DisableMenuItem(MBARHND(i), 0);
934             break;
935
936         case mbarNoWindows:
937         case mbarDA:
938         case mbarNoMap:
939             /* enable the file menu, but ... */
940             EnableMenuItem(MHND_FILE, 0);
941
942             /* ... disable the window commands! */
943             for (i = menuFileRedraw; i <= menuFileEnterExplore; i++)
944                 DisableMenuItem(MHND_FILE, i);
945
946             /* ... and disable the rest of the menus */
947             for (i = menuEdit; i < NUM_MBAR; i++)
948                 DisableMenuItem(MBARHND(i), 0);
949
950             if (theMenubar == mbarDA)
951                 EnableMenuItem(MHND_EDIT, 0);
952
953             break;
954
955         case mbarRegular:
956         case mbarSpecial:
957             /* enable all menus ... */
958             for (i = menuFile; i < NUM_MBAR; i++)
959                 EnableMenuItem(MBARHND(i), 0);
960
961             /* ... except the unused Edit menu */
962             DisableMenuItem(MHND_EDIT, 0);
963
964             /* ... enable the window commands */
965             for (i = menuFileRedraw; i <= menuFileEnterExplore; i++)
966                 EnableMenuItem(MHND_FILE, i);
967
968             if (theMenubar == mbarRegular)
969                 DisableMenuItem(MHND_FILE, menuFilePlayMode);
970             else
971                 DisableMenuItem(MHND_FILE, menuFileEnterExplore);
972
973             break;
974         }
975
976         DrawMenuBar();
977     }
978 }
979
980 void
981 DoMenuEvt(long menuEntry)
982 {
983     short menuID = HiWord(menuEntry);
984     short menuItem = LoWord(menuEntry);
985
986     switch (menuID - ID1_MBAR) /* all submenus are default case */
987     {
988     case menuApple:
989         if (menuItem == menuAppleAboutBox)
990             aboutNetHack();
991 #if !TARGET_API_MAC_CARBON
992         else {
993             unsigned char daName[32];
994
995             GetMenuItemText(MHND_APPLE, menuItem, *(Str255 *) daName);
996             (void) OpenDeskAcc(daName);
997         }
998 #endif
999         break;
1000
1001     /*
1002      * Those direct calls are ugly: they should be installed into cmd.c .
1003      * Those AddToKeyQueue() calls are also ugly: they should be put into
1004      * the 'STR#' resource.
1005      */
1006     case menuFile:
1007         switch (menuItem) {
1008         case menuFileRedraw:
1009             AddToKeyQueue('R' & 0x1f, 1);
1010             break;
1011
1012         case menuFilePrevMsg:
1013             AddToKeyQueue('P' & 0x1f, 1);
1014             break;
1015
1016         case menuFileCleanup:
1017             (void) SanePositions();
1018             break;
1019
1020         case menuFileEnterExplore:
1021             AddToKeyQueue('X', 1);
1022             break;
1023
1024         case menuFileSave:
1025             askSave();
1026             break;
1027
1028         case menuFileQuit:
1029             askQuit();
1030             break;
1031         }
1032         break;
1033
1034     case menuEdit:
1035 #if !TARGET_API_MAC_CARBON
1036         (void) SystemEdit(menuItem - 1);
1037 #endif
1038         break;
1039
1040     default: /* get associated string and add to key queue */
1041     {
1042         Str255 mstr;
1043         short i;
1044
1045         GetIndString(mstr, menuID, menuItem);
1046         if (mstr[0] > QUEUE_LEN)
1047             mstr[0] = QUEUE_LEN;
1048
1049         for (i = 1; ((i <= mstr[0]) && (mstr[i] != mstrEndChar)); i++)
1050             AddToKeyQueue(mstr[i], false);
1051     } break;
1052     }
1053
1054     HiliteMenu(0);
1055 }
1056
1057 static void
1058 aboutNetHack()
1059 {
1060     if (theMenubar >= mbarRegular) {
1061         (void) doversion(); /* is this necessary? */
1062     } else {
1063         unsigned char aboutStr[32] = "\pNetHack 3.4.";
1064
1065         if (PATCHLEVEL > 10) {
1066             aboutStr[++aboutStr[0]] = '0' + PATCHLEVEL / 10;
1067         }
1068
1069         aboutStr[++aboutStr[0]] = '0' + (PATCHLEVEL % 10);
1070
1071         ParamText(aboutStr, "\p\rdevteam@www.nethack.org", "\p", "\p");
1072         (void) Alert(alrtMenuNote, (ModalFilterUPP) 0L);
1073         ResetAlertStage();
1074     }
1075 }
1076
1077 static void
1078 askSave()
1079 {
1080     Boolean doSave = 1;
1081     Boolean doYes = 0;
1082
1083     if (theMenubar < mbarRegular) {
1084         short itemHit;
1085
1086         ParamText("\pReally Save?", "\p", "\p", "\p");
1087         itemHit = Alert(alrtMenu_NY, (ModalFilterUPP) 0L);
1088         ResetAlertStage();
1089
1090         if (itemHit != bttnMenuAlertYes) {
1091             doSave = 0;
1092         } else {
1093             doYes = 1;
1094         }
1095     }
1096     if (doSave) {
1097         AddToKeyQueue('S', 1);
1098         if (doYes) {
1099             AddToKeyQueue('y', 1);
1100         }
1101     }
1102 }
1103
1104 static void
1105 askQuit()
1106 {
1107     Boolean doQuit = 1;
1108     Boolean doYes = 0;
1109     Boolean winMac;
1110     char *quitinput;
1111
1112     if (!strcmp(windowprocs.name, "mac"))
1113         winMac = 1;
1114     else
1115         winMac = 0;
1116
1117     if (theMenubar < mbarRegular) {
1118         short itemHit;
1119
1120         ParamText("\pReally Quit?", "\p", "\p", "\p");
1121         itemHit = Alert(alrtMenu_NY, (ModalFilterUPP) 0L);
1122         ResetAlertStage();
1123
1124         if (itemHit != bttnMenuAlertYes) {
1125             doQuit = 0;
1126         } else {
1127             doYes = 1;
1128         }
1129     }
1130     if (doQuit) {
1131         /* MWM -- forgive me lord, an even uglier kludge to deal with
1132            differences
1133                 in command input handling
1134          */
1135         if (winMac)
1136             quitinput = "#quit\r";
1137         else
1138             quitinput = "#q\r";
1139
1140         /* KMH -- Ugly kludge */
1141         while (*quitinput)
1142             AddToKeyQueue(*quitinput++, 1);
1143         if (doYes) {
1144             if (winMac)
1145                 quitinput = "y\rq\r\r\r";
1146             else
1147                 quitinput = "yq\r";
1148             while (*quitinput)
1149                 AddToKeyQueue(*quitinput++, 1);
1150         }
1151     }
1152 }