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