1 /* The routines in this file provide menu-related functions under the
2    Microsoft Windows environment on an IBM-PC or compatible computer.
3 
4    This module also contains the code for the About and Modes dialog
5    boxes.
6 
7    Must be compiled with Borland C++ 2.0 or MSC 6.0 or later versions.
8 
9    It should not be compiled if the WINDOW_MSWIN symbol is not set */
10 
11 #include    "estruct.h"
12 #include    "elang.h"
13 #include    <stdio.h>
14 #include    "eproto.h"
15 #include    "edef.h"
16 
17 #include    "mswin.h"
18 #include    "mswmenu.h"
19 #include    "mswhelp.h"
20 
21 #define MAXMENUTITLE 50
22 
23 /* codes for the MenuType parameter from AddMenuEntry () */
24 #define MT_DUMMY    1
25 #define MT_MENUBAR  2
26 
27 typedef struct   {
28     WORD    m_word;
29     ETYPE EPOINTER m_ptr;
30 } MENUTAB;
31 
32 #define MB_FNC  0x4000
33 #define MB_BUF  0x8000  /* static IDs should not look like they carry
34 			   this flag */
35 
36 #define MAXDYNMENU  256
37 
38 static MENUTAB MenuDynBind [MAXDYNMENU] = {0, NULL};
39 
40 static MENUTAB MenuStaticBind [] = {
41     {IDM_NULLPROC,      nullproc},
42     {IDM_FILEFIND,      filefind},
43     {IDM_VIEWFILE,      viewfile},
44     {IDM_INSFILE,	insfile},
45     {IDM_FILEREAD,	fileread},
46     {IDM_FILENAME,	filename},
47     {IDM_FILESAVE,	filesave},
48     {IDM_FILEWRITE,	filewrite},
49     {IDM_FILEAPP,	fileapp},
50     {IDM_SETEKEY,	setekey},
51     {IDM_NEXTBUFFER,	nextbuffer},
52     {IDM_USEBUFFER,	usebuffer},
53     {IDM_UNMARK,	unmark},
54     {IDM_NAMEBUFFER,	namebuffer},
55     {IDM_KILLBUFFER,	killbuffer},
56     {IDM_NARROW,	narrow},
57     {IDM_WIDEN,		widen},
58     {IDM_LISTBUFFERS,	listbuffers},
59     {IDM_SPLITWIND,	splitwind},
60     {IDM_DELWIND,	delwind},
61     {IDM_ONLYWIND,	onlywind},
62     {IDM_NEXTWIND,	nextwind},
63     {IDM_PREVWIND,	prevwind},
64     {IDM_MVUPWIND,	mvupwind},
65     {IDM_MVDNWIND,	mvdnwind},
66     {IDM_NEXTUP,	nextup},
67     {IDM_NEXTDOWN,	nextdown},
68     {IDM_ENLARGEWIND,	enlargewind},
69     {IDM_SHRINKWIND,	shrinkwind},
70     {IDM_RESIZE,	resize},
71     {IDM_QUICKEXIT,	quickexit},
72     {IDM_QUIT,		quit},
73         /* here just to compute the key binding: the actual IDM_QUIT
74 	   selection is processed as a special */
75     {IDM_CUTREGION,	cutregion},
76     {IDM_CLIPREGION,	clipregion},
77     {IDM_INSERTCLIP,	insertclip},
78     {IDM_SETMARK,	setmark},
79     {IDM_REMMARK,	remmark},
80     {IDM_SWAPMARK,	swapmark},
81     {IDM_YANK,		yank},
82     {IDM_KILLREGION,	killregion},
83     {IDM_COPYREGION,	copyregion},
84     {IDM_UPPERREGION,	upperregion},
85     {IDM_LOWERREGION,	lowerregion},
86     {IDM_ENTAB,		entab},
87     {IDM_DETAB,		detab},
88     {IDM_TRIM,		trim},
89     {IDM_INDENT_REGION,	indent_region},
90     {IDM_UNDENT_REGION,	undent_region},
91     {IDM_WORDCOUNT,	wordcount},
92     {IDM_FILLPARA,	fillpara},
93     {IDM_KILLPARA,	killpara},
94     {IDM_KILLTEXT,	killtext},
95     {IDM_OPENLINE,	openline},
96     {IDM_DELFWORD,	delfword},
97     {IDM_DELBWORD,	delbword},
98     {IDM_CAPWORD,	capword},
99     {IDM_LOWERWORD,	lowerword},
100     {IDM_UPPERWORD,	upperword},
101     {IDM_DEBLANK,	deblank},
102     {IDM_TWIDDLE,	twiddle},
103     {IDM_TAB,		tab},
104     {IDM_QUOTE,		quote},
105     {IDM_FORWSEARCH,	forwsearch},
106     {IDM_BACKSEARCH,	backsearch},
107     {IDM_FORWHUNT,	forwhunt},
108     {IDM_BACKHUNT,	backhunt},
109     {IDM_FISEARCH,	fisearch},
110     {IDM_RISEARCH,	risearch},
111     {IDM_SREPLACE,	sreplace},
112     {IDM_QREPLACE,	qreplace},
113     {IDM_GOTOMARK,	gotomark},
114     {IDM_GOTOLINE,	gotoline},
115     {IDM_GETFENCE,	getfence},
116     {IDM_GOTOBOB,	gotobob},
117     {IDM_GOTOEOB,	gotoeob},
118     {IDM_FORWPAGE,	forwpage},
119     {IDM_BACKPAGE,	backpage},
120     {IDM_GOTOEOP,	gotoeop},
121     {IDM_GOTOBOP,	gotobop},
122     {IDM_FORWLINE,	forwline},
123     {IDM_BACKLINE,	backline},
124     {IDM_GOTOBOL,	gotobol},
125     {IDM_GOTOEOL,	gotoeol},
126     {IDM_FORWWORD,	forwword},
127     {IDM_BACKWORD,	backword},
128     {IDM_ENDWORD,	endword},
129     {IDM_EXECPRG,	execprg},
130     {IDM_SPAWNCLI,	spawncli},
131     {IDM_SPAWN,		spawn},
132     {IDM_PIPECMD,	pipecmd},
133     {IDM_FILTER,	filter},
134     {IDM_CTLXE,		ctlxe},
135     {IDM_CTLXLP,	ctlxlp},
136     {IDM_CTLXRP,	ctlxrp},
137     {IDM_NAMEDCMD,	namedcmd},
138     {IDM_EXECCMD,	execcmd},
139     {IDM_EXECPROC,	execproc},
140     {IDM_EXECBUF,	execbuf},
141     {IDM_EXECFILE,	execfile},
142     {IDM_BINDTOKEY,	bindtokey},
143     {IDM_MACROTOKEY,	macrotokey},
144     {IDM_BINDTOMENU,	bindtomenu},
145     {IDM_MACROTOMENU,	macrotomenu},
146     {IDM_UNBINDKEY,	unbindkey},
147     {IDM_UNBINDMENU,    unbindmenu},
148     {IDM_DESKEY,	deskey},
149     {IDM_SETVAR,	setvar},
150     {IDM_DISPVAR,	dispvar},
151     {IDM_DESVARS,	desvars},
152     {IDM_SHOWCPOS,	showcpos},
153     {IDM_CTRLG,		ctrlg},
154     {IDM_FIND_SCREEN,	find_screen},
155     {IDM_RENAME_SCREEN,	rename_screen},
156     {IDM_NEWSIZE,	newsize},
157     {IDM_NEWWIDTH,	newwidth},
158     {IDM_DESBIND,	desbind},
159     {IDM_DESFUNC,	desfunc},
160     {IDM_APRO,		apro},
161     {0,                 NULL}
162 };
163 
164 #define MAXMENUPARENT   7
165 
166 typedef struct {    /* current menu description */
167     int     cm_pos;     /* entry position */
168     int     cm_x;       /* index of the last parent handle, -1 if entry
169 			   located in main menu bar */
170     HMENU   cm_parent[MAXMENUPARENT];
171                         /* parent handle, main popup menu at index 0 */
172 } CURMENU;
173 
174 static CURMENU  CurrentMenu = {-1, -1, {NULL}};
175 
176 static unsigned int ctrlx_key = 0;
177 static unsigned int meta_key = 0;       /* for GetKeyText */
178 
179 /* execmenu:    execute a function bound to a menu */
180 /* ========                                        */
181 
182 /* This function is called by the edit loop in main.c, when a MENU
183    extended character is detected. */
184 
execmenu(int f,int n)185 int PASCAL execmenu (int f, int n)
186 /* f, n: arguments to target function */
187 {
188     register UINT    ID;
189     register MENUTAB *MTp;
190 
191     ID = (xpos << 8) + ypos;    /* get_key sees MENU sequences as MOUS */
192     if (ID >= IDM_DYNAMIC) {
193         MTp = &MenuDynBind[ID - IDM_DYNAMIC];
194         if (MTp->m_word & MB_FNC) return (*(MTp->m_ptr.fp)) (f, n);
195         else if (MTp->m_word & MB_BUF) {
196             BOOL    status;
197 
198             if (f == FALSE) n = 1;
199             while (n--) {
200                 status = dobuf (MTp->m_ptr.buf);
201                 if (status != TRUE) return status;
202             }
203         }
204         else return FAILD;
205         return TRUE;
206     }
207     else for (MTp = &MenuStaticBind[0]; MTp->m_word != 0; MTp++) {
208         if (ID == MTp->m_word) return (*(MTp->m_ptr.fp)) (f, n);
209     }
210     return FAILD;      /* not found !!! */
211 } /* execmenu */
212 
213 /* GenerateMenuSeq: send a menu sequence into the input stream */
214 /* ===============                                             */
215 
GenerateMenuSeq(UINT ID)216 void FAR PASCAL GenerateMenuSeq (UINT ID)
217 {
218     if (!in_room (5)) return;
219     in_put (0);             /* escape indicator */
220     in_put (MENU >> 8);     /* MENU prefix */
221     in_put (ID >> 8);       /* menu ID high byte (--> xpos) */
222     in_put (ID & 0xFF);     /* menu ID low byte (--> ypos) */
223     in_put ('m');           /* dummy event */
224 } /* GenerateMenuSeq */
225 
226 /* AboutDlgProc:  About box dialog function */
227 /* ============                             */
AboutDlgProc(HWND hDlg,UINT wMsg,UINT wParam,LONG lParam)228 int EXPORT FAR PASCAL  AboutDlgProc (HWND hDlg, UINT wMsg, UINT wParam,
229                                      LONG lParam)
230 {
231     char    s [50];
232     static RECT FullBox;
233 
234     switch (wMsg) {
235 
236     case WM_INITDIALOG:
237         strcpy (s, PROGNAME);
238         strcat (s, " ");
239         strcat (s, VERSION);
240         SetDlgItemText (hDlg, ID_PROGVER, s);
241         {
242             RECT    Bar;
243 
244 	    GetWindowRect (hDlg, &FullBox);
245             GetWindowRect (GetDlgItem (hDlg, ID_ABOUTBAR), &Bar);
246             MoveWindow (hDlg, FullBox.left, FullBox.top,
247                         Bar.left + GetSystemMetrics (SM_CXDLGFRAME)
248                                  - FullBox.left,
249                         FullBox.bottom - FullBox.top,
250                         FALSE);     /* shrink dialog box, do not repaint */
251         }
252 	return TRUE;
253 
254     case WM_COMMAND:
255 	if (NOTIFICATION_CODE != BN_CLICKED) break;
256 	if (LOWORD(wParam) == 1) {
257 	    EndDialog (hDlg, 0);
258 	    return TRUE;
259 	}
260 	else if (LOWORD(wParam) == ID_MOREABOUT) {
261 	    EnableWindow ((HWND)lParam, FALSE);   /* disable "More >>" */
262 	    SetFocus (GetDlgItem (hDlg, 1));         /* make "OK" default */
263 	    MoveWindow (hDlg, FullBox.left, FullBox.top,
264 	                FullBox.right - FullBox.left,
265                         FullBox.bottom - FullBox.top,
266                         TRUE);      /* enlarge dialog box and repaint */
267 	    return TRUE;
268 	}
269 	break;
270     default:
271 	return FALSE;
272     }
273     return FALSE;
274 } /* AboutDlgProc */
275 
276 /* SetCheck:    puts a check mark in a check box */
277 /* ========                                      */
278 
SetCheck(HWND hDlg,int BoxID)279 void  PASCAL    SetCheck (HWND hDlg, int BoxID)
280 {
281     SendMessage (GetDlgItem (hDlg, BoxID), BM_SETCHECK, 1, 0L);
282 } /* SetCheck */
283 
284 /* GetCheck:    TRUE is the check box is checked */
285 /* ========                                      */
286 
GetCheck(HWND hDlg,int BoxID)287 BOOL  PASCAL    GetCheck (HWND hDlg, int BoxID)
288 {
289     return (SendMessage (GetDlgItem (hDlg, BoxID), BM_GETCHECK, 0, 0L) != 0);
290 } /* GetCheck */
291 
292 /* ModeDlgProc: Modes dialog box function */
293 /* ===========                            */
294 
295 /* must be invoked through DialogBoxParam, with LOWORD(dwInitParam) set
296    to TRUE for global modes and FALSE for current buffer modes */
297 
ModeDlgProc(HWND hDlg,UINT wMsg,UINT wParam,LONG lParam)298 int EXPORT FAR PASCAL  ModeDlgProc (HWND hDlg, UINT wMsg, UINT wParam,
299                                     LONG lParam)
300 {
301     char    s[40+NBUFN];
302     static int *modep;      /* mode flags pointer */
303 
304     switch (wMsg) {
305 
306     case WM_INITDIALOG:
307         if (LOWORD(lParam)) {
308 	    strcpy (s, TEXT331);    /* "Global modes" */
309 	    modep = &gmode;
310 	}
311         else {
312 	    strcpy (s, TEXT332);    /* "Modes for buffer: " */
313 	    strcat (s, curbp->b_bname);
314 	    modep = &(curbp->b_mode);
315 	}
316         SetWindowText (hDlg, s);
317         if (*modep & MDWRAP) SetCheck (hDlg, ID_WRAP);
318 	if (*modep & MDCMOD) SetCheck (hDlg, ID_CMODE);
319         if (*modep & MDEXACT) SetCheck (hDlg, ID_EXACT);
320         if (*modep & MDVIEW) SetCheck (hDlg, ID_VIEW);
321         if (*modep & MDOVER) SetCheck (hDlg, ID_OVER);
322         if (*modep & MDREPL) SetCheck (hDlg, ID_REP);
323         if (*modep & MDMAGIC) SetCheck (hDlg, ID_MAGIC);
324         if (*modep & MDCRYPT) SetCheck (hDlg, ID_CRYPT);
325         if (*modep & MDASAVE) SetCheck (hDlg, ID_ASAVE);
326 	return TRUE;
327 
328     case WM_COMMAND:
329 	if (NOTIFICATION_CODE != BN_CLICKED) break;
330 	switch (LOWORD(wParam)) {
331 
332 	case 1:		/* OK */
333 	    *modep = 0;
334 	    if (GetCheck (hDlg, ID_WRAP)) *modep |= MDWRAP;
335 	    if (GetCheck (hDlg, ID_CMODE)) *modep |= MDCMOD;
336 	    if (GetCheck (hDlg, ID_EXACT)) *modep |= MDEXACT;
337 	    if (GetCheck (hDlg, ID_VIEW)) *modep |= MDVIEW;
338 	    if (GetCheck (hDlg, ID_OVER)) *modep |= MDOVER;
339 	    if (GetCheck (hDlg, ID_REP)) *modep |= MDREPL;
340 	    if (GetCheck (hDlg, ID_MAGIC)) *modep |= MDMAGIC;
341 	    if (GetCheck (hDlg, ID_CRYPT)) *modep |= MDCRYPT;
342 	    if (GetCheck (hDlg, ID_ASAVE)) *modep |= MDASAVE;
343 	    EndDialog (hDlg, 0);
344 	    break;
345 
346 	case 2:		/* Cancel */
347 	    EndDialog (hDlg, -1);
348 	    break;
349 
350 	case ID_OVER:
351 	    /* if OVER set, clear the REP mode check-box */
352 	    if (GetCheck (hDlg, ID_OVER)) {
353 		SendMessage (GetDlgItem (hDlg, ID_REP), BM_SETCHECK, 0, 0L);
354 	    }
355 	    break;
356 
357 	case ID_REP:
358 	    /* if REP set, clear the OVER mode check-box */
359 	    if (GetCheck (hDlg, ID_REP)) {
360 		SendMessage (GetDlgItem (hDlg, ID_OVER), BM_SETCHECK, 0, 0L);
361 	    }
362 	    break;
363 
364 	}
365 	break;
366 
367     default:
368 	return FALSE;
369     }
370     return FALSE;
371 } /* ModeDlgProc */
372 
373 /* IsMenuSeparator:  TRUE if the item is a separator */
374 /* ===============                                   */
IsMenuSeparator(HMENU hMenu,int Position)375 static BOOL PASCAL IsMenuSeparator (HMENU hMenu, int Position)
376 {
377     DWORD state;
378 
379     if (GetSubMenu (hMenu, Position) != 0) return FALSE;
380     if ((state = GetMenuState (hMenu, Position, MF_BYPOSITION)) != -1) {
381         if (state & MF_SEPARATOR) return TRUE;
382     }
383     return FALSE;
384 } /* IsMenuSeparator */
385 
386 /* GetMenuEntryID:   give back ID of MenuItem or popup */
387 /* ==============                                      */
388 
389 /* if the entry is a menu item (not a popup), its ID is returned. If the
390    entry is a popup, the ID of the first entry in this popup is
391    returned, added to n*IDM_POPUP where n is the number of popup levels.
392 
393    If the entry is neither an item nor a popup, an item id of 0 is
394    returned */
395 
GetMenuEntryID(HMENU hMenu,int Position)396 UINT  PASCAL    GetMenuEntryID (HMENU hMenu, int Position)
397 {
398     UINT    id;
399     HMENU   hSubMenu;
400 
401     if (IsMenuSeparator (hMenu, Position)) {
402         id = 0;
403     }
404     else if ((hSubMenu = GetSubMenu (hMenu, Position)) != 0) {
405 	id = IDM_POPUP + GetMenuEntryID (hSubMenu, 0);
406     }
407     else id = GetMenuItemID (hMenu, Position);
408     return id;
409 } /* GetMenuEntryID */
410 
411 /* FindKeyBinding:  scan the key binding table, return first match */
412 /* ==============                                                  */
413 
414 /* If there is no match, the returned value point to the BINDNUL entry
415    */
416 
FindKeyBinding(void * Func)417 KEYTAB * FAR PASCAL FindKeyBinding (void *Func)
418 {
419     register KEYTAB *KTp;
420 
421     for (KTp = &keytab[0]; KTp->k_type != BINDNUL; ++KTp) {
422 	if (((KTp->k_type == BINDFNC) && (KTp->k_ptr.fp == Func)) ||
423 	    ((KTp->k_type == BINDBUF) && (KTp->k_ptr.buf == Func)))
424 	    if (!(KTp->k_code & MOUS)) break;   /* found it! (we skip
425 						   mouse bindings) */
426     }
427     return KTp;
428 
429 } /* FindKeyBinding */
430 
431 /* GetKeyText:  translates a key code into a CUA-type description */
432 /* ==========                                                     */
433 
434 /* Returns the length (excluding the terminating NULL) of the text if it
435    fits in the supplied buffer, 0 otherwise (in which case the buffer
436    contents are unuseable). */
437 
438 /* This function uses the ctrlx_key and meta_key static variables to
439    avoid scanning the key binding table too often. If these are NULL,
440    however, it looks them up. Therefore, these variables can be zeroed
441    if there is a doubt about their validity */
442 
GetKeyText(int Key,char * Text,int TextLength)443 int  PASCAL GetKeyText (int Key, char *Text, int TextLength)
444 {
445     int     i;      /* index in Text */
446     char    c;
447 
448     if (Key & (CTLX | META)) {
449         unsigned *prefix_key_ptr;
450         int     n;
451 
452         prefix_key_ptr = (Key & CTLX) ? &ctrlx_key : &meta_key;
453 	if (*prefix_key_ptr == 0) {
454 	    KEYTAB  *KTp;
455 
456 	    KTp = FindKeyBinding ((Key & CTLX) ? cex : meta);
457 	    if (KTp->k_type == BINDNUL) return 0;
458 	    *prefix_key_ptr = KTp->k_code;
459 	}
460 #define NBSPACES    3   /* # of spaces after prefix */
461 	i = GetKeyText (*prefix_key_ptr, Text, TextLength - NBSPACES);
462 	if (i == 0)  return 0;
463 	for (n = 0; n < NBSPACES; ++n) Text[i++] = ' ';
464     }
465     else {
466 	i = 0;
467     }
468 
469 #define APPENDTEXT(s)   {if (TextLength - i < sizeof(s)) return 0;\
470                          strcpy (&Text[i], s); i += sizeof(s) - 1;}
471 
472     c = (char)Key;
473 
474     if (Key & ALTD) APPENDTEXT(TEXT310) /* "Alt+" */
475 
476     if (Key & SHFT) APPENDTEXT(TEXT311) /* "Shift+" */
477 
478     if (Key & CTRL) {
479         switch (c) {
480 	case 'H':
481 	    APPENDTEXT(TEXT312) /* "BkSp" */
482 	    goto all_done;
483 	case 'I':
484 	    APPENDTEXT(TEXT313) /* "Tab" */
485 	    goto all_done;
486 	case 'M':
487 	    APPENDTEXT(TEXT314) /* "Enter" */
488 	    goto all_done;
489 	case '[':
490 	    APPENDTEXT(TEXT315)  /* "Esc" */
491 	    goto all_done;
492 	default:
493 	    APPENDTEXT(TEXT316) /* "Ctrl+" */
494 	    break;
495 	}
496     }
497 
498     if (Key & SPEC) {
499         switch (c) {
500 	case '<':
501 	    APPENDTEXT(TEXT317) /* "Home" */
502 	    break;
503 	case 'N':
504 	    APPENDTEXT(TEXT318) /* "DownArrow" */
505 	    break;
506 	case 'P':
507 	    APPENDTEXT(TEXT319) /* "UpArrow" */
508 	    break;
509 	case 'B':
510 	    APPENDTEXT(TEXT320) /* "LeftArrow" */
511 	    break;
512 	case 'F':
513 	    APPENDTEXT(TEXT321) /* "RightArrow" */
514 	    break;
515 	case '>':
516 	    APPENDTEXT(TEXT322) /* "End" */
517 	    break;
518 	case 'Z':
519 	    APPENDTEXT(TEXT323) /* "PageUp" */
520 	    break;
521 	case 'V':
522 	    APPENDTEXT(TEXT324) /* "PageDown" */
523 	    break;
524 	case 'C':
525 	    APPENDTEXT(TEXT325) /* "Ins" */
526 	    break;
527 	case 'D':
528 	    APPENDTEXT(TEXT326) /* "Del" */
529 	    break;
530 	case '0':
531 	    APPENDTEXT(TEXT327) /* "F10" */
532 	    break;
533 	default:    /* single digit function key */
534 	    if ((c < '1') || (c > '9')) return 0; /* unknown! */
535 	    if (TextLength - i < 3) return 0;
536 	    Text[i++] = CHAR328;    /* 'F' */
537 	    Text[i++] = c;
538 	    break;
539         }
540     }
541     else if (c == ' ') APPENDTEXT(TEXT329)  /* "SpaceBar" */
542     else {
543         /* normal char */
544         if (TextLength - i < 2) return 0;
545         Text[i++] = c;
546     }
547 all_done:
548     Text[i] = '\0';
549     return i;
550 } /* GetKeyText */
551 
552 /* UpdateMenuItemText:  sets the key binding info in a menu item */
553 /* ==================                                            */
554 
UpdateMenuItemText(HMENU hMenu,int Position,MENUTAB * MTp)555 void  PASCAL    UpdateMenuItemText (HMENU hMenu, int Position,
556                                     MENUTAB *MTp)
557 {
558     KEYTAB  *KTp;
559     char    NewText[MAXMENUTITLE];
560     char    OldText[MAXMENUTITLE];
561     register int i;
562 
563     GetMenuString (hMenu, Position, OldText, MAXMENUTITLE, MF_BYPOSITION);
564     strcpy (NewText, OldText);
565     for (i = 0; (NewText[i] != '\0') && (NewText[i] != '\t'); ++i) ;
566         /* find the first tab char or the string's end */
567 
568     KTp = FindKeyBinding (MTp->m_word & MB_BUF ? (void*)MTp->m_ptr.buf :
569 						 (void*)MTp->m_ptr.fp);
570     if (KTp->k_type != BINDNUL) {
571 	NewText[i] = '\t';
572 	if (!GetKeyText (KTp->k_code, &NewText[i+1], MAXMENUTITLE + 1 - i))
573 	    goto no_binding;    /* if out of room, no binding text */
574     }
575     else {
576 no_binding:
577         /* let's erase the binding text */
578 	NewText[i] = '\0';
579     }
580     if (strcmp (NewText, OldText) == 0) return;
581     ModifyMenu (hMenu, Position, MF_BYPOSITION | MF_STRING,
582                 GetMenuItemID (hMenu, Position), NewText);
583 } /* UpdateMenuItemText */
584 
585 /* InitMenuPopup:   perform popup menu init before display (WM_INITMENUPOPUP) */
586 /* =============                                                              */
587 
588 /* here, we may gray menu entries that are invalid. We also try to
589    display a key binding after each menu item */
590 
InitMenuPopup(HMENU hMenu,LONG lParam)591 void FAR PASCAL InitMenuPopup (HMENU hMenu, LONG lParam)
592 {
593     int     Position;
594     int     ItemCount;
595     BOOL    Enable;
596     UINT    EntryID;
597     register MENUTAB *MTp; /* points the appropriate entry in MenuStaticBind */
598     MENUTAB *PrevMTp;      /* to control MenuStaticBind scanning */
599 
600     if (HIWORD(lParam)) return;     /* do not process the system menu */
601 
602     ctrlx_key = meta_key = 0;       /* these may have changed since the
603 				       last call to GetKeyText */
604 
605     /*-Scan the menu's items */
606     ItemCount = GetMenuItemCount (hMenu);
607     PrevMTp = &MenuStaticBind[0];
608     for (Position =  0; Position < ItemCount; Position++) {
609         EntryID = GetMenuEntryID (hMenu, Position);
610 
611         if (EntryID == 0) continue; /* skip separators */
612 
613         /*-lookup the item in the menu binding table */
614         /* since we assume the items' order follows the binding table,
615 	   we try to optimize the lookup by starting from the last
616 	   found entry */
617         if (EntryID >= IDM_POPUP) {
618 	    /* for popup menus MTp yields the nullproc entry */
619             MTp = &MenuStaticBind[0];
620         }
621         else if (EntryID >= IDM_DYNAMIC) {
622 	    /* for dynamic items, MTp is directly set to the proper
623 	       entry in the MenuDynBind */
624 	    MTp = &MenuDynBind[EntryID - IDM_DYNAMIC];
625 	}
626         else {
627             MTp = PrevMTp + 1;
628 	    for (;;) {
629 	        if (MTp->m_word == EntryID) {
630 	            /* found it! */
631 		    PrevMTp = MTp;  /* prepare for next item */
632 		    break;
633 	        }
634 	        if (MTp == PrevMTp) {
635 	            /* scan completed, item not found! */
636 	            MTp = &MenuStaticBind[0]; /* use default */
637 	            break;
638 	        }
639 	        if (MTp->m_word == 0) {
640                     MTp = &MenuStaticBind[0];   /* cycle through */
641                 }
642                 else ++MTp;
643 	    }
644 	}
645 
646  	/*-for each item found in the binding table, display a key binding */
647 	if (MTp != &MenuStaticBind[0]) UpdateMenuItemText (hMenu, Position, MTp);
648 
649 	/*-if not quiescent, gray most items */
650         if (notquiescent) {
651             switch (EntryID) {
652 #ifdef  IDM_DEBUG
653 	    case IDM_DEBUG:
654 #endif
655 	    case IDM_QUIT:
656 	    case IDM_CTRLG:
657 	    case IDM_ABOUT:
658 	    case IDM_WHELPINDEX:
659 	    case IDM_WHELPKEYBOARD:
660 	    case IDM_WHELPCOMMANDS:
661 	    case IDM_WHELPPROCEDURES:
662 		Enable = TRUE;
663 		break;
664 	    default:
665 		Enable = FALSE;
666 		break;
667 	    }
668 	}
669 	else {
670 	    /*-if quiescent, gray the invalid items, depending on
671 	       appropriate conditions */
672 	    Enable = TRUE;
673             switch (EntryID) {
674 	    case IDM_FILESAVE:
675 	    case IDM_UNMARK:
676 		/* grayed if current buffer is unmarked */
677 		if (!(curbp->b_flag & BFCHG)) Enable = FALSE;
678 		break;
679             case IDM_NEXTBUFFER:
680 	    case IDM_USEBUFFER:
681 	    case IDM_KILLBUFFER:
682 		/* grayed if sole visible buffer */
683 		if (getdefb () == NULL) Enable = FALSE;
684 		break;
685 	    case IDM_NARROW:
686 	        /* grayed if already narrowed */
687 		if (curbp->b_flag & BFNAROW) Enable = FALSE;
688 	        /* and if no mark... */
689             case IDM_CUTREGION:
690             case IDM_CLIPREGION:
691             case IDM_EDIT_REGION_POPUP:
692 		/* grayed if no mark0 in current window */
693 		if (curwp->w_markp[0] == NULL) Enable = FALSE;
694 		break;
695             case IDM_REMMARK:
696             case IDM_SWAPMARK:
697             case IDM_GOTOMARK:
698                 /* grayed if no mark at all in current window */
699                 {
700                     int     i;
701                     Enable = FALSE;
702                     for (i = 0; i <= 9; i++) if (curwp->w_markp[i]) {
703                         Enable = TRUE;
704                         break;
705                     }
706                 }
707                 break;
708 	    case IDM_WIDEN:
709 		/* grayed if not narrowed */
710 		if (!(curbp->b_flag & BFNAROW)) Enable = FALSE;
711 		break;
712 	    case IDM_DELWIND:
713 	    case IDM_ONLYWIND:
714 	    case IDM_NEXTWIND:
715 	    case IDM_PREVWIND:
716 	    case IDM_NEXTUP:
717 	    case IDM_NEXTDOWN:
718 	    case IDM_FILE_WINDOW_SIZE_POPUP:
719 		/* grayed if only one window in current screen */
720 		if (wheadp->w_wndp == NULL) Enable = FALSE;
721 		break;
722 	    case IDM_EDIT_CLIPBOARD_POPUP:
723 		/* grayed if clipboard empty and no mark set */
724 		if ((curwp->w_markp[0] == NULL) &&
725 		    !IsClipboardFormatAvailable (CF_TEXT)) Enable = FALSE;
726 		break;
727 	    case IDM_INSERTCLIP:
728 		/* grayed if clipboard empty */
729 		if (!IsClipboardFormatAvailable (CF_TEXT)) Enable = FALSE;
730 		break;
731 	    case IDM_YANK:
732 		/* grayed if no kill buffer */
733 		if (kbufh[kill_index] == NULL) Enable = FALSE;
734 		break;
735 	    case IDM_FORWHUNT:
736 	    case IDM_BACKHUNT:
737 		/* grayed if no search string */
738 		if (pat[0] == '\0') Enable = FALSE;
739 		break;
740 	    case IDM_CTLXE:
741 		/* grayed if no keyboard macro */
742 		break;
743 	    case IDM_TILE_VERTICALLY:
744 	        if (!Win31API) Enable = FALSE;
745 	        break;
746 	    }
747 
748 	    /*-If view mode, gray the items flagged IDM_NOTVIEW */
749 	    if (curbp->b_mode & MDVIEW) {
750 	        if (!(EntryID & (IDM_DYNAMIC | IDM_POPUP)) &&
751                     (MTp->m_word & IDM_NOTVIEW)) Enable = FALSE;
752 	    }
753         }
754 
755         EnableMenuItem (hMenu, Position,
756                         MF_BYPOSITION | (Enable ? MF_ENABLED : MF_GRAYED));
757     }
758 } /* InitMenuPopup */
759 
760 /* SimulateExtendedKey:    feed an extended key into the input stream */
761 /* ===================                                                */
762 
SimulateExtendedKey(int ec)763 static void PASCAL  SimulateExtendedKey (int ec)
764 {
765     char    prefix;
766 
767     if (in_room(5)) {
768 	prefix = ec >> 8;
769 	if (prefix != 0) {
770 	    in_put (0);
771 	    in_put (prefix);
772 	    if (prefix & (MOUS >> 8)) {
773 		/* in case the key is a mouse action, supply
774 		   dummy mouse position info */
775 		in_put (1);
776 		in_put (1);
777 	    }
778 	}
779 	in_put (ec & 0xFF);
780     }
781 } /* SimulateExtendedKey */
782 
783 /* MenuCommand: WM_COMMAND message handling */
784 /* ===========                              */
785 
786 /* returns TRUE if the command has been recognized and FALSE otherwise
787    */
788 
MenuCommand(UINT wParam,LONG lParam)789 BOOL FAR PASCAL MenuCommand (UINT wParam, LONG lParam)
790 {
791     FARPROC     ProcInstance;
792     DWORD       HelpContext;
793 
794     switch (LOWORD(wParam)) {
795         /* the menu choices from here down to the default statement are
796            valid even in the not-quiescent case */
797 
798     case IDM_ABOUT:
799 	ProcInstance = MakeProcInstance ((FARPROC)AboutDlgProc,
800 					 hEmacsInstance);
801 	DialogBox (hEmacsInstance, "ABOUT", hFrameWnd, ProcInstance);
802 	FreeProcInstance (ProcInstance);
803 	break;
804 
805 #ifdef  IDM_DEBUG
806     case IDM_DEBUG:     /* places a call to the debugger */
807 	DebugBreak ();
808 	break;
809 #endif
810 
811     case IDM_QUIT:
812 	PostMessage (hFrameWnd, WM_CLOSE, 0, 0L);
813 	break;
814 
815     case IDM_CTRLG:
816 	/* this is a special case: we feed the abort key into the input
817 	   stream */
818 	SimulateExtendedKey (abortc);
819 	break;
820 
821     case IDM_WHELPINDEX:
822 	HelpContext = 0;
823 	goto InvokeHelp;
824     case IDM_WHELPKEYBOARD:
825 	HelpContext = HELPID_KEYBOARD;
826 	goto InvokeHelp;
827     case IDM_WHELPCOMMANDS:
828 	HelpContext = HELPID_COMMANDS;
829 	goto InvokeHelp;
830     case IDM_WHELPPROCEDURES:
831 	HelpContext = HELPID_PROCEDURES;
832 InvokeHelp:
833 	WinHelp (hFrameWnd, MainHelpFile,
834                  HelpContext ? HELP_CONTEXT : HELP_INDEX, HelpContext);
835 	MainHelpUsed = TRUE;
836 	break;
837 
838     default:
839 	if (notquiescent) return TRUE;  /* abort processing */
840 
841 	/* the following IDs are processed only if emacs is quiescent */
842 	switch (LOWORD(wParam)) {
843 
844 	case IDM_GLOBMODE:
845 	case IDM_MODE:
846 	    ProcInstance = MakeProcInstance ((FARPROC)ModeDlgProc,
847 					     hEmacsInstance);
848 	    DialogBoxParam (hEmacsInstance, "MODES",
849 			    hFrameWnd, ProcInstance,
850 			    (DWORD)(wParam == IDM_GLOBMODE));
851 	    FreeProcInstance (ProcInstance);
852 	    if (wParam = IDM_MODE) {
853 		upmode ();
854 		update (FALSE);
855 	    }
856 	    break;
857 
858 	case IDM_FONT:
859 	    PickEmacsFont ();
860 	    break;
861 
862 	case IDM_CASCADE:
863 	    SendMessage (hMDIClientWnd, WM_MDICASCADE, 0, 0L);
864 	    break;
865 	case IDM_TILE_HORIZONTALLY:
866 	    SendMessage (hMDIClientWnd, WM_MDITILE, MDITILE_VERTICAL, 0L);
867 	    break;
868 	case IDM_TILE_VERTICALLY:
869 	    SendMessage (hMDIClientWnd, WM_MDITILE, MDITILE_HORIZONTAL, 0L);
870 	    break;
871 	case IDM_ARRANGEICONS:
872 	    SendMessage (hMDIClientWnd, WM_MDIICONARRANGE, 0, 0L);
873 	    break;
874 
875 	case IDM_NORMALIZE:
876 	    {
877 		SCREEN  *sp;
878 
879 		sp = (SCREEN*)GetWindowLong ((HWND)(SendMessage (hMDIClientWnd,
880 					            WM_MDIGETACTIVE, 0, 0L)),
881 		                             GWL_SCRPTR);
882 		newsize (TRUE, sp->s_nrow);
883 		newwidth (TRUE, sp->s_ncol);
884 		update (FALSE);
885 	    }
886 	    break;
887 
888 	default:
889 	    if (wParam >= IDM_FIRSTCHILD) return FALSE;
890 	    GenerateMenuSeq (wParam);
891 	    return TRUE;
892 	}
893     }
894 
895     /* we have processed an internal menu command */
896     if (!notquiescent) GenerateMenuSeq (IDM_NULLPROC);
897         /* this flushes a possible numeric argument */
898     return TRUE;
899 } /* MenuCommand */
900 
901 /* GetScreenMenuHandle: returns the handle to the 'Screen' menu (for MDI mgt) */
902 /* ===================                                                        */
903 
GetScreenMenuHandle(void)904 HMENU FAR PASCAL GetScreenMenuHandle (void)
905 {
906     HMENU   hBaseMenu;
907     int     Pos;
908 
909     hBaseMenu = GetMenu (hFrameWnd);
910     for (Pos = GetMenuItemCount (hBaseMenu) - 1; Pos >= 0; Pos--) {
911         if (GetMenuEntryID (hBaseMenu, Pos) == IDM_SCREEN_POPUP) {
912             return GetSubMenu (hBaseMenu, Pos);
913         }
914     }
915     return NULL;
916 } /* GetScreenMenuHandle */
917 
918 /* MenuEntryCount:  count of menu entries, excluding the MDI buttons */
919 /* ==============                                                    */
920 
MenuEntryCount(HMENU hMenu)921 static int PASCAL   MenuEntryCount (HMENU hMenu)
922 {
923     int     Count;
924 
925     Count = GetMenuItemCount (hMenu);
926     if (hMenu == GetMenu (hFrameWnd)) {
927 #if WINDOW_MSWIN32
928         if (GetWindowLong((HWND)SendMessage (hMDIClientWnd, WM_MDIGETACTIVE,
929                                              0, 0L), GWL_STYLE) &
930             WS_MAXIMIZE) {
931 #else
932         if (HIWORD(SendMessage (hMDIClientWnd, WM_MDIGETACTIVE, 0, 0L))) {
933 #endif
934             /* an MDI child is maximized ==> we have MDI buttons in the
935 	       menu bar */
936 	    Count -= 2;
937 	}
938     }
939     return Count;
940 } /* MenuEntryCount */
941 
942 /* MenuEntryOffset:  position of the first non-MDI button menu entry */
943 /* ===============                                                   */
944 
945 static int PASCAL   MenuEntryOffset (HMENU hMenu)
946 {
947     if (hMenu == GetMenu (hFrameWnd)) {
948 #if WINDOW_MSWIN32
949         if (GetWindowLong((HWND)SendMessage (hMDIClientWnd, WM_MDIGETACTIVE,
950                                              0, 0L), GWL_STYLE) &
951             WS_MAXIMIZE) {
952 #else
953         if (HIWORD(SendMessage (hMDIClientWnd, WM_MDIGETACTIVE, 0, 0L))) {
954 #endif
955             /* an MDI child is maximized ==> we have MDI buttons in the
956 	       menu bar */
957 	    return 1;
958 	}
959     }
960     return 0;
961 
962 } /* MenuEntryOffset */
963 
964 /* ParseMenu:   parse a piece of menu path */
965 /* =========                               */
966 
967 static BOOL PASCAL  ParseMenu (char *Name, char *Title, int *Posp)
968 
969 /* Puts the text part of the menu name in Title (at most MAXMENUTITLE
970    characters including the \0) and the position number in *Posp. If no
971    position is specified, *Posp is set to -1. The returned BOOL is TRUE
972    if no error occured and FAILD if a syntax problem was diagnosed */
973 {
974     register int i;
975 
976     *Posp = -1;
977     for (i = 0; Name[i] != '\0'; i++) {
978         if (i >= MAXMENUTITLE - 1) {
979             mlwrite (TEXT300);  /* "[Incorrect menu]" */
980             return FAILD;
981         }
982         if (Name[i] == '>') break;
983         if (Name[i] == '@') {   /* must parse a position index */
984             unsigned char *s;
985 
986             *Posp = 0;
987             for (s = &Name[i+1]; (*s != '>') && (*s != '\0'); s++) {
988                 if ((*s >= '0') && (*s <= '9')) {
989                     *Posp = (*Posp * 10) + (*s - '0');
990                 }
991                 else {      /* non numerical character! */
992                     mlwrite (TEXT300);  /* "[Incorrect menu]" */
993                     return FAILD;
994                 }
995             }
996             break;
997         }
998         Title[i] = Name[i];
999     }
1000     Title[i] = '\0';
1001     return TRUE;
1002 } /* ParseMenu */
1003 
1004 /* LocateMenu:  locate a menu entry matching a piece of menu path */
1005 /* ==========                                                     */
1006 
1007 static BOOL PASCAL  LocateMenu (char *Name, CURMENU *CM)
1008 
1009 /* The returned BOOL is TRUE if a matching menu entry was found, FALSE
1010    if no such entry was found and FAILD if a syntax problem was
1011    diagnosed. The CURMENU structure is updated if the entry was found.
1012    */
1013 {
1014     HMENU   hMenu;
1015     int     ItemCount;
1016     int     Pos, StartPos, PosOffset;
1017     BOOL    Result, DoScan;
1018     char    Target[MAXMENUTITLE];
1019     char    EntryName[MAXMENUTITLE];
1020 
1021     while (*Name == '<') {
1022 	++Name;
1023 	--CM->cm_x;
1024 	CM->cm_pos = -1;
1025     }
1026     if ((Result = ParseMenu (Name, Target, &Pos)) != TRUE) {
1027 	return Result;
1028     }
1029     if (CM->cm_x < 0) {
1030 	hMenu = GetMenu (hFrameWnd);
1031     }
1032     else hMenu = CM->cm_parent[CM->cm_x];
1033     PosOffset = MenuEntryOffset (hMenu);
1034     ItemCount = GetMenuItemCount (hMenu);
1035     if (DoScan = (Pos < 0)) Pos = CM->cm_pos;
1036     if (Pos < 0) Pos = 0;
1037     Pos += PosOffset;
1038     StartPos = Pos;
1039     do {
1040 	char    *s;
1041 
1042 	if (IsMenuSeparator (hMenu, Pos)) {
1043 	    EntryName[0] = '\0';
1044 	}
1045 	else {
1046             GetMenuString (hMenu, Pos, (LPSTR)&EntryName[0],
1047 		           MAXMENUTITLE, MF_BYPOSITION);
1048 	    if ((s = strchr (EntryName, '\t')) != NULL) *s = '\0';
1049 	        /* discard the accelerator/key-binding info */
1050 	}
1051 	if (strcmp (EntryName, Target) == 0) goto MenuLocated;
1052         if (!DoScan) {
1053             if (*Target == '\0') goto MenuLocated;
1054             else break; /* no matching target at indicated position */
1055         }
1056 	if (++Pos >= ItemCount) Pos = 0;
1057     } while (Pos != StartPos);
1058     return FALSE;   /* not found */
1059 MenuLocated:
1060     CM->cm_pos = Pos - PosOffset;
1061     return TRUE;
1062 } /* LocateMenu */
1063 
1064 /* AddMenuEntry:    add a menu entry (or, recursively, a cascade of entries) */
1065 /* ============                                                              */
1066 
1067 static BOOL PASCAL  AddMenuEntry (char *Name, UINT ID, CURMENU *CM,
1068                                       WORD *MenuType)
1069 /* the MenuType is a set of flags set returned by this function:
1070    MT_DUMMY indicates that the menu item is a mere separator, MT_MENUBAR
1071    indicates that the menu bar has been modified */
1072 {
1073     HMENU   hMenu, hPopup;
1074     int     Pos;
1075     BOOL    Result;
1076     char    EntryName[MAXMENUTITLE];
1077     WORD    MenuFlags;
1078 
1079     if ((Result = ParseMenu (Name, EntryName, &Pos)) != TRUE) {
1080         return Result;
1081     }
1082     if (CM->cm_x < 0) {
1083 	hMenu = GetMenu (hFrameWnd);
1084 	*MenuType |= MT_MENUBAR;
1085     }
1086     else hMenu = CM->cm_parent[CM->cm_x];
1087     Name = strchr(Name, '>');
1088     if (Name != NULL) {     /* our task is to set up a popup menu */
1089 	if (CM->cm_x >= MAXMENUPARENT - 1) {
1090 	    mlwrite (TEXT301);  /* "[Too many nested popup menus]" */
1091 	    return FALSE;
1092 	}
1093 	if ((hPopup = CreatePopupMenu ()) == NULL) {
1094 	    mlwrite (TEXT302);  /* "[lack of resources]" */
1095 	    return ABORT;
1096 	}
1097 	MenuFlags = MF_POPUP | MF_STRING;
1098     }
1099     else {  /* this is a menu entry, not a popup */
1100 	if (strcmp (EntryName, "-") == 0) {    /* only a separator */
1101 	    MenuFlags = MF_SEPARATOR;
1102 	    *MenuType |= MT_DUMMY;
1103 	    ID = 0;     /* contrary to SDK documentation, InsertMenu
1104 			   does not ignore the ID for a separator in 3.0! */
1105 	}
1106 	else {
1107 	    MenuFlags = MF_STRING;
1108 	}
1109     }
1110     if (Pos < 0) {
1111         Pos = CM->cm_pos + 1;       /* to insert after current entry */
1112 	if (Pos <= 0) Pos = MenuEntryCount (hMenu); /* to append at end */
1113     }
1114     Result = InsertMenu (hMenu, Pos + MenuEntryOffset (hMenu),
1115                          MenuFlags | MF_BYPOSITION,
1116                          Name ? (UINT)hPopup : ID,
1117                          (LPSTR)&EntryName[0]);
1118     if (!Result) {
1119         mlwrite (TEXT302);  /* "[lack of resources]" */
1120         Result = FALSE;
1121     }
1122     else Result = TRUE;
1123     if (Name == NULL) {
1124         CM->cm_pos = Pos;
1125     }
1126     else {
1127         if (Result != TRUE) {
1128             DestroyMenu (hPopup);
1129         }
1130         else {
1131 	    CM->cm_parent[++CM->cm_x] = hPopup;
1132 	    CM->cm_pos = -1;
1133 	    ++Name;     /* skip the '>' */
1134 	    if ((Result = AddMenuEntry (Name, ID, CM, MenuType)) != TRUE) {
1135 		/* Menu creation FAILD at some point, we must undo our own */
1136 		DeleteMenu (hMenu, Pos + MenuEntryOffset (hMenu),
1137                             MF_BYPOSITION);
1138 	    }
1139 	}
1140     }
1141     return Result;
1142 } /* AddMenuEntry */
1143 
1144 /* AddMenuBinding:  bind a menu item to an EPOINTER */
1145 /* ==============                                   */
1146 
1147 static BOOL PASCAL  AddMenuBinding (ETYPE EPOINTER eptr, WORD type)
1148 
1149 /* called by bindtomenu and macrotomenu, once the function or macro name
1150    has been parsed */
1151 {
1152     BOOL    Result;
1153     WORD    MenuType = 0;   /* info returned by AddMenuEntry */
1154     char    FullName[NSTRING];
1155     char    *Name;
1156     UINT    DynIndex;
1157     CURMENU CM;
1158 
1159     /*-Let's make sure we have a free entry in the binding table */
1160     for (DynIndex = 0; MenuDynBind[DynIndex].m_word != 0; DynIndex++) {
1161         if (DynIndex >= MAXDYNMENU) {
1162             mlwrite (TEXT17);
1163             /* "Binding table FULL!" */
1164             return FALSE;
1165         }
1166     }
1167 
1168     /*-Parse the menu Name */
1169     Name = &FullName[0];
1170     if ((Result = mlreply (TEXT306, Name, NSTRING)) != TRUE) {
1171             /* "Menu: " */
1172         return Result;
1173     }
1174     if (*Name == '>') {
1175         CM.cm_pos = -1;
1176         CM.cm_x = -1;
1177         ++Name;
1178     }
1179     else CM = CurrentMenu;
1180     while ((Result = LocateMenu (Name, &CM)) == TRUE) {
1181         /* ...looping until we hit a piece of the menu path that does
1182 	   not yet exist */
1183 	HMENU   hMenu;
1184 
1185 	if (CM.cm_x < 0) hMenu = GetMenu (hFrameWnd);
1186 	else hMenu = CM.cm_parent[CM.cm_x];
1187 	hMenu = GetSubMenu (hMenu, CM.cm_pos + MenuEntryOffset (hMenu));
1188 	if (hMenu == NULL) {
1189 	    /* this is not a popup menu, we loose! */
1190 	    mlwrite (TEXT300);  /* "[Incorrect menu]" */
1191 	    return FALSE;
1192 	}
1193 	if (CM.cm_x >= MAXMENUPARENT - 1) {
1194 	    mlwrite (TEXT301);  /* "[Too many nested popup menus]" */
1195 	    return FALSE;
1196 	}
1197 	CM.cm_parent[++CM.cm_x] = hMenu;
1198 	CM.cm_pos = -1;
1199 	Name = strchr (Name, '>');
1200 	if (Name == NULL) {
1201 	    /* premature end of menu path! */
1202 	    mlwrite (TEXT300);  /* "[Incorrect menu]" */
1203 	    return FALSE;
1204 	}
1205 	++Name;     /* skip the '>' */
1206     }
1207     if (Result != FALSE) return Result;  /* syntax error, no cigar! */
1208 
1209     /*-Create the menu entry (or cascade of entries) */
1210     if ((Result = AddMenuEntry (Name, DynIndex + IDM_DYNAMIC, &CM,
1211                                 &MenuType)) != TRUE) {
1212         return Result;
1213     }
1214     CurrentMenu = CM;
1215 
1216     if (!(MenuType & MT_DUMMY)) {
1217 	/*-complete the binding */
1218 	MenuDynBind[DynIndex].m_word = type;
1219 	MenuDynBind[DynIndex].m_ptr = eptr;
1220     }
1221 
1222     if (MenuType & MT_MENUBAR) DrawMenuBar (hFrameWnd);
1223     return TRUE;
1224 } /* AddMenuBinding */
1225 
1226 /* bindtomenu:  bind a menu item to an emacs function */
1227 /* ==========                                         */
1228 
1229 PASCAL bindtomenu (int f, int n)
1230 /* command arguments IGNORED */
1231 {
1232     ETYPE EPOINTER  e;
1233 
1234     e.fp = getname (TEXT304);   /* "Function: " */
1235     if (e.fp == NULL) {
1236         mlwrite (TEXT16);       /* "[No such function]" */
1237         return FALSE;
1238     }
1239     return AddMenuBinding (e, MB_FNC);
1240 } /* bindtomenu */
1241 
1242 /* macrotomenu: bind a menu item to a macro (i.e. a buffer) */
1243 /* ===========                                              */
1244 
1245 PASCAL macrotomenu (int f, int n)
1246 /* command arguments IGNORED */
1247 {
1248     ETYPE EPOINTER  e;
1249     char    Name[NBUFN];
1250     BOOL    Result;
1251 
1252     if ((Result = mlreply(TEXT305, &Name[1], NBUFN-2)) != TRUE) {
1253             /* "Macro: " */
1254         return Result;
1255     }
1256     Name[0] = '[';
1257     strcat (Name, "]");
1258     if ((e.buf = bfind (Name, FALSE, 0)) == NULL) {
1259         mlwrite (TEXT130);
1260         /* "Macro not defined" */
1261         return FALSE;
1262     }
1263     return AddMenuBinding (e, MB_BUF);
1264 } /* macrotomenu */
1265 
1266 /* DeleteMenuBinding:   remove a menu entry and its binding or sub-entries */
1267 /* =================                                                       */
1268 
1269 static BOOL PASCAL  DeleteMenuBinding (HMENU hMenu, int Pos)
1270 /* returns TRUE except when an attempt is made to delete the 'Screen'
1271    menu */
1272 {
1273     BOOL    Result;
1274     UINT    ID;
1275     HMENU   hPopup;
1276 
1277     Pos += MenuEntryOffset (hMenu);
1278     if (!IsMenuSeparator (hMenu, Pos)) {
1279         if ((ID = GetMenuItemID (hMenu, Pos)) != -1) {
1280             /* this is a pure menu item */
1281             if (ID >= IDM_DYNAMIC) {
1282                 /* let's free the dynamic binding table entry */
1283                 MenuDynBind[ID - IDM_DYNAMIC].m_word = 0;
1284             }
1285         }
1286         else if ((hPopup = GetSubMenu (hMenu, Pos)) != NULL) {
1287             /* this is a popup, we must delete all the contained entries */
1288             int     i;
1289 
1290             if (ID == IDM_SCREEN_POPUP) {
1291                 /* this menu is used by the system's MDI manager to list the
1292 	           MDI childs. We cannot remove it */
1293                 return FALSE;
1294             }
1295             for (i = MenuEntryCount (hPopup); i > 0;) {
1296                 DeleteMenuBinding (hPopup, --i);
1297             }
1298         }
1299     }
1300     DeleteMenu (hMenu, Pos, MF_BYPOSITION);
1301     if (hMenu == GetMenu (hFrameWnd)) DrawMenuBar (hFrameWnd);
1302     return TRUE;
1303 } /* DeleteMenuBinding */
1304 
1305 /* unbindmenu:  remove a menu entry */
1306 /* ==========                       */
1307 
1308 PASCAL unbindmenu (int f, int n)
1309 /* command arguments IGNORED */
1310 {
1311     BOOL    Result;
1312     char    FullName[NSTRING];
1313     char    *Name;
1314     CURMENU CM;
1315 
1316     Name = &FullName[0];
1317     if ((Result = mlreply (TEXT303, Name, NSTRING)) != TRUE) {
1318             /* "Menu: " */
1319         return Result;
1320     }
1321     if (*Name == '>') {
1322         CM.cm_pos = -1;
1323         CM.cm_x = -1;
1324         ++Name;
1325     }
1326     else CM = CurrentMenu;
1327     while ((Result = LocateMenu (Name, &CM)) == TRUE) {
1328 	HMENU   hMenu;
1329 
1330 	if (CM.cm_x < 0) hMenu = GetMenu (hFrameWnd);
1331 	else hMenu = CM.cm_parent[CM.cm_x];
1332 	Name = strchr (Name, '>');
1333 	if (Name != NULL) {     /* the menu path continues beyond this... */
1334 	    hMenu = GetSubMenu (hMenu, CM.cm_pos + MenuEntryOffset (hMenu));
1335 	    if (hMenu == NULL) {
1336 	        /* this is not a popup menu, we loose! */
1337 	        Result = FALSE;
1338 	        break;
1339 	    }
1340 	    if (CM.cm_x >= MAXMENUPARENT - 1) {
1341 		mlwrite (TEXT301);  /* "[Too many nested popup menus]" */
1342 		return FALSE;
1343 	    }
1344 	    CM.cm_parent[++CM.cm_x] = hMenu;
1345 	    CM.cm_pos = 0;
1346 	    ++Name;     /* skip the '>' */
1347 	    /* and loop once again... */
1348 	}
1349 	else {                  /* this is it: the end of the menu path */
1350 	    int     Pos;
1351 
1352 	    if ((Result = DeleteMenuBinding (hMenu, CM.cm_pos)) != TRUE) {
1353 	        return FALSE;
1354 	    }
1355 	    if ((Pos = MenuEntryCount (hMenu) - 1) < CM.cm_pos) {
1356 		/* we just deleted the item at the menu's end. This
1357 		   position is no longer valid */
1358 		CM.cm_pos = Pos;
1359 	    }
1360 	    CurrentMenu = CM;
1361 	    break;      /* we are done */
1362 	}
1363     }
1364     /* we arrive here if the menu was not found or a syntax error occured */
1365     if (Result == FALSE) mlwrite (TEXT300); /* "[Incorrect menu]" */
1366     return Result;
1367 } /* unbindmenu */
1368