menus.c revision 29dcb7bf
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 * Copyright 1992 Claude Lecommandeur.
8 */
9
10/***********************************************************************
11 *
12 * $XConsortium: menus.c,v 1.186 91/07/17 13:58:00 dave Exp $
13 *
14 * twm menu code
15 *
16 * 17-Nov-87 Thomas E. LaStrange                File created
17 *
18 * Do the necessary modification to be integrated in ctwm.
19 * Can no longer be used for the standard twm.
20 *
21 * 22-April-92 Claude Lecommandeur.
22 *
23 *
24 ***********************************************************************/
25
26#include "ctwm.h"
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <strings.h>
32
33#include "add_window.h"
34#include "colormaps.h"
35#include "drawing.h"
36#include "events.h"
37#include "functions.h"
38#include "functions_defs.h"
39#include "gram.tab.h"
40#include "iconmgr.h"
41#include "icons_builtin.h"
42#include "icons.h"
43#include "image.h"
44#include "list.h"
45#include "occupation.h"
46#include "otp.h"
47#include "screen.h"
48#ifdef SOUNDS
49#  include "sound.h"
50#endif
51#include "util.h"
52#include "vscreen.h"
53#include "win_iconify.h"
54#include "win_resize.h"
55#include "win_utils.h"
56#include "workspace_manager.h"
57
58MenuRoot *ActiveMenu = NULL;            /* the active menu */
59MenuItem *ActiveItem = NULL;            /* the active menu item */
60bool menuFromFrameOrWindowOrTitlebar = false;
61char *CurrentSelectedWorkspace;
62
63/* Should probably move, since nothing in this file uses anymore */
64int AlternateKeymap;
65bool AlternateContext;
66
67int MenuDepth = 0;              /* number of menus up */
68static struct {
69	int x;
70	int y;
71} MenuOrigins[MAXMENUDEPTH];
72static bool addingdefaults = false;
73
74
75
76static void Paint3DEntry(MenuRoot *mr, MenuItem *mi, bool exposure);
77static void PaintNormalEntry(MenuRoot *mr, MenuItem *mi, bool exposure);
78static void DestroyMenu(MenuRoot *menu);
79
80
81#define SHADOWWIDTH 5                   /* in pixels */
82#define ENTRY_SPACING 4
83
84
85/***********************************************************************
86 *
87 *  Procedure:
88 *      AddFuncKey - add a function key to the list
89 *
90 *  Inputs:
91 *      name    - the name of the key
92 *      cont    - the context to look for the key press in
93 *      nmods   - modifier keys that need to be pressed
94 *      func    - the function to perform
95 *      win_name- the window name (if any)
96 *      action  - the action string associated with the function (if any)
97 *
98 ***********************************************************************
99 */
100
101bool
102AddFuncKey(char *name, int cont, int nmods, int func,
103           MenuRoot *menu, char *win_name, char *action)
104{
105	FuncKey *tmp;
106	KeySym keysym = NoSymbol;
107	KeyCode keycode = 0;
108
109	/*
110	 * Don't let a 0 keycode go through, since that means AnyKey to the
111	 * XGrabKey call in GrabKeys().  Conditionalize on dpy to handle
112	 * special cases where we don't have a server to talk to.
113	 */
114	keysym = XStringToKeysym(name);
115	if(dpy) {
116		keycode = XKeysymToKeycode(dpy, keysym);
117	}
118	if(keysym == NoSymbol || (dpy && keycode == 0)) {
119		fprintf(stderr, "ignore %s key binding (%s)\n", name,
120		        keysym == NoSymbol
121		        ? "key symbol not found"
122		        : "key code not found");
123		return false;
124	}
125
126	/* see if there already is a key defined for this context */
127	for(tmp = Scr->FuncKeyRoot.next; tmp != NULL; tmp = tmp->next) {
128		if(tmp->keysym == keysym &&
129		                tmp->cont == cont &&
130		                tmp->mods == nmods) {
131			break;
132		}
133	}
134
135	if(tmp == NULL) {
136		tmp = malloc(sizeof(FuncKey));
137		tmp->next = Scr->FuncKeyRoot.next;
138		Scr->FuncKeyRoot.next = tmp;
139	}
140
141	tmp->name = name;
142	tmp->keysym = keysym;
143	tmp->keycode = keycode;
144	tmp->cont = cont;
145	tmp->mods = nmods;
146	tmp->func = func;
147	tmp->menu = menu;
148	tmp->win_name = win_name;
149	tmp->action = action;
150
151	return true;
152}
153
154/***********************************************************************
155 *
156 *  Procedure:
157 *      AddFuncButton - add a function button to the list
158 *
159 *  Inputs:
160 *      num     - the num of the button
161 *      cont    - the context to look for the key press in
162 *      nmods   - modifier keys that need to be pressed
163 *      func    - the function to perform
164 *      menu    - the menu (if any)
165 *      item    - the menu item (if any)
166 *
167 ***********************************************************************
168 */
169
170void
171AddFuncButton(int num, int cont, int nmods, int func,
172              MenuRoot *menu, MenuItem *item)
173{
174	FuncButton *tmp;
175
176	/* Find existing def for this button/context/modifier if any */
177	for(tmp = Scr->FuncButtonRoot.next; tmp != NULL; tmp = tmp->next) {
178		if((tmp->num == num) && (tmp->cont == cont) && (tmp->mods == nmods)) {
179			break;
180		}
181	}
182
183	/*
184	 * If it's already set, and we're addingdefault (i.e., called from
185	 * AddDefaultFuncButtons()), just return.  This lets us cram on
186	 * fallback mappings, without worrying about overriding user choices.
187	 */
188	if(tmp && addingdefaults) {
189		return;
190	}
191
192	/* No mapping yet; create a shell */
193	if(tmp == NULL) {
194		tmp = malloc(sizeof(FuncButton));
195		tmp->next = Scr->FuncButtonRoot.next;
196		Scr->FuncButtonRoot.next = tmp;
197	}
198
199	/* Set the new details */
200	tmp->num  = num;
201	tmp->cont = cont;
202	tmp->mods = nmods;
203	tmp->func = func;
204	tmp->menu = menu;
205	tmp->item = item;
206
207	return;
208}
209
210
211/*
212 * AddDefaultFuncButtons - attach default bindings so that naive users
213 * don't get messed up if they provide a minimal twmrc.
214 *
215 * This used to be in add_window.c, and maybe fits better in
216 * decorations_init.c (only place it's called) now, but is currently here
217 * so addingdefaults is in scope.
218 *
219 * XXX Probably better to adjust things so we can do that job _without_
220 * the magic global var...
221 */
222void
223AddDefaultFuncButtons(void)
224{
225	addingdefaults = true;
226
227#define SETDEF(btn, ctx, func) AddFuncButton(btn, ctx, 0, func, NULL, NULL)
228	SETDEF(Button1, C_TITLE,    F_MOVE);
229	SETDEF(Button1, C_ICON,     F_ICONIFY);
230	SETDEF(Button1, C_ICONMGR,  F_ICONIFY);
231
232	SETDEF(Button2, C_TITLE,    F_RAISELOWER);
233	SETDEF(Button2, C_ICON,     F_ICONIFY);
234	SETDEF(Button2, C_ICONMGR,  F_ICONIFY);
235#undef SETDEF
236
237	addingdefaults = false;
238}
239
240
241void
242PaintEntry(MenuRoot *mr, MenuItem *mi, bool exposure)
243{
244	if(Scr->use3Dmenus) {
245		Paint3DEntry(mr, mi, exposure);
246	}
247	else {
248		PaintNormalEntry(mr, mi, exposure);
249	}
250	if(mi->state) {
251		mr->lastactive = mi;
252	}
253}
254
255static void
256Paint3DEntry(MenuRoot *mr, MenuItem *mi, bool exposure)
257{
258	int y_offset;
259	int text_y;
260	GC gc;
261	XRectangle ink_rect, logical_rect;
262	XmbTextExtents(Scr->MenuFont.font_set, mi->item, mi->strlen,
263	               &ink_rect, &logical_rect);
264
265	y_offset = mi->item_num * Scr->EntryHeight + Scr->MenuShadowDepth;
266	text_y = y_offset + (Scr->EntryHeight - logical_rect.height) / 2
267	         - logical_rect.y;
268
269	if(mi->func != F_TITLE) {
270		int x, y;
271
272		gc = Scr->NormalGC;
273		if(mi->state) {
274			Draw3DBorder(mr->w, Scr->MenuShadowDepth, y_offset,
275			             mr->width - 2 * Scr->MenuShadowDepth, Scr->EntryHeight, 1,
276			             mi->highlight, off, true, false);
277			FB(mi->highlight.fore, mi->highlight.back);
278			XmbDrawImageString(dpy, mr->w, Scr->MenuFont.font_set, gc,
279			                   mi->x + Scr->MenuShadowDepth, text_y, mi->item, mi->strlen);
280		}
281		else {
282			if(mi->user_colors || !exposure) {
283				XSetForeground(dpy, gc, mi->normal.back);
284				XFillRectangle(dpy, mr->w, gc,
285				               Scr->MenuShadowDepth, y_offset,
286				               mr->width - 2 * Scr->MenuShadowDepth, Scr->EntryHeight);
287				FB(mi->normal.fore, mi->normal.back);
288			}
289			else {
290				gc = Scr->MenuGC;
291			}
292			XmbDrawImageString(dpy, mr->w, Scr->MenuFont.font_set, gc,
293			                   mi->x + Scr->MenuShadowDepth, text_y,
294			                   mi->item, mi->strlen);
295			if(mi->separated) {
296				FB(Scr->MenuC.shadd, Scr->MenuC.shadc);
297				XDrawLine(dpy, mr->w, Scr->NormalGC,
298				          Scr->MenuShadowDepth,
299				          y_offset + Scr->EntryHeight - 2,
300				          mr->width - Scr->MenuShadowDepth,
301				          y_offset + Scr->EntryHeight - 2);
302				FB(Scr->MenuC.shadc, Scr->MenuC.shadd);
303				XDrawLine(dpy, mr->w, Scr->NormalGC,
304				          Scr->MenuShadowDepth,
305				          y_offset + Scr->EntryHeight - 1,
306				          mr->width - Scr->MenuShadowDepth,
307				          y_offset + Scr->EntryHeight - 1);
308			}
309		}
310
311		if(mi->func == F_MENU) {
312			/* create the pull right pixmap if needed */
313			if(Scr->pullPm == None) {
314				Scr->pullPm = Create3DMenuIcon(Scr->EntryHeight - ENTRY_SPACING, &Scr->pullW,
315				                               &Scr->pullH, Scr->MenuC);
316			}
317			x = mr->width - Scr->pullW - Scr->MenuShadowDepth - 2;
318			y = y_offset + ((Scr->EntryHeight - ENTRY_SPACING - Scr->pullH) / 2) + 2;
319			XCopyArea(dpy, Scr->pullPm, mr->w, gc, 0, 0, Scr->pullW, Scr->pullH, x, y);
320		}
321	}
322	else {
323		Draw3DBorder(mr->w, Scr->MenuShadowDepth, y_offset,
324		             mr->width - 2 * Scr->MenuShadowDepth, Scr->EntryHeight, 1,
325		             mi->normal, off, true, false);
326		FB(mi->normal.fore, mi->normal.back);
327		XmbDrawImageString(dpy, mr->w, Scr->MenuFont.font_set, Scr->NormalGC,
328		                   mi->x + 2, text_y, mi->item, mi->strlen);
329	}
330}
331
332
333static void
334PaintNormalEntry(MenuRoot *mr, MenuItem *mi, bool exposure)
335{
336	int y_offset;
337	int text_y;
338	GC gc;
339	XRectangle ink_rect, logical_rect;
340	XmbTextExtents(Scr->MenuFont.font_set, mi->item, mi->strlen,
341	               &ink_rect, &logical_rect);
342
343	y_offset = mi->item_num * Scr->EntryHeight;
344	text_y = y_offset + (Scr->EntryHeight - logical_rect.height) / 2
345	         - logical_rect.y;
346
347	if(mi->func != F_TITLE) {
348		int x, y;
349
350		if(mi->state) {
351			XSetForeground(dpy, Scr->NormalGC, mi->highlight.back);
352
353			XFillRectangle(dpy, mr->w, Scr->NormalGC, 0, y_offset,
354			               mr->width, Scr->EntryHeight);
355			FB(mi->highlight.fore, mi->highlight.back);
356			XmbDrawString(dpy, mr->w, Scr->MenuFont.font_set, Scr->NormalGC,
357			              mi->x, text_y, mi->item, mi->strlen);
358
359			gc = Scr->NormalGC;
360		}
361		else {
362			if(mi->user_colors || !exposure) {
363				XSetForeground(dpy, Scr->NormalGC, mi->normal.back);
364
365				XFillRectangle(dpy, mr->w, Scr->NormalGC, 0, y_offset,
366				               mr->width, Scr->EntryHeight);
367
368				FB(mi->normal.fore, mi->normal.back);
369				gc = Scr->NormalGC;
370			}
371			else {
372				gc = Scr->MenuGC;
373			}
374			XmbDrawString(dpy, mr->w, Scr->MenuFont.font_set, gc, mi->x,
375			              text_y, mi->item, mi->strlen);
376			if(mi->separated)
377				XDrawLine(dpy, mr->w, gc, 0, y_offset + Scr->EntryHeight - 1,
378				          mr->width, y_offset + Scr->EntryHeight - 1);
379		}
380
381		if(mi->func == F_MENU) {
382			/* create the pull right pixmap if needed */
383			if(Scr->pullPm == None) {
384				Scr->pullPm = CreateMenuIcon(Scr->MenuFont.height,
385				                             &Scr->pullW, &Scr->pullH);
386			}
387			x = mr->width - Scr->pullW - 5;
388			y = y_offset + ((Scr->MenuFont.height - Scr->pullH) / 2);
389			XCopyPlane(dpy, Scr->pullPm, mr->w, gc, 0, 0,
390			           Scr->pullW, Scr->pullH, x, y, 1);
391		}
392	}
393	else {
394		int y;
395
396		XSetForeground(dpy, Scr->NormalGC, mi->normal.back);
397
398		/* fill the rectangle with the title background color */
399		XFillRectangle(dpy, mr->w, Scr->NormalGC, 0, y_offset,
400		               mr->width, Scr->EntryHeight);
401
402		{
403			XSetForeground(dpy, Scr->NormalGC, mi->normal.fore);
404			/* now draw the dividing lines */
405			if(y_offset)
406				XDrawLine(dpy, mr->w, Scr->NormalGC, 0, y_offset,
407				          mr->width, y_offset);
408			y = ((mi->item_num + 1) * Scr->EntryHeight) - 1;
409			XDrawLine(dpy, mr->w, Scr->NormalGC, 0, y, mr->width, y);
410		}
411
412		FB(mi->normal.fore, mi->normal.back);
413		/* finally render the title */
414		XmbDrawString(dpy, mr->w, Scr->MenuFont.font_set, Scr->NormalGC, mi->x,
415		              text_y, mi->item, mi->strlen);
416	}
417}
418
419void PaintMenu(MenuRoot *mr, XEvent *e)
420{
421	MenuItem *mi;
422
423	if(Scr->use3Dmenus) {
424		Draw3DBorder(mr->w, 0, 0, mr->width, mr->height,
425		             Scr->MenuShadowDepth, Scr->MenuC, off, false, false);
426	}
427	for(mi = mr->first; mi != NULL; mi = mi->next) {
428		int y_offset = mi->item_num * Scr->EntryHeight;
429
430		/* be smart about handling the expose, redraw only the entries
431		 * that we need to
432		 */
433		if(e->xexpose.y <= (y_offset + Scr->EntryHeight) &&
434		                (e->xexpose.y + e->xexpose.height) >= y_offset) {
435			PaintEntry(mr, mi, true);
436		}
437	}
438	XSync(dpy, 0);
439}
440
441
442void MakeWorkspacesMenu(void)
443{
444	static char **actions = NULL;
445	WorkSpace *wlist;
446	char **act;
447
448	if(! Scr->Workspaces) {
449		return;
450	}
451	AddToMenu(Scr->Workspaces, "TWM Workspaces", NULL, NULL, F_TITLE, NULL,
452	          NULL);
453	if(! actions) {
454		int count = 0;
455
456		for(wlist = Scr->workSpaceMgr.workSpaceList; wlist != NULL;
457		                wlist = wlist->next) {
458			count++;
459		}
460		count++;
461		actions = calloc(count, sizeof(char *));
462		act = actions;
463		for(wlist = Scr->workSpaceMgr.workSpaceList; wlist != NULL;
464		                wlist = wlist->next) {
465			asprintf(act, "WGOTO : %s", wlist->name);
466			act++;
467		}
468		*act = NULL;
469	}
470	act = actions;
471	for(wlist = Scr->workSpaceMgr.workSpaceList; wlist != NULL;
472	                wlist = wlist->next) {
473		AddToMenu(Scr->Workspaces, wlist->name, *act, Scr->Windows, F_MENU, NULL, NULL);
474		act++;
475	}
476	Scr->Workspaces->pinned = false;
477	MakeMenu(Scr->Workspaces);
478}
479
480static bool fromMenu;
481bool
482cur_fromMenu(void)
483{
484	return fromMenu;
485}
486
487void UpdateMenu(void)
488{
489	MenuItem *mi;
490	int i, x, y, x_root, y_root, entry;
491	bool done;
492	MenuItem *badItem = NULL;
493
494	fromMenu = true;
495
496	while(1) {
497		/* block until there is an event */
498		if(!menuFromFrameOrWindowOrTitlebar) {
499			XMaskEvent(dpy,
500			           ButtonPressMask | ButtonReleaseMask |
501			           KeyPressMask | KeyReleaseMask |
502			           EnterWindowMask | ExposureMask |
503			           VisibilityChangeMask | LeaveWindowMask |
504			           ButtonMotionMask, &Event);
505		}
506		if(Event.type == MotionNotify) {
507			/* discard any extra motion events before a release */
508			while(XCheckMaskEvent(dpy,
509			                      ButtonMotionMask | ButtonReleaseMask, &Event))
510				if(Event.type == ButtonRelease) {
511					break;
512				}
513		}
514
515		if(!DispatchEvent()) {
516			continue;
517		}
518
519		if((! ActiveMenu) || Cancel) {
520			menuFromFrameOrWindowOrTitlebar = false;
521			fromMenu = false;
522			return;
523		}
524
525		if(Event.type != MotionNotify) {
526			continue;
527		}
528
529		done = false;
530		XQueryPointer(dpy, ActiveMenu->w, &JunkRoot, &JunkChild,
531		              &x_root, &y_root, &x, &y, &JunkMask);
532
533		/* if we haven't received the enter notify yet, wait */
534		if(!ActiveMenu->entered) {
535			continue;
536		}
537
538		XFindContext(dpy, ActiveMenu->w, ScreenContext, (XPointer *)&Scr);
539
540		if(x < 0 || y < 0 ||
541		                x >= ActiveMenu->width || y >= ActiveMenu->height) {
542			if(ActiveItem && ActiveItem->func != F_TITLE) {
543				ActiveItem->state = false;
544				PaintEntry(ActiveMenu, ActiveItem, false);
545			}
546			ActiveItem = NULL;
547			continue;
548		}
549
550		/* look for the entry that the mouse is in */
551		entry = y / Scr->EntryHeight;
552		for(i = 0, mi = ActiveMenu->first; mi != NULL; i++, mi = mi->next) {
553			if(i == entry) {
554				break;
555			}
556		}
557
558		/* if there is an active item, we might have to turn it off */
559		if(ActiveItem) {
560			/* is the active item the one we are on ? */
561			if(ActiveItem->item_num == entry && ActiveItem->state) {
562				done = true;
563			}
564
565			/* if we weren't on the active entry, let's turn the old
566			 * active one off
567			 */
568			if(!done && ActiveItem->func != F_TITLE) {
569				ActiveItem->state = false;
570				PaintEntry(ActiveMenu, ActiveItem, false);
571			}
572		}
573
574		/* if we weren't on the active item, change the active item and turn
575		 * it on
576		 */
577		if(!done) {
578			ActiveItem = mi;
579			if(ActiveItem && ActiveItem->func != F_TITLE && !ActiveItem->state) {
580				ActiveItem->state = true;
581				PaintEntry(ActiveMenu, ActiveItem, false);
582			}
583		}
584
585		/* now check to see if we were over the arrow of a pull right entry */
586		if(ActiveItem && ActiveItem->func == F_MENU &&
587		                ((ActiveMenu->width - x) < (ActiveMenu->width / 3))) {
588			MenuRoot *save = ActiveMenu;
589			int savex = MenuOrigins[MenuDepth - 1].x;
590			int savey = MenuOrigins[MenuDepth - 1].y;
591
592			if(MenuDepth < MAXMENUDEPTH) {
593				if(ActiveMenu == Scr->Workspaces) {
594					CurrentSelectedWorkspace = ActiveItem->item;
595				}
596				PopUpMenu(ActiveItem->sub,
597				          (savex + (((2 * ActiveMenu->width) / 3) - 1)),
598				          (savey + ActiveItem->item_num * Scr->EntryHeight)
599				          /*(savey + ActiveItem->item_num * Scr->EntryHeight +
600				           (Scr->EntryHeight >> 1))*/, False);
601				CurrentSelectedWorkspace = NULL;
602			}
603			else if(!badItem) {
604				XBell(dpy, 0);
605				badItem = ActiveItem;
606			}
607
608			/* if the menu did get popped up, unhighlight the active item */
609			if(save != ActiveMenu && ActiveItem->state) {
610				ActiveItem->state = false;
611				PaintEntry(save, ActiveItem, false);
612				ActiveItem = NULL;
613			}
614		}
615		if(badItem != ActiveItem) {
616			badItem = NULL;
617		}
618		XFlush(dpy);
619	}
620}
621
622
623/***********************************************************************
624 *
625 *  Procedure:
626 *      NewMenuRoot - create a new menu root
627 *
628 *  Returned Value:
629 *      (MenuRoot *)
630 *
631 *  Inputs:
632 *      name    - the name of the menu root
633 *
634 ***********************************************************************
635 */
636
637MenuRoot *NewMenuRoot(char *name)
638{
639	MenuRoot *tmp;
640
641#define UNUSED_PIXEL ((unsigned long) (~0))     /* more than 24 bits */
642
643	tmp = malloc(sizeof(MenuRoot));
644	tmp->highlight.fore = UNUSED_PIXEL;
645	tmp->highlight.back = UNUSED_PIXEL;
646	tmp->name = name;
647	tmp->prev = NULL;
648	tmp->first = NULL;
649	tmp->last = NULL;
650	tmp->defaultitem = NULL;
651	tmp->items = 0;
652	tmp->width = 0;
653	tmp->mapped = MRM_NEVER;
654	tmp->pull = false;
655	tmp->w = None;
656	tmp->shadow = None;
657	tmp->real_menu = false;
658
659	if(Scr->MenuList == NULL) {
660		Scr->MenuList = tmp;
661		Scr->MenuList->next = NULL;
662	}
663
664	if(Scr->LastMenu == NULL) {
665		Scr->LastMenu = tmp;
666		Scr->LastMenu->next = NULL;
667	}
668	else {
669		Scr->LastMenu->next = tmp;
670		Scr->LastMenu = tmp;
671		Scr->LastMenu->next = NULL;
672	}
673
674	if(strcmp(name, TWM_WINDOWS) == 0) {
675		Scr->Windows = tmp;
676	}
677
678	if(strcmp(name, TWM_ICONS) == 0) {
679		Scr->Icons = tmp;
680	}
681
682	if(strcmp(name, TWM_WORKSPACES) == 0) {
683		Scr->Workspaces = tmp;
684		if(!Scr->Windows) {
685			NewMenuRoot(TWM_WINDOWS);
686		}
687	}
688	if(strcmp(name, TWM_ALLWINDOWS) == 0) {
689		Scr->AllWindows = tmp;
690	}
691
692	/* Added by dl 2004 */
693	if(strcmp(name, TWM_ALLICONS) == 0) {
694		Scr->AllIcons = tmp;
695	}
696
697	/* Added by Dan Lilliehorn (dl@dl.nu) 2000-02-29       */
698	if(strcmp(name, TWM_KEYS) == 0) {
699		Scr->Keys = tmp;
700	}
701
702	if(strcmp(name, TWM_VISIBLE) == 0) {
703		Scr->Visible = tmp;
704	}
705
706	/* End addition */
707
708	return (tmp);
709}
710
711
712/***********************************************************************
713 *
714 *  Procedure:
715 *      AddToMenu - add an item to a root menu
716 *
717 *  Returned Value:
718 *      (MenuItem *)
719 *
720 *  Inputs:
721 *      menu    - pointer to the root menu to add the item
722 *      item    - the text to appear in the menu
723 *      action  - the string to possibly execute
724 *      sub     - the menu root if it is a pull-right entry
725 *      func    - the numeric function
726 *      fore    - foreground color string
727 *      back    - background color string
728 *
729 ***********************************************************************
730 */
731
732MenuItem *AddToMenu(MenuRoot *menu, char *item, char *action,
733                    MenuRoot *sub, int func, char *fore, char *back)
734{
735	MenuItem *tmp;
736	int width;
737	char *itemname;
738	XRectangle ink_rect;
739	XRectangle logical_rect;
740
741#ifdef DEBUG_MENUS
742	fprintf(stderr, "adding menu item=\"%s\", action=%s, sub=%d, f=%d\n",
743	        item, action, sub, func);
744#endif
745
746	tmp = malloc(sizeof(MenuItem));
747	tmp->root = menu;
748
749	if(menu->first == NULL) {
750		menu->first = tmp;
751		tmp->prev = NULL;
752	}
753	else {
754		menu->last->next = tmp;
755		tmp->prev = menu->last;
756	}
757	menu->last = tmp;
758
759	if((menu == Scr->Workspaces) ||
760	                (menu == Scr->Windows) ||
761	                (menu == Scr->Icons) ||
762	                (menu == Scr->AllWindows) ||
763
764	                /* Added by dl 2004 */
765	                (menu == Scr->AllIcons) ||
766
767	                /* Added by Dan Lillehorn (dl@dl.nu) 2000-02-29 */
768	                (menu == Scr->Keys) ||
769	                (menu == Scr->Visible)) {
770
771		itemname = item;
772	}
773	else if(*item == '*') {
774		itemname = item + 1;
775		menu->defaultitem = tmp;
776	}
777	else {
778		itemname = item;
779	}
780
781	tmp->item = itemname;
782	tmp->strlen = strlen(itemname);
783	tmp->action = action;
784	tmp->next = NULL;
785	tmp->sub = NULL;
786	tmp->state = false;
787	tmp->func = func;
788	tmp->separated = false;
789
790	if(!Scr->HaveFonts) {
791		CreateFonts(Scr);
792	}
793
794	if(dpy) {
795		XmbTextExtents(Scr->MenuFont.font_set,
796		               itemname, tmp->strlen,
797		               &ink_rect, &logical_rect);
798		width = logical_rect.width;
799	}
800	else {
801		// Fake for non-dpy cases
802		width = 25;
803	}
804
805	if(width <= 0) {
806		width = 1;
807	}
808	if(width > menu->width) {
809		menu->width = width;
810	}
811
812	tmp->user_colors = false;
813	if(Scr->Monochrome == COLOR && fore != NULL) {
814		bool save;
815
816		save = Scr->FirstTime;
817		Scr->FirstTime = true;
818		GetColor(COLOR, &tmp->normal.fore, fore);
819		GetColor(COLOR, &tmp->normal.back, back);
820		if(Scr->use3Dmenus && !Scr->BeNiceToColormap) {
821			GetShadeColors(&tmp->normal);
822		}
823		Scr->FirstTime = save;
824		tmp->user_colors = true;
825	}
826	if(sub != NULL) {
827		tmp->sub = sub;
828		menu->pull = true;
829	}
830	tmp->item_num = menu->items++;
831
832	return (tmp);
833}
834
835
836void MakeMenus(void)
837{
838	MenuRoot *mr;
839
840	for(mr = Scr->MenuList; mr != NULL; mr = mr->next) {
841		if(mr->real_menu == false) {
842			continue;
843		}
844
845		mr->pinned = false;
846		MakeMenu(mr);
847	}
848}
849
850
851void MakeMenu(MenuRoot *mr)
852{
853	MenuItem *start, *tmp;
854	XColor f1, f2, f3;
855	XColor b1, b2, b3;
856	XColor save_fore, save_back;
857	int fred, fgreen, fblue;
858	int bred, bgreen, bblue;
859	int width, borderwidth;
860	unsigned long valuemask;
861	XSetWindowAttributes attributes;
862	Colormap cmap = Scr->RootColormaps.cwins[0]->colormap->c;
863	XRectangle ink_rect;
864	XRectangle logical_rect;
865
866	Scr->EntryHeight = Scr->MenuFont.height + 4;
867
868	/* lets first size the window accordingly */
869	if(mr->mapped == MRM_NEVER) {
870		int max_entry_height = 0;
871
872		if(mr->pull == true) {
873			mr->width += 16 + 10;
874		}
875		width = mr->width + 10;
876		for(MenuItem *cur = mr->first; cur != NULL; cur = cur->next) {
877			XmbTextExtents(Scr->MenuFont.font_set, cur->item, cur->strlen,
878			               &ink_rect, &logical_rect);
879			max_entry_height = MAX(max_entry_height, logical_rect.height);
880
881			if(cur->func != F_TITLE) {
882				cur->x = 5;
883			}
884			else {
885				cur->x = width - logical_rect.width;
886				cur->x /= 2;
887			}
888		}
889		Scr->EntryHeight = max_entry_height + ENTRY_SPACING;
890		mr->height = mr->items * Scr->EntryHeight;
891		mr->width += 10;
892		if(Scr->use3Dmenus) {
893			mr->width  += 2 * Scr->MenuShadowDepth;
894			mr->height += 2 * Scr->MenuShadowDepth;
895		}
896		if(Scr->Shadow && ! mr->pinned) {
897			/*
898			 * Make sure that you don't draw into the shadow window or else
899			 * the background bits there will get saved
900			 */
901			valuemask = (CWBackPixel | CWBorderPixel);
902			attributes.background_pixel = Scr->MenuShadowColor;
903			attributes.border_pixel = Scr->MenuShadowColor;
904			if(Scr->SaveUnder) {
905				valuemask |= CWSaveUnder;
906				attributes.save_under = True;
907			}
908			mr->shadow = XCreateWindow(dpy, Scr->Root, 0, 0,
909			                           mr->width,
910			                           mr->height,
911			                           0,
912			                           CopyFromParent,
913			                           CopyFromParent,
914			                           CopyFromParent,
915			                           valuemask, &attributes);
916		}
917
918		valuemask = (CWBackPixel | CWBorderPixel | CWEventMask);
919		attributes.background_pixel = Scr->MenuC.back;
920		attributes.border_pixel = Scr->MenuC.fore;
921		if(mr->pinned) {
922			attributes.event_mask = (ExposureMask | EnterWindowMask
923			                         | LeaveWindowMask | ButtonPressMask
924			                         | ButtonReleaseMask | PointerMotionMask
925			                         | ButtonMotionMask
926			                        );
927			attributes.cursor = Scr->MenuCursor;
928			valuemask |= CWCursor;
929		}
930		else {
931			attributes.event_mask = (ExposureMask | EnterWindowMask);
932		}
933
934		if(Scr->SaveUnder && ! mr->pinned) {
935			valuemask |= CWSaveUnder;
936			attributes.save_under = True;
937		}
938		if(Scr->BackingStore) {
939			valuemask |= CWBackingStore;
940			attributes.backing_store = Always;
941		}
942		borderwidth = Scr->use3Dmenus ? 0 : 1;
943		mr->w = XCreateWindow(dpy, Scr->Root, 0, 0, mr->width,
944		                      mr->height, borderwidth,
945		                      CopyFromParent, CopyFromParent,
946		                      CopyFromParent,
947		                      valuemask, &attributes);
948
949
950		XSaveContext(dpy, mr->w, MenuContext, (XPointer)mr);
951		XSaveContext(dpy, mr->w, ScreenContext, (XPointer)Scr);
952
953		mr->mapped = MRM_UNMAPPED;
954	}
955
956	if(Scr->use3Dmenus && (Scr->Monochrome == COLOR)
957	                && (mr->highlight.back == UNUSED_PIXEL)) {
958		XColor xcol;
959		char colname [32];
960		bool save;
961
962		xcol.pixel = Scr->MenuC.back;
963		XQueryColor(dpy, cmap, &xcol);
964		sprintf(colname, "#%04x%04x%04x",
965		        5 * ((int)xcol.red   / 6),
966		        5 * ((int)xcol.green / 6),
967		        5 * ((int)xcol.blue  / 6));
968		save = Scr->FirstTime;
969		Scr->FirstTime = true;
970		GetColor(Scr->Monochrome, &mr->highlight.back, colname);
971		Scr->FirstTime = save;
972	}
973
974	if(Scr->use3Dmenus && (Scr->Monochrome == COLOR)
975	                && (mr->highlight.fore == UNUSED_PIXEL)) {
976		XColor xcol;
977		char colname [32];
978		bool save;
979
980		xcol.pixel = Scr->MenuC.fore;
981		XQueryColor(dpy, cmap, &xcol);
982		sprintf(colname, "#%04x%04x%04x",
983		        5 * ((int)xcol.red   / 6),
984		        5 * ((int)xcol.green / 6),
985		        5 * ((int)xcol.blue  / 6));
986		save = Scr->FirstTime;
987		Scr->FirstTime = true;
988		GetColor(Scr->Monochrome, &mr->highlight.fore, colname);
989		Scr->FirstTime = save;
990	}
991	if(Scr->use3Dmenus && !Scr->BeNiceToColormap) {
992		GetShadeColors(&mr->highlight);
993	}
994
995	/* get the default colors into the menus */
996	for(tmp = mr->first; tmp != NULL; tmp = tmp->next) {
997		if(!tmp->user_colors) {
998			if(tmp->func != F_TITLE) {
999				tmp->normal.fore = Scr->MenuC.fore;
1000				tmp->normal.back = Scr->MenuC.back;
1001			}
1002			else {
1003				tmp->normal.fore = Scr->MenuTitleC.fore;
1004				tmp->normal.back = Scr->MenuTitleC.back;
1005			}
1006		}
1007
1008		if(mr->highlight.fore != UNUSED_PIXEL) {
1009			tmp->highlight.fore = mr->highlight.fore;
1010			tmp->highlight.back = mr->highlight.back;
1011		}
1012		else {
1013			tmp->highlight.fore = tmp->normal.back;
1014			tmp->highlight.back = tmp->normal.fore;
1015		}
1016		if(Scr->use3Dmenus && !Scr->BeNiceToColormap) {
1017			if(tmp->func != F_TITLE) {
1018				GetShadeColors(&tmp->highlight);
1019			}
1020			else {
1021				GetShadeColors(&tmp->normal);
1022			}
1023		}
1024	}
1025	mr->pmenu = NULL;
1026
1027	if(Scr->Monochrome == MONOCHROME || !Scr->InterpolateMenuColors) {
1028		return;
1029	}
1030
1031	// Do InterpolateMenuColors magic
1032	start = mr->first;
1033	while(1) {
1034		for(; start != NULL; start = start->next) {
1035			if(start->user_colors) {
1036				break;
1037			}
1038		}
1039		if(start == NULL) {
1040			break;
1041		}
1042
1043		MenuItem *end;
1044		for(end = start->next; end != NULL; end = end->next) {
1045			if(end->user_colors) {
1046				break;
1047			}
1048		}
1049		if(end == NULL) {
1050			break;
1051		}
1052
1053		/* we have a start and end to interpolate between */
1054		int num = end->item_num - start->item_num;
1055
1056		f1.pixel = start->normal.fore;
1057		XQueryColor(dpy, cmap, &f1);
1058		f2.pixel = end->normal.fore;
1059		XQueryColor(dpy, cmap, &f2);
1060
1061		b1.pixel = start->normal.back;
1062		XQueryColor(dpy, cmap, &b1);
1063		b2.pixel = end->normal.back;
1064		XQueryColor(dpy, cmap, &b2);
1065
1066		fred = ((int)f2.red - (int)f1.red) / num;
1067		fgreen = ((int)f2.green - (int)f1.green) / num;
1068		fblue = ((int)f2.blue - (int)f1.blue) / num;
1069
1070		bred = ((int)b2.red - (int)b1.red) / num;
1071		bgreen = ((int)b2.green - (int)b1.green) / num;
1072		bblue = ((int)b2.blue - (int)b1.blue) / num;
1073
1074		f3 = f1;
1075		f3.flags = DoRed | DoGreen | DoBlue;
1076
1077		b3 = b1;
1078		b3.flags = DoRed | DoGreen | DoBlue;
1079
1080		start->highlight.back = start->normal.fore;
1081		start->highlight.fore = start->normal.back;
1082		num -= 1;
1083		int i = 0;
1084		MenuItem *cur = start->next;
1085		// XXX Should be impossible to run out of cur's before num's,
1086		// unless the item_num's are wrong (which would break other
1087		// stuff), but add condition to quiet static analysis.
1088		for(; cur != NULL && i < num ; i++, cur = cur->next) {
1089			f3.red += fred;
1090			f3.green += fgreen;
1091			f3.blue += fblue;
1092			save_fore = f3;
1093
1094			b3.red += bred;
1095			b3.green += bgreen;
1096			b3.blue += bblue;
1097			save_back = b3;
1098
1099			XAllocColor(dpy, cmap, &f3);
1100			XAllocColor(dpy, cmap, &b3);
1101			cur->highlight.back = cur->normal.fore = f3.pixel;
1102			cur->highlight.fore = cur->normal.back = b3.pixel;
1103			cur->user_colors = true;
1104
1105			f3 = save_fore;
1106			b3 = save_back;
1107		}
1108		start = end;
1109		start->highlight.back = start->normal.fore;
1110		start->highlight.fore = start->normal.back;
1111	}
1112	return;
1113}
1114
1115
1116/***********************************************************************
1117 *
1118 *  Procedure:
1119 *      PopUpMenu - pop up a pull down menu
1120 *
1121 *  Inputs:
1122 *      menu    - the root pointer of the menu to pop up
1123 *      x, y    - location of upper left of menu
1124 *      center  - whether or not to center horizontally over position
1125 *
1126 ***********************************************************************
1127 */
1128
1129bool
1130PopUpMenu(MenuRoot *menu, int x, int y, bool center)
1131{
1132	int WindowNameCount;
1133	TwmWindow **WindowNames;
1134	TwmWindow *tmp_win2, *tmp_win3;
1135	int i;
1136	bool clipped;
1137	if(!menu) {
1138		return false;
1139	}
1140
1141	InstallRootColormap();
1142
1143	if((menu == Scr->Windows) ||
1144	                (menu == Scr->Icons) ||
1145	                (menu == Scr->AllWindows) ||
1146	                /* Added by Dan 'dl' Lilliehorn 040607 */
1147	                (menu == Scr->AllIcons) ||
1148	                /* Added by Dan Lilliehorn (dl@dl.nu) 2000-02-29 */
1149	                (menu == Scr->Visible)) {
1150		TwmWindow *tmp_win;
1151		WorkSpace *ws;
1152		bool all, icons, visible_, allicons; /* visible, allicons:
1153                                                  Added by dl */
1154		int func;
1155
1156		/* this is the twm windows menu,  let's go ahead and build it */
1157
1158		all = (menu == Scr->AllWindows);
1159		icons = (menu == Scr->Icons);
1160		visible_ = (menu == Scr->Visible);    /* Added by dl */
1161		allicons = (menu == Scr->AllIcons);
1162		DestroyMenu(menu);
1163
1164		menu->first = NULL;
1165		menu->last = NULL;
1166		menu->items = 0;
1167		menu->width = 0;
1168		menu->mapped = MRM_NEVER;
1169		menu->highlight.fore = UNUSED_PIXEL;
1170		menu->highlight.back = UNUSED_PIXEL;
1171		if(menu == Scr->Windows) {
1172			AddToMenu(menu, "TWM Windows", NULL, NULL, F_TITLE, NULL, NULL);
1173		}
1174		else if(menu == Scr->Icons) {
1175			AddToMenu(menu, "TWM Icons", NULL, NULL, F_TITLE, NULL, NULL);
1176		}
1177		else if(menu == Scr->Visible) { /* Added by dl 2000 */
1178			AddToMenu(menu, "TWM Visible", NULL, NULL, F_TITLE, NULL, NULL);
1179		}
1180		else if(menu == Scr->AllIcons) { /* Added by dl 2004 */
1181			AddToMenu(menu, "TWM All Icons", NULL, NULL, F_TITLE, NULL, NULL);
1182		}
1183		else {
1184			AddToMenu(menu, "TWM All Windows", NULL, NULL, F_TITLE, NULL, NULL);
1185		}
1186
1187		ws = NULL;
1188
1189		if(!(all || allicons)
1190		                && CurrentSelectedWorkspace && Scr->workSpaceManagerActive) {
1191			for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
1192				if(strcmp(ws->name, CurrentSelectedWorkspace) == 0) {
1193					break;
1194				}
1195			}
1196		}
1197		if(!Scr->currentvs) {
1198			return false;
1199		}
1200		if(!ws) {
1201			ws = Scr->currentvs->wsw->currentwspc;
1202		}
1203
1204		for(tmp_win = Scr->FirstWindow, WindowNameCount = 0;
1205		                tmp_win != NULL;
1206		                tmp_win = tmp_win->next) {
1207			if(tmp_win == Scr->workSpaceMgr.occupyWindow->twm_win) {
1208				continue;
1209			}
1210			if(Scr->ShortAllWindowsMenus && (tmp_win->iswspmgr || tmp_win->isiconmgr)) {
1211				continue;
1212			}
1213
1214			if(!(all || allicons) && !OCCUPY(tmp_win, ws)) {
1215				continue;
1216			}
1217			if(allicons && !tmp_win->isicon) {
1218				continue;
1219			}
1220			if(icons && !tmp_win->isicon) {
1221				continue;
1222			}
1223			if(visible_ && tmp_win->isicon) {
1224				continue;        /* added by dl */
1225			}
1226			WindowNameCount++;
1227		}
1228
1229		// Hack: always pretend there's at least one window, even if
1230		// there are none; that lets us skip special cases for empty
1231		// lists...
1232		if(WindowNameCount == 0) {
1233			WindowNameCount = 1;
1234		}
1235		WindowNames = calloc(WindowNameCount, sizeof(TwmWindow *));
1236
1237		WindowNameCount = 0;
1238		for(tmp_win = Scr->FirstWindow;
1239		                tmp_win != NULL;
1240		                tmp_win = tmp_win->next) {
1241			if(LookInList(Scr->IconMenuDontShow, tmp_win->name, &tmp_win->class)) {
1242				continue;
1243			}
1244
1245			if(tmp_win == Scr->workSpaceMgr.occupyWindow->twm_win) {
1246				continue;
1247			}
1248			if(Scr->ShortAllWindowsMenus &&
1249			                tmp_win == Scr->currentvs->wsw->twm_win) {
1250				continue;
1251			}
1252			if(Scr->ShortAllWindowsMenus && tmp_win->isiconmgr) {
1253				continue;
1254			}
1255
1256			if(!(all || allicons) && ! OCCUPY(tmp_win, ws)) {
1257				continue;
1258			}
1259			if(allicons && !tmp_win->isicon) {
1260				continue;
1261			}
1262			if(icons && !tmp_win->isicon) {
1263				continue;
1264			}
1265			if(visible_ && tmp_win->isicon) {
1266				continue;        /* added by dl */
1267			}
1268			tmp_win2 = tmp_win;
1269
1270			for(i = 0; i < WindowNameCount; i++) {
1271				int compresult;
1272				char *tmpname1, *tmpname2;
1273				tmpname1 = tmp_win2->name;
1274				tmpname2 = WindowNames[i]->name;
1275				if(Scr->CaseSensitive) {
1276					compresult = strcmp(tmpname1, tmpname2);
1277				}
1278				else {
1279					compresult = strcasecmp(tmpname1, tmpname2);
1280				}
1281				if(compresult < 0) {
1282					tmp_win3 = tmp_win2;
1283					tmp_win2 = WindowNames[i];
1284					WindowNames[i] = tmp_win3;
1285				}
1286			}
1287			WindowNames[WindowNameCount] = tmp_win2;
1288			WindowNameCount++;
1289		}
1290		func = (all || allicons || CurrentSelectedWorkspace) ? F_WINWARP :
1291		       F_POPUP;
1292		for(i = 0; i < WindowNameCount; i++) {
1293			char *tmpname;
1294			tmpname = WindowNames[i]->name;
1295			AddToMenu(menu, tmpname, (char *)WindowNames[i],
1296			          NULL, func, NULL, NULL);
1297		}
1298		free(WindowNames);
1299
1300		menu->pinned = false;
1301		MakeMenu(menu);
1302	}
1303
1304	/* Keys added by dl */
1305
1306	if(menu == Scr->Keys) {
1307		char *oldact = 0;
1308		int oldmod = 0;
1309
1310		DestroyMenu(menu);
1311
1312		menu->first = NULL;
1313		menu->last = NULL;
1314		menu->items = 0;
1315		menu->width = 0;
1316		menu->mapped = MRM_NEVER;
1317		menu->highlight.fore = UNUSED_PIXEL;
1318		menu->highlight.back = UNUSED_PIXEL;
1319
1320		AddToMenu(menu, "Twm Keys", NULL, NULL, F_TITLE, NULL, NULL);
1321
1322		for(const FuncKey *tmpKey = Scr->FuncKeyRoot.next; tmpKey != NULL;
1323		                tmpKey = tmpKey->next) {
1324			char *tmpStr;
1325
1326			if(tmpKey->func != F_EXEC) {
1327				continue;
1328			}
1329			if((tmpKey->action == oldact) && (tmpKey->mods == oldmod)) {
1330				continue;
1331			}
1332
1333			tmpStr = mk_twmkeys_entry(tmpKey);
1334			if(tmpStr == NULL) {
1335				tmpStr = strdup("(error)");
1336			}
1337
1338			AddToMenu(menu, tmpStr, tmpKey->action, NULL, tmpKey->func, NULL, NULL);
1339			oldact = tmpKey->action;
1340			oldmod = tmpKey->mods;
1341		}
1342		menu->pinned = false;
1343		MakeMenu(menu);
1344	}
1345	if(menu->w == None || menu->items == 0) {
1346		return false;
1347	}
1348
1349	/* Prevent recursively bringing up menus. */
1350	if((!menu->pinned) && (menu->mapped == MRM_MAPPED)) {
1351		return false;
1352	}
1353
1354	/*
1355	 * Dynamically set the parent;  this allows pull-ups to also be main
1356	 * menus, or to be brought up from more than one place.
1357	 */
1358	menu->prev = ActiveMenu;
1359
1360	if(menu->pinned) {
1361		ActiveMenu    = menu;
1362		menu->mapped  = MRM_MAPPED;
1363		menu->entered = true;
1364		MenuOrigins [MenuDepth].x = menu->x;
1365		MenuOrigins [MenuDepth].y = menu->y;
1366		MenuDepth++;
1367
1368		XRaiseWindow(dpy, menu->w);
1369		return true;
1370	}
1371
1372	XGrabPointer(dpy, Scr->Root, True,
1373	             ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
1374	             ButtonMotionMask | PointerMotionHintMask,
1375	             GrabModeAsync, GrabModeAsync,
1376	             Scr->Root,
1377	             Scr->MenuCursor, CurrentTime);
1378
1379	XGrabKeyboard(dpy, Scr->Root, True, GrabModeAsync, GrabModeAsync, CurrentTime);
1380
1381	ActiveMenu = menu;
1382	menu->mapped = MRM_MAPPED;
1383	menu->entered = false;
1384
1385	if(center) {
1386		x -= (menu->width / 2);
1387		y -= (Scr->EntryHeight / 2);    /* sticky menus would be nice here */
1388	}
1389
1390	/*
1391	* clip to screen
1392	*/
1393	clipped = ConstrainByLayout(Scr->Layout, -1, &x, menu->width, &y, menu->height);
1394	MenuOrigins[MenuDepth].x = x;
1395	MenuOrigins[MenuDepth].y = y;
1396	MenuDepth++;
1397
1398
1399	/*
1400	 * Position and display the menu, and its shadow if it has one.  We
1401	 * start by positioning and raising (above everything else on screen)
1402	 * the shadow.  Then position the menu itself, raise it up above
1403	 * that, and map it.  Then map the shadow; doing that after raising
1404	 * and mapping the menu avoids spending time drawing the bulk of the
1405	 * window which the menu covers up anyway.
1406	 */
1407	if(Scr->Shadow) {
1408		XMoveWindow(dpy, menu->shadow, x + SHADOWWIDTH, y + SHADOWWIDTH);
1409		XRaiseWindow(dpy, menu->shadow);
1410	}
1411
1412	XMoveWindow(dpy, menu->w, x, y);
1413	XMapRaised(dpy, menu->w);
1414
1415	if(Scr->Shadow) {
1416		XMapWindow(dpy, menu->shadow);
1417	}
1418
1419	/* Move mouse pointer if we're supposed to */
1420	if(!Scr->NoWarpToMenuTitle && clipped && center) {
1421		const int xl = x + (menu->width      / 2);
1422		const int yt = y + (Scr->EntryHeight / 2);
1423		XWarpPointer(dpy, Scr->Root, Scr->Root, x, y,
1424		             menu->width, menu->height, xl, yt);
1425	}
1426
1427
1428	XSync(dpy, 0);
1429	return true;
1430}
1431
1432
1433/***********************************************************************
1434 *
1435 *  Procedure:
1436 *      PopDownMenu - unhighlight the current menu selection and
1437 *              take down the menus
1438 *
1439 ***********************************************************************
1440 */
1441
1442void PopDownMenu(void)
1443{
1444	MenuRoot *tmp;
1445
1446	if(ActiveMenu == NULL) {
1447		return;
1448	}
1449
1450	if(ActiveItem) {
1451		ActiveItem->state = false;
1452		PaintEntry(ActiveMenu, ActiveItem, false);
1453	}
1454
1455	for(tmp = ActiveMenu; tmp != NULL; tmp = tmp->prev) {
1456		if(! tmp->pinned) {
1457			HideMenu(tmp);
1458		}
1459		UninstallRootColormap();
1460	}
1461
1462	XFlush(dpy);
1463	ActiveMenu = NULL;
1464	ActiveItem = NULL;
1465	MenuDepth = 0;
1466	XUngrabKeyboard(dpy, CurrentTime);
1467	if(Context == C_WINDOW || Context == C_FRAME || Context == C_TITLE
1468	                || Context == C_ICON) {
1469		menuFromFrameOrWindowOrTitlebar = true;
1470	}
1471
1472	return;
1473}
1474
1475
1476void HideMenu(MenuRoot *menu)
1477{
1478	if(!menu) {
1479		return;
1480	}
1481
1482	if(Scr->Shadow) {
1483		XUnmapWindow(dpy, menu->shadow);
1484	}
1485	XUnmapWindow(dpy, menu->w);
1486	menu->mapped = MRM_UNMAPPED;
1487}
1488
1489/***********************************************************************
1490 *
1491 *  Procedure:
1492 *      FindMenuRoot - look for a menu root
1493 *
1494 *  Returned Value:
1495 *      (MenuRoot *)  - a pointer to the menu root structure
1496 *
1497 *  Inputs:
1498 *      name    - the name of the menu root
1499 *
1500 ***********************************************************************
1501 */
1502
1503MenuRoot *FindMenuRoot(char *name)
1504{
1505	MenuRoot *tmp;
1506
1507	for(tmp = Scr->MenuList; tmp != NULL; tmp = tmp->next) {
1508		if(strcmp(name, tmp->name) == 0) {
1509			return (tmp);
1510		}
1511	}
1512	return NULL;
1513}
1514
1515
1516
1517static void DestroyMenu(MenuRoot *menu)
1518{
1519	MenuItem *item;
1520
1521	if(menu->w) {
1522		XDeleteContext(dpy, menu->w, MenuContext);
1523		XDeleteContext(dpy, menu->w, ScreenContext);
1524		if(Scr->Shadow) {
1525			XDestroyWindow(dpy, menu->shadow);
1526		}
1527		XDestroyWindow(dpy, menu->w);
1528	}
1529
1530	for(item = menu->first; item;) {
1531		MenuItem *tmp = item;
1532		item = item->next;
1533		free(tmp);
1534	}
1535}
1536
1537
1538void MoveMenu(XEvent *eventp)
1539{
1540	int    XW, YW, newX, newY;
1541	bool   cont;
1542	bool   newev;
1543	unsigned long event_mask;
1544	XEvent ev;
1545
1546	if(! ActiveMenu) {
1547		return;
1548	}
1549	if(! ActiveMenu->pinned) {
1550		return;
1551	}
1552
1553	XW = eventp->xbutton.x_root - ActiveMenu->x;
1554	YW = eventp->xbutton.y_root - ActiveMenu->y;
1555	XGrabPointer(dpy, ActiveMenu->w, True,
1556	             ButtonPressMask  | ButtonReleaseMask | ButtonMotionMask,
1557	             GrabModeAsync, GrabModeAsync,
1558	             None, Scr->MoveCursor, CurrentTime);
1559
1560	newX = ActiveMenu->x;
1561	newY = ActiveMenu->y;
1562	cont = true;
1563	event_mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask |
1564	             ExposureMask;
1565	XMaskEvent(dpy, event_mask, &ev);
1566	while(cont) {
1567		ev.xbutton.x_root -= Scr->rootx;
1568		ev.xbutton.y_root -= Scr->rooty;
1569		switch(ev.xany.type) {
1570			case ButtonRelease :
1571				cont = false;
1572			case MotionNotify :
1573				if(!cont) {
1574					newev = false;
1575					while(XCheckMaskEvent(dpy, ButtonMotionMask | ButtonReleaseMask, &ev)) {
1576						newev = true;
1577						if(ev.type == ButtonRelease) {
1578							break;
1579						}
1580					}
1581					if(ev.type == ButtonRelease) {
1582						continue;
1583					}
1584					if(newev) {
1585						ev.xbutton.x_root -= Scr->rootx;
1586						ev.xbutton.y_root -= Scr->rooty;
1587					}
1588				}
1589				newX = ev.xbutton.x_root - XW;
1590				newY = ev.xbutton.y_root - YW;
1591				if(Scr->DontMoveOff) {
1592					ConstrainByBorders1(&newX, ActiveMenu->width,
1593					                    &newY, ActiveMenu->height);
1594				}
1595				XMoveWindow(dpy, ActiveMenu->w, newX, newY);
1596				XMaskEvent(dpy, event_mask, &ev);
1597				break;
1598			case ButtonPress :
1599				cont = false;
1600				newX = ActiveMenu->x;
1601				newY = ActiveMenu->y;
1602				break;
1603			case Expose:
1604			case NoExpose:
1605				Event = ev;
1606				DispatchEvent();
1607				XMaskEvent(dpy, event_mask, &ev);
1608				break;
1609		}
1610	}
1611	XUngrabPointer(dpy, CurrentTime);
1612	if(ev.xany.type == ButtonRelease) {
1613		ButtonPressed = -1;
1614	}
1615	/*XPutBackEvent (dpy, &ev);*/
1616	XMoveWindow(dpy, ActiveMenu->w, newX, newY);
1617	ActiveMenu->x = newX;
1618	ActiveMenu->y = newY;
1619	MenuOrigins [MenuDepth - 1].x = newX;
1620	MenuOrigins [MenuDepth - 1].y = newY;
1621
1622	return;
1623}
1624
1625
1626void WarpCursorToDefaultEntry(MenuRoot *menu)
1627{
1628	MenuItem    *item;
1629	Window       root;
1630	int          i, x, y, xl, yt;
1631	unsigned int w, h, bw, d;
1632
1633	for(i = 0, item = menu->first; item != menu->last; item = item->next) {
1634		if(item == menu->defaultitem) {
1635			break;
1636		}
1637		i++;
1638	}
1639	if(!XGetGeometry(dpy, menu->w, &root, &x, &y, &w, &h, &bw, &d)) {
1640		return;
1641	}
1642	xl = x + (menu->width / 2);
1643	yt = y + (i + 0.5) * Scr->EntryHeight;
1644
1645	XWarpPointer(dpy, Scr->Root, Scr->Root,
1646	             Event.xbutton.x_root, Event.xbutton.y_root,
1647	             menu->width, menu->height, xl, yt);
1648}
1649
1650
1651
1652/**
1653 * Generate up a string representation of a keybinding->action.
1654 * Internally used in generating TwmKeys menu.
1655 */
1656char *
1657mk_twmkeys_entry(const FuncKey *key)
1658{
1659	char *ret;
1660	//         S+  C+  5(Mx+)  5(Ax+)
1661#define MSLEN (2 + 2 + 2 + 5 * 3 + 5 * 3 + 1 + 26)
1662	char modStr[MSLEN + 1];
1663	char *modStrCur = modStr;
1664
1665	// Init
1666	*modStrCur = '\0';
1667
1668	// Check and add prefixes for each modifier
1669#define DO(mask, str) do { \
1670                if(key->mods & mask##Mask) { \
1671                        const int tslen = sizeof(str) - 1; \
1672                        if((modStrCur - modStr + tslen) >= MSLEN) { \
1673                                fprintf(stderr, "BUG: No space to add '%s' " \
1674                                                "in %s()\n", str, __func__); \
1675                                return NULL; \
1676                        } \
1677                        strcpy(modStrCur, str); \
1678                        modStrCur += tslen; \
1679                } \
1680        } while(0)
1681
1682	// Mod1 is Meta (== Alt), so is special and comes first, apart and
1683	// differing from the other more generic ModX's.
1684	DO(Mod1, "M+");
1685
1686	// Shift/Ctrl are normal common bits.
1687	DO(Shift,   "S+");
1688	DO(Control, "C+");
1689
1690	// Other Mod's and Alt's are weirder, but possible.
1691	DO(Mod2, "M2+");
1692	DO(Mod3, "M3+");
1693	DO(Mod4, "M4+");
1694	DO(Mod5, "M5+");
1695
1696	DO(Alt1, "A1+");
1697	DO(Alt2, "A2+");
1698	DO(Alt3, "A3+");
1699	DO(Alt4, "A4+");
1700	DO(Alt5, "A5+");
1701
1702	// Overflows for test.  Watch out for colliding with X or our *Mask
1703	// defs.
1704	// +1 when combined with above, should be enough
1705#define Over1Mask (1<<30)
1706	DO(Over1, "a");
1707	// Way too big no matter what
1708#define OverAllMask (1<<31)
1709	DO(OverAll, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz");
1710
1711#undef OverAllMask
1712#undef Over1Mask
1713
1714#undef DO
1715
1716	asprintf(&ret, "[%s%s] %s", modStr, key->name, key->action);
1717	return ret;
1718}
1719