1 /*****************************************************************************/
2 /**       Copyright 1988 by Evans & Sutherland Computer Corporation,        **/
3 /**                          Salt Lake City, Utah                           **/
4 /**  Portions Copyright 1989 by the Massachusetts Institute of Technology   **/
5 /**                        Cambridge, Massachusetts                         **/
6 /**                                                                         **/
7 /**                           All Rights Reserved                           **/
8 /**                                                                         **/
9 /**    Permission to use, copy, modify, and distribute this software and    **/
10 /**    its documentation  for  any  purpose  and  without  fee is hereby    **/
11 /**    granted, provided that the above copyright notice appear  in  all    **/
12 /**    copies and that both  that  copyright  notice  and  this  permis-    **/
13 /**    sion  notice appear in supporting  documentation,  and  that  the    **/
14 /**    names of Evans & Sutherland and M.I.T. not be used in advertising    **/
15 /**    in publicity pertaining to distribution of the  software  without    **/
16 /**    specific, written prior permission.                                  **/
17 /**                                                                         **/
18 /**    EVANS & SUTHERLAND AND M.I.T. DISCLAIM ALL WARRANTIES WITH REGARD    **/
19 /**    TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES  OF  MERCHANT-    **/
20 /**    ABILITY  AND  FITNESS,  IN  NO  EVENT SHALL EVANS & SUTHERLAND OR    **/
21 /**    M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL  DAM-    **/
22 /**    AGES OR  ANY DAMAGES WHATSOEVER  RESULTING FROM LOSS OF USE, DATA    **/
23 /**    OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER    **/
24 /**    TORTIOUS ACTION, ARISING OUT OF OR IN  CONNECTION  WITH  THE  USE    **/
25 /**    OR PERFORMANCE OF THIS SOFTWARE.                                     **/
26 /*****************************************************************************/
27 /****************************************************************************
28  * This module is based on Twm, but has been significantly modified
29  * by Rob Nation
30  ****************************************************************************/
31 /***********************************************************************
32  * The rest of it is all my fault -- MLM
33  * mwm - "LessTif Window Manager"
34  ***********************************************************************/
35 
36 
37 #include <LTconfig.h>
38 
39 #include <stdio.h>
40 #include <string.h>
41 #include <ctype.h>
42 
43 #include <Xm/Xm.h>
44 #include <Xm/MwmUtil.h>
45 
46 #include <X11/keysym.h>
47 
48 #include "mwm.h"
49 
50 
51 
52 static int menu_on = 0;
53 static MenuRoot *ActiveMenu = NULL;	/* the active menu */
54 static MenuItem *ActiveItem = NULL;	/* the active menu item */
55 int menuFromFrameOrWindowOrTitlebar = False;
56 int Stashed_X, Stashed_Y;
57 static int MenuY = 0;
58 static MenuRoot *PrevMenu = NULL;
59 static MenuItem *PrevItem = NULL;
60 static int PrevY = 0;
61 
62 /*
63  * Calculate the pixel offsets to the start of the character position we
64  * want to underline and to the next character in the string.  Shrink by
65  * one pixel from each end and the draw a line that long two pixels below
66  * the character...
67  */
68 static void
draw_underline(ScreenInfo * scr,Window w,GC gc,int x,int y,char * txt,int posn)69 draw_underline(ScreenInfo *scr, Window w, GC gc, int x, int y,
70 	       char *txt, int posn)
71 {
72     int off1 = XTextWidth(scr->components[MWM_MENU].font,
73 			  txt, posn);
74     int off2 = XTextWidth(scr->components[MWM_MENU].font,
75 			  txt, posn + 1) - 1;
76 
77     XDrawLine(dpy, w, gc, x + off1, y + 2, x + off2, y + 2);
78 }
79 
80 /*
81  * Draws two horizontal lines to form a separator
82  */
83 static void
draw_separator(Window w,GC TopGC,GC BottomGC,int x1,int y1,int x2,int y2,int extra_off)84 draw_separator(Window w, GC TopGC, GC BottomGC, int x1, int y1, int x2, int y2,
85 	       int extra_off)
86 {
87     XDrawLine(dpy, w, TopGC, x1, y1, x2, y2);
88     XDrawLine(dpy, w, BottomGC, x1 - extra_off, y1 + 1, x2 + extra_off, y2 + 1);
89 }
90 
91 /*
92  *  Draws a little Triangle pattern within a window
93  */
94 static void
draw_arrow(Window w,GC GC1,GC GC2,GC GC3,int l,int u,int r,int b)95 draw_arrow(Window w, GC GC1, GC GC2, GC GC3, int l, int u, int r, int b)
96 {
97     int m;
98 
99     m = (u + b) / 2;
100 
101     XDrawLine(dpy, w, GC1, l, u, l, b);
102 
103     XDrawLine(dpy, w, GC2, l, b, r, m);
104     XDrawLine(dpy, w, GC3, r, m, l, u);
105 }
106 
107 /*
108  * add relief lines to a rectangular window
109  */
110 static void
relieve_rectangle(Window win,int x,int y,int w,int h,GC Hilite,GC Shadow)111 relieve_rectangle(Window win, int x, int y, int w, int h, GC Hilite, GC Shadow)
112 {
113     XDrawLine(dpy, win, Hilite, x, y, w + x - 1, y);
114     XDrawLine(dpy, win, Hilite, x, y, x, h + y - 1);
115 
116     XDrawLine(dpy, win, Shadow, x, h + y - 1, w + x - 1, h + y - 1);
117     XDrawLine(dpy, win, Shadow, w + x - 1, y, w + x - 1, h + y - 1);
118 }
119 
120 /*
121  * add relief lines to the sides only of a rectangular window
122  */
123 static void
relieve_half_rectangle(Window win,int x,int y,int w,int h,GC Hilite,GC Shadow)124 relieve_half_rectangle(Window win, int x, int y, int w, int h,
125 		       GC Hilite, GC Shadow)
126 {
127     XDrawLine(dpy, win, Hilite, x, y - 1, x, h + y);
128     XDrawLine(dpy, win, Hilite, x + 1, y, x + 1, h + y - 1);
129 
130     XDrawLine(dpy, win, Shadow, w + x - 1, y - 1, w + x - 1, h + y);
131     XDrawLine(dpy, win, Shadow, w + x - 2, y, w + x - 2, h + y - 1);
132 }
133 
134 /*
135  * Checks the function described in menuItem mi, and sees if it
136  * is an allowed function for window Tmp_Win,
137  * according to the motif way of life.
138  *
139  * This routine is used to determine whether or not to grey out menu items.
140  * FIXME: This needs to be beefed up to handle other functions (like
141  * f.send_msg) -- MLM
142  */
143 static Boolean
function_allowed(MwmWindow * tmp,MenuItem * mi)144 function_allowed(MwmWindow *tmp, MenuItem *mi)
145 {
146     /* Complex functions are a little tricky... ignore them for now */
147 
148     /* Move is a funny hint. Keeps it out of the menu, but you're still
149      * allowed * to move. */
150     if ((mi->func == F_MOVE) && tmp && !(tmp->functions & MWM_FUNC_MOVE))
151 	return False;
152 
153     if (mi->func == F_RESIZE && tmp && !(tmp->functions & MWM_FUNC_RESIZE))
154 	return False;
155 
156     /* Cannot iconify if the window is already iconified or the window
157        does not allow minimize */
158     if ((mi->func == F_ICONIFY && tmp) &&
159           ((tmp->flags & ICONIFIED) ||
160            !(tmp->functions & MWM_FUNC_MINIMIZE)))
161 	return False;
162 
163     /* Cannot resize if iconified */
164     if (mi->func == F_RESIZE && tmp && (tmp->flags & ICONIFIED))
165         return False;
166 
167     /* Can only normalize if iconified or maximized */
168     if (mi->func == F_NORMALIZE && tmp &&
169         !(tmp->flags & ICONIFIED) && !(tmp->flags & MAXIMIZED))
170 	return False;
171 
172     if (mi->func == F_MAXIMIZE && tmp && !(tmp->functions & MWM_FUNC_MAXIMIZE))
173 	return False;
174 
175     if (mi->func == F_CLOSE && tmp && !(tmp->functions & MWM_FUNC_CLOSE))
176 	return False;
177 
178     return True;
179 }
180 
181 
182 /*
183  * draws a single entry in a poped up menu
184  */
185 static void
paint_entry(ScreenInfo * scr,MwmWindow * tmp,MenuRoot * mr,MenuItem * mi)186 paint_entry(ScreenInfo *scr, MwmWindow *tmp, MenuRoot * mr, MenuItem *mi)
187 {
188     int y_offset, text_y, d, y_height;
189     GC ShadowGC, ReliefGC, currentGC;
190 
191     y_offset = mi->y_offset;
192     y_height = mi->y_height;
193     text_y = y_offset + scr->components[MWM_MENU].f_y;
194 
195     ShadowGC = scr->components[MWM_MENU].bot_GC;
196     ReliefGC = scr->components[MWM_MENU].top_GC;
197 
198     if ((!mi->prev) || (!mi->prev->state))
199 	XClearArea(dpy, mr->w, 0, y_offset - 1, mr->width, y_height + 2, 0);
200     else
201 	XClearArea(dpy, mr->w, 0, y_offset + 1, mr->width, y_height - 1, 0);
202     if ((mi->state) && (mi->func != F_TITLE) &&
203 	(mi->func != F_NOP) && *mi->item)
204     {
205 	relieve_rectangle(mr->w, 3, y_offset, mr->width - 5, mi->y_height,
206 			  ReliefGC, ShadowGC);
207 	relieve_rectangle(mr->w, 2, y_offset - 1,
208 			  mr->width - 3, mi->y_height + 2,
209 			  ReliefGC, ShadowGC);
210     }
211     relieve_half_rectangle(mr->w, 0, y_offset - 1, mr->width,
212 			   y_height + 2, ReliefGC, ShadowGC);
213 
214     if (mi->func == F_TITLE)
215     {
216 	text_y += HEIGHT_EXTRA >> 1;
217 	XDrawLine(dpy, mr->w, ShadowGC, 2, y_offset + y_height - 2,
218 		  mr->width - 3, y_offset + y_height - 2);
219 	XDrawLine(dpy, mr->w, ShadowGC, 2, y_offset + y_height - 4,
220 		  mr->width - 3, y_offset + y_height - 4);
221     }
222     else
223 	text_y += HEIGHT_EXTRA >> 1;
224 
225     if (mi->func == F_NOP && *mi->item == 0)
226     {
227 	draw_separator(mr->w, ShadowGC, ReliefGC,
228 		       2, y_offset - 1 + HEIGHT_SEPARATOR / 2,
229 		       mr->width - 3, y_offset - 1 + HEIGHT_SEPARATOR / 2, 0);
230     }
231     if (mi->next == NULL)
232 	draw_separator(mr->w, ShadowGC, ShadowGC, 1, mr->height - 2,
233 		       mr->width - 2, mr->height - 2, 1);
234 
235     if (mi == mr->first)
236 	draw_separator(mr->w, ReliefGC, ReliefGC, 0, 0, mr->width - 1, 0, -1);
237 
238     if (function_allowed(tmp, mi))
239 	currentGC = scr->components[MWM_MENU].normal_GC;
240     else {
241     /* MLM: FIXME: SHOULD GRAY OUT ITEM */
242 	currentGC = scr->components[MWM_MENU].grayed_GC;
243     }
244 
245     if (*mi->item)
246 	XDrawString(dpy, mr->w, currentGC, mi->x, text_y,
247 		    mi->item, mi->strlen);
248 
249     if (mi->strlen2 > 0 && mi->item2 && *mi->item2)
250 	XDrawString(dpy, mr->w, currentGC, mi->x2, text_y,
251 		    mi->item2, mi->strlen2);
252 
253     /* pete@tecc.co.uk: If the item has a hot key, underline it */
254     if (mi->hotkey > 0)
255 	draw_underline(scr, mr->w, currentGC, mi->x, text_y,
256 		       mi->item, mi->hotkey - 1);
257 
258     if (mi->hotkey < 0)
259 	draw_underline(scr, mr->w, currentGC, mi->x2, text_y,
260 		       mi->item2, -1 - mi->hotkey);
261 
262     d = (scr->components[MWM_MENU].f_height + HEIGHT_EXTRA - 7) / 2;
263     if (mi->func == F_POPUP)
264     {
265 	if (mi->state)
266 	    draw_arrow(mr->w, ShadowGC, ReliefGC, ShadowGC, mr->width - d - 8,
267 		       y_offset + d - 1, mr->width - d - 1, y_offset + d + 7);
268 	else
269 	    draw_arrow(mr->w, ReliefGC, ShadowGC, ReliefGC, mr->width - d - 8,
270 		       y_offset + d - 1, mr->width - d - 1, y_offset + d + 7);
271     }
272 }
273 
274 /*
275  * draws the entire menu
276  */
277 static void
paint_menu(ScreenInfo * scr,MwmWindow * tmp,MenuRoot * mr,XEvent * e)278 paint_menu(ScreenInfo *scr, MwmWindow *tmp, MenuRoot * mr, XEvent *e)
279 {
280     MenuItem *mi;
281 
282     for (mi = mr->first; mi != NULL; mi = mi->next)
283     {
284 	/* be smart about handling the expose, redraw only the entries
285 	 * that we need to
286 	 */
287 	if (e->xexpose.y < (mi->y_offset + mi->y_height) &&
288 	    (e->xexpose.y + e->xexpose.height) > mi->y_offset)
289 	{
290 	    paint_entry(scr, tmp, mr, mi);
291 	}
292     }
293     XSync(dpy, 0);
294 }
295 
296 /*
297  * Updates menu display to reflect the highlighted item
298  */
299 static int
find_entry(ScreenInfo * scr,MwmWindow * tmp)300 find_entry(ScreenInfo *scr, MwmWindow *tmp)
301 {
302     MenuItem *mi;
303     MenuRoot *actual_mr;
304     int retval = MENU_NOP;
305     MenuRoot *PrevPrevMenu;
306     MenuItem *PrevPrevItem;
307     int PrevPrevY;
308     int x, y, ChildY;
309     Window Child;
310 
311     XQueryPointer(dpy, scr->root_win, &JunkRoot, &Child,
312 		  &JunkX, &ChildY, &x, &y, &JunkMask);
313     XQueryPointer(dpy, ActiveMenu->w, &JunkRoot, &JunkChild,
314 		  &JunkX, &ChildY, &x, &y, &JunkMask);
315 
316     /* look for the entry that the mouse is in */
317     for (mi = ActiveMenu->first; mi; mi = mi->next)
318 	if (y >= mi->y_offset && y < mi->y_offset + mi->y_height)
319 	    break;
320     if (x < 0 || x > ActiveMenu->width)
321 	mi = NULL;
322 
323 
324     /* if we weren't on the active entry, let's turn the old active one off */
325     if ((ActiveItem) && (mi != ActiveItem))
326     {
327 	ActiveItem->state = 0;
328 	paint_entry(scr, tmp, ActiveMenu, ActiveItem);
329     }
330 
331     /* if we weren't on the active item, change the active item and turn it
332      * on */
333     if ((mi != ActiveItem) && (mi != NULL) && function_allowed(tmp, mi))
334     {
335 	mi->state = 1;
336 	paint_entry(scr, tmp, ActiveMenu, mi);
337     }
338 
339     ActiveItem = mi;
340 
341     if (ActiveItem)
342     {
343 	/* create a new sub-menu */
344 	if (ActiveItem->func == F_POPUP)
345 	{
346 
347 	    PrevPrevMenu = PrevMenu;
348 	    PrevPrevItem = PrevItem;
349 	    PrevPrevY = PrevY;
350 	    PrevY = MenuY;
351 	    PrevMenu = ActiveMenu;
352 	    PrevItem = ActiveItem;
353 	    retval = MENU_PopupMenu(scr, ActiveItem->menu);
354 
355 	    /* Unfortunately, this is needed (why?) for multi-screen
356 	     * operation */
357 	    MISC_FlushExpose(ActiveMenu->w);
358 	    for (mi = ActiveMenu->first; mi != NULL; mi = mi->next)
359 		paint_entry(scr, tmp, ActiveMenu, mi);
360 
361 	    XSync(dpy, 0);
362 	    MenuY = PrevY;
363 	    PrevMenu = PrevPrevMenu;
364 	    PrevItem = PrevPrevItem;
365 	    PrevY = PrevPrevY;
366 	}
367     }
368     /* end a sub-menu */
369     if (XFindContext(dpy, Child,
370 		     MenuContext, (XPointer *)&actual_mr) == XCNOENT)
371     {
372 	return retval;
373     }
374 
375     if (actual_mr != ActiveMenu)
376     {
377 	if (actual_mr == PrevMenu)
378 	{
379 	    if ((PrevItem->y_offset + PrevY > ChildY) ||
380 		((PrevItem->y_offset + PrevItem->y_height + PrevY) < ChildY))
381 	    {
382 		return SUBMENU_DONE;
383 	    }
384 	}
385 	else
386 	    return SUBMENU_DONE;
387     }
388     return retval;
389 }
390 
391 /*
392  * Function called from update_menu instead of Keyboard_Shortcuts()
393  * when a KeyPress event is received.  If the key is alphanumeric,
394  * then the menu is scanned for a matching hot key.  Otherwise if
395  * it was the escape key then the menu processing is aborted.
396  * If none of these conditions are true, then the default processing
397  * routine is called.
398  */
399 static void
menu_shortcuts(ScreenInfo * scr,XEvent * ev)400 menu_shortcuts(ScreenInfo *scr, XEvent *ev)
401 {
402     MenuItem *mi;
403     KeySym keysym = XLookupKeysym(&ev->xkey, 0);
404 
405     /* Try to match hot keys */
406     if (((keysym >= XK_a) && (keysym <= XK_z)) ||	/* consider alphabetic */
407 	((keysym >= XK_0) && (keysym <= XK_9)))
408     {				/* ...or numeric keys  */
409 	/* Search menu for matching hotkey */
410 	for (mi = ActiveMenu->first; mi; mi = mi->next)
411 	{
412 	    char key;
413 
414 	    if (mi->hotkey == 0)
415 		continue;	/* Item has no hotkey   */
416 	    key = (mi->hotkey > 0) ?	/* Extract hot character */
417 		mi->item[mi->hotkey - 1] : mi->item2[-1 - mi->hotkey];
418 
419 	    /* Convert to lower case to match the keysym */
420 	    if (isupper(key))
421 		key = tolower(key);
422 
423 	    if (keysym == key)
424 	    {
425 		/* Force a menu exit */
426 		ActiveItem = mi;
427 		ev->type = ButtonRelease;
428 		return;
429 	    }
430 	}
431     }
432 
433     switch (keysym)
434     {
435     case XK_Escape:
436 	/* escape exits menu */
437 	ActiveItem = NULL;
438 	ev->type = ButtonRelease;
439 	break;
440 
441 	/* Nothing special --- Allow other shortcuts (cursor movement)    */
442     default:
443 	MISC_KeyboardShortcut(scr, ev, ButtonRelease);
444 	break;
445     }
446 }
447 
448 /*
449  * pop up a pull down menu
450  */
451 static Boolean
pop_up_menu(ScreenInfo * scr,MenuRoot * menu,int x,int y)452 pop_up_menu(ScreenInfo *scr, MenuRoot * menu, int x, int y)
453 {
454 
455     if ((!menu) || (menu->w == None) || (menu->items == 0) || (menu->in_use))
456 	return False;
457 
458     menu_on++;
459     COLOR_PushRootColorMap(scr);
460 
461     Stashed_X = x;
462     Stashed_Y = y;
463 
464     /* pop up the menu */
465     ActiveMenu = menu;
466     ActiveItem = NULL;
467 
468     /* clip to screen */
469     if (x + menu->width > scr->d_width - 2)
470 	x = scr->d_width - menu->width - 2;
471     if (x < 0)
472 	x = 0;
473 
474     if (y + menu->height > scr->d_height - 2)
475 	y = scr->d_height - menu->height - 2;
476     if (y < 0)
477 	y = 0;
478 
479     MenuY = y;
480     XMoveWindow(dpy, menu->w, x, y);
481     XMapRaised(dpy, menu->w);
482     menu->in_use = True;
483     return True;
484 }
485 
486 /*
487  * unhighlight the current menu selection and take down the menus
488  */
489 static void
pop_down_menu(ScreenInfo * scr)490 pop_down_menu(ScreenInfo *scr)
491 {
492     if (ActiveMenu == NULL)
493 	return;
494 
495     menu_on--;
496     if (ActiveItem)
497 	ActiveItem->state = 0;
498 
499     XUnmapWindow(dpy, ActiveMenu->w);
500 
501     COLOR_PopRootColorMap(scr);
502     XFlush(dpy);
503     if (scr->event_context & (C_WINDOW | C_FRAME | C_TITLE | C_FRAME))
504 	menuFromFrameOrWindowOrTitlebar = True;
505     else
506 	menuFromFrameOrWindowOrTitlebar = False;
507     ActiveMenu->in_use = False;
508 }
509 
510 /*
511  * Updates menu display to reflect the highlighted item
512  *
513  *  Returns:
514  *      0 on error condition
515  *      1 on return from submenu to parent menu
516  *      2 on button release return
517  */
518 static int
update_menu(ScreenInfo * scr,MwmWindow * tmp)519 update_menu(ScreenInfo *scr, MwmWindow *tmp)
520 {
521     int done, func;
522     int retval;
523     MenuRoot *actual_mr;
524     XEvent oevent;
525     int x, y;
526 
527     find_entry(scr, tmp);
528     while (True)
529     {
530 	/* block until there is an event */
531 	XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask | ExposureMask |
532 	      KeyPressMask | VisibilityChangeMask | ButtonMotionMask, &oevent);
533 	MISC_StashEventTime(&oevent);
534 	done = 0;
535 	if (oevent.type == MotionNotify)
536 	{
537 	    /* discard any extra motion events before a release */
538 	    while ((XCheckMaskEvent(dpy, ButtonMotionMask | ButtonReleaseMask,
539 				  &oevent)) && (oevent.type != ButtonRelease));
540 	}
541 
542 	/* Handle a limited number of key press events to allow mouseless
543 	 * operation */
544 	if (oevent.type == KeyPress)
545 	    menu_shortcuts(scr, &oevent);
546 
547 	switch (oevent.type)
548 	{
549 	case ButtonRelease:
550 	    /* The following lines holds the menu when the button is released */
551 	    if (!ActiveItem && (menu_on > 1))
552 	    {
553 
554 		XQueryPointer(dpy, scr->root_win, &JunkRoot, &JunkChild,
555 			      &JunkX, &JunkY, &x, &y, &JunkMask);
556 		if ((XFindContext(dpy, JunkChild, MenuContext,
557 				  (XPointer *)&actual_mr) != XCNOENT) &&
558 		    (actual_mr != ActiveMenu))
559 		{
560 		    done = 1;
561 		    break;
562 		}
563 	    }
564 	    pop_down_menu(scr);
565 	    if (ActiveItem)
566 	    {
567 		func = ActiveItem->func;
568 		done = 1;
569 		if (scr->mwm_event)
570 		{
571 		    FUNC_Execute(scr, func, ActiveItem->action,
572 				 scr->mwm_event->frame, scr->mwm_event, &oevent,
573 				 scr->event_context,
574 				 ActiveItem->val1, ActiveItem->val2,
575 				 ActiveItem->val1_unit, ActiveItem->val2_unit,
576 				 ActiveItem->menu);
577 		}
578 		else
579 		{
580 		    FUNC_Execute(scr, func, ActiveItem->action,
581 				 None, None, &oevent, scr->event_context,
582 				 ActiveItem->val1, ActiveItem->val2,
583 				 ActiveItem->val1_unit, ActiveItem->val2_unit,
584 				 ActiveItem->menu);
585 		}
586 	    }
587 	    ActiveItem = NULL;
588 	    ActiveMenu = NULL;
589 	    menuFromFrameOrWindowOrTitlebar = False;
590 	    return MENU_DONE;
591 
592 	case ButtonPress:
593 	    XQueryPointer(dpy, scr->root_win, &JunkRoot, &JunkChild,
594 			  &JunkX, &JunkY, &x, &y, &JunkMask);
595 	    if ((XFindContext(dpy, JunkChild, MenuContext,
596 			      (XPointer *)&actual_mr) == XCNOENT))
597 	    {
598 		pop_down_menu(scr);
599 		ActiveItem = NULL;
600 		ActiveMenu = NULL;
601 		menuFromFrameOrWindowOrTitlebar = False;
602 		XPutBackEvent(dpy, &oevent);
603 		return MENU_DONE;
604 	    }
605 	    done = 1;
606 	    break;
607 
608 	case KeyPress:
609 	case VisibilityNotify:
610 	    done = 1;
611 	    break;
612 
613 	case MotionNotify:
614 	    done = 1;
615 
616 	    retval = find_entry(scr, tmp);
617 	    if ((retval == MENU_DONE) || (retval == SUBMENU_DONE))
618 	    {
619 		pop_down_menu(scr);
620 		ActiveItem = NULL;
621 		ActiveMenu = NULL;
622 		menuFromFrameOrWindowOrTitlebar = False;
623 	    }
624 
625 	    if (retval == MENU_DONE)
626 		return MENU_DONE;
627 	    else if (retval == SUBMENU_DONE)
628 		return MENU_NOP;
629 
630 	    break;
631 
632 	case Expose:
633 	    /* grab our expose events, let the rest go through */
634 	    if ((XFindContext(dpy, oevent.xany.window, MenuContext,
635 			      (XPointer *)&actual_mr) != XCNOENT))
636 	    {
637 		paint_menu(scr, tmp, actual_mr, &oevent);
638 		done = 1;
639 	    }
640 	    break;
641 
642 	default:
643 	    break;
644 	}
645 
646 	if (!done)
647 	    EVENT_Dispatch(&oevent);
648 	XFlush(dpy);
649     }
650 }
651 
652 /*
653  * Look for hotkey markers in a MenuItem (pete@tecc.co.uk)
654  */
655 void
MENU_FindHotKey(MenuItem * it,KeySym key)656 MENU_FindHotKey(MenuItem *it, KeySym key)
657 {
658     char *str, *ptr;
659 
660     if (key != AnyKey)
661     {
662 	str = XKeysymToString(key);
663 	for (ptr = it->item; ptr && *ptr && *ptr != *str; ptr++)
664 	    ;
665 	if (ptr)
666 	{
667 	    if (!*ptr)
668 		it->hotkey = 0;
669 	    else
670 		it->hotkey = ptr - it->item + 1;
671 	}
672 	else
673 	    it->hotkey = 0;
674     }
675     else
676 	it->hotkey = 0;
677 }
678 
679 /*
680  * create a new menu root
681  */
682 MenuRoot *
MENU_Create(const char * name)683 MENU_Create(const char *name)
684 {
685     MenuRoot *tmp;
686 
687     tmp  = (MenuRoot *) XtMalloc(sizeof(MenuRoot));
688     tmp->name   = XtNewString(name);
689     tmp->first  = NULL;
690     tmp->last   = NULL;
691     tmp->items  = 0;
692     tmp->width  = 0;
693     tmp->width2 = 0;
694     tmp->w      = None;
695 
696     return (tmp);
697 }
698 
699 /*
700  * add a menu to the menu list
701  */
702 void
MENU_Add(ScreenInfo * scr,MenuRoot * menu)703 MENU_Add(ScreenInfo *scr, MenuRoot * menu)
704 {
705     if (scr->max_popups == 0)
706     {
707 	scr->max_popups = 8;
708 	scr->num_popups = 0;
709 	scr->popups = (MenuRoot **) XtMalloc(scr->max_popups *
710 					     sizeof(MenuRoot *));
711     }
712     else if (scr->num_popups == scr->max_popups)
713     {
714 	scr->max_popups *= 2;
715 	scr->popups = (MenuRoot **) XtRealloc((char *)scr->popups,
716 					      scr->max_popups *
717 					      sizeof(MenuRoot *));
718     }
719     scr->popups[scr->num_popups] = menu;
720     scr->num_popups++;
721 }
722 
723 /*
724  * remove a menu from the screen's list of popups
725  */
726 void
MENU_Remove(ScreenInfo * scr,MenuRoot * menu)727 MENU_Remove(ScreenInfo *scr, MenuRoot * menu)
728 {
729     int i;
730 
731     for (i = 0; i < scr->num_popups; i++)
732     {
733 	if (menu == scr->popups[i])
734 	{
735 	    if (scr->num_popups != (i + 1))
736 		memmove(&scr->popups[i], &scr->popups[i + 1],
737 		      (scr->num_popups - i - 1) * sizeof(MenuRoot *));
738 	    scr->num_popups--;
739 	}
740     }
741 }
742 
743 /*
744  * add an item to a root menu
745  */
746 void
MENU_AddItem(ScreenInfo * scr,MenuRoot * menu,char * item,char * item2,char * action,int func,long func_val_1,long func_val_2,char unit_1,char unit_2)747 MENU_AddItem(ScreenInfo *scr, MenuRoot * menu, char *item, char *item2,
748 	     char *action, int func, long func_val_1, long func_val_2,
749 	     char unit_1, char unit_2)
750 {
751     MenuItem *tmp;
752     int width;
753 
754 
755     if (item == NULL)
756 	return;
757     tmp = (MenuItem *)XtMalloc(sizeof(MenuItem));
758     if (menu->first == NULL)
759     {
760 	menu->first = tmp;
761 	tmp->prev = NULL;
762     }
763     else
764     {
765 	menu->last->next = tmp;
766 	tmp->prev = menu->last;
767     }
768     menu->last = tmp;
769 
770     tmp->item = item;
771     if (item != (char *)0)
772     {
773 	MENU_FindHotKey(tmp, AnyKey);
774 	tmp->strlen = strlen(item);
775     }
776     else
777 	tmp->strlen = 0;
778     tmp->item2 = item2;
779     if (item2)
780 	tmp->strlen2 = strlen(item2);
781     else
782 	tmp->strlen2 = 0;
783 
784     tmp->menu = 0;
785 
786     if (func == F_POPUP)
787     {
788 	int i;
789 
790 	if (action != (char *)0)
791 	{
792 	    for (i = 0; i < scr->num_popups; i++)
793 #if 0
794   /* amai: it seems we don't ever use this catch!?! */
795 		if (strcasecmp(scr->popups[i]->name, action) == 0)
796 #else
797 		if (strcmp(scr->popups[i]->name, action) == 0)
798 #endif
799 		{
800 		    tmp->menu = scr->popups[i];
801 		    break;
802 		}
803 	}
804 	if (tmp->menu == (MenuRoot *) 0)
805 	{
806 	    fprintf(stderr, "No popup menu %s.", action);
807 	    func = F_NOP;
808 	}
809     }
810     tmp->action = action;
811     tmp->next = NULL;
812     tmp->state = 0;
813     tmp->func = func;
814     tmp->val1 = func_val_1;
815     tmp->val2 = func_val_2;
816     if ((unit_1 == 'p') || (unit_1 == 'P'))
817 	tmp->val1_unit = 100;
818     else
819 	tmp->val1_unit = scr->d_width;
820     if ((unit_2 == 'p') || (unit_2 == 'P'))
821 	tmp->val2_unit = 100;
822     else
823 	tmp->val2_unit = scr->d_height;
824 
825     width = XTextWidth(scr->components[MWM_MENU].font, item, tmp->strlen);
826     if (tmp->func == F_POPUP)
827 	width += 15;
828     if (width <= 0)
829 	width = 1;
830     if (width > menu->width)
831 	menu->width = width;
832     width = XTextWidth(scr->components[MWM_MENU].font, item2, tmp->strlen2);
833     if (width < 0)
834 	width = 0;
835     if (width > menu->width2)
836 	menu->width2 = width;
837     if ((width == 0) && (tmp->strlen2 > 0))
838 	menu->width2 = 1;
839 
840     menu->items++;
841 }
842 
843 /*
844  * Generates the window for a menu
845  */
846 void
MENU_Realize(ScreenInfo * scr,MenuRoot * mr)847 MENU_Realize(ScreenInfo *scr, MenuRoot * mr)
848 {
849     MenuItem *cur;
850     unsigned long valuemask;
851     XSetWindowAttributes attributes;
852     int y;
853 
854     /* lets first size the window accordingly */
855     mr->width += 10;
856     if (mr->width2 > 0)
857 	mr->width += 5;
858 
859     /* allow two pixels for top border */
860     for (y = 2, cur = mr->first; cur != NULL; cur = cur->next)
861     {
862 	cur->y_offset = y;
863 	cur->x = 5;
864 	if (cur->func == F_TITLE)
865 	{
866 	    /* Title */
867 	    if (cur->strlen2 == 0)
868 		cur->x = (mr->width - XTextWidth(scr->components[MWM_MENU].font,
869 						 cur->item,
870 						 cur->strlen)) >> 1;
871 
872 	    cur->y_height = scr->components[MWM_MENU].f_height +
873 		HEIGHT_EXTRA + HEIGHT_EXTRA_TITLE;
874 	}
875 	else if (cur->func == F_NOP && *cur->item == 0)
876 	    /* Separator */
877 	    cur->y_height = HEIGHT_SEPARATOR;
878 	else
879 	    /* Normal text entry */
880 	    cur->y_height = scr->components[MWM_MENU].f_height + HEIGHT_EXTRA;
881 	y += cur->y_height;
882 	if (mr->width2 == 0)
883 	{
884 	    cur->x2 = cur->x;
885 	}
886 	else
887 	{
888 	    cur->x2 = mr->width - 5;
889 	}
890     }
891     mr->in_use = 0;
892     mr->height = y + 2;
893 
894     valuemask = (CWBackPixel | CWEventMask | CWCursor | CWSaveUnder);
895     attributes.background_pixel = scr->components[MWM_MENU].background;
896     attributes.event_mask = (ExposureMask | EnterWindowMask);
897     attributes.cursor = scr->cursors[MENU_CURS];
898     attributes.save_under = True;
899     mr->width = mr->width + mr->width2;
900     mr->w = XCreateWindow(dpy, scr->root_win, 0, 0, (unsigned int)(mr->width),
901 			  (unsigned int)mr->height, (unsigned int)0,
902 			  CopyFromParent, (unsigned int)InputOutput,
903 			  (Visual *)CopyFromParent,
904 			  valuemask, &attributes);
905     XSaveContext(dpy, mr->w, MenuContext, (XPointer)mr);
906 }
907 
908 /*
909  * realize all the menus
910  */
911 void
MENU_RealizeMenus(ScreenInfo * scr)912 MENU_RealizeMenus(ScreenInfo *scr)
913 {
914     int i;
915 
916     for (i = 0; i < scr->num_popups; i++)
917 	MENU_Realize(scr, scr->popups[i]);
918 }
919 
920 /*
921  * menu ordering isn't required by mwm (meaning you can use f.menu <menu-name>
922  * before <menu-name> has been declared).  Therefore, we have to defer linking
923  * menus together until after the parse phase.
924  */
925 void
MENU_LinkUp(ScreenInfo * scr)926 MENU_LinkUp(ScreenInfo *scr)
927 {
928     int i, k;
929     MenuRoot *mr = NULL;
930     MenuItem *mi = NULL;
931     FuncKey *fk = NULL;
932     MouseButton *mb = NULL;
933 
934     for (i = 0; i < scr->num_popups; i++)
935     {
936 
937 	mr = scr->popups[i];
938 
939 	mi = mr->first;
940 	while (mi != NULL)
941 	{
942 
943 	    if (mi->func == F_POPUP)
944 	    {
945 
946 		for (k = 0; k < scr->num_popups; k++)
947 		{
948 		    if (strcmp(scr->popups[k]->name, mi->action) == 0)
949 		    {
950 			mi->menu = scr->popups[k];
951 			break;
952 		    }
953 		}
954 		if (mi->menu == NULL)
955 		{
956 		    fprintf(stderr, "Can't find requested menu: %s", mi->action);
957 		    mi->func = F_NOP;
958 		}
959 	    }
960 
961 	    if (mi == mr->last)
962 		break;
963 	    else
964 		mi = mi->next;
965 	}
966     }
967 
968     for (mb = scr->buttons; mb != NULL; mb = mb->next)
969     {
970 	if (mb->func == F_POPUP || mb->func == F_W_POPUP)
971 	{
972 	    for (k = 0; k < scr->num_popups; k++)
973 	    {
974 		if (strcmp(scr->popups[k]->name, (char *)mb->menu) == 0)
975 		{
976 		    mb->menu = scr->popups[k];
977 		    break;
978 		}
979 	    }
980 	    if (mb->menu == NULL)
981 	    {
982 		fprintf(stderr, "Can't find requested menu: %s", mi->action);
983 		mb->func = F_NOP;
984 	    }
985 	}
986     }
987 
988     for (fk = scr->keys; fk != NULL; fk = fk->next)
989     {
990 	if (fk->func == F_POPUP || fk->func == F_W_POPUP)
991 	{
992 	    for (k = 0; k < scr->num_popups; k++)
993 	    {
994 		if (strcmp(scr->popups[k]->name, fk->action) == 0)
995 		{
996 		    fk->menu = scr->popups[k];
997 		    break;
998 		}
999 	    }
1000 	    if (fk->menu == NULL)
1001 	    {
1002 		fprintf(stderr, "Can't find requested menu: %s", mi->action);
1003 		fk->func = F_NOP;
1004 	    }
1005 	}
1006     }
1007 }
1008 
1009 /*
1010  * do a window menu popup
1011  */
1012 int
MENU_WinMenu(ScreenInfo * scr,MenuRoot * menu,MwmWindow * win,Boolean button,Boolean icon)1013 MENU_WinMenu(ScreenInfo *scr, MenuRoot *menu,
1014 	     MwmWindow *win, Boolean button, Boolean icon)
1015 {
1016     int prevStashedX = 0, prevStashedY = 0;
1017     MenuRoot *PrevActiveMenu = 0;
1018     MenuItem *PrevActiveItem = 0;
1019     int retval = MENU_NOP;
1020     int x, y;
1021 
1022     /* this condition could get ugly */
1023     if (menu->in_use)
1024 	return MENU_ERROR;
1025 
1026     if (!win)
1027 	return MENU_ERROR;
1028 
1029     /*
1030      * In case we wind up with a move from a menu which is
1031      * from a window border, we'll return to here to start
1032      * the move.  At least, if we came from a button press
1033      */
1034     x = y = 0;
1035     if (button && !icon)
1036     {
1037 	XQueryPointer(dpy, scr->root_win, &JunkRoot, &JunkChild,
1038 		      &x, &y, &JunkX, &JunkY, &JunkMask);
1039     }
1040     else if ((win->flags & ICONIFIED) && win->icon_w != None)
1041     {
1042 	/* the icon window had better be a child of the root window */
1043 	if (win->icon_pixmap_w != None)
1044 	{
1045 	    XGetGeometry(dpy, win->icon_pixmap_w, &JunkRoot, &x, &y,
1046 			 &JunkWidth, &JunkHeight, &JunkBW, &JunkDepth);
1047 	}
1048 	else
1049 	{
1050 	    XGetGeometry(dpy, win->icon_w, &JunkRoot, &x, &y,
1051 			 &JunkWidth, &JunkHeight, &JunkBW, &JunkDepth);
1052 	}
1053 
1054 	if (y - menu->height > 0)
1055 	{
1056 	    y -= menu->height;
1057 	}
1058 	else
1059 	{
1060 	    y += JunkHeight;
1061 	}
1062     }
1063     else
1064     {
1065 	XGetGeometry(dpy, win->w, &JunkRoot, &JunkX, &JunkY,
1066 		     &JunkWidth, &JunkHeight, &JunkBW, &JunkDepth);
1067 
1068 	XTranslateCoordinates(dpy, win->w, scr->root_win, JunkX, JunkY,
1069 			      &x, &y, &JunkChild);
1070 
1071 	x -= win->matte_width;
1072 	y -= win->matte_width;
1073     }
1074 
1075     if (!MISC_Grab(scr, MENU_CURS))
1076     {
1077 	XBell(dpy, scr->screen);
1078 	return MENU_DONE;
1079     }
1080 
1081     if (pop_up_menu(scr, menu, x, y))
1082     {
1083 	retval = update_menu(scr, win);
1084     }
1085     else
1086     {
1087 	XBell(dpy, scr->screen);
1088     }
1089 
1090     ActiveMenu = PrevActiveMenu;
1091     ActiveItem = PrevActiveItem;
1092     if ((ActiveItem) && (menu_on))
1093     {
1094 	ActiveItem->state = 1;
1095     }
1096 
1097     Stashed_X = prevStashedX;
1098     Stashed_Y = prevStashedY;
1099 
1100 
1101     if (!menu_on)
1102     {
1103 	MISC_Ungrab(scr);
1104     }
1105 
1106     return retval;
1107 }
1108 
1109 /*
1110  * Initiates a menu pop-up
1111  */
1112 int
MENU_PopupMenu(ScreenInfo * scr,MenuRoot * menu)1113 MENU_PopupMenu(ScreenInfo *scr, MenuRoot * menu)
1114 {
1115     int prevStashedX = 0, prevStashedY = 0;
1116     MenuRoot *PrevActiveMenu = 0;
1117     MenuItem *PrevActiveItem = 0;
1118     int retval = MENU_NOP;
1119     int x, y, d;
1120 
1121     /* this condition could get ugly */
1122     if (menu->in_use)
1123 	return MENU_ERROR;
1124 
1125     /* In case we wind up with a move from a menu which is
1126      * from a window border, we'll return to here to start
1127      * the move */
1128     XQueryPointer(dpy, scr->root_win, &JunkRoot, &JunkChild,
1129 		  &x, &y, &JunkX, &JunkY, &JunkMask);
1130 
1131     x -= (menu->width >> 1);
1132     y -= ((scr->components[MWM_MENU].f_height + HEIGHT_EXTRA) >> 1);
1133 
1134     if (menu_on)
1135     {
1136 	prevStashedX = Stashed_X;
1137 	prevStashedY = Stashed_Y;
1138 	PrevActiveMenu = ActiveMenu;
1139 	PrevActiveItem = ActiveItem;
1140 	/* this d is from paint_entry() */
1141 	d = (scr->components[MWM_MENU].f_height + HEIGHT_EXTRA - 7) / 2;
1142 	if (ActiveMenu)
1143 	    x = Stashed_X + (ActiveMenu->width ) -d ;
1144 
1145 	if (ActiveItem)
1146 	    y = ActiveItem->y_offset + MenuY +
1147 		((scr->components[MWM_MENU].f_height + HEIGHT_EXTRA) >> 2);
1148     }
1149     else
1150     {
1151 	if (!MISC_Grab(scr, MENU_CURS))
1152 	{
1153 	    XBell(dpy, scr->screen);
1154 	    return MENU_DONE;
1155 	}
1156 	x += (menu->width >> 1) - 3;
1157     }
1158     if (pop_up_menu(scr, menu, x, y))
1159     {
1160 	retval = update_menu(scr, NULL);
1161     }
1162     else
1163 	XBell(dpy, scr->screen);
1164 
1165     ActiveMenu = PrevActiveMenu;
1166     ActiveItem = PrevActiveItem;
1167     if ((ActiveItem) && (menu_on))
1168 	ActiveItem->state = 1;
1169     Stashed_X = prevStashedX;
1170     Stashed_Y = prevStashedY;
1171 
1172 
1173     if (!menu_on)
1174 	MISC_Ungrab(scr);
1175 
1176     return retval;
1177 }
1178 
1179 /*
1180  * destroy one menu
1181  */
1182 void
MENU_Destroy(MenuRoot * menu)1183 MENU_Destroy(MenuRoot * menu)
1184 {
1185     MenuItem *mi, *tmp;
1186     if (!menu)
1187 	return;
1188 
1189     XDestroyWindow(dpy, menu->w);
1190 
1191     for (mi = menu->first; mi != NULL; mi = tmp)
1192     {
1193 	tmp = mi->next;
1194 	XtFree((char *)mi);
1195 	mi = tmp;
1196     }
1197 
1198     XtFree((char *)menu);
1199 }
1200 
1201 /*
1202  * destroy all menus
1203  */
1204 void
MENU_DestroyMenus(ScreenInfo * scr)1205 MENU_DestroyMenus(ScreenInfo *scr)
1206 {
1207     int i;
1208 
1209     for (i = 0; i < scr->num_popups; i++)
1210 	if (scr->popups[i]->w != None)
1211 	    XDestroyWindow(dpy, scr->popups[i]->w);
1212 }
1213 
1214 /*
1215  * reset the menuing system
1216  */
1217 void
MENU_Reset(void)1218 MENU_Reset(void)
1219 {
1220     ActiveItem = NULL;
1221     ActiveMenu = NULL;
1222 }
1223 
1224 #define INTERNAL_MENU "INTERNAL_MWM_WIN_MENU_"
1225 /*
1226  * if the user has a custom window menu, build it
1227  */
1228 void
MENU_BuildWindowMenu(ScreenInfo * scr,MwmWindow * win)1229 MENU_BuildWindowMenu(ScreenInfo *scr, MwmWindow *win)
1230 {
1231     MenuRoot *mr, *def;
1232     char *buf;
1233     int i;
1234     MenuItem *mi, *tmp;
1235 
1236     mr = NULL;
1237 
1238     if (win->mwm_menu && strlen(win->mwm_menu) != 0)
1239     {
1240 
1241 	buf = XtMalloc(strlen(win->mwm_menu) + 64);
1242 	sprintf(buf, "Menu %s {\n", INTERNAL_MENU);
1243 	strcat(buf, win->mwm_menu);
1244 	strcat(buf, "\n}");
1245 
1246 	PARSE_buf(scr, buf);
1247 
1248 	for (i = 0; i < scr->num_popups; i++)
1249 	{
1250 	    if (strcmp(scr->popups[i]->name, INTERNAL_MENU) == 0)
1251 		break;
1252 	}
1253 	if (i == scr->num_popups)
1254 	{
1255 	    fprintf(stderr, "Couldn't find user defined menu!\n");
1256 	    return;
1257 	}
1258 
1259 	mr = scr->popups[i];
1260 	MENU_Remove(scr, mr);
1261 	win->custom_menu = mr;
1262     }
1263 
1264     for (i = 0; i < scr->num_popups; i++)
1265     {
1266 	if (strcmp(scr->popups[i]->name, win->window_menu) == 0)
1267 	    break;
1268     }
1269     if (i == scr->num_popups)
1270     {
1271 	fprintf(stderr, "Couldn't find default window menu!\n");
1272 	return;
1273     }
1274 
1275     def = scr->popups[i];
1276 
1277     if (mr)
1278     {
1279 	if (def->width > mr->width)
1280 	    mr->width = def->width;
1281 	if (def->width2 > mr->width2)
1282 	    mr->width2 = def->width2;
1283     }
1284     else
1285     {
1286 	mr = (MenuRoot *) XtMalloc(sizeof(MenuRoot));
1287 	memcpy(mr, def, sizeof(MenuRoot));
1288 	mr->first = mr->last = NULL;
1289     }
1290 
1291     for (mi = def->last; mi != NULL; mi = mi->prev)
1292     {
1293 	tmp = (MenuItem *)XtMalloc(sizeof(MenuItem));
1294 	memcpy(tmp, mi, sizeof(MenuItem));
1295 	if (mr->first == NULL)
1296 	{
1297 	    mr->first = tmp;
1298 	    tmp->prev = NULL;
1299 	    tmp->next = NULL;
1300 	}
1301 	else
1302 	{
1303 	    tmp->next = mr->first;
1304 	    mr->first->prev = tmp;
1305 	    mr->first = tmp;
1306 	}
1307 	if (mr->last == NULL)
1308 	    mr->last = tmp;
1309 	mr->items++;
1310     }
1311 
1312     MENU_Realize(scr, mr);
1313 }
1314 
1315 /*
1316  * if a window has a custom window menu, destroy it
1317  */
1318 void
MENU_DestroyWindowMenu(ScreenInfo * scr,MwmWindow * win)1319 MENU_DestroyWindowMenu(ScreenInfo *scr, MwmWindow *win)
1320 {
1321     if (!win->custom_menu)
1322 	return;
1323 
1324     MENU_Destroy(win->custom_menu);
1325 
1326     win->custom_menu = NULL;
1327 }
1328 
1329 /*
1330  * convert a keysym and a mod mask into a string
1331  */
1332 char
1333  *
MENU_AcceleratorString(ScreenInfo * scr,KeySym keysym,int modifiers)1334 MENU_AcceleratorString(ScreenInfo *scr, KeySym keysym, int modifiers)
1335 {
1336     Boolean haveone = False;
1337     static char buf[128];
1338     char *key;
1339 
1340     buf[0] = 0;
1341 
1342     if (modifiers & ControlMask)
1343     {
1344 	strcat(buf, "<Ctrl");
1345 	haveone = True;
1346     }
1347     if (modifiers & ShiftMask)
1348     {
1349 	if (haveone)
1350 	    strcat(buf, "+");
1351 	else
1352 	    strcat(buf, "<");
1353 	strcat(buf, "Shift");
1354 	haveone = True;
1355     }
1356     if (modifiers & scr->alt_mask)
1357     {
1358 	if (haveone)
1359 	    strcat(buf, "+");
1360 	else
1361 	    strcat(buf, "<");
1362 	strcat(buf, "Alt");
1363 	haveone = True;
1364     }
1365     if (modifiers & LockMask)
1366     {
1367 	if (haveone)
1368 	    strcat(buf, "+");
1369 	else
1370 	    strcat(buf, "<");
1371 	strcat(buf, "Lock");
1372 	haveone = True;
1373     }
1374     if (modifiers & Mod1Mask && scr->alt_mask != Mod1Mask)
1375     {
1376 	if (haveone)
1377 	    strcat(buf, "+");
1378 	else
1379 	    strcat(buf, "<");
1380 	strcat(buf, "Mod1");
1381 	haveone = True;
1382     }
1383     if (modifiers & Mod2Mask && scr->alt_mask != Mod2Mask)
1384     {
1385 	if (haveone)
1386 	    strcat(buf, "+");
1387 	else
1388 	    strcat(buf, "<");
1389 	strcat(buf, "Mod2");
1390 	haveone = True;
1391     }
1392     if (modifiers & Mod3Mask && scr->alt_mask != Mod3Mask)
1393     {
1394 	if (haveone)
1395 	    strcat(buf, "+");
1396 	else
1397 	    strcat(buf, "<");
1398 	strcat(buf, "Mod3");
1399 	haveone = True;
1400     }
1401     if (modifiers & Mod4Mask && scr->alt_mask != Mod4Mask)
1402     {
1403 	if (haveone)
1404 	    strcat(buf, "+");
1405 	else
1406 	    strcat(buf, "<");
1407 	strcat(buf, "Mod4");
1408 	haveone = True;
1409     }
1410     if (modifiers & Mod5Mask && scr->alt_mask != Mod5Mask)
1411     {
1412 	if (haveone)
1413 	    strcat(buf, "+");
1414 	else
1415 	    strcat(buf, "<");
1416 	strcat(buf, "Mod5");
1417 	haveone = True;
1418     }
1419     if ((key = XKeysymToString(keysym)) != NULL)
1420     {
1421 	if (haveone)
1422 	    strcat(buf, ">");
1423 	strcat(buf, key);
1424     }
1425     return buf;
1426 }
1427