1 /* -*-c-*- */
2 /* This program is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or
5  * (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, see: <http://www.gnu.org/licenses/>
14  */
15 
16 /* IMPORTANT NOTE: Do *not* use any constant numbers in this file. All values
17  * have to be #defined in the section below or in defaults.h to ensure full
18  * control over the menus. */
19 
20 /* ---------------------------- included header files ---------------------- */
21 
22 #include "config.h"
23 
24 #include <stdio.h>
25 #include <assert.h>
26 #include <X11/keysym.h>
27 
28 #include "libs/ftime.h"
29 #include "libs/fvwmlib.h"
30 #include "libs/FScreen.h"
31 #include "libs/Grab.h"
32 #include "libs/Parse.h"
33 #include "libs/ColorUtils.h"
34 #include "libs/Picture.h"
35 #include "libs/PictureUtils.h"
36 #include "libs/Graphics.h"
37 #include "libs/PictureGraphics.h"
38 #include "libs/charmap.h"
39 #include "libs/wcontext.h"
40 #include "fvwm.h"
41 #include "externs.h"
42 #include "execcontext.h"
43 #include "events.h"
44 #include "eventhandler.h"
45 #include "eventmask.h"
46 #include "cursor.h"
47 #include "functions.h"
48 #include "commands.h"
49 #include "misc.h"
50 #include "screen.h"
51 #include "colormaps.h"
52 #include "geometry.h"
53 #include "move_resize.h"
54 #include "menudim.h"
55 #include "menuitem.h"
56 #include "menuroot.h"
57 #include "menustyle.h"
58 #include "bindings.h"
59 #include "menubindings.h"
60 #include "menugeometry.h"
61 #include "menuparameters.h"
62 #include "menus.h"
63 #include "libs/FGettext.h"
64 
65 /* ---------------------------- local definitions -------------------------- */
66 
67 /* used in float to int arithmetic */
68 #define ROUNDING_ERROR_TOLERANCE  0.005
69 
70 /* ---------------------------- local macros ------------------------------- */
71 
72 #define SCTX_SET_MI(ctx,item)	((ctx).type = SCTX_MENU_ITEM, \
73 				 (ctx).menu_item.menu_item = (item))
74 #define SCTX_GET_MI(ctx)	((ctx).type == SCTX_MENU_ITEM ? \
75 				 (ctx).menu_item.menu_item : NULL)
76 #define SCTX_SET_MR(ctx,root)	((ctx).type = SCTX_MENU_ROOT, \
77 				 (ctx).menu_root.menu_root = (root))
78 #define SCTX_GET_MR(ctx)	((ctx).type == SCTX_MENU_ROOT ? \
79 				 (ctx).menu_root.menu_root : NULL)
80 
81 /* ---------------------------- imports ------------------------------------ */
82 
83 /* This external is safe. It's written only during startup. */
84 extern XContext MenuContext;
85 
86 /* ---------------------------- included code files ------------------------ */
87 
88 /* ---------------------------- local types -------------------------------- */
89 
90 /* patch to pass the last popups position hints to popup_func */
91 typedef struct
92 {
93 	struct
94 	{
95 		unsigned is_last_menu_pos_hints_valid : 1;
96 		unsigned do_ignore_pos_hints : 1;
97 		unsigned do_warp_title : 1;
98 	} flags;
99 	struct MenuPosHints pos_hints;
100 } saved_pos_hints;
101 
102 typedef struct MenuInfo
103 {
104 	MenuRoot *all;
105 	int n_destroyed_menus;
106 } MenuInfo;
107 
108 typedef struct MenuSizingParameters
109 {
110 	MenuRoot *menu;
111 	/* number of item labels present in the item format */
112 	int used_item_labels;
113 	/* same for mini icons */
114 	int used_mini_icons;
115 	struct
116 	{
117 		int sidepic_width;
118 		MenuItemPartSizesT i;
119 	} max;
120 	struct
121 	{
122 		unsigned is_popup_indicator_used : 1;
123 	} flags;
124 } MenuSizingParameters;
125 
126 typedef enum
127 {
128 	SCTX_MENU_ROOT,
129 	SCTX_MENU_ITEM
130 } string_context_type_t;
131 
132 typedef union
133 {
134 	string_context_type_t type;
135 	struct
136 	{
137 		string_context_type_t type;
138 		MenuRoot *menu_root;
139 	} menu_root;
140 	struct
141 	{
142 		string_context_type_t type;
143 		MenuItem *menu_item;
144 	} menu_item;
145 } string_context_t;
146 
147 typedef struct
148 {
149 	char delimiter;
150 	Bool (*string_handler)(
151 		char *string, char delimiter,
152 		string_context_t *user_data);
153 } string_def_t;
154 
155 /* ---------------------------- menu loop types ---------------------------- */
156 
157 typedef enum
158 {
159 	MENU_MLOOP_RET_NORMAL,
160 	MENU_MLOOP_RET_LOOP,
161 	MENU_MLOOP_RET_END
162 } mloop_ret_code_t;
163 
164 typedef struct
165 {
166 	unsigned do_popup_immediately : 1;
167 	/* used for delay popups, to just popup the menu */
168 	unsigned do_popup_now : 1;
169 	unsigned do_popdown_now : 1;
170 	/* used for keystrokes, to popup and move to that menu */
171 	unsigned do_popup_and_warp : 1;
172 	unsigned do_force_reposition : 1;
173 	unsigned do_force_popup : 1;
174 	unsigned do_popdown : 1;
175 	unsigned do_popup : 1;
176 	unsigned do_menu : 1;
177 	unsigned do_recycle_event : 1;
178 	unsigned do_propagate_event_into_submenu : 1;
179 	unsigned has_mouse_moved : 1;
180 	unsigned is_off_menu_allowed : 1;
181 	unsigned is_key_press : 1;
182 	unsigned is_item_entered_by_key_press : 1;
183 	unsigned is_motion_faked : 1;
184 	unsigned is_popped_up_by_timeout : 1;
185 	unsigned is_pointer_in_active_item_area : 1;
186 	unsigned is_motion_first : 1;
187 	unsigned is_release_first : 1;
188 	unsigned is_submenu_mapped : 1;
189 	unsigned was_item_unposted : 1;
190 	unsigned is_button_release : 1;
191 } mloop_flags_t;
192 
193 typedef struct
194 {
195 	MenuItem *mi;
196 	MenuRoot *mrMi;
197 	int x_offset;
198 	int popdown_delay_10ms;
199 	int popup_delay_10ms;
200 } mloop_evh_data_t;
201 
202 typedef struct
203 {
204 	MenuRoot *mrPopup;
205 	MenuRoot *mrPopdown;
206 	mloop_flags_t mif;
207 	/* used to reduce network traffic with delayed popup/popdown */
208 	MenuItem *mi_with_popup;
209 	MenuItem *mi_wants_popup;
210 	MenuItem *miRemovedSubmenu;
211 } mloop_evh_input_t;
212 
213 /* values that are set once when the menu loop is entered */
214 typedef struct mloop_static_info_t
215 {
216 	int x_init;
217 	int y_init;
218 	Time t0;
219 	unsigned int event_mask;
220 } mloop_static_info_t;
221 
222 /* ---------------------------- forward declarations ----------------------- */
223 
224 /* ---------------------------- local variables ---------------------------- */
225 
226 /* This global is saved and restored every time a function is called that
227  * might modify them, so we can safely let it live outside a function. */
228 static saved_pos_hints last_saved_pos_hints;
229 
230 /* structures for menus */
231 static MenuInfo Menus;
232 
233 /* ---------------------------- exported variables (globals) --------------- */
234 
235 /* ---------------------------- local functions ---------------------------- */
236 
__menu_execute_function(const exec_context_t ** pexc,char * action)237 static void __menu_execute_function(const exec_context_t **pexc, char *action)
238 {
239 	const exec_context_t *exc;
240 	exec_context_changes_t ecc;
241 	int old_emf;
242 
243 	ecc.w.w = ((*pexc)->w.fw) ? FW_W((*pexc)->w.fw) : None;
244 	exc = exc_clone_context(*pexc, &ecc, ECC_W);
245 	old_emf = Scr.flags.is_executing_menu_function;
246 	Scr.flags.is_executing_menu_function = 1;
247 	execute_function(NULL, exc, action, FUNC_DONT_EXPAND_COMMAND);
248 	Scr.flags.is_executing_menu_function = old_emf;
249 	exc_destroy_context(exc);
250 	/* See if the window has been deleted */
251 	if (!check_if_fvwm_window_exists((*pexc)->w.fw))
252 	{
253 		ecc.w.fw = NULL;
254 		ecc.w.w = None;
255 		ecc.w.wcontext = 0;
256 		exc = exc_clone_context(
257 			*pexc, &ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
258 		exc_destroy_context(*pexc);
259 		*pexc = exc;
260 	}
261 
262 	return;
263 }
264 
pointer_in_active_item_area(int x_offset,MenuRoot * mr)265 static Bool pointer_in_active_item_area(int x_offset, MenuRoot *mr)
266 {
267 	float ratio = (float)MST_ACTIVE_AREA_PERCENT(mr) / 100.0;
268 
269 	if (MST_ACTIVE_AREA_PERCENT(mr) >= 100)
270 	{
271 		return False;
272 	}
273 	if (MST_USE_LEFT_SUBMENUS(mr))
274 	{
275 		return (x_offset <=
276 			MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) -
277 			MR_ITEM_WIDTH(mr) * ratio);
278 	}
279 	else
280 	{
281 		return (x_offset >=
282 			MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) * ratio);
283 	}
284 }
285 
pointer_in_passive_item_area(int x_offset,MenuRoot * mr)286 static Bool pointer_in_passive_item_area(int x_offset, MenuRoot *mr)
287 {
288 	float ratio = (float)MST_ACTIVE_AREA_PERCENT(mr) / 100.0;
289 
290 	if (MST_ACTIVE_AREA_PERCENT(mr) >= 100)
291 	{
292 		return False;
293 	}
294 	if (MST_USE_LEFT_SUBMENUS(mr))
295 	{
296 		return (x_offset >=
297 			MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) * ratio);
298 	}
299 	else
300 	{
301 		return (x_offset <=
302 			MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) -
303 			MR_ITEM_WIDTH(mr) * ratio);
304 	}
305 }
306 
307 /*
308  * warping functions
309  */
310 
warp_pointer_to_title(MenuRoot * mr)311 static void warp_pointer_to_title(MenuRoot *mr)
312 {
313 	FWarpPointer(
314 		dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0,
315 		menudim_middle_x_offset(&MR_DIM(mr)),
316 		menuitem_middle_y_offset(MR_FIRST_ITEM(mr), MR_STYLE(mr)));
317 }
318 
warp_pointer_to_item(MenuRoot * mr,MenuItem * mi,Bool do_skip_title)319 static MenuItem *warp_pointer_to_item(
320 	MenuRoot *mr, MenuItem *mi, Bool do_skip_title)
321 {
322 	if (do_skip_title)
323 	{
324 		while (MI_NEXT_ITEM(mi) != NULL &&
325 		       (!MI_IS_SELECTABLE(mi) || MI_IS_TEAR_OFF_BAR(mi)))
326 		{
327 			/* skip separators, titles and tear off bars until the
328 			 * first 'real' item is found */
329 			mi = MI_NEXT_ITEM(mi);
330 		}
331 	}
332 	if (mi == NULL)
333 	{
334 		mi = MR_LAST_ITEM(mr);
335 	}
336 	if (mi == NULL)
337 	{
338 		return mi;
339 	}
340 	FWarpPointer(
341 		dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0,
342 		menudim_middle_x_offset(&MR_DIM(mr)),
343 		menuitem_middle_y_offset(mi, MR_STYLE(mr)));
344 
345 	return mi;
346 }
347 
348 /*
349  * menu animation functions
350  */
351 
352 /* prepares the parameters to be passed to AnimatedMoveOfWindow
353  * mr - the menu instance that holds the menu item
354  * fw - the FvwmWindow structure to check against allowed functions */
get_menu_repaint_transparent_parameters(MenuRepaintTransparentParameters * pmrtp,MenuRoot * mr,FvwmWindow * fw)355 static void get_menu_repaint_transparent_parameters(
356 	MenuRepaintTransparentParameters *pmrtp, MenuRoot *mr, FvwmWindow *fw)
357 {
358 	pmrtp->mr = mr;
359 	pmrtp->fw = fw;
360 
361 	return;
362 }
363 
364 
365 /* Undo the animation of a menu */
animated_move_back(MenuRoot * mr,Bool do_warp_pointer,FvwmWindow * fw)366 static void animated_move_back(
367 	MenuRoot *mr, Bool do_warp_pointer, FvwmWindow *fw)
368 {
369 	MenuRepaintTransparentParameters mrtp;
370 	int act_x;
371 	int act_y;
372 
373 	if (MR_XANIMATION(mr) == 0)
374 	{
375 		return;
376 	}
377 	if (menu_get_geometry(
378 		    mr, &JunkRoot, &act_x, &act_y, &JunkWidth, &JunkHeight,
379 		    &JunkBW, &JunkDepth))
380 	{
381 		Bool transparent_bg = False;
382 
383 		/* move it back */
384 		if (ST_HAS_MENU_CSET(MR_STYLE(mr)) &&
385 		    CSET_IS_TRANSPARENT(ST_CSET_MENU(MR_STYLE(mr))))
386 		{
387 			transparent_bg = True;
388 			get_menu_repaint_transparent_parameters(
389 				&mrtp, mr, fw);
390 		}
391 		AnimatedMoveOfWindow(
392 			MR_WINDOW(mr), act_x, act_y, act_x - MR_XANIMATION(mr),
393 			act_y, do_warp_pointer, -1, NULL,
394 			(transparent_bg)? &mrtp:NULL);
395 		MR_XANIMATION(mr) = 0;
396 	}
397 
398 	return;
399 }
400 
401 /* move a menu or a tear-off menu preserving transparency.
402  * tear-off menus are moved with their frame coordinates. */
move_any_menu(MenuRoot * mr,MenuParameters * pmp,int endX,int endY)403 static void move_any_menu(
404 	MenuRoot *mr, MenuParameters *pmp, int endX, int endY)
405 {
406 	if (MR_IS_TEAR_OFF_MENU(mr))
407 	{
408 		float fFull = 1.0;
409 
410 		/* this moves the tearoff menu, updating of transparency
411 		 * will not be as good as if menu repaint parameters
412 		 * are used. */
413 		AnimatedMoveFvwmWindow(
414 			pmp->tear_off_root_menu_window,
415 			FW_W_FRAME(pmp->tear_off_root_menu_window),
416 			-1, -1, endX, endY, False, 0, &fFull);
417 	}
418 	else
419 	{
420 		int x;
421 		int y;
422 		int JunkDept;
423 
424 		menu_get_geometry(mr, &JunkRoot, &x, &y, &JunkWidth,
425 				  &JunkHeight, &JunkBW, &JunkDept);
426 		if (x == endX && y == endY)
427 		{
428 			return;
429 		}
430 		if (ST_HAS_MENU_CSET(MR_STYLE(mr)) &&
431 		    CSET_IS_TRANSPARENT(ST_CSET_MENU(MR_STYLE(mr))))
432 		{
433 			MenuRepaintTransparentParameters mrtp;
434 
435 			get_menu_repaint_transparent_parameters(
436 				&mrtp, mr, (*pmp->pexc)->w.fw);
437 			update_transparent_menu_bg(
438 				&mrtp, x, y, endX, endY, endX, endY);
439 			XMoveWindow(dpy, MR_WINDOW(mr), endX, endY);
440 			repaint_transparent_menu(
441 				&mrtp, False, endX,endY, endX, endY, True);
442       	      	}
443 		else
444 		{
445 			XMoveWindow(dpy, MR_WINDOW(mr), endX, endY);
446 		}
447 	}
448 }
449 
450 /* ---------------------------- submenu function --------------------------- */
451 
452 /* Search for a submenu that was popped up by the given item in the given
453  * instance of the menu. */
seek_submenu_instance(MenuRoot * parent_menu,MenuItem * parent_item)454 static MenuRoot *seek_submenu_instance(
455 	MenuRoot *parent_menu, MenuItem *parent_item)
456 {
457 	MenuRoot *mr;
458 
459 	for (mr = Menus.all; mr != NULL; mr = MR_NEXT_MENU(mr))
460 	{
461 		if (MR_PARENT_MENU(mr) == parent_menu &&
462 		    MR_PARENT_ITEM(mr) == parent_item)
463 		{
464 			/* here it is */
465 			break;
466 		}
467 	}
468 
469 	return mr;
470 }
471 
is_submenu_mapped(MenuRoot * parent_menu,MenuItem * parent_item)472 static Bool is_submenu_mapped(MenuRoot *parent_menu, MenuItem *parent_item)
473 {
474 	XWindowAttributes win_attribs;
475 	MenuRoot *mr;
476 
477 	mr = seek_submenu_instance(parent_menu, parent_item);
478 	if (mr == NULL)
479 	{
480 		return False;
481 	}
482 
483 	if (MR_WINDOW(mr) == None)
484 	{
485 		return False;
486 	}
487 	if (!XGetWindowAttributes(dpy, MR_WINDOW(mr), &win_attribs))
488 	{
489 		return False;
490 	}
491 
492 	return (win_attribs.map_state == IsViewable);
493 }
494 
495 /* Returns the menu root that a given menu item pops up */
mr_popup_for_mi(MenuRoot * mr,MenuItem * mi)496 static MenuRoot *mr_popup_for_mi(MenuRoot *mr, MenuItem *mi)
497 {
498 	char *menu_name;
499 	MenuRoot *menu = NULL;
500 
501 	/* This checks if mi is != NULL too */
502 	if (!mi || !MI_IS_POPUP(mi))
503 	{
504 		return NULL;
505 	}
506 
507 	/* first look for a menu that is aleady mapped */
508 	menu = seek_submenu_instance(mr, mi);
509 	if (menu)
510 	{
511 		return menu;
512 	}
513 
514 	/* just look past "Popup " in the action, and find that menu root */
515 	menu_name = PeekToken(SkipNTokens(MI_ACTION(mi), 1), NULL);
516 	menu = menus_find_menu(menu_name);
517 
518 	return menu;
519 }
520 
521 /* ---------------------------- item handling ------------------------------ */
522 
523 /*
524  * find_entry()
525  *
526  * Returns the menu item the pointer is over and optionally the offset
527  * from the left side of the menu entry (if px_offset is != NULL) and
528  * the MenuRoot the pointer is over (if pmr is != NULL).
529  */
find_entry(MenuParameters * pmp,int * px_offset,MenuRoot ** pmr,Window p_child,int p_rx,int p_ry)530 static MenuItem *find_entry(
531 	MenuParameters *pmp,
532 	int *px_offset /*NULL means don't return this value */,
533 	MenuRoot **pmr /*NULL means don't return this value */,
534 	/* values passed in from caller it FQueryPointer was already called
535 	 * there */
536 	Window p_child, int p_rx, int p_ry)
537 {
538 	MenuItem *mi;
539 	MenuRoot *mr;
540 	int root_x, root_y;
541 	int x, y;
542 	Window Child;
543 	int r;
544 
545 	/* x_offset returns the x offset of the pointer in the found menu item
546 	 */
547 	if (px_offset)
548 	{
549 		*px_offset = 0;
550 	}
551 	if (pmr)
552 	{
553 		*pmr = NULL;
554 	}
555 	/* get the pointer position */
556 	if (p_rx < 0)
557 	{
558 		if (!FQueryPointer(
559 			    dpy, Scr.Root, &JunkRoot, &Child,
560 			    &root_x, &root_y, &JunkX, &JunkY, &JunkMask))
561 		{
562 			/* pointer is on a different screen */
563 			return NULL;
564 		}
565 	}
566 	else
567 	{
568 		root_x = p_rx;
569 		root_y = p_ry;
570 		Child = p_child;
571 	}
572 	/* find out the menu the pointer is in */
573 	if (pmp->tear_off_root_menu_window != NULL &&
574 	    Child == FW_W_FRAME(pmp->tear_off_root_menu_window))
575 	{
576 		/* we're in the top level torn off menu */
577 		Child = FW_W(pmp->tear_off_root_menu_window);
578 	}
579 	if (XFindContext(dpy, Child, MenuContext, (caddr_t *)&mr) == XCNOENT)
580 	{
581 		return NULL;
582 	}
583 	/* get position in that child window */
584 	if (!XTranslateCoordinates(
585 		    dpy, Scr.Root, MR_WINDOW(mr), root_x, root_y, &x, &y,
586 		    &JunkChild))
587 	{
588 		return NULL;
589 	}
590 	if (x < 0 || y < 0 || x >= MR_WIDTH(mr) || y >= MR_HEIGHT(mr))
591 	{
592 		return NULL;
593 	}
594 	if (pmr)
595 	{
596 		*pmr = mr;
597 	}
598 	r = MST_RELIEF_THICKNESS(mr);
599 	/* look for the entry that the mouse is in */
600 	for (mi = MR_FIRST_ITEM(mr); mi; mi = MI_NEXT_ITEM(mi))
601 	{
602 		int a;
603 		int b;
604 
605 		a = (MI_PREV_ITEM(mi) &&
606 		     MI_IS_SELECTABLE(MI_PREV_ITEM(mi))) ? r / 2 : 0;
607 		if (!MI_IS_SELECTABLE(mi))
608 		{
609 			b = 0;
610 		}
611 		else if (MI_NEXT_ITEM(mi) &&
612 			 MI_IS_SELECTABLE(MI_NEXT_ITEM(mi)))
613 		{
614 			b = r / 2;
615 		}
616 		else
617 		{
618 			b = r;
619 		}
620 		if (y >= MI_Y_OFFSET(mi) - a &&
621 		    y < MI_Y_OFFSET(mi) + MI_HEIGHT(mi) + b)
622 		{
623 			break;
624 		}
625 	}
626 	if (x < MR_ITEM_X_OFFSET(mr) ||
627 	    x >= MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) - 1)
628 	{
629 		mi = NULL;
630 	}
631 	if (mi && px_offset)
632 	{
633 		*px_offset = x;
634 	}
635 
636 	return mi;
637 }
638 
639 /* ---------------------------- keyboard shortcuts ------------------------- */
640 
is_double_click(Time t0,MenuItem * mi,MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,Bool has_mouse_moved)641 static Bool is_double_click(
642 	Time t0, MenuItem *mi, MenuParameters *pmp, MenuReturn *pmret,
643 	double_keypress *pdkp, Bool has_mouse_moved)
644 {
645 	if ((*pmp->pexc)->x.elast->type == KeyPress)
646 	{
647 		return False;
648 	}
649 	if (fev_get_evtime() - t0 >= MST_DOUBLE_CLICK_TIME(pmp->menu))
650 	{
651 		return False;
652 	}
653 	if (has_mouse_moved)
654 	{
655 		return False;
656 	}
657 	if (!pmp->flags.has_default_action &&
658 	    (mi && mi == MR_FIRST_ITEM(pmp->menu) && MI_IS_SELECTABLE(mi)))
659 	{
660 		return False;
661 	}
662 	if (pmp->flags.is_submenu)
663 	{
664 		return False;
665 	}
666 	if (pmp->flags.is_invoked_by_key_press && pdkp->timestamp == 0)
667 	{
668 		return False;
669 	}
670 
671 	return True;
672 }
673 
674 /* ---------------------------- item label parsing ------------------------- */
675 
676 /*
677  * Procedure:
678  *      scanForHotkeys - Look for hotkey markers in a MenuItem
679  *                                                      (pete@tecc.co.uk)
680  *
681  * Inputs:
682  *      it      - MenuItem to scan
683  *      column  - The column number in which to look for a hotkey.
684  *
685  */
scanForHotkeys(MenuItem * it,int column)686 static void scanForHotkeys(
687 	MenuItem *it, int column)
688 {
689 	char *start;
690 	char *s;
691 	char *t;
692 
693 	/* Get start of string */
694 	start = MI_LABEL(it)[column];
695 	/* Scan whole string */
696 	for (s = start; *s != '\0'; s++)
697 	{
698 		if (*s != '&')
699 		{
700 			continue;
701 		}
702 		if (s[1] != '&')
703 		{
704 			/* found a hotkey - only one hotkey per item */
705 			break;
706 		}
707 		/* Just an escaped '&'; copy the string down over it    */
708 		for (t = s; *t != '\0'; t++)
709 		{
710 			t[0] = t[1];
711 		}
712 
713 	}
714 	if (*s != 0)
715 	{
716 		/* It's a hot key marker - work out the offset value */
717 		MI_HOTKEY_COFFSET(it) = s - start;
718 		MI_HOTKEY_COLUMN(it) = column;
719 		MI_HAS_HOTKEY(it) = (s[1] != '\0');
720 		MI_IS_HOTKEY_AUTOMATIC(it) = 0;
721 		for ( ; *s != '\0'; s++)
722 		{
723 			/* Copy down.. */
724 			s[0] = s[1];
725 		}
726 	}
727 
728 	return;
729 }
730 
__copy_down(char * remove_from,char * remove_to)731 static void __copy_down(char *remove_from, char *remove_to)
732 {
733 	char *t1;
734 	char *t2;
735 
736 	for (t1 = remove_from, t2 = remove_to; *t2 != '\0'; t2++, t1++)
737 	{
738 		*t1 = *t2;
739 	}
740 	*t1 = '\0';
741 
742 	return;
743 }
744 
__check_for_delimiter(char * s,const string_def_t * string_defs)745 static int __check_for_delimiter(char *s, const string_def_t *string_defs)
746 {
747 	int type;
748 
749 	for (type = 0; string_defs[type].delimiter != '\0'; type++)
750 	{
751 		if (s[0] == string_defs[type].delimiter)
752 		{
753 			if (s[1] != string_defs[type].delimiter)
754 			{
755 				return type;
756 			}
757 			else
758 			{
759 				/* escaped delimiter, copy the
760 				 * string down over it */
761 				__copy_down(s, s+1);
762 
763 				return -1;
764 			}
765 		}
766 	}
767 
768 	return -1;
769 }
770 
771 /* This scans for strings within delimiters and calls a callback based
772  * on the delimiter found for each found string */
scanForStrings(char * instring,const string_def_t * string_defs,string_context_t * context)773 static void scanForStrings(
774 	char *instring, const string_def_t *string_defs,
775 	string_context_t *context)
776 {
777 	char *s;
778 	int type;
779 	char *string;
780 
781 	type = -1;
782 	/* string is set whenever type >= 0, and unused otherwise
783 	 * set to NULL to supress compiler warning */
784 	string = NULL;
785 	for (s = instring; *s != '\0'; s++)
786 	{
787 		if (type < 0)
788 		{
789 			/* look for starting delimiters */
790 			type = __check_for_delimiter(s, string_defs);
791 			if (type >= 0)
792 			{
793 				/* start of a string */
794 				string = s + 1;
795 			}
796 		}
797 		else if (
798 			s[0] == string_defs[type].delimiter &&
799 			s[1] != string_defs[type].delimiter)
800 		{
801 			/* found ending delimiter */
802 			Bool is_valid;
803 
804 			/* terminate the string pointer */
805 			s[0] = '\0';
806 			is_valid = string_defs[type].string_handler(
807 				string, string_defs[type].delimiter, context);
808 			/* restore the string */
809 			s[0] = string_defs[type].delimiter;
810 
811 			if (is_valid)
812 			{
813 				/* the string was OK, remove it from
814 				 * instring */
815 				__copy_down(string - 1, s + 1);
816 				/* continue next iteration at the
817 				 * first character after the string */
818 				s = string - 2;
819 			}
820 
821 			type = -1;
822 		}
823 		else if (s[0] == string_defs[type].delimiter)
824 		{
825 			/* escaped delimiter, copy the string down over
826 			 * it */
827 			__copy_down(s, s + 1);
828 		}
829 	}
830 }
831 
832 /* Side picture support: this scans for a color int the menu name
833    for colorization */
__scan_for_color(char * name,char type,string_context_t * context)834 static Bool __scan_for_color(
835 	char *name, char type, string_context_t *context)
836 {
837 	if (type != '^' || SCTX_GET_MR(*context) == NULL)
838 	{
839 		abort();
840 	}
841 
842 	if (MR_HAS_SIDECOLOR(SCTX_GET_MR(*context)))
843 	{
844 		return False;
845 	}
846 
847 	MR_SIDECOLOR(SCTX_GET_MR(*context)) = GetColor(name);
848 	MR_HAS_SIDECOLOR(SCTX_GET_MR(*context)) = True;
849 
850 	return True;
851 }
852 
__scan_for_pixmap(char * name,char type,string_context_t * context)853 static Bool __scan_for_pixmap(
854 	char *name, char type, string_context_t *context)
855 {
856 	FvwmPicture *p;
857 	FvwmPictureAttributes fpa;
858 	int current_mini_icon;
859 
860 	/* check that more pictures are allowed before trying to load the
861 	 * picture */
862 	current_mini_icon = -999999999;
863 	switch (type)
864 	{
865 	case '@':
866 		if (SCTX_GET_MR(*context) == NULL)
867 		{
868 			abort();
869 		}
870 		if (MR_SIDEPIC(SCTX_GET_MR(*context)))
871 		{
872 			return False;
873 		}
874 		break;
875 	case '*':
876 		/* menu item picture, requires menu item */
877 		if (SCTX_GET_MI(*context) == NULL)
878 		{
879 			abort();
880 		}
881 		if (MI_PICTURE(SCTX_GET_MI(*context)))
882 		{
883 			return False;
884 		}
885 		break;
886 	case '%':
887 		/* mini icon - look for next free spot */
888 		if (SCTX_GET_MI(*context) == NULL)
889 		{
890 			abort();
891 		}
892 		current_mini_icon  = 0;
893 		while (current_mini_icon < MAX_MENU_ITEM_MINI_ICONS)
894 		{
895 			if (
896 				MI_MINI_ICON(SCTX_GET_MI(*context))
897 				[current_mini_icon])
898 			{
899 				current_mini_icon++;
900 			}
901 			else
902 			{
903 				break;
904 			}
905 		}
906 		if (current_mini_icon == MAX_MENU_ITEM_MINI_ICONS)
907 		{
908 			return False;
909 		}
910 		break;
911 	default:
912 		abort();
913 	}
914 
915 	fpa.mask = 0;
916 	p = PCacheFvwmPicture(
917 		dpy, Scr.NoFocusWin, NULL, name, fpa);
918 	if (!p)
919 	{
920 		fvwm_msg(WARN, "scanForPixmap",
921 			 "Couldn't load image from %s", name);
922 		fvwm_msg(WARN, "scanForPixmap",
923 			 "Check that FVWM has support for the filetype it's "
924 			 "being asked to load.");
925 
926 		/* return true to make missing pictures not appear in the
927 		 * label/name */
928 		return True;
929 	}
930 
931 	switch (type)
932 	{
933 	case '@':
934 		MR_SIDEPIC(SCTX_GET_MR(*context)) = p;
935 
936 		break;
937 	case '*':
938 		MI_PICTURE(SCTX_GET_MI(*context)) = p;
939 		MI_HAS_PICTURE(SCTX_GET_MI(*context)) = True;
940 
941 		break;
942 	case '%':
943 		MI_MINI_ICON(SCTX_GET_MI(*context))[current_mini_icon] = p;
944 		MI_HAS_PICTURE(SCTX_GET_MI(*context)) = True;
945 
946 		break;
947 	}
948 
949 	return True;
950 }
951 
952 /* ---------------------------- item list handling ------------------------- */
953 
unlink_item_from_menu(MenuRoot * mr,MenuItem * mi)954 static void unlink_item_from_menu(
955 	MenuRoot *mr, MenuItem *mi)
956 {
957 	MenuItem *next;
958 	MenuItem *prev;
959 
960 	next = MI_NEXT_ITEM(mi);
961 	prev = MI_PREV_ITEM(mi);
962 	if (next != NULL)
963 	{
964 		MI_PREV_ITEM(next) = prev;
965 	}
966 	else
967 	{
968 		MR_LAST_ITEM(mr) = prev;
969 	}
970 	if (prev != NULL)
971 	{
972 		MI_NEXT_ITEM(prev) = next;
973 	}
974 	else
975 	{
976 		MR_FIRST_ITEM(mr) = next;
977 	}
978 	MI_NEXT_ITEM(mi) = NULL;
979 	MI_PREV_ITEM(mi) = NULL;
980 	MR_ITEMS(mr)--;
981 
982 	return;
983 }
984 
985 /* Add the given menu item to the menu.  If the first item of the menu is a
986  * title, and the do_replace_title flag is True, the old title is deleted and
987  * replaced by the new item.  Otherwise the item is appended at the end of the
988  * menu. */
append_item_to_menu(MenuRoot * mr,MenuItem * mi,Bool do_replace_title)989 static void append_item_to_menu(
990 	MenuRoot *mr, MenuItem *mi, Bool do_replace_title)
991 {
992 	if (MR_FIRST_ITEM(mr) == NULL)
993 	{
994 		MR_FIRST_ITEM(mr) = mi;
995 		MR_LAST_ITEM(mr) = mi;
996 		MI_NEXT_ITEM(mi) = NULL;
997 		MI_PREV_ITEM(mi) = NULL;
998 	}
999 	else if (do_replace_title)
1000 	{
1001 		if (MI_IS_TITLE(MR_FIRST_ITEM(mr)))
1002 		{
1003 			if (MR_FIRST_ITEM(mr) == MR_LAST_ITEM(mr))
1004 			{
1005 				MR_LAST_ITEM(mr) = mi;
1006 			}
1007 			if (MI_NEXT_ITEM(MR_FIRST_ITEM(mr)) != NULL)
1008 			{
1009 				MI_PREV_ITEM(MI_NEXT_ITEM(
1010 						     MR_FIRST_ITEM(mr))) = mi;
1011 			}
1012 			MI_NEXT_ITEM(mi) = MI_NEXT_ITEM(MR_FIRST_ITEM(mr));
1013 			menuitem_free(MR_FIRST_ITEM(mr));
1014 		}
1015 		else
1016 		{
1017 			MI_PREV_ITEM(MR_FIRST_ITEM(mr)) = mi;
1018 			MI_NEXT_ITEM(mi) = MR_FIRST_ITEM(mr);
1019 		}
1020 		MI_PREV_ITEM(mi) = NULL;
1021 		MR_FIRST_ITEM(mr) = mi;
1022 	}
1023 	else
1024 	{
1025 		MI_NEXT_ITEM(MR_LAST_ITEM(mr)) = mi;
1026 		MI_PREV_ITEM(mi) = MR_LAST_ITEM(mr);
1027 		MR_LAST_ITEM(mr) = mi;
1028 	}
1029 
1030 	return;
1031 }
1032 
clone_menu_item_list(MenuRoot * dest_mr,MenuRoot * src_mr)1033 static void clone_menu_item_list(
1034 	MenuRoot *dest_mr, MenuRoot *src_mr)
1035 {
1036 	MenuItem *mi;
1037 	MenuItem *cloned_mi;
1038 	MenuRoot *mr;
1039 
1040 	MR_FIRST_ITEM(dest_mr) = NULL;
1041 	MR_LAST_ITEM(dest_mr) = NULL;
1042 	/* traverse the menu and all its continuations */
1043 	for (mr = src_mr; mr != NULL; mr = MR_CONTINUATION_MENU(mr))
1044 	{
1045 		/* duplicate all items in the current menu */
1046 		for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
1047 		{
1048 			if (MI_IS_CONTINUATION(mi))
1049 			{
1050 				/* skip this item */
1051 				continue;
1052 			}
1053 			cloned_mi = menuitem_clone(mi);
1054 			append_item_to_menu(dest_mr, cloned_mi, False);
1055 		}
1056 	}
1057 
1058 	return;
1059 }
1060 
1061 /* ---------------------------- MenuRoot maintenance functions ------------- */
1062 
1063 /* Extract interesting values from the item format string that are needed by
1064  * the size_menu_... functions. */
calculate_item_sizes(MenuSizingParameters * msp)1065 static void calculate_item_sizes(MenuSizingParameters *msp)
1066 {
1067 	MenuItem *mi;
1068 	MenuItemPartSizesT mipst;
1069 	int i;
1070 	Bool do_reverse_icon_order =
1071 		(MST_USE_LEFT_SUBMENUS(msp->menu)) ? True : False;
1072 
1073 	memset(&(msp->max), 0, sizeof(msp->max));
1074 	/* Calculate the widths for all columns of all items. */
1075 	for (mi = MR_FIRST_ITEM(msp->menu); mi != NULL; mi = MI_NEXT_ITEM(mi))
1076 	{
1077 		if (MI_IS_TITLE(mi))
1078 		{
1079 			menuitem_get_size(
1080 				mi, &mipst, MST_PTITLEFONT(msp->menu),
1081 				do_reverse_icon_order);
1082 		}
1083 		else
1084 		{
1085 			menuitem_get_size(
1086 				mi, &mipst, MST_PSTDFONT(msp->menu),
1087 				do_reverse_icon_order);
1088 		}
1089 		/* adjust maximums */
1090 		if (msp->max.i.triangle_width < mipst.triangle_width)
1091 		{
1092 			msp->max.i.triangle_width = mipst.triangle_width;
1093 		}
1094 		if (msp->max.i.title_width < mipst.title_width)
1095 		{
1096 			msp->max.i.title_width = mipst.title_width;
1097 		}
1098 		for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1099 		{
1100 			if (msp->max.i.label_width[i] < mipst.label_width[i])
1101 			{
1102 				msp->max.i.label_width[i] =
1103 					mipst.label_width[i];
1104 			}
1105 		}
1106 		if (msp->max.i.picture_width < mipst.picture_width)
1107 		{
1108 			msp->max.i.picture_width = mipst.picture_width;
1109 		}
1110 		for (i = 0; i < MAX_MENU_ITEM_MINI_ICONS; i++)
1111 		{
1112 			if (msp->max.i.icon_width[i] < mipst.icon_width[i])
1113 			{
1114 				msp->max.i.icon_width[i] = mipst.icon_width[i];
1115 			}
1116 		}
1117 	}
1118 	if (MR_SIDEPIC(msp->menu))
1119 	{
1120 		msp->max.sidepic_width = MR_SIDEPIC(msp->menu)->width;
1121 	}
1122 	else if (MST_SIDEPIC(msp->menu))
1123 	{
1124 		msp->max.sidepic_width = MST_SIDEPIC(msp->menu)->width;
1125 	}
1126 
1127 	return;
1128 }
1129 
1130 /*
1131  *
1132  * Calculate the positions of the columns in the menu.
1133  * Called by make_menu().
1134  *
1135  */
size_menu_horizontally(MenuSizingParameters * msp)1136 static void size_menu_horizontally(MenuSizingParameters *msp)
1137 {
1138 	MenuItem *mi;
1139 	Bool sidepic_is_left = True;
1140 	int total_width;
1141 	int sidepic_space = 0;
1142 	int label_offset[MAX_MENU_ITEM_LABELS];
1143 	char lcr_column[MAX_MENU_ITEM_LABELS];
1144 	int i;
1145 	int d;
1146 	int relief_thickness = MST_RELIEF_THICKNESS(msp->menu);
1147 	int *item_order[
1148 		MAX_MENU_ITEM_LABELS + MAX_MENU_ITEM_MINI_ICONS +
1149 		1 /* triangle */ + 2 /* relief markers */];
1150 	int used_objects = 0;
1151 	int left_objects = 0;
1152 	int right_objects = 0;
1153 	int x;
1154 	unsigned char icons_placed = 0;
1155 	Bool sidepic_placed = False;
1156 	Bool triangle_placed = False;
1157 	Bool relief_begin_placed = False;
1158 	Bool relief_end_placed = False;
1159 	char *format;
1160 	Bool first = True;
1161 	Bool done = False;
1162 	Bool is_last_object_left = True;
1163 	unsigned char columns_placed = 0;
1164 	int relief_gap = 0;
1165 	int gap_left;
1166 	int gap_right;
1167 	int chars;
1168 
1169 	memset(item_order, 0, sizeof(item_order));
1170 	for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1171 	{
1172 		lcr_column[i] = 'l';
1173 	}
1174 
1175 	/* Now calculate the offsets for the columns. */
1176 	format = MST_ITEM_FORMAT(msp->menu);
1177 	if (!format)
1178 	{
1179 		format = (MST_USE_LEFT_SUBMENUS(msp->menu)) ?
1180 			DEFAULT_LEFT_MENU_ITEM_FORMAT :
1181 			DEFAULT_MENU_ITEM_FORMAT;
1182 	}
1183 	/* Place the individual items off the menu in case they are not
1184 	 * set in the format string. */
1185 	for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1186 	{
1187 		label_offset[i] = 2 * Scr.MyDisplayWidth;
1188 	}
1189 
1190 	x = MST_BORDER_WIDTH(msp->menu);
1191 	while (*format && !done)
1192 	{
1193 		switch (*format)
1194 		{
1195 		case '%':
1196 			format++;
1197 			chars = 0;
1198 			gap_left = 0;
1199 			gap_right = 0;
1200 
1201 			/* Insert a gap of %d pixels. */
1202 			if (sscanf(format, "%d.%d%n", &gap_left,
1203 				   &gap_right, &chars) >= 2 ||
1204 			    (sscanf(format, "%d.%n", &gap_left,
1205 				    &chars) >= 1 && chars > 0) ||
1206 			    sscanf(format, "%d%n", &gap_left,
1207 				   &chars) >= 1 ||
1208 			    sscanf(format, ".%d%n", &gap_right,
1209 				   &chars) >= 1)
1210 			{
1211 				if (gap_left > MR_SCREEN_WIDTH(msp->menu) ||
1212 				    gap_left < -MR_SCREEN_WIDTH(msp->menu))
1213 				{
1214 					gap_left = 0;
1215 				}
1216 				if (gap_right > MR_SCREEN_HEIGHT(msp->menu) ||
1217 				    gap_right < -MR_SCREEN_HEIGHT(msp->menu))
1218 				{
1219 					gap_right = 0;
1220 				}
1221 				/* Skip the number. */
1222 				format += chars;
1223 			}
1224 			else if (*format == '.')
1225 			{
1226 				/* Skip a dot without values */
1227 				format++;
1228 			}
1229 			if (!*format)
1230 			{
1231 				break;
1232 			}
1233 			switch (*format)
1234 			{
1235 			case 'l':
1236 			case 'c':
1237 			case 'r':
1238 				/* A left, center or right aligned column. */
1239 				if (columns_placed >=
1240 				    MAX_MENU_ITEM_LABELS)
1241 				{
1242 					break;
1243 				}
1244 				if (
1245 					msp->max.i.label_width[columns_placed]
1246 					<= 0)
1247 				{
1248 					columns_placed++;
1249 					break;
1250 				}
1251 				lcr_column[columns_placed] = *format;
1252 				x += gap_left;
1253 				label_offset[columns_placed] = x;
1254 				x += msp->max.i.label_width[columns_placed] +
1255 					gap_right;
1256 				item_order[used_objects++] =
1257 					&(label_offset[columns_placed]);
1258 				if (is_last_object_left && (*format == 'l'))
1259 				{
1260 					left_objects++;
1261 				}
1262 				else
1263 				{
1264 					is_last_object_left = False;
1265 					if (*format == 'r')
1266 					{
1267 						right_objects++;
1268 					}
1269 					else
1270 					{
1271 						right_objects = 0;
1272 					}
1273 				}
1274 				columns_placed++;
1275 				break;
1276 			case 's':
1277 				/* the sidepic */
1278 				if (sidepic_placed)
1279 				{
1280 					break;
1281 				}
1282 				sidepic_placed = True;
1283 				if (msp->max.sidepic_width <= 0)
1284 				{
1285 					break;
1286 				}
1287 				x += gap_left;
1288 				MR_SIDEPIC_X_OFFSET(msp->menu) = x;
1289 				sidepic_is_left = first;
1290 				sidepic_space = msp->max.sidepic_width +
1291 					((sidepic_is_left) ?
1292 					 gap_left : gap_right);
1293 				x += msp->max.sidepic_width + gap_right;
1294 				break;
1295 			case 'i':
1296 				/* a mini icon */
1297 				if (icons_placed >=
1298 				    MAX_MENU_ITEM_MINI_ICONS)
1299 				{
1300 					break;
1301 				}
1302 				if (msp->max.i.icon_width[icons_placed] > 0)
1303 				{
1304 					x += gap_left;
1305 					MR_ICON_X_OFFSET(msp->menu)
1306 						[icons_placed] = x;
1307 					x += msp->max.i.icon_width
1308 						[icons_placed] + gap_right;
1309 					item_order[used_objects++] =
1310 						&(MR_ICON_X_OFFSET(msp->menu)
1311 						  [icons_placed]);
1312 					if (is_last_object_left)
1313 					{
1314 						left_objects++;
1315 					}
1316 					else
1317 					{
1318 						right_objects++;
1319 					}
1320 				}
1321 				icons_placed++;
1322 				break;
1323 			case '|':
1324 				if (!relief_begin_placed)
1325 				{
1326 					relief_begin_placed = True;
1327 					x += gap_left;
1328 					MR_HILIGHT_X_OFFSET(msp->menu) =
1329 						x;
1330 					x += relief_thickness +
1331 						gap_right;
1332 					relief_gap += gap_right;
1333 					item_order[used_objects++] =
1334 						&(MR_HILIGHT_X_OFFSET(
1335 							  msp->menu));
1336 					if (is_last_object_left)
1337 					{
1338 						left_objects++;
1339 					}
1340 					else
1341 					{
1342 						right_objects = 0;
1343 					}
1344 				}
1345 				else if (!relief_end_placed)
1346 				{
1347 					relief_end_placed = True;
1348 					x += relief_thickness + gap_left;
1349 					/* This is a hack: for now we record
1350 					 * the x coordinate of the end of the
1351 					 * hilight area, but later we'll place
1352 					 * the width in here. */
1353 					MR_HILIGHT_WIDTH(msp->menu) = x;
1354 					x += gap_right;
1355 					relief_gap += gap_left;
1356 					item_order[used_objects++] =
1357 						&(MR_HILIGHT_WIDTH(msp->menu));
1358 					right_objects++;
1359 				}
1360 				break;
1361 			case '>':
1362 			case '<':
1363 				/* the triangle for popup menus */
1364 				if (triangle_placed)
1365 				{
1366 					break;
1367 				}
1368 				triangle_placed = True;
1369 				if (msp->max.i.triangle_width > 0)
1370 				{
1371 					x += gap_left;
1372 					MR_TRIANGLE_X_OFFSET(
1373 						msp->menu) = x;
1374 					MR_IS_LEFT_TRIANGLE(msp->menu) =
1375 						(*format == '<');
1376 					x += msp->max.i.triangle_width +
1377 						gap_right;
1378 					item_order[used_objects++] =
1379 						&(MR_TRIANGLE_X_OFFSET(
1380 							  msp->menu));
1381 					if (is_last_object_left &&
1382 					    *format == '<')
1383 					{
1384 						left_objects++;
1385 					}
1386 					else
1387 					{
1388 						is_last_object_left = False;
1389 						right_objects++;
1390 					}
1391 				}
1392 				break;
1393 			case 'p':
1394 				/* Simply add a gap. */
1395 				x += gap_right + gap_left;
1396 				break;
1397 			case '\t':
1398 				x += MENU_TAB_WIDTH * FlocaleTextWidth(
1399 					MST_PSTDFONT(
1400 						msp->menu), " ", 1);
1401 				break;
1402 			case ' ':
1403 				/* Advance the x position. */
1404 				x += FlocaleTextWidth(
1405 					MST_PSTDFONT(msp->menu), format,
1406 					1);
1407 				break;
1408 			default:
1409 				/* Ignore unknown characters. */
1410 				break;
1411 			} /* switch (*format) */
1412 			break;
1413 		case '\t':
1414 			x += MENU_TAB_WIDTH * FlocaleTextWidth(
1415 				MST_PSTDFONT(msp->menu), " ", 1);
1416 			break;
1417 		case ' ':
1418 			/* Advance the x position. */
1419 			x += FlocaleTextWidth(
1420 				MST_PSTDFONT(msp->menu), format, 1);
1421 			break;
1422 		default:
1423 			/* Ignore unknown characters. */
1424 			break;
1425 		} /* switch (*format) */
1426 		format++;
1427 		first = False;
1428 	} /* while (*format) */
1429 	/* stored for vertical sizing */
1430 	msp->used_item_labels = columns_placed;
1431 	msp->used_mini_icons = icons_placed;
1432 
1433 	/* Hide unplaced parts of the menu. */
1434 	if (!sidepic_placed)
1435 	{
1436 		MR_SIDEPIC_X_OFFSET(msp->menu) =
1437 			2 * MR_SCREEN_WIDTH(msp->menu);
1438 	}
1439 	for (i = icons_placed; i < MAX_MENU_ITEM_MINI_ICONS; i++)
1440 	{
1441 		MR_ICON_X_OFFSET(msp->menu)[i] =
1442 			2 * MR_SCREEN_WIDTH(msp->menu);
1443 	}
1444 	if (!triangle_placed)
1445 	{
1446 		MR_TRIANGLE_X_OFFSET(msp->menu) =
1447 			2 * MR_SCREEN_WIDTH(msp->menu);
1448 	}
1449 	msp->flags.is_popup_indicator_used = triangle_placed;
1450 
1451 	total_width = x - MST_BORDER_WIDTH(msp->menu);
1452 	d = (sidepic_space + 2 * relief_thickness +
1453 	     max(msp->max.i.title_width, msp->max.i.picture_width)) -
1454 		total_width;
1455 	if (d > 0)
1456 	{
1457 		int m = 1 - left_objects;
1458 		int n = 1 + used_objects - left_objects - right_objects;
1459 
1460 		/* The title is larger than all menu items. Stretch the
1461 		 * gaps between the items up to the total width of the
1462 		 * title. */
1463 		for (i = 0; i < used_objects; i++)
1464 		{
1465 			if (i < left_objects)
1466 			{
1467 				continue;
1468 			}
1469 			if (i >= used_objects - right_objects)
1470 			{
1471 				/* Right aligned item. */
1472 				*(item_order[i]) += d;
1473 			}
1474 			else
1475 			{
1476 				/* Neither left nor right aligned item.
1477 				 * Divide the overhead gap evenly
1478 				 * between the items. */
1479 				*(item_order[i]) += d * (m + i) / n;
1480 			}
1481 		}
1482 		total_width += d;
1483 		if (!sidepic_is_left)
1484 		{
1485 			MR_SIDEPIC_X_OFFSET(msp->menu) += d;
1486 		}
1487 	} /* if (d > 0) */
1488 	MR_WIDTH(msp->menu) =
1489 		total_width + 2 * MST_BORDER_WIDTH(msp->menu);
1490 	MR_ITEM_WIDTH(msp->menu) = total_width - sidepic_space;
1491 	MR_ITEM_X_OFFSET(msp->menu) = MST_BORDER_WIDTH(msp->menu);
1492 	if (sidepic_is_left)
1493 	{
1494 		MR_ITEM_X_OFFSET(msp->menu) += sidepic_space;
1495 	}
1496 	if (!relief_begin_placed)
1497 	{
1498 		MR_HILIGHT_X_OFFSET(msp->menu) = MR_ITEM_X_OFFSET(msp->menu);
1499 	}
1500 	if (relief_end_placed)
1501 	{
1502 		MR_HILIGHT_WIDTH(msp->menu) =
1503 			MR_HILIGHT_WIDTH(msp->menu) -
1504 			MR_HILIGHT_X_OFFSET(msp->menu);
1505 	}
1506 	else
1507 	{
1508 		MR_HILIGHT_WIDTH(msp->menu) =
1509 			MR_ITEM_WIDTH(msp->menu) +
1510 			MR_ITEM_X_OFFSET(msp->menu) -
1511 			MR_HILIGHT_X_OFFSET(msp->menu);
1512 	}
1513 
1514 	/* Now calculate the offsets for the individual labels. */
1515 	for (mi = MR_FIRST_ITEM(msp->menu); mi != NULL; mi = MI_NEXT_ITEM(mi))
1516 	{
1517 		for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1518 		{
1519 			if (MI_LABEL(mi)[i] == NULL)
1520 			{
1521 				continue;
1522 			}
1523 			if (!MI_IS_TITLE(mi) || !MI_IS_TITLE_CENTERED(mi))
1524 			{
1525 				switch (lcr_column[i])
1526 				{
1527 				case 'l':
1528 					MI_LABEL_OFFSET(mi)[i] =
1529 						label_offset[i];
1530 					break;
1531 				case 'c':
1532 					MI_LABEL_OFFSET(mi)[i] =
1533 						label_offset[i] +
1534 						(msp->max.i.label_width[i] -
1535 						 MI_LABEL_OFFSET(mi)[i]) / 2;
1536 					break;
1537 				case 'r':
1538 					MI_LABEL_OFFSET(mi)[i] =
1539 						label_offset[i] +
1540 						msp->max.i.label_width[i] -
1541 						MI_LABEL_OFFSET(mi)[i];
1542 					break;
1543 				}
1544 			}
1545 			else
1546 			{
1547 				/* This is a centered title item (indicated by
1548 				 * negative width). */
1549 				MI_LABEL_OFFSET(mi)[i] =
1550 					menudim_middle_x_offset(
1551 						&MR_DIM(msp->menu)) -
1552 					MI_LABEL_OFFSET(mi)[i] / 2;
1553 			}
1554 		} /* for */
1555 	} /* for */
1556 
1557 	return;
1558 }
1559 
calc_more_item_height(MenuSizingParameters * msp)1560 static int calc_more_item_height(MenuSizingParameters *msp)
1561 {
1562 	int height;
1563 
1564 	height =
1565 		MST_PSTDFONT(msp->menu)->height +
1566 		MST_ITEM_GAP_ABOVE(msp->menu) +
1567 		MST_ITEM_GAP_BELOW(msp->menu) +
1568 		MST_RELIEF_THICKNESS(msp->menu);
1569 
1570 	return height;
1571 }
1572 
calc_normal_item_height(MenuSizingParameters * msp,MenuItem * mi)1573 static int calc_normal_item_height(MenuSizingParameters *msp, MenuItem *mi)
1574 {
1575 	int height;
1576 
1577 	height =
1578 		MST_ITEM_GAP_ABOVE(msp->menu) +
1579 		MST_ITEM_GAP_BELOW(msp->menu) +
1580 		MST_RELIEF_THICKNESS(msp->menu);
1581 	/* Normal text entry or an entry with a sub menu triangle */
1582 	if (
1583 		(MI_HAS_TEXT(mi) && msp->used_item_labels) ||
1584 		(MI_IS_POPUP(mi) &&
1585 		 msp->flags.is_popup_indicator_used))
1586 	{
1587 		height += MST_PSTDFONT(msp->menu)->height;
1588 	}
1589 
1590 	return height;
1591 }
1592 
1593 /*
1594  *
1595  * Calculate the positions of the columns in the menu.
1596  * Called by make_menu().
1597  *
1598  */
size_menu_vertically(MenuSizingParameters * msp)1599 static Bool size_menu_vertically(MenuSizingParameters *msp)
1600 {
1601 	MenuItem *mi;
1602 	int y;
1603 	int cItems;
1604 	int relief_thickness = MST_RELIEF_THICKNESS(msp->menu);
1605 	int i;
1606 	Bool has_continuation_menu = False;
1607 
1608 	MR_ITEM_TEXT_Y_OFFSET(msp->menu) =
1609 		MST_PSTDFONT(msp->menu)->ascent + relief_thickness +
1610 		MST_ITEM_GAP_ABOVE(msp->menu);
1611 
1612 	/* mi_prev trails one behind mi, since we need to move that
1613 	   into a newly-made menu if we run out of space */
1614 	y = MST_BORDER_WIDTH(msp->menu) + MST_VERTICAL_MARGIN_TOP(msp->menu);
1615 	for (
1616 		cItems = 0, mi = MR_FIRST_ITEM(msp->menu); mi != NULL;
1617 		mi = MI_NEXT_ITEM(mi), cItems++)
1618 	{
1619 		Bool last_item_has_relief =
1620 			(MI_PREV_ITEM(mi)) ?
1621 			MI_IS_SELECTABLE(MI_PREV_ITEM(mi)) : False;
1622 		Bool has_mini_icon = False;
1623 		int separator_height;
1624 		int menu_height;
1625 
1626 		separator_height = (last_item_has_relief) ?
1627 			MENU_SEPARATOR_HEIGHT + relief_thickness :
1628 			MENU_SEPARATOR_TOTAL_HEIGHT;
1629 		MI_Y_OFFSET(mi) = y;
1630 		if (MI_IS_TITLE(mi))
1631 		{
1632 			MI_HEIGHT(mi) = MST_PTITLEFONT(msp->menu)->height +
1633 				MST_TITLE_GAP_ABOVE(msp->menu) +
1634 				MST_TITLE_GAP_BELOW(msp->menu);
1635 		}
1636 		else if (MI_IS_SEPARATOR(mi))
1637 		{
1638 			/* Separator */
1639 			MI_HEIGHT(mi) = separator_height;
1640 		}
1641 		else if (MI_IS_TEAR_OFF_BAR(mi))
1642 		{
1643 			/* Tear off bar */
1644 			MI_HEIGHT(mi) = relief_thickness +
1645 				MENU_TEAR_OFF_BAR_HEIGHT;
1646 		}
1647 		else
1648 		{
1649 			MI_HEIGHT(mi) = calc_normal_item_height(msp, mi);
1650 		}
1651 		if (MI_IS_TITLE(mi))
1652 		{
1653 			/* add space for the underlines */
1654 			switch (MST_TITLE_UNDERLINES(msp->menu))
1655 			{
1656 			case 0:
1657 				if (last_item_has_relief)
1658 					MI_HEIGHT(mi) += relief_thickness;
1659 				break;
1660 			case 1:
1661 				if (mi != MR_FIRST_ITEM(msp->menu))
1662 				{
1663 					/* Space to draw the separator plus a
1664 					 * gap above */
1665 					MI_HEIGHT(mi) += separator_height;
1666 				}
1667 				if (MI_NEXT_ITEM(mi) != NULL)
1668 				{
1669 					/* Space to draw the separator */
1670 					MI_HEIGHT(mi) += MENU_SEPARATOR_HEIGHT;
1671 				}
1672 				break;
1673 			default:
1674 				/* Space to draw n underlines. */
1675 				MI_HEIGHT(mi) +=
1676 					MENU_UNDERLINE_HEIGHT *
1677 					MST_TITLE_UNDERLINES(msp->menu);
1678 				if (last_item_has_relief)
1679 					MI_HEIGHT(mi) += relief_thickness;
1680 				break;
1681 			}
1682 		}
1683 		for (i = 0; i < msp->used_mini_icons; i++)
1684 		{
1685 			if (MI_MINI_ICON(mi)[i])
1686 			{
1687 				has_mini_icon = True;
1688 			}
1689 			if (MI_MINI_ICON(mi)[i] &&
1690 			    MI_HEIGHT(mi) <
1691 			    MI_MINI_ICON(mi)[i]->height + relief_thickness)
1692 			{
1693 				MI_HEIGHT(mi) = MI_MINI_ICON(mi)[i]->height +
1694 					relief_thickness;
1695 			}
1696 		}
1697 		if (MI_PICTURE(mi))
1698 		{
1699 			if ((MI_HAS_TEXT(mi) && msp->used_item_labels) ||
1700 			    has_mini_icon)
1701 			{
1702 				MI_HEIGHT(mi) += MI_PICTURE(mi)->height;
1703 			}
1704 			else
1705 			{
1706 				MI_HEIGHT(mi) = MI_PICTURE(mi)->height +
1707 					relief_thickness;
1708 			}
1709 		}
1710 		y += MI_HEIGHT(mi);
1711 		/* this item would have to be the last item, or else
1712 		 * we need to add a "More..." entry pointing to a new menu */
1713 		menu_height =
1714 			y + MST_BORDER_WIDTH(msp->menu) +
1715 			MST_VERTICAL_MARGIN_BOTTOM(msp->menu) +
1716 			((MI_IS_SELECTABLE(mi)) ? relief_thickness : 0);
1717 		if (menu_height > MR_SCREEN_HEIGHT(msp->menu))
1718 		{
1719 			/* Item does not fit on screen anymore. */
1720 			char *t;
1721 			char *tempname;
1722 			const char *gt_name;
1723 			char *name;
1724 			MenuRoot *menuContinuation;
1725 			int more_item_height;
1726 
1727 			more_item_height = calc_more_item_height(msp);
1728 			/* Remove items form the menu until it fits (plus a
1729 			 * 'More' entry). */
1730 			while (
1731 				MI_PREV_ITEM(mi) != NULL &&
1732 				menu_height > MR_SCREEN_HEIGHT(msp->menu))
1733 			{
1734 				/* Remove current item. */
1735 				y -= MI_HEIGHT(mi);
1736 				mi = MI_PREV_ITEM(mi);
1737 				cItems--;
1738 				menu_height =
1739 					y + MST_BORDER_WIDTH(msp->menu) +
1740 					more_item_height + relief_thickness +
1741 					MST_VERTICAL_MARGIN_BOTTOM(msp->menu);
1742 			}
1743 			if (
1744 				MI_PREV_ITEM(mi) == NULL ||
1745 				menu_height > MR_SCREEN_HEIGHT(msp->menu))
1746 			{
1747 				fvwm_msg(ERR, "size_menu_vertically",
1748 					 "Menu entry does not fit on screen");
1749 				/* leave a coredump */
1750 				abort();
1751 				exit(1);
1752 			}
1753 
1754 			t = EscapeString(MR_NAME(msp->menu), "\"", '\\');
1755 			tempname = (char *)safemalloc(
1756 				(10 + strlen(t)) * sizeof(char));
1757 			strcpy(tempname, "Popup \"");
1758 			strcat(tempname, t);
1759 			strcat(tempname, "$\"");
1760 			free(t);
1761 			/* NewMenuRoot inserts at the head of the list of menus
1762 			   but, we need it at the end.  (Give it just the name,
1763 			   * which is 6 chars past the action since
1764 			   * strlen("Popup ")==6 ) */
1765 			t = (char *)safemalloc(strlen(MR_NAME(msp->menu)) + 2);
1766 			strcpy(t, MR_NAME(msp->menu));
1767 			strcat(t, "$");
1768 			menuContinuation = NewMenuRoot(t);
1769 			free(t);
1770 			MR_CONTINUATION_MENU(msp->menu) = menuContinuation;
1771 
1772 			/* Now move this item and the remaining items into the
1773 			 * new menu */
1774 			MR_FIRST_ITEM(menuContinuation) = MI_NEXT_ITEM(mi);
1775 			MR_LAST_ITEM(menuContinuation) =
1776 				MR_LAST_ITEM(msp->menu);
1777 			MR_ITEMS(menuContinuation) =
1778 				MR_ITEMS(msp->menu) - cItems;
1779 			MI_PREV_ITEM(MI_NEXT_ITEM(mi)) = NULL;
1780 
1781 			/* mi_prev is now the last item in the parent menu */
1782 			MR_LAST_ITEM(msp->menu) = mi;
1783 			MR_ITEMS(msp->menu) = cItems;
1784 			MI_NEXT_ITEM(mi) = NULL;
1785 
1786 			/* use the same style for the submenu */
1787 			MR_STYLE(menuContinuation) = MR_STYLE(msp->menu);
1788 			MR_IS_LEFT_TRIANGLE(menuContinuation) =
1789 				MR_IS_LEFT_TRIANGLE(msp->menu);
1790 			/* migo: propagate missing_submenu_func */
1791 			if (MR_MISSING_SUBMENU_FUNC(msp->menu))
1792 			{
1793 				MR_MISSING_SUBMENU_FUNC(menuContinuation) =
1794 					safestrdup(MR_MISSING_SUBMENU_FUNC(
1795 							   msp->menu));
1796 			}
1797 			/* don't propagate sidepic, sidecolor, popup and
1798 			 * popdown actions */
1799 			/* And add the entry pointing to the new menu */
1800 			gt_name = gettext("More&...");
1801 			name = safestrdup(gt_name);
1802 			AddToMenu(
1803 				msp->menu, name, tempname,
1804 				False /* no pixmap scan */, False, True);
1805 			free(name);
1806 			free(tempname);
1807 			has_continuation_menu = True;
1808 		}
1809 	} /* for */
1810 	/* The menu may be empty here! */
1811 	if (MR_LAST_ITEM(msp->menu) != NULL &&
1812 	    MI_IS_SELECTABLE(MR_LAST_ITEM(msp->menu)))
1813 	{
1814 		y += relief_thickness;
1815 	}
1816 	MR_HEIGHT(msp->menu) =
1817 		y + MST_BORDER_WIDTH(msp->menu) +
1818 		MST_VERTICAL_MARGIN_BOTTOM(msp->menu);
1819 
1820 	return has_continuation_menu;
1821 }
1822 
1823 /*
1824  *
1825  * Merge menu continuations back into the original menu.
1826  * Called by make_menu().
1827  *
1828  */
merge_continuation_menus(MenuRoot * mr)1829 static void merge_continuation_menus(MenuRoot *mr)
1830 {
1831 	/* merge menu continuations into one menu again - needed when changing
1832 	 * the font size of a long menu. */
1833 	while (MR_CONTINUATION_MENU(mr) != NULL)
1834 	{
1835 		MenuRoot *cont = MR_CONTINUATION_MENU(mr);
1836 
1837 		/* link first item of continuation to item before 'more...' */
1838 		MI_NEXT_ITEM(MI_PREV_ITEM(MR_LAST_ITEM(mr))) =
1839 			MR_FIRST_ITEM(cont);
1840 		MI_PREV_ITEM(MR_FIRST_ITEM(cont)) =
1841 			MI_PREV_ITEM(MR_LAST_ITEM(mr));
1842 		menuitem_free(MR_LAST_ITEM(mr));
1843 		MR_LAST_ITEM(mr) = MR_LAST_ITEM(cont);
1844 		MR_CONTINUATION_MENU(mr) = MR_CONTINUATION_MENU(cont);
1845 		/* fake an empty menu so that DestroyMenu does not destroy the
1846 		 * items. */
1847 		MR_FIRST_ITEM(cont) = NULL;
1848 		DestroyMenu(cont, False, False);
1849 	}
1850 
1851 	return;
1852 }
1853 
1854 /*
1855  *
1856  * Creates the window for the menu.
1857  *
1858  */
make_menu_window(MenuRoot * mr,Bool is_tear_off)1859 static void make_menu_window(MenuRoot *mr, Bool is_tear_off)
1860 {
1861 	unsigned long valuemask;
1862 	XSetWindowAttributes attributes;
1863 	int w;
1864 	int h;
1865 	unsigned int evmask;
1866 
1867 	w = MR_WIDTH(mr);
1868 	if (w == 0)
1869 	{
1870 		w = 1;
1871 	}
1872 	h = MR_HEIGHT(mr);
1873 	if (h == 0)
1874 	{
1875 		h = 1;
1876 	}
1877 
1878 	attributes.background_pixel = (MST_HAS_MENU_CSET(mr)) ?
1879 		Colorset[MST_CSET_MENU(mr)].bg : MST_MENU_COLORS(mr).back;
1880 	if (MR_WINDOW(mr) != None)
1881 	{
1882 		/* just resize the existing window */
1883 		XResizeWindow(dpy, MR_WINDOW(mr), w, h);
1884 		/* and change the background color */
1885 		valuemask = CWBackPixel | CWCursor;
1886 		XChangeWindowAttributes(
1887 			dpy, MR_WINDOW(mr), valuemask, &attributes);
1888 	}
1889 	else
1890 	{
1891 		/* create a new window */
1892 		valuemask = CWBackPixel | CWEventMask | CWCursor | CWColormap
1893 			| CWBorderPixel | CWSaveUnder;
1894 		attributes.border_pixel = 0;
1895 		attributes.colormap = Pcmap;
1896 		evmask = XEVMASK_MENUW;
1897 		attributes.event_mask = 0;
1898 		attributes.cursor = Scr.FvwmCursors[CRS_MENU];
1899 		attributes.save_under = True;
1900 
1901 		/* Create a display used to create the window.  Can't use the
1902 		 * normal display because 'xkill' would  kill the window
1903 		 * manager if used on a tear off menu.  The display can't be
1904 		 * deleted right now because that would either destroy the new
1905 		 * window or leave it as an orphan if fvwm dies or is
1906 		 * restarted. */
1907 		if (is_tear_off)
1908 		{
1909 			MR_CREATE_DPY(mr) = XOpenDisplay(display_name);
1910 			if (MR_CREATE_DPY(mr) == NULL)
1911 			{
1912 				/* Doh.  Use the standard display instead. */
1913 				MR_CREATE_DPY(mr) = dpy;
1914 			}
1915 		}
1916 		else
1917 		{
1918 			MR_CREATE_DPY(mr) = dpy;
1919 		}
1920 		MR_WINDOW(mr) = XCreateWindow(
1921 			MR_CREATE_DPY(mr), Scr.Root, 0, 0, w, h,
1922 			0, Pdepth, InputOutput, Pvisual, valuemask,
1923 			&attributes);
1924 		if (MR_CREATE_DPY(mr) != dpy)
1925 		{
1926 			/* We *must* synchronize the display here.  Otherwise
1927 			 * the request will never be processed. */
1928 			XSync(MR_CREATE_DPY(mr), 1);
1929 		}
1930 		if (MR_WINDOW(mr) != None)
1931 		{
1932 			/* select events for the window from the standard
1933 			 * display */
1934 			XSelectInput(dpy, MR_WINDOW(mr), evmask);
1935 		}
1936 		XSaveContext(dpy, MR_WINDOW(mr), MenuContext,(caddr_t)mr);
1937 	}
1938 
1939 	return;
1940 }
1941 
1942 
1943 /*
1944  *
1945  * Generates the window for a menu
1946  *
1947  */
make_menu(MenuRoot * mr,Bool is_tear_off)1948 static void make_menu(MenuRoot *mr, Bool is_tear_off)
1949 {
1950 	MenuSizingParameters msp;
1951 	Bool has_continuation_menu = False;
1952 
1953 	if (MR_MAPPED_COPIES(mr) > 0)
1954 	{
1955 		return;
1956 	}
1957 	merge_continuation_menus(mr);
1958 	do
1959 	{
1960 		memset(&msp, 0, sizeof(MenuSizingParameters));
1961 		msp.menu = mr;
1962 		calculate_item_sizes(&msp);
1963 		/* Call size_menu_horizontally first because it calculated
1964 		 * some values used by size_menu_vertically. */
1965 		size_menu_horizontally(&msp);
1966 		has_continuation_menu = size_menu_vertically(&msp);
1967 		/* repeat this step if the menu was split */
1968 	} while (has_continuation_menu);
1969 	MR_USED_MINI_ICONS(mr) = msp.used_mini_icons;
1970 
1971 	MR_XANIMATION(mr) = 0;
1972 	memset(&(MR_DYNAMIC_FLAGS(mr)), 0, sizeof(MR_DYNAMIC_FLAGS(mr)));
1973 
1974 	/* create a new window for the menu */
1975 	make_menu_window(mr, is_tear_off);
1976 	MR_IS_UPDATED(mr) = 0;
1977 
1978 	return;
1979 }
1980 
1981 /* Make sure the menu is properly rebuilt when the style or the menu has
1982  * changed. */
update_menu(MenuRoot * mr,MenuParameters * pmp)1983 static void update_menu(MenuRoot *mr, MenuParameters *pmp)
1984 {
1985 	int sw;
1986 	int sh;
1987 	Bool has_screen_size_changed = False;
1988 	fscreen_scr_arg fscr;
1989 
1990 	if (MST_IS_UPDATED(mr))
1991 	{
1992 		/* The menu style has changed. */
1993 		MenuRoot *menu;
1994 
1995 		for (menu = Menus.all; menu; menu = MR_NEXT_MENU(menu))
1996 		{
1997 			if (MR_STYLE(menu) == MR_STYLE(mr))
1998 			{
1999 				/* Mark all other menus with the same style as
2000 				 * changed. */
2001 				MR_IS_UPDATED(menu) = 1;
2002 			}
2003 		}
2004 		MST_IS_UPDATED(mr) = 0;
2005 	}
2006 	fscr.xypos.x = pmp->screen_origin_x;
2007 	fscr.xypos.y = pmp->screen_origin_y;
2008 	FScreenGetScrRect(&fscr, FSCREEN_XYPOS, &JunkX, &JunkY, &sw, &sh);
2009 	if (sw != MR_SCREEN_WIDTH(mr) || sh != MR_SCREEN_HEIGHT(mr))
2010 	{
2011 		has_screen_size_changed = True;
2012 		MR_SCREEN_WIDTH(mr) = sw;
2013 		MR_SCREEN_HEIGHT(mr) = sh;
2014 	}
2015 	if (MR_IS_UPDATED(mr) || has_screen_size_changed)
2016 	{
2017 		/* The menu or the screen dimensions have changed. We have to
2018 		 * re-make it. */
2019 		make_menu(mr, False);
2020 	}
2021 
2022 	return;
2023 }
2024 
2025 /*
2026  *
2027  *  Procedure:
2028  *      copy_menu_root - creates a new instance of an existing menu
2029  *
2030  *  Returned Value:
2031  *      (MenuRoot *)
2032  *
2033  *  Inputs:
2034  *      mr      - the MenuRoot structure of the existing menu
2035  *
2036  */
copy_menu_root(MenuRoot * mr)2037 static MenuRoot *copy_menu_root(MenuRoot *mr)
2038 {
2039 	MenuRoot *tmp;
2040 
2041 	if (!mr || MR_COPIES(mr) >= MAX_MENU_COPIES)
2042 	{
2043 		return NULL;
2044 	}
2045 	tmp = (MenuRoot *)safemalloc(sizeof(MenuRoot));
2046 	tmp->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic));
2047 	memset(tmp->d, 0, sizeof(MenuRootDynamic));
2048 	tmp->s = mr->s;
2049 
2050 	MR_COPIES(mr)++;
2051 	MR_ORIGINAL_MENU(tmp) = MR_ORIGINAL_MENU(mr);
2052 	MR_CONTINUATION_MENU(tmp) = MR_CONTINUATION_MENU(mr);
2053 	MR_NEXT_MENU(tmp) = MR_NEXT_MENU(mr);
2054 	MR_NEXT_MENU(mr) = tmp;
2055 	MR_WINDOW(tmp) = None;
2056 	memset(&(MR_DYNAMIC_FLAGS(tmp)), 0, sizeof(MR_DYNAMIC_FLAGS(tmp)));
2057 
2058 	return tmp;
2059 }
2060 
2061 /*
2062  *
2063  *  Procedure:
2064  *      clone_menu - duplicates an existing menu in newly allocated
2065  memory.  The new menu is independent of the original.
2066  *
2067  *  Returned Value:
2068  *      (MenuRoot *)
2069  *
2070  *  Inputs:
2071  *      mr      - the MenuRoot structure of the existing menu
2072  *
2073  */
clone_menu_root_static(MenuRoot * dest_mr,MenuRoot * src_mr)2074 static void clone_menu_root_static(
2075 	MenuRoot *dest_mr, MenuRoot *src_mr)
2076 {
2077 	dest_mr->s = (MenuRootStatic *)safemalloc(sizeof(MenuRootStatic));
2078 	/* copy everything */
2079 	memcpy(dest_mr->s, src_mr->s, sizeof(MenuRootStatic));
2080 	/* special treatment for a few parts */
2081 	if (MR_NAME(src_mr) != NULL)
2082 	{
2083 		MR_NAME(dest_mr) = safestrdup(MR_NAME(src_mr));
2084 	}
2085 	MR_COPIES(dest_mr) = 1;
2086 	MR_MAPPED_COPIES(dest_mr) = 0;
2087 	MR_POPUP_ACTION(dest_mr) = NULL;
2088 	MR_POPDOWN_ACTION(dest_mr) = NULL;
2089 	if (MR_MISSING_SUBMENU_FUNC(src_mr))
2090 	{
2091 		MR_MISSING_SUBMENU_FUNC(dest_mr) =
2092 			safestrdup(MR_MISSING_SUBMENU_FUNC(src_mr));
2093 	}
2094 	if (MR_HAS_SIDECOLOR(src_mr))
2095 	{
2096 		MR_SIDECOLOR(dest_mr) =
2097 			fvwmlib_clone_color(MR_SIDECOLOR(src_mr));
2098 	}
2099 	MR_SIDEPIC(dest_mr) = PCloneFvwmPicture(MR_SIDEPIC(src_mr));
2100 	clone_menu_item_list(dest_mr, src_mr);
2101 
2102 	return;
2103 }
2104 
clone_menu(MenuRoot * mr)2105 static MenuRoot *clone_menu(MenuRoot *mr)
2106 {
2107 	MenuRoot *new_mr;
2108 
2109 	new_mr = (MenuRoot *)safemalloc(sizeof(MenuRoot));
2110 	new_mr->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic));
2111 	memset(new_mr->d, 0, sizeof(MenuRootDynamic));
2112 	clone_menu_root_static(new_mr, mr);
2113 
2114 	return new_mr;
2115 }
2116 
2117 /* ---------------------------- position hints ----------------------------- */
2118 
float_to_int_with_tolerance(float f)2119 static int float_to_int_with_tolerance(float f)
2120 {
2121 	int low;
2122 
2123 	if (f < 0)
2124 	{
2125 		low = (int)(f - ROUNDING_ERROR_TOLERANCE);
2126 	}
2127 	else
2128 	{
2129 		low = (int)(f + ROUNDING_ERROR_TOLERANCE);
2130 	}
2131 	if ((int)f != low)
2132 	{
2133 		return low;
2134 	}
2135 	else
2136 	{
2137 		return (int)(f);
2138 	}
2139 }
2140 
get_xy_from_position_hints(struct MenuPosHints * ph,int width,int height,int context_width,Bool do_reverse_x,int * ret_x,int * ret_y)2141 static void get_xy_from_position_hints(
2142 	struct MenuPosHints *ph, int width, int height, int context_width,
2143 	Bool do_reverse_x, int *ret_x, int *ret_y)
2144 {
2145 	float x_add;
2146 	float y_add;
2147 
2148 	*ret_x = ph->x;
2149 	*ret_y = ph->y;
2150 	if (ph->is_menu_relative)
2151 	{
2152 		if (do_reverse_x)
2153 		{
2154 			*ret_x -= ph->x_offset;
2155 			x_add = width * (-1.0 - ph->x_factor) +
2156 				ph->menu_width * (1.0 - ph->context_x_factor);
2157 		}
2158 		else
2159 		{
2160 			*ret_x += ph->x_offset;
2161 			x_add = width * ph->x_factor +
2162 				ph->menu_width * ph->context_x_factor;
2163 		}
2164 		y_add = height * ph->y_factor;
2165 	}
2166 	else
2167 	{
2168 		x_add = width * ph->x_factor;
2169 		y_add = height * ph->y_factor;
2170 	}
2171 	*ret_x += float_to_int_with_tolerance(x_add);
2172 	*ret_y += float_to_int_with_tolerance(y_add);
2173 
2174 	return;
2175 }
2176 
2177 /*
2178  * Used by get_menu_options
2179  *
2180  * The vars are named for the x-direction, but this is used for both x and y
2181  */
get_one_menu_position_argument(char * action,int x,int w,int * pFinalX,int * x_offset,float * width_factor,float * context_width_factor,Bool * is_menu_relative)2182 static char *get_one_menu_position_argument(
2183 	char *action, int x, int w, int *pFinalX, int *x_offset,
2184 	float *width_factor, float *context_width_factor,
2185 	Bool *is_menu_relative)
2186 {
2187 	char *token, *orgtoken, *naction;
2188 	char c;
2189 	int val;
2190 	int chars;
2191 	float fval;
2192 	float factor = (float)w/100;
2193 	float x_add = 0;
2194 
2195 	naction = GetNextToken(action, &token);
2196 	if (token == NULL)
2197 	{
2198 		return action;
2199 	}
2200 	orgtoken = token;
2201 	*pFinalX = x;
2202 	*x_offset = 0;
2203 	*width_factor = 0.0;
2204 	*context_width_factor = 0.0;
2205 	if (sscanf(token,"o%d%n", &val, &chars) >= 1)
2206 	{
2207 		fval = val;
2208 		token += chars;
2209 		x_add += fval*factor;
2210 		*width_factor -= fval / 100.0;
2211 		*context_width_factor += fval / 100.0;
2212 	}
2213 	else if (token[0] == 'c')
2214 	{
2215 		token++;
2216 		x_add += ((float)w) / 2.0;
2217 		*width_factor -= 0.5;
2218 		*context_width_factor += 0.5;
2219 	}
2220 	while (*token != 0)
2221 	{
2222 		if (sscanf(token,"%d%n", &val, &chars) < 1)
2223 		{
2224 			naction = action;
2225 			break;
2226 		}
2227 		fval = (float)val;
2228 		token += chars;
2229 		if (sscanf(token,"%c", &c) == 1)
2230 		{
2231 			switch (c)
2232 			{
2233 			case 'm':
2234 				token++;
2235 				*width_factor += fval / 100.0;
2236 				*is_menu_relative = True;
2237 				break;
2238 			case 'p':
2239 				token++;
2240 				x_add += val;
2241 				*x_offset += val;
2242 				break;
2243 			default:
2244 				x_add += fval * factor;
2245 				*context_width_factor += fval / 100.0;
2246 				break;
2247 			}
2248 		}
2249 		else
2250 		{
2251 			x_add += fval * factor;
2252 			*context_width_factor += fval / 100.0;
2253 		}
2254 	}
2255 	*pFinalX += float_to_int_with_tolerance(x_add);
2256 	free(orgtoken);
2257 
2258 	return naction;
2259 }
2260 
2261 /* Returns the menu options for the menu that a given menu item pops up */
get_popup_options(MenuParameters * pmp,MenuItem * mi,MenuOptions * pops)2262 static void get_popup_options(
2263 	MenuParameters *pmp, MenuItem *mi, MenuOptions *pops)
2264 {
2265 	if (!mi)
2266 	{
2267 		return;
2268 	}
2269 	pops->flags.has_poshints = 0;
2270 	pops->pos_hints.has_screen_origin = True;
2271 	pops->pos_hints.screen_origin_x = pmp->screen_origin_x;
2272 	pops->pos_hints.screen_origin_y = pmp->screen_origin_y;
2273 	/* just look past "Popup <name>" in the action */
2274 	get_menu_options(
2275 		SkipNTokens(MI_ACTION(mi), 2), MR_WINDOW(pmp->menu), NULL,
2276 		NULL, pmp->menu, mi, pops);
2277 
2278 	return;
2279 }
2280 
2281 /* ---------------------------- menu painting functions --------------------- */
2282 
clear_expose_menu_area(Window win,XEvent * e)2283 static void clear_expose_menu_area(Window win, XEvent *e)
2284 {
2285 	if (e == NULL)
2286 	{
2287 		XClearWindow(dpy, win);
2288 	}
2289 	else
2290 	{
2291 		XClearArea(
2292 			dpy, win, e->xexpose.x, e->xexpose.y, e->xexpose.width,
2293 			e->xexpose.height, False);
2294 	}
2295 
2296 	return;
2297 }
2298 
2299 /*
2300  *
2301  * Draws a picture on the left side of the menu
2302  * What about a SidePic Colorset ? (olicha 2002-08-21)
2303  *
2304  */
paint_side_pic(MenuRoot * mr,XEvent * pevent)2305 static void paint_side_pic(MenuRoot *mr, XEvent *pevent)
2306 {
2307 	GC gc;
2308 	FvwmPicture *sidePic;
2309 	int ys;
2310 	int yt;
2311 	int h;
2312 	int bw = MST_BORDER_WIDTH(mr);
2313 
2314 	if (MR_SIDEPIC(mr))
2315 	{
2316 		sidePic = MR_SIDEPIC(mr);
2317 	}
2318 	else if (MST_SIDEPIC(mr))
2319 	{
2320 		sidePic = MST_SIDEPIC(mr);
2321 	}
2322 	else
2323 	{
2324 		return;
2325 	}
2326 	if (Pdepth < 2)
2327 	{
2328 		/* ? */
2329 		gc = SHADOW_GC(MST_MENU_INACTIVE_GCS(mr));
2330 	}
2331 	else
2332 	{
2333 		gc = FORE_GC(MST_MENU_INACTIVE_GCS(mr));
2334 	}
2335 	if (sidePic->height > MR_HEIGHT(mr) - 2 * bw)
2336 	{
2337 		h = MR_HEIGHT(mr) - 2 * bw;
2338 		ys = sidePic->height - h;
2339 		yt = bw;
2340 	}
2341 	else
2342 	{
2343 		h = sidePic->height;
2344 		ys = 0;
2345 		yt = MR_HEIGHT(mr) - bw - sidePic->height;
2346 	}
2347 	if (pevent != NULL && pevent->type == Expose)
2348 	{
2349 		if (
2350 			pevent->xexpose.x + pevent->xexpose.width <
2351 			MR_SIDEPIC_X_OFFSET(mr) ||
2352 			pevent->xexpose.x >=
2353 			MR_SIDEPIC_X_OFFSET(mr) +  sidePic->width)
2354 		{
2355 			/* out of x-range for side bar */
2356 			return;
2357 		}
2358 		if (
2359 			pevent->xexpose.y + pevent->xexpose.height < bw ||
2360 			pevent->xexpose.y >= bw + MR_HEIGHT(mr))
2361 		{
2362 			/* in the border */
2363 			return;
2364 		}
2365 		if (
2366 			!(MR_HAS_SIDECOLOR(mr) || MST_HAS_SIDE_COLOR(mr)) &&
2367 			pevent->xexpose.y + pevent->xexpose.height < yt)
2368 		{
2369 			/* outside picture and no background */
2370 			return;
2371 		}
2372 
2373 	}
2374 	if (MR_HAS_SIDECOLOR(mr))
2375 	{
2376 		Globalgcv.foreground = MR_SIDECOLOR(mr);
2377 	}
2378 	else if (MST_HAS_SIDE_COLOR(mr))
2379 	{
2380 		Globalgcv.foreground = MST_SIDE_COLOR(mr);
2381 	}
2382 	if (MR_HAS_SIDECOLOR(mr) || MST_HAS_SIDE_COLOR(mr))
2383 	{
2384 		Globalgcm = GCForeground;
2385 		XChangeGC(dpy, Scr.ScratchGC1, Globalgcm, &Globalgcv);
2386 		XFillRectangle(
2387 			dpy, MR_WINDOW(mr), Scr.ScratchGC1,
2388 			MR_SIDEPIC_X_OFFSET(mr), bw, sidePic->width,
2389 			MR_HEIGHT(mr) - 2 * bw);
2390 	}
2391 	else if (sidePic->alpha != None)
2392 	{
2393 		XClearArea(
2394 			dpy, MR_WINDOW(mr),
2395 			MR_SIDEPIC_X_OFFSET(mr), yt, sidePic->width, h, False);
2396 	}
2397 	PGraphicsRenderPicture(
2398 		dpy, MR_WINDOW(mr), sidePic, 0, MR_WINDOW(mr),
2399 		gc, Scr.MonoGC, Scr.AlphaGC,
2400 		0, ys, sidePic->width, h,
2401 		MR_SIDEPIC_X_OFFSET(mr), yt, sidePic->width, h, False);
2402 
2403 	return;
2404 }
2405 
paint_menu_gradient_background(MenuRoot * mr,XEvent * pevent)2406 static Bool paint_menu_gradient_background(
2407 	MenuRoot *mr, XEvent *pevent)
2408 {
2409 	MenuStyle *ms = MR_STYLE(mr);
2410 	int bw = MST_BORDER_WIDTH(mr);
2411 	XRectangle bounds;
2412 	Pixmap pmap;
2413 	GC pmapgc;
2414 	XGCValues gcv;
2415 	unsigned long gcm = GCLineWidth;
2416 	int switcher = -1;
2417 	Bool do_clear = False;
2418 
2419 	gcv.line_width = 1;
2420 	bounds.x = bw;
2421 	bounds.y = bw;
2422 	bounds.width = MR_WIDTH(mr) - bw;
2423 	bounds.height = MR_HEIGHT(mr) - bw;
2424 	/* H, V, D and B gradients are optimized and have
2425 	 * their own code here. (if no dither) */
2426 	if (!ST_FACE(ms).u.grad.do_dither)
2427 	{
2428 		switcher = ST_FACE(ms).gradient_type;
2429 	}
2430 	switch (switcher)
2431 	{
2432 	case H_GRADIENT:
2433 		if (MR_IS_BACKGROUND_SET(mr) == False)
2434 		{
2435 			register int i;
2436 			register int dw;
2437 
2438 			pmap = XCreatePixmap(
2439 				dpy, MR_WINDOW(mr), MR_WIDTH(mr),
2440 				DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS,
2441 				Pdepth);
2442 			pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv);
2443 			dw = (float)
2444 				(bounds.width / ST_FACE(ms).u.grad.npixels)
2445 				+ 1;
2446 			for (i = 0; i < ST_FACE(ms).u.grad.npixels; i++)
2447 			{
2448 				int x;
2449 
2450 				x = i * bounds.width /
2451 					ST_FACE(ms).u.grad.npixels;
2452 				XSetForeground(
2453 					dpy, pmapgc,
2454 					ST_FACE(ms).u.grad.xcs[i].pixel);
2455 				XFillRectangle(
2456 					dpy, pmap, pmapgc, x, 0, dw,
2457 					DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS);
2458 			}
2459 			XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap);
2460 			XFreeGC(dpy,pmapgc);
2461 			XFreePixmap(dpy,pmap);
2462 			MR_IS_BACKGROUND_SET(mr) = True;
2463 		}
2464 		do_clear = True;
2465 		break;
2466 	case V_GRADIENT:
2467 		if (MR_IS_BACKGROUND_SET(mr) == False)
2468 		{
2469 			register int i;
2470 			register int dh;
2471 			static int best_tile_width = 0;
2472 			int junk;
2473 
2474 			if (best_tile_width == 0)
2475 			{
2476 				int tw = DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS;
2477 
2478 				if (!XQueryBestTile(
2479 					    dpy, Scr.screen, tw, tw,
2480 					    (unsigned int*)&best_tile_width,
2481 					    (unsigned int*)&junk))
2482 				{
2483 					/* call failed, use default and risk a
2484 					 * screwed up tile */
2485 					best_tile_width = tw;
2486 				}
2487 			}
2488 			pmap = XCreatePixmap(
2489 				dpy, MR_WINDOW(mr), best_tile_width,
2490 				MR_HEIGHT(mr), Pdepth);
2491 			pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv);
2492 			dh = (float) (bounds.height /
2493 				      ST_FACE(ms).u.grad.npixels) + 1;
2494 			for (i = 0; i < ST_FACE(ms).u.grad.npixels; i++)
2495 			{
2496 				int y;
2497 
2498 				y = i * bounds.height /
2499 					ST_FACE(ms).u.grad.npixels;
2500 				XSetForeground(
2501 					dpy, pmapgc,
2502 					ST_FACE(ms).u.grad.xcs[i].pixel);
2503 				XFillRectangle(
2504 					dpy, pmap, pmapgc, 0, y,
2505 					best_tile_width, dh);
2506 			}
2507 			XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap);
2508 			XFreeGC(dpy,pmapgc);
2509 			XFreePixmap(dpy,pmap);
2510 			MR_IS_BACKGROUND_SET(mr) = True;
2511 		}
2512 		do_clear = True;
2513 		break;
2514 	case D_GRADIENT:
2515 	case B_GRADIENT:
2516 	{
2517 		register int i = 0, numLines;
2518 		int cindex = -1;
2519 		XRectangle r;
2520 
2521 		if (pevent)
2522 		{
2523 			r.x = pevent->xexpose.x;
2524 			r.y = pevent->xexpose.y;
2525 			r.width = pevent->xexpose.width;
2526 			r.height = pevent->xexpose.height;
2527 		}
2528 		else
2529 		{
2530 			r.x = bw;
2531 			r.y = bw;
2532 			r.width = MR_WIDTH(mr) - 2 * bw;
2533 			r.height = MR_HEIGHT(mr) - 2 * bw;
2534 		}
2535 		XSetClipRectangles(
2536 			dpy, Scr.TransMaskGC, 0, 0, &r, 1, Unsorted);
2537 		numLines = MR_WIDTH(mr) + MR_HEIGHT(mr) - 2 * bw;
2538 		for (i = 0; i < numLines; i++)
2539 		{
2540 			if ((int)(i * ST_FACE(ms).u.grad.npixels / numLines) >
2541 			    cindex)
2542 			{
2543 				/* pick the next colour (skip if necc.) */
2544 				cindex = i * ST_FACE(ms).u.grad.npixels /
2545 					numLines;
2546 				XSetForeground(
2547 					dpy, Scr.TransMaskGC,
2548 					ST_FACE(ms).u.grad.xcs[cindex].pixel);
2549 			}
2550 			if (ST_FACE(ms).gradient_type == D_GRADIENT)
2551 			{
2552 				XDrawLine(dpy, MR_WINDOW(mr),
2553 					  Scr.TransMaskGC, 0, i, i, 0);
2554 			}
2555 			else /* B_GRADIENT */
2556 			{
2557 				XDrawLine(dpy, MR_WINDOW(mr), Scr.TransMaskGC,
2558 					  0, MR_HEIGHT(mr) - 1 - i, i,
2559 					  MR_HEIGHT(mr) - 1);
2560 			}
2561 		}
2562 	}
2563 	XSetClipMask(dpy, Scr.TransMaskGC, None);
2564 	break;
2565 	default:
2566 		if (MR_IS_BACKGROUND_SET(mr) == False)
2567 		{
2568 			int g_width;
2569 			int g_height;
2570 
2571 			/* let library take care of all other gradients */
2572 			pmap = XCreatePixmap(
2573 				dpy, MR_WINDOW(mr), MR_WIDTH(mr),
2574 				MR_HEIGHT(mr), Pdepth);
2575 			pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv);
2576 
2577 			/* find out the size the pixmap should be */
2578 			CalculateGradientDimensions(
2579 				dpy, MR_WINDOW(mr), ST_FACE(ms).u.grad.npixels,
2580 				ST_FACE(ms).gradient_type,
2581 				ST_FACE(ms).u.grad.do_dither, &g_width,
2582 				&g_height);
2583 			/* draw the gradient directly into the window */
2584 			CreateGradientPixmap(
2585 				dpy, MR_WINDOW(mr), pmapgc,
2586 				ST_FACE(ms).gradient_type, g_width, g_height,
2587 				ST_FACE(ms).u.grad.npixels,
2588 				ST_FACE(ms).u.grad.xcs,
2589 				ST_FACE(ms).u.grad.do_dither,
2590 				&(MR_STORED_PIXELS(mr).d_pixels),
2591 				&(MR_STORED_PIXELS(mr).d_npixels),
2592 				pmap, bw, bw,
2593 				MR_WIDTH(mr) - bw, MR_HEIGHT(mr) - bw, NULL);
2594 			XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap);
2595 			XFreeGC(dpy, pmapgc);
2596 			XFreePixmap(dpy, pmap);
2597 			MR_IS_BACKGROUND_SET(mr) = True;
2598 		}
2599 		do_clear = True;
2600 		break;
2601 	}
2602 	return do_clear;
2603 }
2604 
paint_menu_pixmap_background(MenuRoot * mr,XEvent * pevent)2605 static Bool paint_menu_pixmap_background(
2606 	MenuRoot *mr, XEvent *pevent)
2607 {
2608 	MenuStyle *ms = MR_STYLE(mr);
2609 	int width, height, x, y;
2610 	int bw = MST_BORDER_WIDTH(mr);
2611 	FvwmPicture *p;
2612 
2613 	p = ST_FACE(ms).u.p;
2614 	width = MR_WIDTH(mr) - 2 * bw;
2615 	height = MR_HEIGHT(mr) - 2 * bw;
2616 	y = (int)(height - p->height) / 2;
2617 	x = (int)(width - p->width) / 2;
2618 	if (x < bw)
2619 	{
2620 		x = bw;
2621 	}
2622 	if (y < bw)
2623 	{
2624 		y = bw;
2625 	}
2626 	if (width > p->width)
2627 	{
2628 		width = p->width;
2629 	}
2630 	if (height > p->height)
2631 	{
2632 		height = p->height;
2633 	}
2634 	if (width > MR_WIDTH(mr) - x - bw)
2635 	{
2636 		width = MR_WIDTH(mr) - x - bw;
2637 	}
2638 	if (height > MR_HEIGHT(mr) - y - bw)
2639 	{
2640 		height = MR_HEIGHT(mr) - y - bw;
2641 	}
2642 	XSetClipMask(dpy, Scr.TransMaskGC, p->mask);
2643 	XSetClipOrigin(dpy, Scr.TransMaskGC, x, y);
2644 	XCopyArea(
2645 		dpy, p->picture, MR_WINDOW(mr), Scr.TransMaskGC,
2646 		bw, bw, width, height, x, y);
2647 
2648 	return False;
2649 }
2650 
2651 /*
2652  *
2653  *  Procedure:
2654  *      get_menu_paint_item_parameters - prepares the parameters to be
2655  *      passed to menuitem_paint().
2656  *
2657  *      mr - the menu instance that holds the menu item
2658  *      mi - the menu item to redraw
2659  *      fw - the FvwmWindow structure to check against allowed functions
2660  *
2661  */
get_menu_paint_item_parameters(MenuPaintItemParameters * mpip,MenuRoot * mr,MenuItem * mi,FvwmWindow * fw,XEvent * pevent,Bool do_redraw_menu_border)2662 static void get_menu_paint_item_parameters(
2663 	MenuPaintItemParameters *mpip, MenuRoot *mr, MenuItem *mi,
2664 	FvwmWindow *fw, XEvent *pevent, Bool do_redraw_menu_border)
2665 {
2666 	mpip->ms = MR_STYLE(mr);
2667 	mpip->w = MR_WINDOW(mr);
2668 	mpip->selected_item = MR_SELECTED_ITEM(mr);
2669 	mpip->dim = &MR_DIM(mr);
2670 	mpip->fw = fw;
2671 	mpip->used_mini_icons = MR_USED_MINI_ICONS(mr);
2672 	mpip->cb_mr = mr;
2673 	mpip->cb_reset_bg = paint_menu_gradient_background;
2674 	mpip->flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
2675 	mpip->flags.is_left_triangle = MR_IS_LEFT_TRIANGLE(mr);
2676 	mpip->ev = pevent;
2677 
2678 	return;
2679 }
2680 
2681 /*
2682  *
2683  *  Procedure:
2684  *      paint_menu - draws the entire menu
2685  *
2686  */
paint_menu(MenuRoot * mr,XEvent * pevent,FvwmWindow * fw)2687 static void paint_menu(
2688 	MenuRoot *mr, XEvent *pevent, FvwmWindow *fw)
2689 {
2690 	MenuItem *mi;
2691 	MenuStyle *ms = MR_STYLE(mr);
2692 	int bw = MST_BORDER_WIDTH(mr);
2693 	int relief_thickness = ST_RELIEF_THICKNESS(MR_STYLE(mr));
2694 
2695 	if (fw && !check_if_fvwm_window_exists(fw))
2696 	{
2697 		fw = NULL;
2698 	}
2699 	if (MR_IS_PAINTED(mr) && pevent &&
2700 	    (pevent->xexpose.x >= MR_WIDTH(mr) - bw ||
2701 	     pevent->xexpose.x + pevent->xexpose.width <= bw ||
2702 	     pevent->xexpose.y >= MR_HEIGHT(mr) - bw ||
2703 	     pevent->xexpose.y + pevent->xexpose.height <= bw))
2704 	{
2705 		/* Only the border was obscured. Redraw it centrally instead of
2706 		 * redrawing several menu items. */
2707 		RelieveRectangle(
2708 			dpy, MR_WINDOW(mr), 0, 0, MR_WIDTH(mr) - 1,
2709 			MR_HEIGHT(mr) - 1, (Pdepth < 2) ?
2710 			SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)) :
2711 			HILIGHT_GC(MST_MENU_INACTIVE_GCS(mr)),
2712 			SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)), bw);
2713 		{
2714 			return;
2715 		}
2716 	}
2717 	MR_IS_PAINTED(mr) = 1;
2718 	/* paint the menu background */
2719 	if (ms && ST_HAS_MENU_CSET(ms))
2720 	{
2721 		if (MR_IS_BACKGROUND_SET(mr) == False)
2722 		{
2723 			SetWindowBackground(
2724 				dpy, MR_WINDOW(mr), MR_WIDTH(mr),
2725 				MR_HEIGHT(mr), &Colorset[ST_CSET_MENU(ms)],
2726 				Pdepth, FORE_GC(ST_MENU_INACTIVE_GCS(ms)),
2727 				True);
2728 			MR_IS_BACKGROUND_SET(mr) = True;
2729 		}
2730 	}
2731 	else if (ms)
2732 	{
2733 		int type;
2734 		Bool do_clear = False;
2735 
2736 		type = ST_FACE(ms).type;
2737 		switch(type)
2738 		{
2739 		case SolidMenu:
2740 			XSetWindowBackground(
2741 				dpy, MR_WINDOW(mr), MST_FACE(mr).u.back);
2742 			do_clear = True;
2743 			break;
2744 		case GradientMenu:
2745 			do_clear = paint_menu_gradient_background(mr, pevent);
2746 			break;
2747 		case PixmapMenu:
2748 			do_clear = paint_menu_pixmap_background(mr, pevent);
2749 			break;
2750 		case TiledPixmapMenu:
2751 			XSetWindowBackgroundPixmap(
2752 				dpy, MR_WINDOW(mr), ST_FACE(ms).u.p->picture);
2753 			do_clear = True;
2754 			break;
2755 		} /* switch(type) */
2756 		if (do_clear == True)
2757 		{
2758 			clear_expose_menu_area(MR_WINDOW(mr), pevent);
2759 		}
2760 	} /* if (ms) */
2761 	/* draw the relief */
2762 	RelieveRectangle(dpy, MR_WINDOW(mr), 0, 0, MR_WIDTH(mr) - 1,
2763 			 MR_HEIGHT(mr) - 1, (Pdepth < 2) ?
2764 			 SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)) :
2765 			 HILIGHT_GC(MST_MENU_INACTIVE_GCS(mr)),
2766 			 SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)), bw);
2767 	/* paint the menu items */
2768 	for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
2769 	{
2770 		int do_draw = 0;
2771 
2772 		/* be smart about handling the expose, redraw only the entries
2773 		 * that we need to */
2774 		if (pevent == NULL)
2775 		{
2776 			do_draw = True;
2777 		}
2778 		else
2779 		{
2780 			register int b_offset;
2781 
2782 			b_offset = MI_Y_OFFSET(mi) + MI_HEIGHT(mi);
2783 			if (MR_SELECTED_ITEM(mr) == mi)
2784 			{
2785 				b_offset += relief_thickness;
2786 			}
2787 			if (pevent->xexpose.y < b_offset &&
2788 			    (pevent->xexpose.y + pevent->xexpose.height) >
2789 			    MI_Y_OFFSET(mi))
2790 			{
2791 				do_draw = True;
2792 			}
2793 		}
2794 		if (do_draw)
2795 		{
2796 			MenuPaintItemParameters mpip;
2797 
2798 			get_menu_paint_item_parameters(
2799 				&mpip, mr, NULL, fw, pevent, True);
2800 			mpip.flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
2801 			menuitem_paint(mi, &mpip);
2802 		}
2803 	}
2804 	paint_side_pic(mr, pevent);
2805 	XFlush(dpy);
2806 
2807 	return;
2808 }
2809 
2810 /* Set the selected-ness state of the menuitem passed in */
select_menu_item(MenuRoot * mr,MenuItem * mi,Bool select,FvwmWindow * fw)2811 static void select_menu_item(
2812 	MenuRoot *mr, MenuItem *mi, Bool select, FvwmWindow *fw)
2813 {
2814 	if (select == True && MR_SELECTED_ITEM(mr) != NULL &&
2815 	    MR_SELECTED_ITEM(mr) != mi)
2816 	{
2817 		select_menu_item(mr, MR_SELECTED_ITEM(mr), False, fw);
2818 	}
2819 	else if (select == False && MR_SELECTED_ITEM(mr) == NULL)
2820 	{
2821 		return;
2822 	}
2823 	else if (select == True && MR_SELECTED_ITEM(mr) == mi)
2824 	{
2825 		return;
2826 	}
2827 	if (!MI_IS_SELECTABLE(mi))
2828 	{
2829 		return;
2830 	}
2831 
2832 	if (select == False)
2833 	{
2834 		MI_WAS_DESELECTED(mi) = True;
2835 	}
2836 
2837 	if (!MST_HAS_MENU_CSET(mr))
2838 	{
2839 		switch (MST_FACE(mr).type)
2840 		{
2841 		case GradientMenu:
2842 			if (select == True)
2843 			{
2844 				int iy;
2845 				int ih;
2846 				int mw;
2847 				XEvent e;
2848 
2849 				if (!MR_IS_PAINTED(mr))
2850 				{
2851 					FCheckWeedTypedWindowEvents(
2852 						dpy, MR_WINDOW(mr), Expose,
2853 						NULL);
2854 					paint_menu(mr, NULL, fw);
2855 				}
2856 				iy = MI_Y_OFFSET(mi);
2857 				ih = MI_HEIGHT(mi) +
2858 					(MI_IS_SELECTABLE(mi) ?
2859 					 MST_RELIEF_THICKNESS(mr) : 0);
2860 				if (iy < 0)
2861 				{
2862 					ih += iy;
2863 					iy = 0;
2864 				}
2865 				mw = MR_WIDTH(mr) - 2 * MST_BORDER_WIDTH(mr);
2866 				if (iy + ih > MR_HEIGHT(mr))
2867 					ih = MR_HEIGHT(mr) - iy;
2868 				/* grab image */
2869 				MR_STORED_ITEM(mr).stored =
2870 					XCreatePixmap(
2871 						dpy, Scr.NoFocusWin, mw, ih,
2872 						Pdepth);
2873 				XSetGraphicsExposures(
2874 					dpy,
2875 					FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2876 					True);
2877 				XSync(dpy, 0);
2878 				while (FCheckTypedEvent(dpy, NoExpose, &e))
2879 				{
2880 					/* nothing to do here */
2881 				}
2882 				XCopyArea(
2883 					dpy, MR_WINDOW(mr),
2884 					MR_STORED_ITEM(mr).stored,
2885 					FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2886 					MST_BORDER_WIDTH(mr), iy, mw, ih, 0,
2887 					0);
2888 				XSync(dpy, 0);
2889 				if (FCheckTypedEvent(dpy, NoExpose, &e))
2890 				{
2891 					MR_STORED_ITEM(mr).y = iy;
2892 					MR_STORED_ITEM(mr).width = mw;
2893 					MR_STORED_ITEM(mr).height = ih;
2894 				}
2895 				else
2896 				{
2897 					XFreePixmap(
2898 						dpy,
2899 						MR_STORED_ITEM(mr).stored);
2900 					MR_STORED_ITEM(mr).stored = None;
2901 					MR_STORED_ITEM(mr).width = 0;
2902 					MR_STORED_ITEM(mr).height = 0;
2903 					MR_STORED_ITEM(mr).y = 0;
2904 				}
2905 				XSetGraphicsExposures(
2906 					dpy,
2907 					FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2908 					False);
2909 			}
2910 			else if (select == False &&
2911 				 MR_STORED_ITEM(mr).width != 0)
2912 			{
2913 				/* ungrab image */
2914 				XCopyArea(
2915 					dpy, MR_STORED_ITEM(mr).stored,
2916 					MR_WINDOW(mr),
2917 					FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2918 					0, 0, MR_STORED_ITEM(mr).width,
2919 					MR_STORED_ITEM(mr).height,
2920 					MST_BORDER_WIDTH(mr),
2921 					MR_STORED_ITEM(mr).y);
2922 
2923 				if (MR_STORED_ITEM(mr).stored != None)
2924 				{
2925 					XFreePixmap(
2926 						dpy, MR_STORED_ITEM(mr).stored);
2927 				}
2928 				MR_STORED_ITEM(mr).stored = None;
2929 				MR_STORED_ITEM(mr).width = 0;
2930 				MR_STORED_ITEM(mr).height = 0;
2931 				MR_STORED_ITEM(mr).y = 0;
2932 			}
2933 			else if (select == False)
2934 			{
2935 				XEvent e;
2936 				FvwmPicture *sidePic = NULL;
2937 
2938 				e.type = Expose;
2939 				e.xexpose.x = MST_BORDER_WIDTH(mr);
2940 				e.xexpose.y = MI_Y_OFFSET(mi);
2941 				e.xexpose.width = MR_WIDTH(mr) - 2 *
2942 					MST_BORDER_WIDTH(mr);
2943 				e.xexpose.height = MI_HEIGHT(mi) +
2944 					(MI_IS_SELECTABLE(mi) ?
2945 					 MST_RELIEF_THICKNESS(mr) : 0);
2946 				if (MR_SIDEPIC(mr))
2947 				{
2948 					sidePic = MR_SIDEPIC(mr);
2949 				}
2950 				else if (MST_SIDEPIC(mr))
2951 				{
2952 					sidePic = MST_SIDEPIC(mr);
2953 				}
2954 				if (sidePic)
2955 				{
2956 					e.xexpose.width -= sidePic->width;
2957 					if (MR_SIDEPIC_X_OFFSET(mr) ==
2958 					    MST_BORDER_WIDTH(mr))
2959 					{
2960 						e.xexpose.x += sidePic->width;
2961 					}
2962 				}
2963 				MR_SELECTED_ITEM(mr) = (select) ? mi : NULL;
2964 				paint_menu(mr, &e, fw);
2965 			}
2966 			break;
2967 		default:
2968 			if (MR_STORED_ITEM(mr).width != 0)
2969 			{
2970 				if (MR_STORED_ITEM(mr).stored != None)
2971 				{
2972 					XFreePixmap(
2973 						dpy,
2974 						MR_STORED_ITEM(mr).stored);
2975 				}
2976 				MR_STORED_ITEM(mr).stored = None;
2977 				MR_STORED_ITEM(mr).width = 0;
2978 				MR_STORED_ITEM(mr).height = 0;
2979 				MR_STORED_ITEM(mr).y = 0;
2980 			}
2981 			break;
2982 		} /* switch */
2983 	} /* if */
2984 
2985 	if (fw && !check_if_fvwm_window_exists(fw))
2986 	{
2987 		fw = NULL;
2988 	}
2989 	MR_SELECTED_ITEM(mr) = (select) ? mi : NULL;
2990 	if (MR_IS_PAINTED(mr))
2991 	{
2992 		MenuPaintItemParameters mpip;
2993 
2994 		get_menu_paint_item_parameters(&mpip, mr, mi, fw, NULL, False);
2995 		menuitem_paint(mi, &mpip);
2996 	}
2997 
2998 	return;
2999 }
3000 
3001 /* ---------------------------- popping menu up or down -------------------- */
3002 
get_left_popup_x_position(MenuRoot * mr,MenuRoot * submenu,int x)3003 static int get_left_popup_x_position(MenuRoot *mr, MenuRoot *submenu, int x)
3004 {
3005 	if (MST_USE_LEFT_SUBMENUS(mr))
3006 	{
3007 		return (x - MST_POPUP_OFFSET_ADD(mr) - MR_WIDTH(submenu) +
3008 			MR_WIDTH(mr) *
3009 			(100 - MST_POPUP_OFFSET_PERCENT(mr)) / 100);
3010 	}
3011 	else
3012 	{
3013 		return (x - MR_WIDTH(submenu) + MST_BORDER_WIDTH(mr));
3014 	}
3015 }
3016 
get_right_popup_x_position(MenuRoot * mr,MenuRoot * submenu,int x)3017 static int get_right_popup_x_position(MenuRoot *mr, MenuRoot *submenu, int x)
3018 {
3019 	if (MST_USE_LEFT_SUBMENUS(mr))
3020 	{
3021 		return (x + MR_WIDTH(mr) - MST_BORDER_WIDTH(mr));
3022 	}
3023 	else
3024 	{
3025 		return (x + MR_WIDTH(mr) * MST_POPUP_OFFSET_PERCENT(mr) / 100 +
3026 			MST_POPUP_OFFSET_ADD(mr));
3027 	}
3028 }
3029 
get_prefered_popup_position(MenuRoot * mr,MenuRoot * submenu,int * px,int * py,Bool * pprefer_left_submenus)3030 static void get_prefered_popup_position(
3031 	MenuRoot *mr, MenuRoot *submenu, int *px, int *py,
3032 	Bool *pprefer_left_submenus)
3033 {
3034 	int menu_x, menu_y;
3035 	MenuItem *mi = NULL;
3036 
3037 	if (!menu_get_geometry(
3038 		    mr, &JunkRoot, &menu_x, &menu_y, &JunkWidth, &JunkHeight,
3039 		    &JunkBW, &JunkDepth))
3040 	{
3041 		*px = 0;
3042 		*py = 0;
3043 		*pprefer_left_submenus = False;
3044 		fvwm_msg(ERR, "get_prefered_popup_position",
3045 			 "can't get geometry of menu %s", MR_NAME(mr));
3046 		return;
3047 	}
3048 	/* set the direction flag */
3049 	*pprefer_left_submenus =
3050 		(MR_HAS_POPPED_UP_LEFT(mr) ||
3051 		 (MST_USE_LEFT_SUBMENUS(mr) &&
3052 		  !MR_HAS_POPPED_UP_RIGHT(mr)));
3053 	/* get the x position */
3054 	if (*pprefer_left_submenus)
3055 	{
3056 		*px = get_left_popup_x_position(mr, submenu, menu_x);
3057 	}
3058 	else
3059 	{
3060 		*px = get_right_popup_x_position(mr, submenu, menu_x);
3061 	}
3062 	/* get the y position */
3063 	if (MR_SELECTED_ITEM(mr))
3064 	{
3065 		mi = MR_SELECTED_ITEM(mr);
3066 	}
3067 	if (mi)
3068 	{
3069 		*py = menu_y + MI_Y_OFFSET(mi) -
3070 			MST_BORDER_WIDTH(mr) + MST_RELIEF_THICKNESS(mr);
3071 	}
3072 	else
3073 	{
3074 		*py = menu_y;
3075 	}
3076 }
3077 
do_menus_overlap(MenuRoot * mr,int x,int y,int width,int height,int h_tolerance,int v_tolerance,int s_tolerance,Bool allow_popup_offset_tolerance)3078 static int do_menus_overlap(
3079 	MenuRoot *mr, int x, int y, int width, int height, int h_tolerance,
3080 	int v_tolerance, int s_tolerance, Bool allow_popup_offset_tolerance)
3081 {
3082 	int prior_x, prior_y, x_overlap;
3083 	int prior_width, prior_height;
3084 
3085 	if (mr == NULL)
3086 	{
3087 		return 0;
3088 	}
3089 	if (!menu_get_geometry(
3090 		    mr, &JunkRoot,&prior_x,&prior_y, &prior_width,
3091 		    &prior_height, &JunkBW, &JunkDepth))
3092 	{
3093 		return 0;
3094 	}
3095 	x_overlap = 0;
3096 	if (allow_popup_offset_tolerance)
3097 	{
3098 		if (MST_POPUP_OFFSET_ADD(mr) < 0)
3099 		{
3100 			s_tolerance = -MST_POPUP_OFFSET_ADD(mr);
3101 		}
3102 		if (MST_USE_LEFT_SUBMENUS(mr))
3103 		{
3104 			prior_x += (100 - MST_POPUP_OFFSET_PERCENT(mr)) / 100;
3105 		}
3106 		else
3107 		{
3108 			prior_width *=
3109 				(float)(MST_POPUP_OFFSET_PERCENT(mr)) / 100.0;
3110 		}
3111 	}
3112 	if (MST_USE_LEFT_SUBMENUS(mr))
3113 	{
3114 		int t = s_tolerance;
3115 		s_tolerance = h_tolerance;
3116 		h_tolerance = t;
3117 	}
3118 	if (y < prior_y + prior_height - v_tolerance &&
3119 	    prior_y < y + height - v_tolerance &&
3120 	    x < prior_x + prior_width - s_tolerance &&
3121 	    prior_x < x + width - h_tolerance)
3122 	{
3123 		x_overlap = x - prior_x;
3124 		if (x <= prior_x)
3125 		{
3126 			x_overlap--;
3127 		}
3128 	}
3129 
3130 	return x_overlap;
3131 }
3132 
3133 /*
3134  *
3135  *  Procedure:
3136  *      pop_menu_up - pop up a pull down menu
3137  *
3138  *  Inputs:
3139  *      x, y      - location of upper left of menu
3140  *      do_warp_to_item - warp pointer to the first item after title
3141  *      pops      - pointer to the menu options for new menu
3142  *
3143  */
pop_menu_up(MenuRoot ** pmenu,MenuParameters * pmp,MenuRoot * parent_menu,MenuItem * parent_item,const exec_context_t ** pexc,int x,int y,Bool prefer_left_submenu,Bool do_warp_to_item,MenuOptions * pops,Bool * ret_overlap,Window popdown_window)3144 static int pop_menu_up(
3145 	MenuRoot **pmenu, MenuParameters *pmp, MenuRoot *parent_menu,
3146 	MenuItem *parent_item, const exec_context_t **pexc, int x, int y,
3147 	Bool prefer_left_submenu, Bool do_warp_to_item, MenuOptions *pops,
3148 	Bool *ret_overlap, Window popdown_window)
3149 {
3150 	Bool do_warp_to_title = False;
3151 	int x_overlap = 0;
3152 	int x_clipped_overlap;
3153 	MenuRoot *mrMi;
3154 	MenuRoot *mr;
3155 	FvwmWindow *fw;
3156 	int context;
3157 	int bw = 0;
3158 	int bwp = 0;
3159 	int prev_x;
3160 	int prev_y;
3161 	int prev_width;
3162 	int prev_height;
3163 	unsigned int event_mask;
3164 	int scr_x, scr_y;
3165 	int scr_w, scr_h;
3166 
3167 	mr = *pmenu;
3168 	if (!mr ||
3169 	    (MR_MAPPED_COPIES(mr) > 0 && MR_COPIES(mr) >= MAX_MENU_COPIES))
3170 	{
3171 		return False;
3172 	}
3173 
3174 	/*
3175 	 * handle dynamic menu actions
3176 	 */
3177 
3178 	/* First of all, execute the popup action (if defined). */
3179 	if (MR_POPUP_ACTION(mr))
3180 	{
3181 		char *menu_name;
3182 		saved_pos_hints pos_hints;
3183 		Bool is_busy_grabbed = False;
3184 		int mapped_copies = MR_MAPPED_COPIES(mr);
3185 
3186 		/* save variables that we still need but that may be
3187 		 * overwritten */
3188 		menu_name = safestrdup(MR_NAME(mr));
3189 		pos_hints = last_saved_pos_hints;
3190 		if (Scr.BusyCursor & BUSY_DYNAMICMENU)
3191 		{
3192 			is_busy_grabbed = GrabEm(CRS_WAIT, GRAB_BUSYMENU);
3193 		}
3194 		/* Execute the action */
3195 		__menu_execute_function(pmp->pexc, MR_POPUP_ACTION(mr));
3196 		if (is_busy_grabbed)
3197 		{
3198 			UngrabEm(GRAB_BUSYMENU);
3199 		}
3200 		/* restore the stuff we saved */
3201 		last_saved_pos_hints = pos_hints;
3202 		if (mapped_copies == 0)
3203 		{
3204 			/* Now let's see if the menu still exists. It may have
3205 			 * been destroyed and recreated, so we have to look for
3206 			 * a menu with the saved name. menus_find_menu() always
3207 			 * returns the original menu, not one of its copies, so
3208 			 * below logic would fail miserably if used with a menu
3209 			 * copy. On the other hand, menu copies can't be
3210 			 * deleted within a dynamic popup action, so just
3211 			 * ignore this case. */
3212 			*pmenu = menus_find_menu(menu_name);
3213 			if (menu_name)
3214 			{
3215 				free(menu_name);
3216 			}
3217 			mr = *pmenu;
3218 		}
3219 	}
3220 	if (mr)
3221 	{
3222 		update_menu(mr, pmp);
3223 	}
3224 	if (mr == NULL || MR_FIRST_ITEM(mr) == NULL || MR_ITEMS(mr) == 0)
3225 	{
3226 		/* The menu deleted itself or all its items or it has been
3227 		 * empty from the start. */
3228 		return False;
3229 	}
3230 	fw = (*pexc)->w.fw;
3231 	if (fw && !check_if_fvwm_window_exists(fw))
3232 	{
3233 		fw = NULL;
3234 	}
3235 	context = (*pexc)->w.wcontext;
3236 
3237 	/*
3238 	 * Create a new menu instance (if necessary)
3239 	 */
3240 
3241 	if (MR_MAPPED_COPIES(mr) > 0)
3242 	{
3243 		/* create a new instance of the menu */
3244 		*pmenu = copy_menu_root(*pmenu);
3245 		if (!*pmenu)
3246 		{
3247 			return False;
3248 		}
3249 		make_menu_window(*pmenu, False);
3250 		mr = *pmenu;
3251 	}
3252 
3253 	/*
3254 	 * Evaluate position hints
3255 	 */
3256 
3257 	/* calculate position from position hints if available */
3258 	if (pops->flags.has_poshints &&
3259 	    !last_saved_pos_hints.flags.do_ignore_pos_hints)
3260 	{
3261 		get_xy_from_position_hints(
3262 			&(pops->pos_hints), MR_WIDTH(mr), MR_HEIGHT(mr),
3263 			(parent_menu) ? MR_WIDTH(parent_menu) : 0,
3264 			prefer_left_submenu, &x, &y);
3265 	}
3266 
3267 	/*
3268 	 * Initialise new menu
3269 	 */
3270 
3271 	MR_PARENT_MENU(mr) = parent_menu;
3272 	MR_PARENT_ITEM(mr) = parent_item;
3273 	MR_IS_PAINTED(mr) = 0;
3274 	MR_IS_LEFT(mr) = 0;
3275 	MR_IS_RIGHT(mr) = 0;
3276 	MR_IS_UP(mr) = 0;
3277 	MR_IS_DOWN(mr) = 0;
3278 	MR_XANIMATION(mr) = 0;
3279 	InstallFvwmColormap();
3280 
3281 	/*
3282 	 * Handle popups from button clicks on buttons in the title bar,
3283 	 * or the title bar itself. Position hints override this.
3284 	 */
3285 
3286 	if (!pops->flags.has_poshints && fw && parent_menu == NULL &&
3287 	    pmp->flags.is_first_root_menu)
3288 	{
3289 		int cx;
3290 		int cy;
3291 		int gx;
3292 		int gy;
3293 		Bool has_context;
3294 		rectangle button_g;
3295 
3296 		has_context = get_title_button_geometry(
3297 			fw, &button_g, context);
3298 		if (has_context)
3299 		{
3300 			fscreen_scr_arg fscr;
3301 
3302 			get_title_gravity_factors(fw, &gx, &gy);
3303 			cx = button_g.x + button_g.width / 2;
3304 			cy = button_g.y + button_g.height / 2;
3305 			if (gx != 0)
3306 			{
3307 				x = button_g.x +
3308 					(gx == 1) * button_g.width -
3309 					(gx == -1) * MR_WIDTH(mr);
3310 			}
3311 			if (gy != 0)
3312 			{
3313 				y = button_g.y +
3314 					(gy == 1) * button_g.height -
3315 					(gy == -1) * MR_HEIGHT(mr);
3316 			}
3317 			if (gx == 0 && x < button_g.x)
3318 			{
3319 				x = button_g.x;
3320 			}
3321 			else if (gx == 0 && x + MR_WIDTH(mr) >=
3322 				 button_g.x + button_g.width)
3323 			{
3324 				x = button_g.x + button_g.width - MR_WIDTH(mr);
3325 			}
3326 			if (gy == 0 && y < button_g.y)
3327 			{
3328 				y = button_g.y;
3329 			}
3330 			else if (gy == 0 && y + MR_HEIGHT(mr) >=
3331 				 button_g.y + button_g.height)
3332 			{
3333 				y = button_g.y + button_g.height -
3334 					MR_HEIGHT(mr);
3335 			}
3336 			pops->pos_hints.has_screen_origin = True;
3337 			fscr.xypos.x = cx;
3338 			fscr.xypos.y = cy;
3339 			if (FScreenGetScrRect(
3340 				    &fscr, FSCREEN_XYPOS, &JunkX, &JunkY,
3341 				    &JunkWidth, &JunkHeight))
3342 			{
3343 				/* use current cx/cy */
3344 			}
3345 			else if (FQueryPointer(
3346 					 dpy, Scr.Root, &JunkRoot, &JunkChild,
3347 					 &cx, &cy, &JunkX, &JunkY, &JunkMask))
3348 			{
3349 				/* use pointer's position */
3350 			}
3351 			else
3352 			{
3353 				cx = -1;
3354 				cy = -1;
3355 			}
3356 			pops->pos_hints.screen_origin_x = cx;
3357 			pops->pos_hints.screen_origin_y = cy;
3358 		}
3359 	} /* if (pops->flags.has_poshints) */
3360 
3361 	/*
3362 	 * Clip to screen
3363 	 */
3364 
3365 	{
3366 		fscreen_scr_arg fscr;
3367 
3368 		fscr.xypos.x = pops->pos_hints.screen_origin_x;
3369 		fscr.xypos.y = pops->pos_hints.screen_origin_y;
3370 		/* clip to screen */
3371 		FScreenClipToScreen(
3372 			&fscr, FSCREEN_XYPOS, &x, &y, MR_WIDTH(mr),
3373 			MR_HEIGHT(mr));
3374 		/* "this" screen is defined -- so get its coords for future
3375 		 * reference */
3376 		FScreenGetScrRect(
3377 			&fscr, FSCREEN_XYPOS, &scr_x, &scr_y, &scr_w, &scr_h);
3378 	}
3379 
3380 	if (parent_menu)
3381 	{
3382 		bw = MST_BORDER_WIDTH(mr);
3383 		bwp = MST_BORDER_WIDTH(parent_menu);
3384 		x_overlap = do_menus_overlap(
3385 			parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr), bwp,
3386 			bwp, bwp, True);
3387 	}
3388 
3389 	/*
3390 	 * Calculate position and animate menus
3391 	 */
3392 
3393 	if (parent_menu == NULL ||
3394 	    !menu_get_geometry(
3395 		    parent_menu, &JunkRoot, &prev_x, &prev_y, &prev_width,
3396 		    &prev_height, &JunkBW, &JunkDepth))
3397 	{
3398 		MR_HAS_POPPED_UP_LEFT(mr) = 0;
3399 		MR_HAS_POPPED_UP_RIGHT(mr) = 0;
3400 	}
3401 	else
3402 	{
3403 		int left_x;
3404 		int right_x;
3405 		Bool use_left_submenus = MST_USE_LEFT_SUBMENUS(mr);
3406 		MenuRepaintTransparentParameters mrtp;
3407 		Bool transparent_bg = False;
3408 
3409 		/* check if menus overlap */
3410 		x_clipped_overlap =
3411 			do_menus_overlap(
3412 				parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr),
3413 				bwp, bwp, bwp, True);
3414 		if (x_clipped_overlap &&
3415 		    (!pops->flags.has_poshints ||
3416 		     pops->pos_hints.is_relative == False ||
3417 		     x_overlap == 0))
3418 		{
3419 			/* menus do overlap, but do not reposition if overlap
3420 			 * was caused by relative positioning hints */
3421 			left_x = get_left_popup_x_position(
3422 				parent_menu, mr, prev_x);
3423 			right_x = get_right_popup_x_position(
3424 				parent_menu, mr, prev_x);
3425 			if (use_left_submenus)
3426 			{
3427 				if (left_x + MR_WIDTH(mr) < prev_x + bwp)
3428 				{
3429 					left_x = prev_x + bwp - MR_WIDTH(mr);
3430 				}
3431 			}
3432 			else
3433 			{
3434 				if (right_x > prev_x + prev_width - bwp)
3435 				{
3436 					right_x = prev_x + prev_width - bwp;
3437 				}
3438 			}
3439 
3440 			/*
3441 			 * Animate parent menu
3442 			 */
3443 
3444 			if (MST_IS_ANIMATED(mr))
3445 			{
3446 				/* animate previous out of the way */
3447 				int a_left_x;
3448 				int a_right_x;
3449 				int end_x;
3450 				Window w;
3451 
3452 				if (use_left_submenus)
3453 				{
3454 					if (prev_x - left_x < MR_WIDTH(mr))
3455 					{
3456 						a_right_x =
3457 							x + (prev_x - left_x);
3458 					}
3459 					else
3460 					{
3461 						a_right_x =
3462 							x + MR_WIDTH(mr) - bw;
3463 					}
3464 					a_left_x = x - MR_WIDTH(parent_menu);
3465 				}
3466 				else
3467 				{
3468 					if (right_x - prev_x < prev_width)
3469 					{
3470 						a_left_x =
3471 							x + (prev_x - right_x);
3472 					}
3473 					else
3474 					{
3475 						a_left_x =
3476 							x - prev_width + bw;
3477 					}
3478 					a_right_x = x + MR_WIDTH(mr);
3479 				}
3480 				if (prefer_left_submenu)
3481 				{
3482 					/* popup menu is left of old menu, try
3483 					 * to move prior menu right */
3484 					if (a_right_x + prev_width <=
3485 					    scr_x + scr_w)
3486 					{
3487 						end_x = a_right_x;
3488 					}
3489 					else if (a_left_x >= scr_x)
3490 					{
3491 						end_x = a_left_x;
3492 					}
3493 					else
3494 					{
3495 						end_x = scr_x + scr_w -
3496 							prev_width;
3497 					}
3498 				}
3499 				else
3500 				{
3501 					/* popup menu is right of old menu, try
3502 					 * to move prior menu left */
3503 					if (a_left_x >= scr_x)
3504 					{
3505 						end_x = a_left_x;
3506 					}
3507 					else if (a_right_x + prev_width <=
3508 						 scr_x + scr_w)
3509 					{
3510 						end_x = a_right_x;
3511 					}
3512 					else
3513 					{
3514 						end_x = scr_x;
3515 					}
3516 				}
3517 				if (end_x == a_left_x || end_x == 0)
3518 				{
3519 					MR_HAS_POPPED_UP_LEFT(mr) = 0;
3520 					MR_HAS_POPPED_UP_RIGHT(mr) = 1;
3521 				}
3522 				else
3523 				{
3524 					MR_HAS_POPPED_UP_LEFT(mr) = 1;
3525 					MR_HAS_POPPED_UP_RIGHT(mr) = 0;
3526 				}
3527 				MR_XANIMATION(parent_menu) += end_x - prev_x;
3528 				if (ST_HAS_MENU_CSET(MR_STYLE(parent_menu)) &&
3529 				    CSET_IS_TRANSPARENT(
3530 					    ST_CSET_MENU(
3531 						    MR_STYLE(parent_menu))))
3532 				{
3533 					transparent_bg = True;
3534 					get_menu_repaint_transparent_parameters(
3535 						&mrtp, parent_menu, fw);
3536 				}
3537 
3538 				if (MR_IS_TEAR_OFF_MENU(parent_menu))
3539 				{
3540 					int cx;
3541 					int cy;
3542 
3543 					w = FW_W_FRAME(
3544 						pmp->tear_off_root_menu_window
3545 						);
3546 					if (XGetGeometry(
3547 						    dpy, w, &JunkRoot, &cx,
3548 						    &cy,
3549 						    (unsigned int*)&JunkWidth,
3550 						    (unsigned int*)&JunkHeight,
3551 						    (unsigned int*)&JunkBW,
3552 						    (unsigned int*)&JunkDepth))
3553 					{
3554 						end_x += (cx - prev_x );
3555 						prev_x = cx;
3556 						prev_y = cy;
3557 					}
3558 				}
3559 				else
3560 				{
3561 					w = MR_WINDOW(parent_menu);
3562 				}
3563 				AnimatedMoveOfWindow(
3564 					w, prev_x, prev_y, end_x, prev_y, True,
3565 					-1, NULL,
3566 					(transparent_bg)? &mrtp:NULL);
3567 			} /* if (MST_IS_ANIMATED(mr)) */
3568 
3569 			/*
3570 			 * Try the other side of the parent menu
3571 			 */
3572 
3573 			else if (!pops->flags.is_fixed)
3574 			{
3575 				Bool may_use_left;
3576 				Bool may_use_right;
3577 				Bool do_use_left;
3578 				Bool use_left_as_last_resort;
3579 
3580 				use_left_as_last_resort =
3581 					(left_x > scr_x + scr_w - right_x -
3582 					 MR_WIDTH(mr));
3583 				may_use_left = (left_x >= scr_x);
3584 				may_use_right = (right_x + MR_WIDTH(mr) <=
3585 						 scr_x + scr_w);
3586 				if (!may_use_left && !may_use_right)
3587 				{
3588 					/* If everything goes wrong, put the
3589 					 * submenu on the side with more free
3590 					 * space. */
3591 					do_use_left = use_left_as_last_resort;
3592 				}
3593 				else if (may_use_left &&
3594 					 (prefer_left_submenu ||
3595 					  !may_use_right))
3596 				{
3597 					do_use_left = True;
3598 				}
3599 				else
3600 				{
3601 					do_use_left = False;
3602 				}
3603 				x = (do_use_left) ? left_x : right_x;
3604 				MR_HAS_POPPED_UP_LEFT(mr) = do_use_left;
3605 				MR_HAS_POPPED_UP_RIGHT(mr) = !do_use_left;
3606 
3607 				/* Force the menu onto the screen, but leave at
3608 				 * least PARENT_MENU_FORCE_VISIBLE_WIDTH pixels
3609 				 * of the parent menu visible */
3610 				if (x + MR_WIDTH(mr) > scr_x + scr_w)
3611 				{
3612 					int d = x + MR_WIDTH(mr) -
3613 						(scr_x + scr_w);
3614 					int c;
3615 
3616 					if (prev_width >=
3617 					    PARENT_MENU_FORCE_VISIBLE_WIDTH)
3618 					{
3619 						c = PARENT_MENU_FORCE_VISIBLE_WIDTH +
3620 							prev_x;
3621 					}
3622 					else
3623 					{
3624 						c = prev_x + prev_width;
3625 					}
3626 
3627 					if (x - c >= d || x <= prev_x)
3628 					{
3629 						x -= d;
3630 					}
3631 					else if (x > c)
3632 					{
3633 						x = c;
3634 					}
3635 				}
3636 				if (x < scr_x)
3637 				{
3638 					int c = prev_width -
3639 						PARENT_MENU_FORCE_VISIBLE_WIDTH;
3640 
3641 					if (c < 0)
3642 					{
3643 						c = 0;
3644 					}
3645 					if (scr_x - x > c)
3646 					{
3647 						x += c;
3648 					}
3649 					else
3650 					{
3651 						x = scr_x;
3652 					}
3653 				}
3654 			} /* else if (non-overlapping menu style) */
3655 		} /* if (x_clipped_overlap && ...) */
3656 		else
3657 		{
3658 			MR_HAS_POPPED_UP_LEFT(mr) = prefer_left_submenu;
3659 			MR_HAS_POPPED_UP_RIGHT(mr) = !prefer_left_submenu;
3660 		}
3661 
3662 		if (x < prev_x)
3663 		{
3664 			MR_IS_LEFT(mr) = 1;
3665 		}
3666 		if (x + MR_WIDTH(mr) > prev_x + prev_width)
3667 		{
3668 			MR_IS_RIGHT(mr) = 1;
3669 		}
3670 		if (y < prev_y)
3671 		{
3672 			MR_IS_UP(mr) = 1;
3673 		}
3674 		if (y + MR_HEIGHT(mr) > prev_y + prev_height)
3675 		{
3676 			MR_IS_DOWN(mr) = 1;
3677 		}
3678 		if (!MR_IS_LEFT(mr) && !MR_IS_RIGHT(mr))
3679 		{
3680 			MR_IS_LEFT(mr) = 1;
3681 			MR_IS_RIGHT(mr) = 1;
3682 		}
3683 	} /* if (parent_menu) */
3684 
3685 	/*
3686 	 * Make sure we have the correct events selected
3687 	 */
3688 
3689 	if (pmp->tear_off_root_menu_window == NULL)
3690 	{
3691 		/* normal menus and sub menus */
3692 		event_mask = XEVMASK_MENUW;
3693 	}
3694 	else if (parent_menu == NULL)
3695 	{
3696 		/* tear off menu needs more events */
3697 		event_mask = XEVMASK_TEAR_OFF_MENUW;
3698 	}
3699 	else
3700 	{
3701 		/* sub menus of tear off menus need LeaveNotify */
3702 		event_mask = XEVMASK_TEAR_OFF_SUBMENUW;
3703 	}
3704 
3705 	/*
3706 	 * Pop up the menu
3707 	 */
3708 
3709 	XMoveWindow(dpy, MR_WINDOW(mr), x, y);
3710 	XSelectInput(dpy, MR_WINDOW(mr), event_mask);
3711 	XMapRaised(dpy, MR_WINDOW(mr));
3712 	if (popdown_window)
3713 		XUnmapWindow(dpy, popdown_window);
3714 	XFlush(dpy);
3715 	MR_MAPPED_COPIES(mr)++;
3716 	MST_USAGE_COUNT(mr)++;
3717 	if (ret_overlap)
3718 	{
3719 		*ret_overlap =
3720 			do_menus_overlap(
3721 				parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr),
3722 				0, 0, 0, False) ? True : False;
3723 	}
3724 
3725 	/*
3726 	 * Warp the pointer
3727 	 */
3728 
3729 	if (!do_warp_to_item && parent_menu != NULL)
3730 	{
3731 		MenuItem *mi;
3732 
3733 		mi = find_entry(pmp, NULL, &mrMi, None, -1, -1);
3734 		if (mi && mrMi == mr && mi != MR_FIRST_ITEM(mrMi))
3735 		{
3736 			/* pointer is on an item of the popup */
3737 			if (MST_DO_WARP_TO_TITLE(mr))
3738 			{
3739 				/* warp pointer if not on a root menu */
3740 				do_warp_to_title = True;
3741 			}
3742 		}
3743 	} /* if (!do_warp_to_item) */
3744 
3745 	if (pops->flags.do_not_warp)
3746 	{
3747 		do_warp_to_title = False;
3748 	}
3749 	else if (pops->flags.do_warp_title)
3750 	{
3751 		do_warp_to_title = True;
3752 	}
3753 	if (pops->flags.has_poshints &&
3754 	    !last_saved_pos_hints.flags.do_ignore_pos_hints &&
3755 	    pops->flags.do_warp_title)
3756 	{
3757 		do_warp_to_title = True;
3758 	}
3759 	if (do_warp_to_item)
3760 	{
3761 		/* also warp */
3762 		MR_SELECTED_ITEM(mr) = NULL;
3763 		warp_pointer_to_item(
3764 			mr, MR_FIRST_ITEM(mr), True /* skip Title */);
3765 		select_menu_item(mr, MR_SELECTED_ITEM(mr), True, fw);
3766 	}
3767 	else if (do_warp_to_title)
3768 	{
3769 		/* Warp pointer to middle of top line, since we don't
3770 		 * want the user to come up directly on an option */
3771 		warp_pointer_to_title(mr);
3772 	}
3773 
3774 	return True;
3775 }
3776 
3777 /*
3778  *
3779  *  Procedure:
3780  *      pop_menu_down - unhighlight the current menu selection and
3781  *                    take down the menus
3782  *
3783  *      mr     - menu to pop down; this pointer is invalid after the function
3784  *               returns. Don't use it anymore!
3785  *      parent - the menu that has spawned mr (may be NULL). this is
3786  *               used to see if mr was spawned by itself on some level.
3787  *               this is a hack to allow specifying 'Popup foo' within
3788  *               menu foo. You must use the MenuRoot that is currently
3789  *               being processed here. DO NOT USE MR_PARENT_MENU(mr) here!
3790  *
3791  */
pop_menu_down(MenuRoot ** pmr,MenuParameters * pmp)3792 static void pop_menu_down(MenuRoot **pmr, MenuParameters *pmp)
3793 {
3794 	MenuItem *mi;
3795 
3796 	assert(*pmr);
3797 	memset(&(MR_DYNAMIC_FLAGS(*pmr)), 0, sizeof(MR_DYNAMIC_FLAGS(*pmr)));
3798 	XUnmapWindow(dpy, MR_WINDOW(*pmr));
3799 	MR_MAPPED_COPIES(*pmr)--;
3800 	MST_USAGE_COUNT(*pmr)--;
3801 	UninstallFvwmColormap();
3802 	XFlush(dpy);
3803 	if ((mi = MR_SELECTED_ITEM(*pmr)) != NULL)
3804 	{
3805 		select_menu_item(*pmr, mi, False, (*pmp->pexc)->w.fw);
3806 	}
3807 	if (MR_STORED_PIXELS(*pmr).d_pixels != NULL)
3808 	{
3809 		PictureFreeColors(
3810 			dpy, Pcmap, MR_STORED_PIXELS(*pmr).d_pixels,
3811 			MR_STORED_PIXELS(*pmr).d_npixels, 0, False);
3812 		free(MR_STORED_PIXELS(*pmr).d_pixels);
3813 		MR_STORED_PIXELS(*pmr).d_pixels = NULL;
3814 	}
3815 	if (MR_COPIES(*pmr) > 1)
3816 	{
3817 		/* delete this instance of the menu */
3818 		DestroyMenu(*pmr, False, False);
3819 	}
3820 	else if (MR_POPDOWN_ACTION(*pmr))
3821 	{
3822 		/* Finally execute the popdown action (if defined). */
3823 		saved_pos_hints pos_hints;
3824 
3825 		/* save variables that we still need but that may be
3826 		 * overwritten */
3827 		pos_hints = last_saved_pos_hints;
3828 		/* Execute the action */
3829 		__menu_execute_function(pmp->pexc, MR_POPDOWN_ACTION(*pmr));
3830 		/* restore the stuff we saved */
3831 		last_saved_pos_hints = pos_hints;
3832 	}
3833 
3834 	return;
3835 }
3836 
3837 /*
3838  *
3839  *  Procedure:
3840  *      pop_menu_down_and_repaint_parent - Pops down a menu and repaints the
3841  *      overlapped portions of the parent menu. This is done only if
3842  *      *fSubmenuOverlaps is True. *fSubmenuOverlaps is set to False
3843  *      afterwards.
3844  *
3845  */
pop_menu_down_and_repaint_parent(MenuRoot ** pmr,Bool * fSubmenuOverlaps,MenuParameters * pmp)3846 static void pop_menu_down_and_repaint_parent(
3847 	MenuRoot **pmr, Bool *fSubmenuOverlaps, MenuParameters *pmp)
3848 {
3849 	MenuRoot *parent = MR_PARENT_MENU(*pmr);
3850 	XEvent event;
3851 	int mr_x;
3852 	int mr_y;
3853 	int mr_width;
3854 	int mr_height;
3855 	int parent_x;
3856 	int parent_y;
3857 	int parent_width;
3858 	int parent_height;
3859 
3860 	if (*fSubmenuOverlaps && parent)
3861 	{
3862 		/* popping down the menu may destroy the menu via the dynamic
3863 		 * popdown action! Thus we must not access *pmr afterwards. */
3864 		/* Create a fake event to pass into paint_menu */
3865 		event.type = Expose;
3866 		if (!menu_get_geometry(
3867 			    *pmr, &JunkRoot, &mr_x, &mr_y, &mr_width,
3868 			    &mr_height, &JunkBW, &JunkDepth) ||
3869 		    !menu_get_geometry(
3870 			    parent, &JunkRoot, &parent_x, &parent_y,
3871 			    &parent_width, &parent_height, &JunkBW,
3872 			    &JunkDepth))
3873 		{
3874 			pop_menu_down(pmr, pmp);
3875 			paint_menu(parent, NULL, (*pmp->pexc)->w.fw);
3876 		}
3877 		else
3878 		{
3879 			pop_menu_down(pmr, pmp);
3880 			event.xexpose.x = mr_x - parent_x;
3881 			event.xexpose.width = mr_width;
3882 			if (event.xexpose.x < 0)
3883 			{
3884 				event.xexpose.width += event.xexpose.x;
3885 				event.xexpose.x = 0;
3886 			}
3887 			if (event.xexpose.x + event.xexpose.width >
3888 			    parent_width)
3889 			{
3890 				event.xexpose.width =
3891 					parent_width - event.xexpose.x;
3892 			}
3893 			event.xexpose.y = mr_y - parent_y;
3894 			event.xexpose.height = mr_height;
3895 			if (event.xexpose.y < 0)
3896 			{
3897 				event.xexpose.height += event.xexpose.y;
3898 				event.xexpose.y = 0;
3899 			}
3900 			if (event.xexpose.y + event.xexpose.height >
3901 			    parent_height)
3902 			{
3903 				event.xexpose.height =
3904 					parent_height - event.xexpose.y;
3905 			}
3906 			flush_accumulate_expose(MR_WINDOW(parent), &event);
3907 			paint_menu(parent, &event, (*pmp->pexc)->w.fw);
3908 		}
3909 	}
3910 	else
3911 	{
3912 		/* popping down the menu may destroy the menu via the dynamic
3913 		 * popdown action! Thus we must not access *pmr afterwards. */
3914 		pop_menu_down(pmr, pmp);
3915 	}
3916 	*fSubmenuOverlaps = False;
3917 
3918 	return;
3919 }
3920 
3921 /* ---------------------------- menu main loop ------------------------------ */
3922 
__mloop_init(MenuParameters * pmp,MenuReturn * pmret,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi,MenuOptions * pops)3923 static void __mloop_init(
3924 	MenuParameters *pmp, MenuReturn *pmret,
3925 	mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
3926 	MenuOptions *pops)
3927 {
3928 	memset(in, 0, sizeof(*in));
3929 	in->mif.do_force_reposition = 1;
3930 	memset(med, 0, sizeof(*med));
3931 	msi->t0 = fev_get_evtime();
3932 	pmret->rc = MENU_NOP;
3933 	memset(pops, 0, sizeof(*pops));
3934 	/* remember where the pointer was so we can tell if it has moved */
3935 	if (FQueryPointer(
3936 		    dpy, Scr.Root, &JunkRoot, &JunkChild, &(msi->x_init),
3937 		    &(msi->y_init), &JunkX, &JunkY, &JunkMask) == False)
3938 	{
3939 		/* pointer is on a different screen */
3940 		msi->x_init = 0;
3941 		msi->y_init = 0;
3942 	}
3943 	/* get the event mask right */
3944 	msi->event_mask = (pmp->tear_off_root_menu_window == NULL) ?
3945 		XEVMASK_MENU : XEVMASK_TEAR_OFF_MENU;
3946 
3947 	return;
3948 }
3949 
__mloop_get_event_timeout_loop(MenuParameters * pmp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi)3950 static void __mloop_get_event_timeout_loop(
3951 	MenuParameters *pmp,
3952 	mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
3953 {
3954 	XEvent e = *(*pmp->pexc)->x.elast;
3955 
3956 	while (!FPending(dpy) || !FCheckMaskEvent(dpy, msi->event_mask, &e))
3957 	{
3958 		Bool is_popup_timed_out =
3959 			(MST_POPUP_DELAY(pmp->menu) > 0 &&
3960 			 med->popup_delay_10ms++ >=
3961 			 MST_POPUP_DELAY(pmp->menu) + 1);
3962 		Bool is_popdown_timed_out =
3963 			(MST_POPDOWN_DELAY(pmp->menu) > 0 && in->mrPopdown &&
3964 			 med->popdown_delay_10ms++ >=
3965 			 MST_POPDOWN_DELAY(pmp->menu) + 1);
3966 		Bool do_fake_motion = False;
3967 
3968 		if (is_popup_timed_out)
3969 		{
3970 			med->popup_delay_10ms = MST_POPUP_DELAY(pmp->menu);
3971 		}
3972 		if (is_popdown_timed_out)
3973 		{
3974 			med->popdown_delay_10ms = MST_POPDOWN_DELAY(pmp->menu);
3975 		}
3976 		if (
3977 			in->mif.do_force_popup ||
3978 			(is_popup_timed_out &&
3979 			 (is_popdown_timed_out ||
3980 			  in->mif.is_item_entered_by_key_press ||
3981 			  !in->mrPopdown ||
3982 			  MST_DO_POPDOWN_IMMEDIATELY(pmp->menu))))
3983 		{
3984 			in->mif.do_popup_now = True;
3985 			in->mif.do_force_popup = False;
3986 			do_fake_motion = True;
3987 			in->mif.is_popped_up_by_timeout = True;
3988 			is_popdown_timed_out = True;
3989 		}
3990 		if ((in->mrPopdown || in->mrPopup) &&
3991 		    (is_popdown_timed_out ||
3992 		     MST_DO_POPDOWN_IMMEDIATELY(pmp->menu)))
3993 		{
3994 			MenuRoot *m = (in->mrPopdown) ?
3995 				in->mrPopdown : in->mrPopup;
3996 
3997 			if (!m || med->mi != MR_PARENT_ITEM(m))
3998 			{
3999 				in->mif.do_popdown_now = True;
4000 				do_fake_motion = True;
4001 				if (MST_DO_POPUP_IMMEDIATELY(pmp->menu))
4002 				{
4003 					in->mif.do_popup_now = True;
4004 					in->mif.is_popped_up_by_timeout = True;
4005 				}
4006 				else if (
4007 					!MST_DO_POPUP_IMMEDIATELY(pmp->menu) &&
4008 					in->mif.is_pointer_in_active_item_area)
4009 				{
4010 					in->mif.do_popup_now = True;
4011 					in->mif.is_popped_up_by_timeout = True;
4012 				}
4013 				else if (
4014 					!MST_DO_POPUP_IMMEDIATELY(pmp->menu) &&
4015 					!MST_DO_POPDOWN_IMMEDIATELY(pmp->menu)
4016 					&& MST_POPUP_DELAY(pmp->menu) <=
4017 					MST_POPDOWN_DELAY(pmp->menu) &&
4018 					med->popup_delay_10ms ==
4019 					med->popdown_delay_10ms)
4020 				{
4021 					in->mif.do_popup_now = True;
4022 					in->mif.is_popped_up_by_timeout = True;
4023 				}
4024 			}
4025 		}
4026 		if (in->mif.do_popup_now && med->mi == in->miRemovedSubmenu &&
4027 		    !in->mif.is_key_press)
4028 		{
4029 			/* prevent popping up the menu again with
4030 			 * RemoveSubemenus */
4031 			in->mif.do_popup_now = False;
4032 			in->mif.do_force_popup = False;
4033 			do_fake_motion = in->mif.do_popdown_now;
4034 			in->mif.is_popped_up_by_timeout = False;
4035 		}
4036 
4037 		if (do_fake_motion)
4038 		{
4039 			/* fake a motion event, and set in->mif.do_popup_now */
4040 			e.type = MotionNotify;
4041 			e.xmotion.time = fev_get_evtime();
4042 			fev_fake_event(&e);
4043 			in->mif.is_motion_faked = True;
4044 			break;
4045 		}
4046 		usleep(10000 /* 10 ms*/);
4047 	}
4048 
4049 	return;
4050 }
4051 
__mloop_get_event(MenuParameters * pmp,MenuReturn * pmret,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi)4052 static mloop_ret_code_t __mloop_get_event(
4053 	MenuParameters *pmp, MenuReturn *pmret,
4054 	mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
4055 {
4056 	XEvent e = *(*pmp->pexc)->x.elast;
4057 
4058 	in->mif.do_popup_and_warp = False;
4059 	in->mif.do_popup_now = False;
4060 	in->mif.do_popdown_now = False;
4061 	in->mif.do_propagate_event_into_submenu = False;
4062 	in->mif.is_key_press = False;
4063 	in->mif.is_button_release = 0;
4064 	if (pmp->event_propagate_to_submenu)
4065 	{
4066 		/* handle an event that was passed in from the parent menu */
4067 		fev_fake_event(pmp->event_propagate_to_submenu);
4068 		pmp->event_propagate_to_submenu = NULL;
4069 	}
4070 	else if (in->mif.do_recycle_event)
4071 	{
4072 		in->mif.is_popped_up_by_timeout = False;
4073 		in->mif.do_recycle_event = 0;
4074 		if (pmp->menu != pmret->target_menu)
4075 		{
4076 			/* the event is for a previous menu, just close this
4077 			 * one */
4078 			pmret->rc = MENU_PROPAGATE_EVENT;
4079 			return MENU_MLOOP_RET_END;
4080 		}
4081 		if ((*pmp->pexc)->x.elast->type == KeyPress)
4082 		{
4083 			/* since the pointer has been warped since the key was
4084 			 * pressed, fake a different key press position */
4085 			if (FQueryPointer(
4086 				    dpy, Scr.Root, &JunkRoot, &JunkChild,
4087 				    &e.xkey.x_root, &e.xkey.y_root,
4088 				    &JunkX, &JunkY, &e.xkey.state) == False)
4089 			{
4090 				/* pointer is on a different screen */
4091 				e.xkey.x_root = 0;
4092 				e.xkey.y_root = 0;
4093 			}
4094 			fev_fake_event(&e);
4095 			med->mi = MR_SELECTED_ITEM(pmp->menu);
4096 		}
4097 	}
4098 	else if (pmp->tear_off_root_menu_window != NULL &&
4099 		 FCheckTypedWindowEvent(
4100 			 dpy, FW_W_PARENT(pmp->tear_off_root_menu_window),
4101 			 ClientMessage, &e))
4102 	{
4103 		/* Got a ClientMessage for the tear out menu */
4104 	}
4105 	else
4106 	{
4107 		if (in->mif.do_force_reposition)
4108 		{
4109 			e.type = MotionNotify;
4110 			e.xmotion.time = fev_get_evtime();
4111 			in->mif.is_motion_faked = True;
4112 			in->mif.do_force_reposition = False;
4113 			in->mif.is_popped_up_by_timeout = False;
4114 			fev_fake_event(&e);
4115 		}
4116 		else if (!FCheckMaskEvent(dpy, ExposureMask, &e))
4117 		{
4118 			Bool is_popdown_timer_active = False;
4119 			Bool is_popup_timer_active = False;
4120 
4121 			if (MST_POPDOWN_DELAY(pmp->menu) > 0 &&
4122 			    in->mi_with_popup != NULL &&
4123 			    in->mi_with_popup != MR_SELECTED_ITEM(pmp->menu))
4124 			{
4125 				is_popdown_timer_active = True;
4126 			}
4127 			if (MST_POPUP_DELAY(pmp->menu) > 0 &&
4128 			    !in->mif.is_popped_up_by_timeout &&
4129 			    in->mi_wants_popup != NULL)
4130 			{
4131 				is_popup_timer_active = True;
4132 			}
4133 			/* handle exposure events first */
4134 			if (in->mif.do_force_popup ||
4135 			    in->mif.is_pointer_in_active_item_area ||
4136 			    is_popdown_timer_active || is_popup_timer_active)
4137 			{
4138 				__mloop_get_event_timeout_loop(
4139 					pmp, in, med, msi);
4140 			}
4141 			else
4142 			{
4143 				/* block until there is an event */
4144 				FMaskEvent(dpy, msi->event_mask, &e);
4145 				in->mif.is_popped_up_by_timeout = False;
4146 			}
4147 		}
4148 		else
4149 		{
4150 			in->mif.is_popped_up_by_timeout = False;
4151 		}
4152 	}
4153 
4154 	in->mif.is_pointer_in_active_item_area = False;
4155 	if (e.type == MotionNotify)
4156 	{
4157 		/* discard any extra motion events before a release */
4158 		while (FCheckMaskEvent(
4159 			       dpy, ButtonMotionMask | ButtonReleaseMask,
4160 			       &e) && (e.type != ButtonRelease))
4161 		{
4162 			/* nothing */
4163 		}
4164 	}
4165 
4166 	return MENU_MLOOP_RET_NORMAL;
4167 }
4168 
__mloop_handle_event(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi)4169 static mloop_ret_code_t __mloop_handle_event(
4170 	MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
4171 	mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
4172 {
4173 	MenuRoot *tmrMi;
4174 	Bool rc;
4175 
4176 	pmret->rc = MENU_NOP;
4177 	switch ((*pmp->pexc)->x.elast->type)
4178 	{
4179 	case ButtonRelease:
4180 		in->mif.is_button_release = 1;
4181 		med->mi = find_entry(
4182 			pmp, &med->x_offset, &med->mrMi,
4183 			(*pmp->pexc)->x.elast->xbutton.subwindow,
4184 			(*pmp->pexc)->x.elast->xbutton.x_root,
4185 			(*pmp->pexc)->x.elast->xbutton.y_root);
4186 		/* hold the menu up when the button is released
4187 		 * for the first time if released OFF of the menu */
4188 		if (pmp->flags.is_sticky && !in->mif.is_motion_first)
4189 		{
4190 			in->mif.is_release_first = True;
4191 			pmp->flags.is_sticky = False;
4192 			return MENU_MLOOP_RET_LOOP;
4193 		}
4194 		if (med->mrMi != NULL)
4195 		{
4196 			int menu_x;
4197 			int menu_y;
4198 
4199 			menu_shortcuts(
4200 				med->mrMi, pmp, pmret, (*pmp->pexc)->x.elast,
4201 				&med->mi, pdkp, &menu_x, &menu_y);
4202 			if (pmret->rc == MENU_NEWITEM_MOVEMENU)
4203 			{
4204 				move_any_menu(med->mrMi, pmp, menu_x, menu_y);
4205 				pmret->rc = MENU_NEWITEM;
4206 			}
4207 			else if (pmret->rc == MENU_NEWITEM_FIND)
4208 			{
4209 				med->mi = find_entry(
4210 					pmp, NULL, NULL, None, -1, -1);
4211 				pmret->rc = MENU_NEWITEM;
4212 			}
4213 		}
4214 		else
4215 		{
4216 			pmret->rc = MENU_SELECTED;
4217 		}
4218 		switch (pmret->rc)
4219 		{
4220 		case MENU_NOP:
4221 			return MENU_MLOOP_RET_LOOP;
4222 		case MENU_POPDOWN:
4223 		case MENU_ABORTED:
4224 		case MENU_DOUBLE_CLICKED:
4225 		case MENU_TEAR_OFF:
4226 		case MENU_KILL_TEAR_OFF_MENU:
4227 		case MENU_EXEC_CMD:
4228 			return MENU_MLOOP_RET_END;
4229 		case MENU_POPUP:
4230 			/* Allow for MoveLeft/MoveRight action to work with
4231 			 * Mouse */
4232 			in->mif.do_popup_and_warp = True;
4233 		case MENU_NEWITEM:
4234 			/* unpost the menu if posted */
4235 			pmret->flags.is_menu_posted = 0;
4236 			return MENU_MLOOP_RET_NORMAL;
4237 		case MENU_SELECTED:
4238 			in->mif.was_item_unposted = 0;
4239 			if (pmret->flags.is_menu_posted && med->mrMi != NULL)
4240 			{
4241 				if (pmret->flags.do_unpost_submenu)
4242 				{
4243 					pmret->flags.do_unpost_submenu = 0;
4244 					pmret->flags.is_menu_posted = 0;
4245 					/* just ignore the event */
4246 					return MENU_MLOOP_RET_LOOP;
4247 				}
4248 				else if (med->mi && MI_IS_POPUP(med->mi)
4249 					 && med->mrMi == pmp->menu)
4250 				{
4251 					/* post menu - done below */
4252 				}
4253 				else if (MR_PARENT_ITEM(pmp->menu) &&
4254 					 med->mi == MR_PARENT_ITEM(pmp->menu))
4255 				{
4256 					/* propagate back to parent menu
4257 					 * and unpost current menu */
4258 					pmret->flags.do_unpost_submenu = 1;
4259 					pmret->rc = MENU_PROPAGATE_EVENT;
4260 					pmret->target_menu = med->mrMi;
4261 					return MENU_MLOOP_RET_END;
4262 				}
4263 				else if (med->mrMi != pmp->menu &&
4264 					 med->mrMi != in->mrPopup)
4265 				{
4266 					/* unpost and propagate back to
4267 					 * ancestor */
4268 					pmret->flags.is_menu_posted = 0;
4269 					pmret->rc = MENU_PROPAGATE_EVENT;
4270 					pmret->target_menu = med->mrMi;
4271 					return MENU_MLOOP_RET_END;
4272 				}
4273 				else if (in->mrPopup && med->mrMi ==
4274 					 in->mrPopup)
4275 				{
4276 					/* unpost and propagate into
4277 					 * submenu */
4278 					in->mif.was_item_unposted = 1;
4279 					in->mif.do_propagate_event_into_submenu
4280 						= True;
4281 					break;
4282 				}
4283 				else
4284 				{
4285 					/* simply unpost the menu */
4286 					pmret->flags.is_menu_posted = 0;
4287 				}
4288 			}
4289 			if (is_double_click(
4290 				    msi->t0, med->mi, pmp, pmret, pdkp,
4291 				    in->mif.has_mouse_moved))
4292 			{
4293 				pmret->rc = MENU_DOUBLE_CLICKED;
4294 				return MENU_MLOOP_RET_END;
4295 			}
4296 			if (med->mi == NULL)
4297 			{
4298 				pmret->rc = MENU_ABORTED;
4299 			}
4300 			else if (MI_IS_POPUP(med->mi))
4301 			{
4302 				switch (MST_DO_POPUP_AS(pmp->menu))
4303 				{
4304 				case MDP_POST_MENU:
4305 					if (in->mif.was_item_unposted)
4306 					{
4307 						pmret->flags.is_menu_posted =
4308 							0;
4309 						pmret->rc = MENU_UNPOST;
4310 						pmret->target_menu = NULL;
4311 						in->mif.do_popup_now = False;
4312 					}
4313 					else
4314 					{
4315 						pmret->flags.is_menu_posted =
4316 							1;
4317 						pmret->rc = MENU_POST;
4318 						pmret->target_menu = NULL;
4319 						in->mif.do_popup_now = True;
4320 						if ((in->mrPopup ||
4321 						     in->mrPopdown)
4322 						    && med->mi !=
4323 						    MR_SELECTED_ITEM(
4324 							    pmp->menu))
4325 						{
4326 							in->mif.do_popdown_now
4327 								= True;
4328 						}
4329 					}
4330 					return MENU_MLOOP_RET_NORMAL;
4331 				case MDP_IGNORE:
4332 					pmret->rc = MENU_NOP;
4333 					return MENU_MLOOP_RET_NORMAL;
4334 				case MDP_CLOSE:
4335 					pmret->rc = MENU_ABORTED;
4336 					break;
4337 				case MDP_ROOT_MENU:
4338 				default:
4339 					break;
4340 				}
4341 			}
4342 			break;
4343 		default:
4344 			break;
4345 		}
4346 		pdkp->timestamp = 0;
4347 
4348 		return MENU_MLOOP_RET_END;
4349 
4350 	case ButtonPress:
4351 		/* if the first event is a button press allow the release to
4352 		 * select something */
4353 		pmp->flags.is_sticky = False;
4354 		return MENU_MLOOP_RET_LOOP;
4355 
4356 	case VisibilityNotify:
4357 		return MENU_MLOOP_RET_LOOP;
4358 
4359 	case KeyRelease:
4360 		if ((*pmp->pexc)->x.elast->xkey.keycode !=
4361 		    MST_SELECT_ON_RELEASE_KEY(pmp->menu))
4362 		{
4363 			return MENU_MLOOP_RET_LOOP;
4364 		}
4365 		/* fall through to KeyPress */
4366 
4367 	case KeyPress:
4368 		/* Handle a key press events to allow mouseless operation */
4369 		in->mif.is_key_press = True;
4370 		med->x_offset = menudim_middle_x_offset(&MR_DIM(pmp->menu));
4371 
4372 		/* if there is a posted menu we may have to move back into a
4373 		 * previous menu or possibly ignore the mouse position */
4374 		if (pmret->flags.is_menu_posted)
4375 		{
4376 			MenuRoot *l_mrMi;
4377 			int l_x_offset;
4378 			XEvent e;
4379 
4380 			pmret->flags.is_menu_posted = 0;
4381 			(void)find_entry(
4382 				pmp, &l_x_offset, &l_mrMi, None, -1, -1);
4383 			if (l_mrMi != NULL)
4384 			{
4385 				if (pmp->menu != l_mrMi)
4386 				{
4387 					/* unpost the menu and propagate the
4388 					 * event to the correct menu */
4389 					pmret->rc = MENU_PROPAGATE_EVENT;
4390 					pmret->target_menu = l_mrMi;
4391 					return MENU_MLOOP_RET_END;
4392 				}
4393 			}
4394 			med->mi = MR_SELECTED_ITEM(pmp->menu);
4395 			e = *(*pmp->pexc)->x.elast;
4396 			e.xkey.x_root = med->x_offset;
4397 			e.xkey.y_root = menuitem_middle_y_offset(
4398 				med->mi, MR_STYLE(pmp->menu));
4399 			fev_fake_event(&e);
4400 		}
4401 
4402 		/* now handle the actual key press */
4403 		{
4404 			int menu_x;
4405 			int menu_y;
4406 
4407 			menu_shortcuts(
4408 				pmp->menu, pmp, pmret, (*pmp->pexc)->x.elast,
4409 				&med->mi, pdkp, &menu_x, &menu_y);
4410 			if (pmret->rc == MENU_NEWITEM_MOVEMENU)
4411 			{
4412 				move_any_menu(pmp->menu, pmp, menu_x, menu_y);
4413 				pmret->rc = MENU_NEWITEM;
4414 			}
4415 			else if (pmret->rc == MENU_NEWITEM_FIND)
4416 			{
4417 				med->mi = find_entry(
4418 					pmp, NULL, NULL, None, -1, -1);
4419 				pmret->rc = MENU_NEWITEM;
4420 			}
4421 		}
4422 		if (pmret->rc != MENU_NOP)
4423 		{
4424 			/* using a key 'unposts' the posted menu */
4425 			pmret->flags.is_menu_posted = 0;
4426 		}
4427 		switch (pmret->rc)
4428 		{
4429 		case MENU_SELECTED:
4430 			if (med->mi && MI_IS_POPUP(med->mi))
4431 			{
4432 				switch (MST_DO_POPUP_AS(pmp->menu))
4433 				{
4434 				case MDP_POST_MENU:
4435 					pmret->rc = MENU_POPUP;
4436 					break;
4437 				case MDP_IGNORE:
4438 					pmret->rc = MENU_NOP;
4439 					return MENU_MLOOP_RET_NORMAL;
4440 				case MDP_CLOSE:
4441 					pmret->rc = MENU_ABORTED;
4442 					return MENU_MLOOP_RET_END;
4443 				case MDP_ROOT_MENU:
4444 				default:
4445 					return MENU_MLOOP_RET_END;
4446 				}
4447 				break;
4448 			}
4449 			return MENU_MLOOP_RET_END;
4450 		case MENU_POPDOWN:
4451 		case MENU_ABORTED:
4452 		case MENU_DOUBLE_CLICKED:
4453 		case MENU_TEAR_OFF:
4454 		case MENU_KILL_TEAR_OFF_MENU:
4455 		case MENU_EXEC_CMD:
4456 			return MENU_MLOOP_RET_END;
4457 		case MENU_NEWITEM:
4458 		case MENU_POPUP:
4459 			if (med->mrMi == NULL)
4460 			{
4461 				/* Set the MenuRoot of the current item in case
4462 				 * we have just warped to the menu from the
4463 				 * void or unposted a popup menu. */
4464 				med->mrMi = pmp->menu;
4465 			}
4466 			/*tmrMi = med->mrMi;*/
4467 			break;
4468 		default:
4469 			break;
4470 		}
4471 		/* now warp to the new menu item, if any */
4472 		if (pmret->rc == MENU_NEWITEM && med->mi)
4473 		{
4474 			warp_pointer_to_item(med->mrMi, med->mi, False);
4475 		}
4476 		if (pmret->rc == MENU_POPUP && med->mi && MI_IS_POPUP(med->mi))
4477 		{
4478 			in->mif.do_popup_and_warp = True;
4479 			break;
4480 		}
4481 		break;
4482 
4483 	case MotionNotify:
4484 	{
4485 		int p_rx = -1;
4486 		int p_ry = -1;
4487 		Window p_child = None;
4488 
4489 		if (in->mif.has_mouse_moved == False)
4490 		{
4491 			if (FQueryPointer(
4492 				    dpy, Scr.Root, &JunkRoot, &p_child, &p_rx,
4493 				    &p_ry, &JunkX, &JunkY, &JunkMask) == False)
4494 			{
4495 				/* pointer is on a different screen */
4496 				p_rx = 0;
4497 				p_ry = 0;
4498 			}
4499 			if (p_rx - msi->x_init > Scr.MoveThreshold ||
4500 			    msi->x_init - p_rx > Scr.MoveThreshold ||
4501 			    p_ry - msi->y_init > Scr.MoveThreshold ||
4502 			    msi->y_init - p_ry > Scr.MoveThreshold)
4503 			{
4504 				/* remember that this isn't just a click any
4505 				 * more since the pointer moved */
4506 				in->mif.has_mouse_moved = True;
4507 			}
4508 		}
4509 		med->mi = find_entry(
4510 			pmp, &med->x_offset, &med->mrMi, p_child, p_rx, p_ry);
4511 		if (pmret->flags.is_menu_posted &&
4512 		    med->mrMi != pmret->target_menu)
4513 		{
4514 			/* ignore mouse movement outside a posted menu */
4515 			med->mrMi = NULL;
4516 			med->mi = NULL;
4517 		}
4518 		if (!in->mif.is_release_first && !in->mif.is_motion_faked &&
4519 		    in->mif.has_mouse_moved)
4520 		{
4521 			in->mif.is_motion_first = True;
4522 		}
4523 		in->mif.is_motion_faked = False;
4524 		break;
4525 	}
4526 
4527 	case Expose:
4528 		/* grab our expose events, let the rest go through */
4529 		XFlush(dpy);
4530 		rc = menu_expose((*pmp->pexc)->x.elast, (*pmp->pexc)->w.fw);
4531 		/* we want to dispatch this too so that icons and maybe tear
4532 		 * off get redrawn after being obscured by menus. */
4533 		if (rc == False)
4534 		{
4535 			dispatch_event((*pmp->pexc)->x.elast);
4536 		}
4537 		return MENU_MLOOP_RET_LOOP;
4538 
4539 	case ClientMessage:
4540 		if ((*pmp->pexc)->x.elast->xclient.format == 32 &&
4541 		    (*pmp->pexc)->x.elast->xclient.data.l[0] ==
4542 		    _XA_WM_DELETE_WINDOW &&
4543 		    pmp->tear_off_root_menu_window != NULL &&
4544 		    (*pmp->pexc)->x.elast->xclient.window == FW_W_PARENT(
4545 			    pmp->tear_off_root_menu_window))
4546 		{
4547 			/* handle deletion of tear out menus */
4548 			pmret->rc = MENU_KILL_TEAR_OFF_MENU;
4549 			return MENU_MLOOP_RET_END;
4550 		}
4551 		break;
4552 
4553 	case EnterNotify:
4554 		/* ignore EnterNotify events */
4555 		break;
4556 
4557 	case LeaveNotify:
4558 		if (pmp->tear_off_root_menu_window != NULL &&
4559 		    find_entry(pmp, NULL, &tmrMi, None, -1, -1) == NULL &&
4560 		    tmrMi == NULL)
4561 		{
4562 			/* handle deletion of tear out menus */
4563 			pmret->rc = MENU_ABORTED;
4564 			return MENU_MLOOP_RET_END;
4565 		}
4566 		/* ignore it */
4567 		return MENU_MLOOP_RET_LOOP;
4568 
4569 	case UnmapNotify:
4570 		/* should never happen, but does not hurt */
4571 		if (pmp->tear_off_root_menu_window != NULL &&
4572 		    (*pmp->pexc)->x.elast->xunmap.window ==
4573 		    FW_W(pmp->tear_off_root_menu_window))
4574 		{
4575 			/* handle deletion of tear out menus */
4576 			pmret->rc = MENU_KILL_TEAR_OFF_MENU;
4577 			/* extra safety: pass event back to main event loop to
4578 			 * make sure the window is destroyed */
4579 			FPutBackEvent(dpy, (*pmp->pexc)->x.elast);
4580 			return MENU_MLOOP_RET_END;
4581 		}
4582 		break;
4583 
4584 	default:
4585 		/* We must not dispatch events here.  There is no guarantee
4586 		 * that dispatch_event doesn't destroy a window stored in the
4587 		 * menu structures.  Anyway, no events should ever get here
4588 		 * except to tear off menus and these must be handled
4589 		 * individually. */
4590 #if 0
4591 		dispatch_event((*pmp->pexc)->x.elast);
4592 #endif
4593 		break;
4594 	}
4595 
4596 	return MENU_MLOOP_RET_NORMAL;
4597 }
4598 
__mloop_select_item(MenuParameters * pmp,mloop_evh_input_t * in,mloop_evh_data_t * med,Bool does_submenu_overlap,Bool * pdoes_popdown_submenu_overlap)4599 static void __mloop_select_item(
4600 	MenuParameters *pmp, mloop_evh_input_t *in, mloop_evh_data_t *med,
4601 	Bool does_submenu_overlap, Bool *pdoes_popdown_submenu_overlap)
4602 {
4603 	in->mif.is_item_entered_by_key_press = in->mif.is_key_press;
4604 	med->popup_delay_10ms = 0;
4605 	/* we're on the same menu, but a different item, so we need to unselect
4606 	 * the old item */
4607 	if (MR_SELECTED_ITEM(pmp->menu))
4608 	{
4609 		/* something else was already selected on this menu.  We have
4610 		 * to pop down the menu before unselecting the item in case we
4611 		 * are using gradient menus. The recalled image would paint
4612 		 * over the submenu. */
4613 		if (in->mrPopup && in->mrPopup != in->mrPopdown)
4614 		{
4615 			in->mrPopdown = in->mrPopup;
4616 			med->popdown_delay_10ms = 0;
4617 			*pdoes_popdown_submenu_overlap = does_submenu_overlap;
4618 			in->mrPopup = NULL;
4619 		}
4620 		select_menu_item(
4621 			pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
4622 			(*pmp->pexc)->w.fw);
4623 	}
4624 	/* highlight the new item; sets MR_SELECTED_ITEM(pmp->menu) too */
4625 	select_menu_item(pmp->menu, med->mi, True, (*pmp->pexc)->w.fw);
4626 
4627 	return;
4628 }
4629 
__mloop_wants_popup(MenuParameters * pmp,mloop_evh_input_t * in,mloop_evh_data_t * med,MenuRoot * mrMiPopup)4630 static void __mloop_wants_popup(
4631 	MenuParameters *pmp, mloop_evh_input_t *in, mloop_evh_data_t *med,
4632 	MenuRoot *mrMiPopup)
4633 {
4634 	Bool do_it_now = False;
4635 
4636 	in->mi_wants_popup = med->mi;
4637 	if (in->mif.do_popup_now)
4638 	{
4639 		do_it_now = True;
4640 	}
4641 	else if (MST_DO_POPUP_IMMEDIATELY(pmp->menu) &&
4642 		 med->mi != in->miRemovedSubmenu)
4643 	{
4644 		if (in->mif.is_key_press ||
4645 		    MST_DO_POPDOWN_IMMEDIATELY(pmp->menu) || !in->mrPopdown)
4646 		{
4647 			do_it_now = True;
4648 		}
4649 	}
4650 	else if (pointer_in_active_item_area(med->x_offset, med->mrMi))
4651 	{
4652 		if (in->mif.is_key_press || med->mi == in->miRemovedSubmenu ||
4653 		    MST_DO_POPDOWN_IMMEDIATELY(pmp->menu) || !in->mrPopdown)
4654 		{
4655 			do_it_now = True;
4656 		}
4657 		else
4658 		{
4659 			in->mif.is_pointer_in_active_item_area = 1;
4660 		}
4661 	}
4662 	if (do_it_now)
4663 	{
4664 		in->miRemovedSubmenu = NULL;
4665 		/* must create a new menu or popup */
4666 		if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
4667 		{
4668 			if (in->mif.do_popup_now)
4669 			{
4670 				in->mif.do_popup = True;
4671 			}
4672 			else
4673 			{
4674 				/* pop up in next pass through loop */
4675 				in->mif.do_force_popup = True;
4676 			}
4677 		}
4678 		else if (in->mif.do_popup_and_warp)
4679 		{
4680 			warp_pointer_to_item(
4681 				in->mrPopup, MR_FIRST_ITEM(in->mrPopup), True);
4682 		}
4683 	}
4684 
4685 	return;
4686 }
4687 
__mloop_make_popup(MenuParameters * pmp,MenuReturn * pmret,mloop_evh_input_t * in,mloop_evh_data_t * med,MenuOptions * pops,Bool * pdoes_submenu_overlap)4688 static mloop_ret_code_t __mloop_make_popup(
4689 	MenuParameters *pmp, MenuReturn *pmret,
4690 	mloop_evh_input_t *in, mloop_evh_data_t *med,
4691 	MenuOptions *pops, Bool *pdoes_submenu_overlap)
4692 {
4693 	/* create a popup menu */
4694 	if (!is_submenu_mapped(pmp->menu, med->mi))
4695 	{
4696 		/* We want to pop prepop menus so it doesn't *have* to be
4697 		   unpopped; do_menu pops down any menus it pops up, but we
4698 		   want to be able to popdown w/o actually removing the menu */
4699 		int x;
4700 		int y;
4701 		Bool prefer_left_submenus;
4702 
4703 		/* Make sure we are using the latest style and menu layout. */
4704 		update_menu(in->mrPopup, pmp);
4705 
4706 		get_prefered_popup_position(
4707 			pmp->menu, in->mrPopup, &x, &y, &prefer_left_submenus);
4708 		/* Note that we don't care if popping up the menu works. If it
4709 		 * doesn't we'll catch it below. */
4710 		pop_menu_up(
4711 			&in->mrPopup, pmp, pmp->menu, med->mi, pmp->pexc, x, y,
4712 			prefer_left_submenus, in->mif.do_popup_and_warp, pops,
4713 			pdoes_submenu_overlap,
4714 			(in->mrPopdown) ? MR_WINDOW(in->mrPopdown) : None);
4715 		in->mi_with_popup = med->mi;
4716 		in->mi_wants_popup = NULL;
4717 		if (in->mrPopup == NULL)
4718 		{
4719 			/* the menu deleted itself when execution the dynamic
4720 			 * popup * action */
4721 			pmret->rc = MENU_ERROR;
4722 			return MENU_MLOOP_RET_END;
4723 		}
4724 		MR_SUBMENU_ITEM(pmp->menu) = med->mi;
4725 	}
4726 
4727 	return MENU_MLOOP_RET_NORMAL;
4728 }
4729 
__mloop_get_mi_actions(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi,MenuRoot * mrMiPopup,Bool * pdoes_submenu_overlap,Bool * pdoes_popdown_submenu_overlap)4730 static mloop_ret_code_t __mloop_get_mi_actions(
4731 	MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
4732 	mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
4733 	MenuRoot *mrMiPopup, Bool *pdoes_submenu_overlap,
4734 	Bool *pdoes_popdown_submenu_overlap)
4735 {
4736 	in->mif.do_popdown = False;
4737 	in->mif.do_popup = False;
4738 	in->mif.do_menu = False;
4739 	in->mif.is_submenu_mapped = False;
4740 	if (!in->mrPopup && in->mrPopdown &&
4741 	    med->mi == MR_PARENT_ITEM(in->mrPopdown))
4742 	{
4743 		/* We're again on the item that we left before.
4744 		 * Deschedule popping it down. */
4745 		in->mrPopup = in->mrPopdown;
4746 		in->mrPopdown = NULL;
4747 	}
4748 	if (med->mrMi == in->mrPopup)
4749 	{
4750 		/* must make current popup menu a real menu */
4751 		in->mif.do_menu = True;
4752 		in->mif.is_submenu_mapped = True;
4753 	}
4754 	else if (pmret->rc == MENU_POST)
4755 	{
4756 		/* must create a real menu and warp into it */
4757 		if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
4758 		{
4759 			in->mif.do_popup = True;
4760 		}
4761 		in->mif.do_menu = True;
4762 		in->mif.is_submenu_mapped = True;
4763 	}
4764 	else if (in->mif.do_popup_and_warp)
4765 	{
4766 		/* must create a real menu and warp into it */
4767 		if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
4768 		{
4769 			in->mif.do_popup = True;
4770 		}
4771 		else
4772 		{
4773 			XRaiseWindow(dpy, MR_WINDOW(in->mrPopup));
4774 			warp_pointer_to_item(
4775 				in->mrPopup, MR_FIRST_ITEM(in->mrPopup), True);
4776 			in->mif.do_menu = True;
4777 			in->mif.is_submenu_mapped = True;
4778 		}
4779 	}
4780 	else if (med->mi && MI_IS_POPUP(med->mi))
4781 	{
4782 		if ((*pmp->pexc)->x.elast->type == ButtonPress &&
4783 		    is_double_click(
4784 			    msi->t0, med->mi, pmp, pmret, pdkp,
4785 			    in->mif.has_mouse_moved))
4786 		{
4787 			pmret->rc = MENU_DOUBLE_CLICKED;
4788 			return MENU_MLOOP_RET_END;
4789 		}
4790 		else if (!in->mrPopup || in->mrPopup != mrMiPopup ||
4791 			 in->mif.do_popup_and_warp)
4792 		{
4793 			__mloop_wants_popup(pmp, in, med, mrMiPopup);
4794 		}
4795 	}
4796 	if (in->mif.do_popdown_now)
4797 	{
4798 		in->mif.do_popdown = True;
4799 		in->mif.do_popdown_now = False;
4800 	}
4801 	else if (in->mif.do_popup)
4802 	{
4803 		if (in->mrPopup && in->mrPopup != mrMiPopup)
4804 		{
4805 			in->mif.do_popdown = True;
4806 			in->mrPopdown = in->mrPopup;
4807 			med->popdown_delay_10ms = 0;
4808 			*pdoes_popdown_submenu_overlap =
4809 				*pdoes_submenu_overlap;
4810 		}
4811 		else if (in->mrPopdown && in->mrPopdown != mrMiPopup)
4812 		{
4813 			in->mif.do_popdown = True;
4814 		}
4815 		else if (in->mrPopdown && in->mrPopdown == mrMiPopup)
4816 		{
4817 			in->mrPopup = in->mrPopdown;
4818 			*pdoes_submenu_overlap =
4819 				*pdoes_popdown_submenu_overlap;
4820 			in->mrPopdown = NULL;
4821 			in->mif.do_popup = False;
4822 			in->mif.do_popdown = False;
4823 		}
4824 	}
4825 
4826 	return MENU_MLOOP_RET_NORMAL;
4827 }
4828 
__mloop_do_popdown(MenuParameters * pmp,mloop_evh_input_t * in,Bool * pdoes_popdown_submenu_overlap)4829 static void __mloop_do_popdown(
4830 	MenuParameters *pmp, mloop_evh_input_t *in,
4831 	Bool *pdoes_popdown_submenu_overlap)
4832 {
4833 	if (in->mrPopdown)
4834 	{
4835 		pop_menu_down_and_repaint_parent(
4836 			&in->mrPopdown, pdoes_popdown_submenu_overlap, pmp);
4837 		in->mi_with_popup = NULL;
4838 		MR_SUBMENU_ITEM(pmp->menu) = NULL;
4839 		if (in->mrPopup == in->mrPopdown)
4840 		{
4841 			in->mrPopup = NULL;
4842 		}
4843 		in->mrPopdown = NULL;
4844 	}
4845 	in->mif.do_popdown = False;
4846 
4847 	return;
4848 }
4849 
__mloop_do_popup(MenuParameters * pmp,MenuReturn * pmret,mloop_evh_input_t * in,mloop_evh_data_t * med,MenuOptions * pops,MenuRoot * mrMiPopup,Bool * pdoes_submenu_overlap,Bool * pdoes_popdown_submenu_overlap)4850 static mloop_ret_code_t __mloop_do_popup(
4851 	MenuParameters *pmp, MenuReturn *pmret,
4852 	mloop_evh_input_t *in, mloop_evh_data_t *med,
4853 	MenuOptions *pops, MenuRoot *mrMiPopup, Bool *pdoes_submenu_overlap,
4854 	Bool *pdoes_popdown_submenu_overlap)
4855 {
4856 	if (!MR_IS_PAINTED(pmp->menu))
4857 	{
4858 		/* draw the parent menu if it is not already drawn */
4859 		FCheckWeedTypedWindowEvents(
4860 			dpy, MR_WINDOW(pmp->menu), Expose, NULL);
4861 		paint_menu(pmp->menu, NULL, (*pmp->pexc)->w.fw);
4862 	}
4863 	/* get pos hints for item's action */
4864 	get_popup_options(pmp, med->mi, pops);
4865 	if (med->mrMi == pmp->menu && mrMiPopup == NULL &&
4866 	    MI_IS_POPUP(med->mi) && MR_MISSING_SUBMENU_FUNC(pmp->menu))
4867 	{
4868 		/* We're on a submenu item, but the submenu does not exist.
4869 		 * The user defined missing_submenu_action may create it. */
4870 		Bool is_complex_function;
4871 		Bool is_busy_grabbed = False;
4872 		char *menu_name;
4873 		char *action;
4874 		char *missing_action = MR_MISSING_SUBMENU_FUNC(pmp->menu);
4875 
4876 		menu_name = PeekToken(
4877 			SkipNTokens(MI_ACTION(med->mi), 1), NULL);
4878 		if (!menu_name)
4879 		{
4880 			menu_name = "";
4881 		}
4882 		is_complex_function =
4883 			functions_is_complex_function(missing_action);
4884 		if (is_complex_function)
4885 		{
4886 			char *action_ptr;
4887 			action =
4888 				safemalloc(
4889 					strlen("Function") + 3 +
4890 					strlen(missing_action) * 2 + 3 +
4891 					strlen(menu_name) * 2 + 1);
4892 			strcpy(action, "Function ");
4893 			action_ptr = action + strlen(action);
4894 			action_ptr = QuoteString(action_ptr, missing_action);
4895 			*action_ptr++ = ' ';
4896 			action_ptr = QuoteString(action_ptr, menu_name);
4897 		}
4898 		else
4899 		{
4900 			action = MR_MISSING_SUBMENU_FUNC(pmp->menu);
4901 		}
4902 		if (Scr.BusyCursor & BUSY_DYNAMICMENU)
4903 		{
4904 			is_busy_grabbed = GrabEm(CRS_WAIT, GRAB_BUSYMENU);
4905 		}
4906 		/* Execute the action */
4907 		__menu_execute_function(pmp->pexc, action);
4908 		if (is_complex_function)
4909 		{
4910 			free(action);
4911 		}
4912 		if (is_busy_grabbed)
4913 		{
4914 			UngrabEm(GRAB_BUSYMENU);
4915 		}
4916 		/* Let's see if the menu exists now. */
4917 		mrMiPopup = mr_popup_for_mi(pmp->menu, med->mi);
4918 	} /* run MISSING_SUBMENU_FUNCTION */
4919 	in->mrPopup = mrMiPopup;
4920 	if (!in->mrPopup)
4921 	{
4922 		in->mif.do_menu = False;
4923 		pmret->flags.is_menu_posted = 0;
4924 	}
4925 	else
4926 	{
4927 		if (__mloop_make_popup(
4928 			    pmp, pmret, in, med, pops, pdoes_submenu_overlap)
4929 		    == MENU_MLOOP_RET_END)
4930 		{
4931 			return MENU_MLOOP_RET_END;
4932 		}
4933 	} /* else (in->mrPopup) */
4934 	if (in->mif.do_popdown)
4935 	{
4936 		if (in->mrPopdown)
4937 		{
4938 			if (in->mrPopdown != in->mrPopup)
4939 			{
4940 				if (in->mi_with_popup ==
4941 				    MR_PARENT_ITEM(in->mrPopdown))
4942 				{
4943 					in->mi_with_popup = NULL;
4944 				}
4945 				pop_menu_down_and_repaint_parent(
4946 					&in->mrPopdown,
4947 					pdoes_popdown_submenu_overlap, pmp);
4948 			}
4949 			in->mrPopdown = NULL;
4950 		}
4951 		in->mif.do_popdown = False;
4952 	}
4953 	if (in->mrPopup)
4954 	{
4955 		if (MR_PARENT_MENU(in->mrPopup) == pmp->menu)
4956 		{
4957 			med->mi = find_entry(
4958 				pmp, NULL, &med->mrMi, None, -1, -1);
4959 			if (med->mi && med->mrMi == in->mrPopup)
4960 			{
4961 				in->mif.do_menu = True;
4962 				in->mif.is_submenu_mapped = True;
4963 			}
4964 		}
4965 		else
4966 		{
4967 			/* This menu must be already mapped somewhere else, so
4968 			 * ignore it completely.  This can only happen if we
4969 			 * have reached the maximum allowed number of menu
4970 			 * copies. */
4971 			in->mif.do_menu = False;
4972 			in->mif.do_popdown = False;
4973 			in->mrPopup = NULL;
4974 		}
4975 	}
4976 
4977 	return MENU_MLOOP_RET_NORMAL;
4978 }
4979 
__mloop_do_menu(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,MenuOptions * pops,Bool * pdoes_submenu_overlap)4980 static mloop_ret_code_t __mloop_do_menu(
4981 	MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
4982 	mloop_evh_input_t *in, mloop_evh_data_t *med, MenuOptions *pops,
4983 	Bool *pdoes_submenu_overlap)
4984 {
4985 	MenuParameters mp;
4986 	XEvent e;
4987 
4988 	memset(&mp, 0, sizeof(mp));
4989 	mp.menu = in->mrPopup;
4990 	mp.pexc = pmp->pexc;
4991 	mp.parent_menu = pmp->menu;
4992 	mp.parent_item = med->mi;
4993 	mp.tear_off_root_menu_window = pmp->tear_off_root_menu_window;
4994 	MR_IS_TEAR_OFF_MENU(in->mrPopup) = 0;
4995 	mp.flags.has_default_action = False;
4996 	mp.flags.is_already_mapped = in->mif.is_submenu_mapped;
4997 	mp.flags.is_sticky = False;
4998 	mp.flags.is_submenu = True;
4999 	mp.flags.is_triggered_by_keypress = !!in->mif.do_popup_and_warp;
5000 	mp.pops = pops;
5001 	mp.ret_paction = pmp->ret_paction;
5002 	mp.screen_origin_x = pmp->screen_origin_x;
5003 	mp.screen_origin_y = pmp->screen_origin_y;
5004 	if (in->mif.do_propagate_event_into_submenu)
5005 	{
5006 		e = *(*pmp->pexc)->x.elast;
5007 		mp.event_propagate_to_submenu = &e;
5008 	}
5009 	else
5010 	{
5011 		mp.event_propagate_to_submenu = NULL;
5012 	}
5013 
5014 	/* recursively do the new menu we've moved into */
5015 	do_menu(&mp, pmret);
5016 
5017 	in->mif.do_propagate_event_into_submenu = False;
5018 	if (pmret->rc == MENU_PROPAGATE_EVENT)
5019 	{
5020 		in->mif.do_recycle_event = 1;
5021 		return MENU_MLOOP_RET_LOOP;
5022 	}
5023 	if (IS_MENU_RETURN(pmret->rc))
5024 	{
5025 		pdkp->timestamp = 0;
5026 		return MENU_MLOOP_RET_END;
5027 	}
5028 	if (MST_DO_UNMAP_SUBMENU_ON_POPDOWN(pmp->menu) &&
5029 	    pmret->flags.is_key_press)
5030 	{
5031 		in->miRemovedSubmenu = MR_PARENT_ITEM(in->mrPopup);
5032 		pop_menu_down_and_repaint_parent(
5033 			&in->mrPopup, pdoes_submenu_overlap, pmp);
5034 		in->mi_with_popup = NULL;
5035 		MR_SUBMENU_ITEM(pmp->menu) = NULL;
5036 		if (in->mrPopup == in->mrPopdown)
5037 		{
5038 			in->mrPopdown = NULL;
5039 		}
5040 		in->mrPopup = NULL;
5041 	}
5042 	if (pmret->rc == MENU_POPDOWN)
5043 	{
5044 		med->popup_delay_10ms = 0;
5045 		in->mif.do_force_reposition = True;
5046 	}
5047 
5048 	return MENU_MLOOP_RET_NORMAL;
5049 }
5050 
__mloop_handle_action_with_mi(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi,MenuOptions * pops,Bool * pdoes_submenu_overlap,Bool * pdoes_popdown_submenu_overlap)5051 static mloop_ret_code_t __mloop_handle_action_with_mi(
5052 	MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
5053 	mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
5054 	MenuOptions *pops, Bool *pdoes_submenu_overlap,
5055 	Bool *pdoes_popdown_submenu_overlap)
5056 {
5057 	MenuItem *tmi;
5058 	MenuRoot *tmrMi;
5059 	MenuRoot *mrMiPopup = NULL;
5060 
5061 	pmret->flags.do_unpost_submenu = 0;
5062 	/* we're on a menu item */
5063 	in->mif.is_off_menu_allowed = False;
5064 	if (med->mrMi == pmp->menu && med->mi != in->miRemovedSubmenu)
5065 	{
5066 		in->miRemovedSubmenu = NULL;
5067 	}
5068 	if (med->mrMi != pmp->menu && med->mrMi != in->mrPopup &&
5069 	    med->mrMi != in->mrPopdown)
5070 	{
5071 		/* we're on an item from a prior menu */
5072 		if (med->mrMi != MR_PARENT_MENU(pmp->menu))
5073 		{
5074 			/* the event is for a previous menu, just close
5075 			 * this one */
5076 			pmret->rc = MENU_PROPAGATE_EVENT;
5077 			pmret->target_menu = med->mrMi;
5078 		}
5079 		else
5080 		{
5081 			pmret->rc = MENU_POPDOWN;
5082 		}
5083 		pdkp->timestamp = 0;
5084 		return MENU_MLOOP_RET_END;
5085 	}
5086 	if (med->mi != MR_SELECTED_ITEM(pmp->menu) && med->mrMi == pmp->menu)
5087 	{
5088 		/* new item of the same menu */
5089 		__mloop_select_item(
5090 			pmp, in, med, *pdoes_submenu_overlap,
5091 			pdoes_popdown_submenu_overlap);
5092 	}
5093 	else if (med->mi != MR_SELECTED_ITEM(pmp->menu) && med->mrMi &&
5094 		 med->mrMi == in->mrPopdown)
5095 	{
5096 		/* we're on the popup menu of a different menu item of this
5097 		 * menu */
5098 		med->mi = MR_PARENT_ITEM(in->mrPopdown);
5099 		in->mrPopup = in->mrPopdown;
5100 		in->mrPopdown = NULL;
5101 		*pdoes_submenu_overlap = *pdoes_popdown_submenu_overlap;
5102 		select_menu_item(pmp->menu, med->mi, True, (*pmp->pexc)->w.fw);
5103 	}
5104 	mrMiPopup = mr_popup_for_mi(pmp->menu, med->mi);
5105 	/* check what has to be done with the item */
5106 	if (__mloop_get_mi_actions(
5107 		    pmp, pmret, pdkp, in, med, msi, mrMiPopup,
5108 		    pdoes_submenu_overlap, pdoes_popdown_submenu_overlap) ==
5109 	    MENU_MLOOP_RET_END)
5110 	{
5111 		return MENU_MLOOP_RET_END;
5112 	}
5113 	/* do what needs to be done */
5114 	if (in->mif.do_popdown && !in->mif.do_popup)
5115 	{
5116 		/* popdown previous popup */
5117 		__mloop_do_popdown(pmp, in, pdoes_popdown_submenu_overlap);
5118 	}
5119 	if (in->mif.do_popup)
5120 	{
5121 		if (__mloop_do_popup(
5122 			    pmp, pmret, in, med, pops, mrMiPopup,
5123 			    pdoes_submenu_overlap,
5124 			    pdoes_popdown_submenu_overlap) ==
5125 		    MENU_MLOOP_RET_END)
5126 		{
5127 			return MENU_MLOOP_RET_END;
5128 		}
5129 	}
5130 	/* remember the 'posted' menu */
5131 	if (pmret->flags.is_menu_posted && in->mrPopup != NULL &&
5132 	    pmret->target_menu == NULL)
5133 	{
5134 		pmret->target_menu = in->mrPopup;
5135 	}
5136 	else if (pmret->flags.is_menu_posted && in->mrPopup == NULL)
5137 	{
5138 		pmret->flags.is_menu_posted = 0;
5139 	}
5140 	if (in->mif.do_menu)
5141 	{
5142 		mloop_ret_code_t rc;
5143 
5144 		rc = __mloop_do_menu(
5145 			pmp, pmret, pdkp, in, med, pops,
5146 			pdoes_submenu_overlap);
5147 		if (rc != MENU_MLOOP_RET_NORMAL)
5148 		{
5149 			return rc;
5150 		}
5151 	}
5152 	/* Now check whether we can animate the current popup menu back to the
5153 	 * original place to unobscure the current menu;  this happens only
5154 	 * when using animation */
5155 	if (in->mrPopup && MR_XANIMATION(in->mrPopup) &&
5156 	    (tmi = find_entry(pmp, NULL, &tmrMi, None, -1, -1))  &&
5157 	    (tmi == MR_SELECTED_ITEM(pmp->menu) || tmrMi != pmp->menu))
5158 	{
5159 		animated_move_back(in->mrPopup, False, (*pmp->pexc)->w.fw);
5160 	}
5161 	/* now check whether we should animate the current real menu
5162 	 * over to the right to unobscure the prior menu; only a very
5163 	 * limited case where this might be helpful and not too disruptive */
5164 	/* but this cause terrible back-and-forth under certain circonstance,
5165 	 * I think we should disable this ... 2002-09-17 olicha */
5166 	if (in->mrPopup == NULL && pmp->parent_menu != NULL &&
5167 	    MR_XANIMATION(pmp->menu) != 0 &&
5168 	    pointer_in_passive_item_area(med->x_offset, med->mrMi))
5169 	{
5170 		/* we have to see if we need menu to be moved */
5171 		animated_move_back(pmp->menu, True, (*pmp->pexc)->w.fw);
5172 		if (in->mrPopdown)
5173 		{
5174 			if (in->mrPopdown != in->mrPopup)
5175 			{
5176 				pop_menu_down_and_repaint_parent(
5177 					&in->mrPopdown,
5178 					pdoes_popdown_submenu_overlap, pmp);
5179 				in->mi_with_popup = NULL;
5180 			}
5181 			in->mrPopdown = NULL;
5182 		}
5183 		in->mif.do_popdown = False;
5184 	}
5185 
5186 	return MENU_MLOOP_RET_NORMAL;
5187 }
5188 
__mloop_handle_action_without_mi(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi,MenuOptions * pops,Bool * pdoes_submenu_overlap,Bool * pdoes_popdown_submenu_overlap)5189 static mloop_ret_code_t __mloop_handle_action_without_mi(
5190 	MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
5191 	mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
5192 	MenuOptions *pops, Bool *pdoes_submenu_overlap,
5193 	Bool *pdoes_popdown_submenu_overlap)
5194 {
5195 	pmret->flags.do_unpost_submenu = 0;
5196 	/* moved off menu, deselect selected item... */
5197 	if (!MR_SELECTED_ITEM(pmp->menu) ||
5198 	    in->mif.is_off_menu_allowed == True || pmret->flags.is_menu_posted)
5199 	{
5200 		/* nothing to do */
5201 		return MENU_MLOOP_RET_NORMAL;
5202 	}
5203 	if (in->mrPopup)
5204 	{
5205 		int x, y, mx, my;
5206 		int mw, mh;
5207 
5208 		if (FQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
5209 				  &x, &y, &JunkX, &JunkY, &JunkMask) == False)
5210 		{
5211 			/* pointer is on a different screen */
5212 			x = 0;
5213 			y = 0;
5214 		}
5215 		if (menu_get_geometry(
5216 			    pmp->menu, &JunkRoot, &mx, &my, &mw, &mh, &JunkBW,
5217 			    &JunkDepth) &&
5218 		    ((!MR_IS_LEFT(in->mrPopup)  && x < mx) ||
5219 		     (!MR_IS_RIGHT(in->mrPopup) && x > mx + mw) ||
5220 		     (!MR_IS_UP(in->mrPopup)    && y < my) ||
5221 		     (!MR_IS_DOWN(in->mrPopup)  && y > my + mh)))
5222 		{
5223 			select_menu_item(
5224 				pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
5225 				(*pmp->pexc)->w.fw);
5226 			pop_menu_down_and_repaint_parent(
5227 				&in->mrPopup, pdoes_submenu_overlap, pmp);
5228 			in->mi_with_popup = NULL;
5229 			MR_SUBMENU_ITEM(pmp->menu) = NULL;
5230 			if (in->mrPopup == in->mrPopdown)
5231 			{
5232 				in->mrPopdown = NULL;
5233 			}
5234 			in->mrPopup = NULL;
5235 		}
5236 		else if (x < mx || x >= mx + mw || y < my || y >= my + mh)
5237 		{
5238 			/* pointer is outside the menu but do not pop down */
5239 			in->mif.is_off_menu_allowed = True;
5240 		}
5241 		else
5242 		{
5243 			/* Pointer is still in the menu. Postpone the decision
5244 			 * if we have to pop down. */
5245 		}
5246 	} /* if (in->mrPopup) */
5247 	else
5248 	{
5249 		select_menu_item(
5250 			pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
5251 			(*pmp->pexc)->w.fw);
5252 	}
5253 
5254 	return MENU_MLOOP_RET_NORMAL;
5255 }
5256 
__mloop_exit_warp_back(MenuParameters * pmp)5257 static void __mloop_exit_warp_back(MenuParameters *pmp)
5258 {
5259 	MenuRoot *tmrMi;
5260 
5261 	if (pmp->parent_menu && MR_SELECTED_ITEM(pmp->parent_menu))
5262 	{
5263 		warp_pointer_to_item(
5264 			pmp->parent_menu, MR_SELECTED_ITEM(pmp->parent_menu),
5265 			False);
5266 		(void)find_entry(pmp, NULL, &tmrMi, None, -1, -1);
5267 		if (pmp->parent_menu != tmrMi && MR_XANIMATION(pmp->menu) == 0)
5268 		{
5269 			/* Warping didn't take us to the correct menu, i.e. the
5270 			 * spot we want to warp to is obscured. So raise our
5271 			 * window first. */
5272 			XRaiseWindow(dpy, MR_WINDOW(pmp->parent_menu));
5273 		}
5274 	}
5275 
5276 	return;
5277 }
5278 
__mloop_exit_select_in_place(MenuParameters * pmp,mloop_evh_data_t * med,MenuOptions * pops)5279 static void __mloop_exit_select_in_place(
5280 	MenuParameters *pmp, mloop_evh_data_t *med, MenuOptions *pops)
5281 {
5282 	MenuRoot *submenu;
5283 	XWindowAttributes win_attribs;
5284 
5285 	last_saved_pos_hints.flags.is_last_menu_pos_hints_valid = True;
5286 	last_saved_pos_hints.pos_hints.x_offset = 0;
5287 	last_saved_pos_hints.pos_hints.x_factor = 0;
5288 	last_saved_pos_hints.pos_hints.context_x_factor = 0;
5289 	last_saved_pos_hints.pos_hints.y_factor = 0;
5290 	last_saved_pos_hints.pos_hints.is_relative = False;
5291 	last_saved_pos_hints.pos_hints.is_menu_relative = False;
5292 	submenu = mr_popup_for_mi(pmp->menu, med->mi);
5293 	if (submenu && MR_WINDOW(submenu) != None &&
5294 	    XGetWindowAttributes(dpy, MR_WINDOW(submenu), &win_attribs) &&
5295 	    win_attribs.map_state == IsViewable &&
5296 	    menu_get_geometry(
5297 		    submenu, &JunkRoot, &last_saved_pos_hints.pos_hints.x,
5298 		    &last_saved_pos_hints.pos_hints.y, &JunkWidth, &JunkHeight,
5299 		    &JunkBW, &JunkDepth))
5300 	{
5301 		/* The submenu is mapped, just take its position and put it in
5302 		 * the position hints. */
5303 	}
5304 	else if (pops->flags.has_poshints)
5305 	{
5306 		last_saved_pos_hints.pos_hints = pops->pos_hints;
5307 	}
5308 	else
5309 	{
5310 		Bool dummy;
5311 
5312 		get_prefered_popup_position(
5313 			pmp->menu, submenu, &last_saved_pos_hints. pos_hints.x,
5314 			&last_saved_pos_hints. pos_hints.y, &dummy);
5315 	}
5316 	if (pops->flags.do_warp_on_select)
5317 	{
5318 		last_saved_pos_hints.flags.do_warp_title = 1;
5319 	}
5320 
5321 	return;
5322 }
5323 
__mloop_exit_selected(MenuParameters * pmp,MenuReturn * pmret,mloop_evh_data_t * med,MenuOptions * pops)5324 static void __mloop_exit_selected(
5325 	MenuParameters *pmp, MenuReturn *pmret, mloop_evh_data_t *med,
5326 	MenuOptions *pops)
5327 {
5328 	/* save action to execute so that the menu may be destroyed now */
5329 	if (pmp->ret_paction)
5330 	{
5331 		if (pmret->rc == MENU_EXEC_CMD)
5332 		{
5333 			*pmp->ret_paction = safestrdup(*pmp->ret_paction);
5334 		}
5335 		else
5336 		{
5337 			if (*pmp->ret_paction)
5338 			{
5339 				free(*pmp->ret_paction);
5340 			}
5341 			*pmp->ret_paction = (med->mi) ?
5342 				safestrdup(MI_ACTION(med->mi)) : NULL;
5343 		}
5344 	}
5345 	if (
5346 		pmp->ret_paction && *pmp->ret_paction &&
5347 		med->mi && MI_IS_POPUP(med->mi))
5348 	{
5349 		get_popup_options(pmp, med->mi, pops);
5350 		if (pops->flags.do_select_in_place)
5351 		{
5352 			__mloop_exit_select_in_place(pmp, med, pops);
5353 		}
5354 		else
5355 		{
5356 			last_saved_pos_hints.flags.do_ignore_pos_hints = True;
5357 		} /* pops->flags.do_select_in_place */
5358 		last_saved_pos_hints.pos_hints.screen_origin_x =
5359 			pmp->screen_origin_x;
5360 		last_saved_pos_hints.pos_hints.screen_origin_y =
5361 			pmp->screen_origin_y;
5362 	}
5363 
5364 	return;
5365 }
5366 
__mloop_exit(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi,MenuOptions * pops)5367 static void __mloop_exit(
5368 	MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
5369 	mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
5370 	MenuOptions *pops)
5371 {
5372 	Bool no = False;
5373 	Bool do_deselect = False;
5374 
5375 	if (in->mrPopdown)
5376 	{
5377 		pop_menu_down_and_repaint_parent(&in->mrPopdown, &no, pmp);
5378 		MR_SUBMENU_ITEM(pmp->menu) = NULL;
5379 	}
5380 	if (
5381 		pmret->rc == MENU_SELECTED && is_double_click(
5382 			msi->t0, med->mi, pmp, pmret, pdkp,
5383 			in->mif.has_mouse_moved))
5384 	{
5385 		pmret->rc = MENU_DOUBLE_CLICKED;
5386 	}
5387 	if (
5388 		pmret->rc == MENU_SELECTED && med->mi &&
5389 		MI_FUNC_TYPE(med->mi) == F_TEARMENUOFF)
5390 	{
5391 		pmret->rc = (MR_IS_TEAR_OFF_MENU(pmp->menu)) ?
5392 			MENU_KILL_TEAR_OFF_MENU : MENU_TEAR_OFF;
5393 	}
5394 	switch (pmret->rc)
5395 	{
5396 	case MENU_POPDOWN:
5397 	case MENU_PROPAGATE_EVENT:
5398 	case MENU_DOUBLE_CLICKED:
5399 		do_deselect = True;
5400 		/* Allow popdown to warp back pointer to main menu with mouse
5401 		   button control. (MoveLeft/MoveRight on a mouse binding) */
5402 		if (((pmret->rc == MENU_POPDOWN && in->mif.is_button_release)
5403 		     || in->mif.is_key_press) &&
5404 		    pmret->rc != MENU_DOUBLE_CLICKED)
5405 		{
5406 			if (!pmp->flags.is_submenu)
5407 			{
5408 				/* abort a root menu rather than pop it down */
5409 				pmret->rc = MENU_ABORTED;
5410 			}
5411 			__mloop_exit_warp_back(pmp);
5412 		}
5413 		break;
5414 	case MENU_ABORTED:
5415 	case MENU_TEAR_OFF:
5416 	case MENU_KILL_TEAR_OFF_MENU:
5417 		do_deselect = True;
5418 		break;
5419 	case MENU_SELECTED:
5420 	case MENU_EXEC_CMD:
5421 		__mloop_exit_selected(pmp, pmret, med, pops);
5422 		pmret->rc = MENU_DONE;
5423 		break;
5424 	default:
5425 		break;
5426 	}
5427 	if (do_deselect && MR_SELECTED_ITEM(pmp->menu))
5428 	{
5429 		select_menu_item(
5430 			pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
5431 			(*pmp->pexc)->w.fw);
5432 	}
5433 	if (pmret->rc == MENU_SUBMENU_TORN_OFF)
5434 	{
5435 		in->mrPopup = NULL;
5436 		MR_SUBMENU_ITEM(pmp->menu) = NULL;
5437 	}
5438 	if (in->mrPopup)
5439 	{
5440 		pop_menu_down_and_repaint_parent(&in->mrPopup, &no, pmp);
5441 		MR_SUBMENU_ITEM(pmp->menu) = NULL;
5442 	}
5443 	pmret->flags.is_key_press = in->mif.is_key_press;
5444 
5445 	return;
5446 }
5447 
__menu_loop(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp)5448 static void __menu_loop(
5449 	MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp)
5450 {
5451 	mloop_evh_input_t mei;
5452 	mloop_ret_code_t mloop_ret;
5453 	mloop_evh_data_t med;
5454 	mloop_static_info_t msi;
5455 	MenuOptions mops;
5456 	Bool is_finished;
5457 	Bool does_submenu_overlap = False;
5458 	Bool does_popdown_submenu_overlap = False;
5459 
5460 	__mloop_init(pmp, pmret, &mei, &med, &msi, &mops);
5461 	for (is_finished = False; !is_finished; )
5462 	{
5463 		mloop_ret = __mloop_get_event(pmp, pmret, &mei, &med, &msi);
5464 		switch (mloop_ret)
5465 		{
5466 		case MENU_MLOOP_RET_END:
5467 			is_finished = True;
5468 		case MENU_MLOOP_RET_LOOP:
5469 			continue;
5470 		default:
5471 			break;
5472 		}
5473 		mloop_ret = __mloop_handle_event(
5474 			pmp, pmret, pdkp, &mei, &med, &msi);
5475 		switch (mloop_ret)
5476 		{
5477 		case MENU_MLOOP_RET_END:
5478 			is_finished = True;
5479 			continue;
5480 		case MENU_MLOOP_RET_LOOP:
5481 			continue;
5482 		default:
5483 			break;
5484 		}
5485 		/* Now handle new menu items, whether it is from a keypress or
5486 		 * a pointer motion event. */
5487 		if (med.mi != NULL)
5488 		{
5489 			mloop_ret = __mloop_handle_action_with_mi(
5490 				pmp, pmret, pdkp, &mei, &med, &msi, &mops,
5491 				&does_submenu_overlap,
5492 				&does_popdown_submenu_overlap);
5493 		}
5494 		else
5495 		{
5496 			mloop_ret = __mloop_handle_action_without_mi(
5497 				pmp, pmret, pdkp, &mei, &med, &msi, &mops,
5498 				&does_submenu_overlap,
5499 				&does_popdown_submenu_overlap);
5500 		}
5501 		if (mloop_ret == MENU_MLOOP_RET_END)
5502 		{
5503 			is_finished = True;
5504 		}
5505 		XFlush(dpy);
5506 	}
5507 	__mloop_exit(pmp, pmret, pdkp, &mei, &med, &msi, &mops);
5508 
5509 	return;
5510 }
5511 
5512 /*
5513  * Functions dealing with tear off menus
5514  */
5515 
menu_strip_tear_off_title(MenuRoot * mr)5516 static char *menu_strip_tear_off_title(MenuRoot *mr)
5517 {
5518 	MenuItem *mi;
5519 	int i;
5520 	int len;
5521 	char *name;
5522 
5523 	for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
5524 	{
5525 		if (MI_IS_TITLE(mi))
5526 		{
5527 			break;
5528 		}
5529 		else if (!MI_IS_SEPARATOR(mi) && !MI_IS_TEAR_OFF_BAR(mi))
5530 		{
5531 			/* a normal item, no title found */
5532 			return NULL;
5533 		}
5534 		/* skip separators and tear off bars */
5535 	}
5536 	if (mi == NULL || !MI_HAS_TEXT(mi) || MI_NEXT_ITEM(mi) == NULL)
5537 	{
5538 		return NULL;
5539 	}
5540 	/* extract the window title from the labels */
5541 	for (i = 0, len = 0; i < MAX_MENU_ITEM_LABELS; i++)
5542 	{
5543 		if (MI_LABEL(mi)[i] != 0)
5544 		{
5545 			len += strlen(MI_LABEL(mi)[i]) + 1;
5546 		}
5547 	}
5548 	if (len == 0)
5549 	{
5550 		return NULL;
5551 	}
5552 	name = safemalloc(len + 1);
5553 	*name = 0;
5554 	for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
5555 	{
5556 		if (MI_LABEL(mi)[i] != 0)
5557 		{
5558 			strcat(name, MI_LABEL(mi)[i]);
5559 			strcat(name, " ");
5560 		}
5561 	}
5562 	/* strip the last space */
5563 	name[len - 1] = 0;
5564 	/* unlink and destroy the item */
5565 	unlink_item_from_menu(mr, mi);
5566 	menuitem_free(mi);
5567 
5568 	return name;
5569 }
5570 
_pred_menu_window_weed_events(Display * display,XEvent * event,XPointer arg)5571 static int _pred_menu_window_weed_events(
5572 	Display *display, XEvent *event, XPointer arg)
5573 {
5574 	switch (event->type)
5575 	{
5576 	case CirculateNotify:
5577 	case ConfigureNotify:
5578 	case CreateNotify:
5579 	case DestroyNotify:
5580 	case GravityNotify:
5581 	case MapNotify:
5582 	case ReparentNotify:
5583 	case UnmapNotify:
5584 		/* events in SubstructureNotifyMask */
5585 		return 1;
5586 	default:
5587 		return 0;
5588 	}
5589 }
5590 
menu_tear_off(MenuRoot * mr_to_copy)5591 static void menu_tear_off(MenuRoot *mr_to_copy)
5592 {
5593 	MenuRoot *mr;
5594 	MenuStyle *ms;
5595 	XEvent ev;
5596 	XSizeHints menusizehints;
5597 	XClassHint menuclasshints;
5598 	XTextProperty menunametext;
5599 	XWMHints menuwmhints;
5600 	char *list[] ={ NULL, NULL };
5601 	char *t;
5602 	char *name = NULL;
5603 	Atom protocols[1];
5604 	int x = 0;
5605 	int y = 0;
5606 	unsigned int add_mask = 0;
5607 	initial_window_options_t win_opts;
5608 	evh_args_t ea;
5609 	exec_context_changes_t ecc;
5610 	char *buffer;
5611 	char *action;
5612 	cond_rc_t *cond_rc = NULL;
5613 	const exec_context_t *exc = NULL;
5614 
5615 	/* keep the menu open */
5616 	if (MR_WINDOW(mr_to_copy) != None)
5617 	{
5618 		XSync(dpy, 0);
5619 		FWeedIfWindowEvents(
5620 			dpy, MR_WINDOW(mr_to_copy),
5621 			_pred_menu_window_weed_events, NULL);
5622 	}
5623 	mr = clone_menu(mr_to_copy);
5624 	/* also dump the menu style */
5625 	buffer = (char *)safemalloc(23);
5626 	sprintf(buffer,"%lu",(unsigned long)mr);
5627 	action = buffer;
5628 	ms = menustyle_parse_style(F_PASS_ARGS);
5629 	if (!ms)
5630 	{
5631 		/* this must never happen */
5632 		fvwm_msg(
5633 			ERR, "menu_tear_off",
5634 			"impossible to create %s menu style", buffer);
5635 		free(buffer);
5636 		DestroyMenu(mr, False, False);
5637 		return;
5638 	}
5639 	free(buffer);
5640 	menustyle_copy(MR_STYLE(mr_to_copy),ms);
5641 	MR_STYLE(mr) = ms;
5642 	MST_USAGE_COUNT(mr) = 0;
5643 	name = menu_strip_tear_off_title(mr);
5644 	/* create the menu window and size the menu */
5645 	make_menu(mr, True);
5646 	/* set position */
5647 	if (menu_get_geometry(
5648 		    mr_to_copy, &JunkRoot, &x, &y, &JunkWidth, &JunkHeight,
5649 		    &JunkBW, &JunkDepth))
5650 	{
5651 		add_mask = PPosition;
5652 		XMoveWindow(dpy, MR_WINDOW(mr), x, y);
5653 	}
5654 	else
5655 	{
5656 		add_mask = 0;
5657 	}
5658 	/* focus policy */
5659 	menuwmhints.flags = InputHint;
5660 	menuwmhints.input = False;
5661 	/* size hints */
5662 	menusizehints.flags = PBaseSize | PMinSize | PMaxSize | add_mask;
5663 	menusizehints.base_width  = 0;
5664 	menusizehints.base_height = 0;
5665 	menusizehints.min_width = MR_WIDTH(mr);
5666 	menusizehints.min_height = MR_HEIGHT(mr);
5667 	menusizehints.max_width = MR_WIDTH(mr);
5668 	menusizehints.max_height = MR_HEIGHT(mr);
5669 	/* class, resource and names */
5670 	menuclasshints.res_name =
5671 		(name != NULL) ? name : safestrdup(MR_NAME(mr));
5672 	menuclasshints.res_class = safestrdup("fvwm_menu");
5673 	for (t = menuclasshints.res_name; t != NULL && *t != 0; t++)
5674 	{
5675 		/* replace tabs in the title with spaces */
5676 		if (*t == '\t')
5677 		{
5678 			*t = ' ';
5679 		}
5680 	}
5681 	list[0] = menuclasshints.res_name;
5682 	menunametext.value = NULL;
5683 	XStringListToTextProperty(list, 1, &menunametext);
5684 	/* set all properties and hints */
5685 	XSetWMProperties(
5686 		dpy, MR_WINDOW(mr), &menunametext, &menunametext, NULL, 0,
5687 		&menusizehints, NULL, &menuclasshints);
5688 	XSetWMHints(dpy, MR_WINDOW(mr), &menuwmhints);
5689 	protocols[0] = _XA_WM_DELETE_WINDOW;
5690 	XSetWMProtocols(dpy, MR_WINDOW(mr), &(protocols[0]), 1);
5691 
5692 	/* free memory */
5693 	if (menunametext.value != NULL)
5694 	{
5695 		XFree(menunametext.value);
5696 	}
5697 	free(menuclasshints.res_class);
5698 	free(menuclasshints.res_name);
5699 
5700 	/* manage the window */
5701 	memset(&win_opts, 0, sizeof(win_opts));
5702 	win_opts.flags.is_menu = True;
5703 	ev.type = MapRequest;
5704 	ev.xmaprequest.send_event = True;
5705 	ev.xmaprequest.display = dpy;
5706 	ev.xmaprequest.parent = Scr.Root;
5707 	ev.xmaprequest.window = MR_WINDOW(mr);
5708 	fev_fake_event(&ev);
5709 	ecc.type = EXCT_NULL;
5710 	ecc.x.etrigger = &ev;
5711 	ecc.w.w = MR_WINDOW(mr);
5712 	ecc.w.wcontext = C_ROOT;
5713 	ea.exc = exc_create_context(
5714 		&ecc, ECC_TYPE | ECC_ETRIGGER | ECC_W | ECC_WCONTEXT);
5715 	HandleMapRequestKeepRaised(&ea, None, NULL, &win_opts);
5716 	exc_destroy_context(ea.exc);
5717 
5718 	return;
5719 }
5720 
do_menu_close_tear_off_menu(MenuRoot * mr,MenuParameters mp)5721 static void do_menu_close_tear_off_menu(MenuRoot *mr, MenuParameters mp)
5722 {
5723 	pop_menu_down(&mr, &mp);
5724 	menustyle_free(MR_STYLE(mr));
5725 	DestroyMenu(mr, False, False);
5726 }
5727 
5728 /* ---------------------------- interface functions ------------------------- */
5729 
menus_init(void)5730 void menus_init(void)
5731 {
5732 	memset((&Menus), 0, sizeof(MenuInfo));
5733 
5734 	return;
5735 }
5736 
5737 /* menus_find_menu expects a token as the input. Make sure you have used
5738  * GetNextToken before passing a menu name to remove quotes (if necessary) */
menus_find_menu(char * name)5739 MenuRoot *menus_find_menu(char *name)
5740 {
5741 	MenuRoot *mr;
5742 
5743 	if (name == NULL)
5744 	{
5745 		return NULL;
5746 	}
5747 	for (mr = Menus.all; mr != NULL; mr = MR_NEXT_MENU(mr))
5748 	{
5749 		if (!MR_IS_DESTROYED(mr) && mr == MR_ORIGINAL_MENU(mr) &&
5750 		    MR_NAME(mr) != NULL && StrEquals(name, MR_NAME(mr)))
5751 		{
5752 			break;
5753 		}
5754 	}
5755 
5756 	return mr;
5757 }
5758 
menus_remove_style_from_menus(MenuStyle * ms)5759 void menus_remove_style_from_menus(MenuStyle *ms)
5760 {
5761 	MenuRoot *mr;
5762 
5763 	for (mr = Menus.all; mr; mr = MR_NEXT_MENU(mr))
5764 	{
5765 		if (MR_STYLE(mr) == ms)
5766 		{
5767 			MR_STYLE(mr) = menustyle_get_default_style();
5768 			MR_IS_UPDATED(mr) = 1;
5769 		}
5770 	}
5771 
5772 	return;
5773 }
5774 
5775 /*
5776  * Functions dealing with tear off menus
5777  */
5778 
menu_enter_tear_off_menu(const exec_context_t * exc)5779 void menu_enter_tear_off_menu(const exec_context_t *exc)
5780 {
5781 	MenuRoot *mr;
5782 	char *ret_action = NULL;
5783 	MenuOptions mops;
5784 	MenuParameters mp;
5785 	MenuReturn mret;
5786 	const exec_context_t *exc2;
5787 	exec_context_changes_t ecc;
5788 
5789 	if (XFindContext(
5790 		    dpy, FW_W(exc->w.fw), MenuContext,
5791 		    (caddr_t *)&mr) == XCNOENT)
5792 	{
5793 		return;
5794 	}
5795 	ecc.w.fw = NULL;
5796 	ecc.w.w = None;
5797 	ecc.w.wcontext = C_ROOT;
5798 	exc2 = exc_clone_context(exc, &ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
5799 	memset(&mops, 0, sizeof(mops));
5800 	memset(&mret, 0, sizeof(MenuReturn));
5801 	memset(&mp, 0, sizeof(mp));
5802 	mp.menu = mr;
5803 	mp.pexc = &exc2;
5804 	mp.tear_off_root_menu_window = exc->w.fw;
5805 	MR_IS_TEAR_OFF_MENU(mr) = 1;
5806 	mp.flags.has_default_action = 0;
5807 	mp.flags.is_already_mapped = True;
5808 	mp.flags.is_sticky = False;
5809 	mp.flags.is_submenu = False;
5810 	mp.pops = &mops;
5811 	mp.ret_paction = &ret_action;
5812 	do_menu(&mp, &mret);
5813 	exc_destroy_context(exc2);
5814 
5815 	return;
5816 }
5817 
menu_close_tear_off_menu(FvwmWindow * fw)5818 void menu_close_tear_off_menu(FvwmWindow *fw)
5819 {
5820 	MenuRoot *mr;
5821 	MenuParameters mp;
5822 	const exec_context_t *exc;
5823 	exec_context_changes_t ecc;
5824 
5825 	if (XFindContext(
5826 		    dpy, FW_W(fw), MenuContext, (caddr_t *)&mr) == XCNOENT)
5827 	{
5828 		return;
5829 	}
5830 	memset(&mp, 0, sizeof(mp));
5831 	mp.menu = mr;
5832 	ecc.w.fw = NULL;
5833 	ecc.w.w = None;
5834 	ecc.w.wcontext = C_ROOT;
5835 	exc = exc_create_context(&ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
5836 	mp.pexc = &exc;
5837 	do_menu_close_tear_off_menu(mr, mp);
5838 	exc_destroy_context(exc);
5839 
5840 	return;
5841 }
5842 
menu_redraw_transparent_tear_off_menu(FvwmWindow * fw,Bool pr_only)5843 Bool menu_redraw_transparent_tear_off_menu(FvwmWindow *fw, Bool pr_only)
5844 {
5845 	MenuRoot *mr;
5846 	MenuStyle *ms = NULL;
5847 	int cs;
5848 
5849 	if (!(IS_TEAR_OFF_MENU(fw) &&
5850 	      XFindContext(dpy, FW_W(fw), MenuContext,(caddr_t *)&mr) !=
5851 	      XCNOENT &&
5852 	      (ms = MR_STYLE(mr)) &&
5853 	      ST_HAS_MENU_CSET(ms)))
5854 	{
5855 		return False;
5856 	}
5857 
5858 	cs = ST_CSET_MENU(ms);
5859 
5860 	if (!CSET_IS_TRANSPARENT(cs) ||
5861 	    (pr_only && !CSET_IS_TRANSPARENT_PR(cs)))
5862 	{
5863 		return False;
5864 	}
5865 
5866 	return UpdateBackgroundTransparency(
5867 		dpy, MR_WINDOW(mr), MR_WIDTH(mr),
5868 		MR_HEIGHT(mr),
5869 		&Colorset[ST_CSET_MENU(ms)],
5870 		Pdepth,
5871 		FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
5872 		True);
5873 }
5874 
5875 /*
5876  *
5877  * Initiates a menu pop-up
5878  *
5879  * fStick = True  = sticky menu, stays up on initial button release.
5880  * fStick = False = transient menu, drops on initial release.
5881  *
5882  * eventp = 0: menu opened by mouse, do not warp
5883  * eventp > 1: root menu opened by keypress with 'Menu', warp pointer and
5884  *             allow 'double-keypress'.
5885  * eventp = 1: menu opened by keypress, warp but forbid 'double-keypress'
5886  *             this should always be used except in the call in 'staysup_func'
5887  *             in builtin.c
5888  *
5889  * Returns one of MENU_NOP, MENU_ERROR, MENU_ABORTED, MENU_DONE
5890  * do_menu() may destroy the *pmp->pexec member and replace it with a new
5891  * copy.  Be sure not to rely on the original structure being kept intact
5892  * when calling do_menu().
5893  */
do_menu(MenuParameters * pmp,MenuReturn * pmret)5894 void do_menu(MenuParameters *pmp, MenuReturn *pmret)
5895 {
5896 	int x;
5897 	int y;
5898 	Bool fWasAlreadyPopped = False;
5899 	Bool key_press;
5900 	Bool is_pointer_grabbed = False;
5901 	Bool is_pointer_ungrabbed = False;
5902 	Bool do_menu_interaction;
5903 	Bool do_check_pop_down;
5904 	Bool do_warp;
5905 	Time t0 = fev_get_evtime();
5906 	XEvent tmpevent;
5907 	double_keypress dkp;
5908 	/* don't save these ones, we want them to work even within recursive
5909 	 * menus popped up by dynamic actions. */
5910 	static int indirect_depth = 0;
5911 	static int x_start;
5912 	static int y_start;
5913 	int scr_x, scr_y;
5914 	int scr_w, scr_h;
5915 
5916 	pmret->rc = MENU_NOP;
5917 	if (pmp->flags.is_sticky && !pmp->flags.is_submenu)
5918 	{
5919 		FCheckTypedEvent(dpy, ButtonPressMask, &tmpevent);
5920 	}
5921 	if (pmp->menu == NULL)
5922 	{
5923 		pmret->rc = MENU_ERROR;
5924 		return;
5925 	}
5926 	key_press = pmp->flags.is_triggered_by_keypress;
5927 
5928 	/* Try to pick a root-relative optimal x,y to
5929 	 * put the mouse right on the title w/o warping */
5930 	if (FQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
5931 			  &x, &y, &JunkX, &JunkY, &JunkMask) == False)
5932 	{
5933 		/* pointer is on a different screen */
5934 		x = 0;
5935 		y = 0;
5936 	}
5937 	/* Save these - we want to warp back here if this is a top level
5938 	 * menu brought up by a keystroke */
5939 	if (!pmp->flags.is_submenu)
5940 	{
5941 		pmp->flags.is_invoked_by_key_press = key_press;
5942 		pmp->flags.is_first_root_menu = !indirect_depth;
5943 	}
5944 	else
5945 	{
5946 		pmp->flags.is_first_root_menu = 0;
5947 	}
5948 	if (!pmp->flags.is_submenu && indirect_depth == 0)
5949 	{
5950 		if (key_press)
5951 		{
5952 			x_start = x;
5953 			y_start = y;
5954 		}
5955 		else
5956 		{
5957 			x_start = -1;
5958 			y_start = -1;
5959 		}
5960 		pmp->screen_origin_x = pmp->pops->pos_hints.screen_origin_x;
5961 		pmp->screen_origin_y = pmp->pops->pos_hints.screen_origin_y;
5962 	}
5963 	if (pmp->pops->flags.do_warp_title)
5964 	{
5965 		do_warp = True;
5966 	}
5967 	else if (pmp->pops->flags.do_not_warp)
5968 	{
5969 		do_warp = False;
5970 	}
5971 	else if (key_press)
5972 	{
5973 		do_warp = True;
5974 	}
5975 	else
5976 	{
5977 		do_warp = False;
5978 	}
5979 	/* Figure out where we should popup, if possible */
5980 	if (!pmp->flags.is_already_mapped)
5981 	{
5982 		Bool prefer_left_submenus = False;
5983 		/* Make sure we are using the latest style and menu layout. */
5984 		update_menu(pmp->menu, pmp);
5985 
5986 		if (pmp->flags.is_submenu)
5987 		{
5988 			/* this is a submenu popup */
5989 			get_prefered_popup_position(
5990 				pmp->parent_menu, pmp->menu, &x, &y,
5991 				&prefer_left_submenus);
5992 		}
5993 		else
5994 		{
5995 			fscreen_scr_arg fscr;
5996 
5997 			/* we're a top level menu */
5998 			if (!GrabEm(CRS_MENU, GRAB_MENU))
5999 			{
6000 				/* GrabEm specifies the cursor to use */
6001 				XBell(dpy, 0);
6002 				pmret->rc = MENU_ABORTED;
6003 				return;
6004 			}
6005 			is_pointer_grabbed = True;
6006 			/* Make the menu appear under the pointer rather than
6007 			 * warping */
6008 			fscr.xypos.x = x;
6009 			fscr.xypos.y = y;
6010 			FScreenGetScrRect(
6011 				&fscr, FSCREEN_XYPOS, &scr_x, &scr_y, &scr_w,
6012 				&scr_h);
6013 			x -= menudim_middle_x_offset(&MR_DIM(pmp->menu));
6014 			y -= menuitem_middle_y_offset(
6015 				MR_FIRST_ITEM(pmp->menu), MR_STYLE(pmp->menu));
6016 			if (x < scr_x)
6017 			{
6018 				x = scr_x;
6019 			}
6020 			if (y < scr_y)
6021 			{
6022 				y = scr_y;
6023 			}
6024 		}
6025 
6026 		/* pop_menu_up may move the x,y to make it fit on screen more
6027 		 * nicely. It might also move parent_menu out of the way. */
6028 		if (!pop_menu_up(
6029 			    &(pmp->menu), pmp, pmp->parent_menu, NULL,
6030 			    pmp->pexc, x, y, prefer_left_submenus, do_warp,
6031 			    pmp->pops, NULL, None))
6032 		{
6033 			XBell(dpy, 0);
6034 			UngrabEm(GRAB_MENU);
6035 			pmret->rc = MENU_ERROR;
6036 			return;
6037 		}
6038 	}
6039 	else
6040 	{
6041 		fWasAlreadyPopped = True;
6042 		if (pmp->tear_off_root_menu_window != NULL)
6043 		{
6044 			if (!GrabEm(CRS_MENU, GRAB_MENU))
6045 			{
6046 				/* GrabEm specifies the cursor to use */
6047 				XBell(dpy, 0);
6048 				pmret->rc = MENU_ABORTED;
6049 				return;
6050 			}
6051 			is_pointer_grabbed = True;
6052 		}
6053 		if (key_press)
6054 		{
6055 			warp_pointer_to_item(
6056 				pmp->menu, MR_FIRST_ITEM(pmp->menu),
6057 				True /* skip Title */);
6058 		}
6059 	}
6060 
6061 	/* Remember the key that popped up the root menu. */
6062 	if (pmp->flags.is_submenu)
6063 	{
6064 		dkp.timestamp = 0;
6065 	}
6066 	else
6067 	{
6068 		if (pmp->flags.is_triggered_by_keypress)
6069 		{
6070 			/* we have a real key event */
6071 			dkp.keystate = (*pmp->pexc)->x.etrigger->xkey.state;
6072 			dkp.keycode = (*pmp->pexc)->x.etrigger->xkey.keycode;
6073 		}
6074 		dkp.timestamp =
6075 			(key_press && pmp->flags.has_default_action) ? t0 : 0;
6076 	}
6077 	if (!pmp->flags.is_submenu && indirect_depth == 0)
6078 	{
6079 		/* we need to grab the keyboard so we are sure no key presses
6080 		 * are lost */
6081 		MyXGrabKeyboard(dpy);
6082 	}
6083 	/* This may loop for tear off menus */
6084 	for (do_menu_interaction = True; do_menu_interaction == True; )
6085 	{
6086 		if (is_pointer_ungrabbed && !GrabEm(CRS_MENU, GRAB_MENU))
6087 		{
6088 			/* re-grab the pointer in this cycle */
6089 			XBell(dpy, 0);
6090 			pmret->rc = MENU_ABORTED;
6091 			break;
6092 		}
6093 		do_menu_interaction = False;
6094 		if (pmp->pops->flags.do_tear_off_immediately == 1)
6095 		{
6096 			pmret->rc = MENU_TEAR_OFF;
6097 		}
6098 		else
6099 		{
6100 			if (!pmp->flags.is_submenu)
6101 			{
6102 				XSelectInput(
6103 					dpy, Scr.NoFocusWin, XEVMASK_MENUNFW);
6104 				XFlush(dpy);
6105 			}
6106 			__menu_loop(pmp, pmret, &dkp);
6107 			if (!pmp->flags.is_submenu)
6108 			{
6109 				XSelectInput(
6110 					dpy, Scr.NoFocusWin, XEVMASK_NOFOCUSW);
6111 				XFlush(dpy);
6112 			}
6113 		}
6114 		do_check_pop_down = False;
6115 		switch (pmret->rc)
6116 		{
6117 		case MENU_TEAR_OFF:
6118 			menu_tear_off(pmp->menu);
6119 			pop_menu_down(&pmp->menu, pmp);
6120 			break;
6121 		case MENU_KILL_TEAR_OFF_MENU:
6122 			if (MR_IS_TEAR_OFF_MENU(pmp->menu))
6123 			{
6124 				/* kill the menu */
6125 				do_menu_close_tear_off_menu(pmp->menu, *pmp);
6126 				pmret->rc = MENU_ABORTED;
6127 			}
6128 			else
6129 			{
6130 				/* pass return code up to the torn off menu */
6131 			}
6132 			break;
6133 		case MENU_DOUBLE_CLICKED:
6134 		case MENU_DONE:
6135 			if (MR_IS_TEAR_OFF_MENU(pmp->menu))
6136 			{
6137 				do_menu_interaction = True;
6138 			}
6139 			else
6140 			{
6141 				do_check_pop_down = True;
6142 			}
6143 			break;
6144 		case MENU_SUBMENU_TORN_OFF:
6145 			pmret->rc = MENU_ABORTED;
6146 			do_check_pop_down = True;
6147 			break;
6148 		default:
6149 			do_check_pop_down = True;
6150 			break;
6151 		}
6152 		if (do_check_pop_down == True)
6153 		{
6154 			/* popping down may destroy the menu via the dynamic
6155 			 * popdown action! */
6156 			if (!MR_IS_TEAR_OFF_MENU(pmp->menu) &&
6157 			    fWasAlreadyPopped == False)
6158 			{
6159 				pop_menu_down(&pmp->menu, pmp);
6160 			}
6161 		}
6162 		XFlush(dpy);
6163 
6164 		if (!pmp->flags.is_submenu && x_start >= 0 && y_start >= 0 &&
6165 		    pmret->flags.is_key_press && pmret->rc != MENU_TEAR_OFF)
6166 		{
6167 			/* warp pointer back to where invoked if this was
6168 			 * brought up with a keypress and we're returning from
6169 			 * a top level menu, and a button release event didn't
6170 			 * end it */
6171 			FWarpPointer(
6172 				dpy, 0, Scr.Root, 0, 0, Scr.MyDisplayWidth,
6173 				Scr.MyDisplayHeight, x_start, y_start);
6174 			if ((*pmp->pexc)->x.elast->type == KeyPress)
6175 			{
6176 				XEvent e = *(*pmp->pexc)->x.elast;
6177 
6178 				e.xkey.x_root = x_start;
6179 				e.xkey.y_root = y_start;
6180 				fev_fake_event(&e);
6181 			}
6182 		}
6183 		if (pmret->rc == MENU_TEAR_OFF)
6184 		{
6185 			pmret->rc = MENU_SUBMENU_TORN_OFF;
6186 		}
6187 
6188 		dkp.timestamp = 0;
6189 		if (is_pointer_grabbed)
6190 		{
6191 			UngrabEm(GRAB_MENU);
6192 			WaitForButtonsUp(True);
6193 			is_pointer_ungrabbed = True;
6194 		}
6195 		if (!pmp->flags.is_submenu)
6196 		{
6197 			if (pmret->rc == MENU_DONE)
6198 			{
6199 				if (pmp->ret_paction && *pmp->ret_paction)
6200 				{
6201 					indirect_depth++;
6202 					/* Execute the action */
6203 					__menu_execute_function(
6204 						pmp->pexc, *pmp->ret_paction);
6205 					indirect_depth--;
6206 					free(*pmp->ret_paction);
6207 					*pmp->ret_paction = NULL;
6208 				}
6209 				last_saved_pos_hints.flags.
6210 					do_ignore_pos_hints = False;
6211 				last_saved_pos_hints.flags.
6212 					is_last_menu_pos_hints_valid = False;
6213 			}
6214 			if (indirect_depth == 0)
6215 			{
6216 				last_saved_pos_hints.flags.
6217 					do_ignore_pos_hints = False;
6218 				last_saved_pos_hints.flags.
6219 					is_last_menu_pos_hints_valid = False;
6220 			}
6221 		}
6222 	}
6223 	if (!pmp->flags.is_submenu && indirect_depth == 0)
6224 	{
6225 		/* release the keyboard when the last menu closes */
6226 		MyXUngrabKeyboard(dpy);
6227 	}
6228 
6229 	return;
6230 }
6231 
menu_expose(XEvent * event,FvwmWindow * fw)6232 Bool menu_expose(XEvent *event, FvwmWindow *fw)
6233 {
6234 	MenuRoot *mr = NULL;
6235 
6236 	if ((XFindContext(
6237 		     dpy, event->xany.window, MenuContext, (caddr_t *)&mr) !=
6238 	     XCNOENT))
6239 	{
6240 		flush_accumulate_expose(event->xany.window, event);
6241 		paint_menu(mr, event, fw);
6242 
6243 		return True;
6244 	}
6245 	else
6246 	{
6247 		return False;
6248 	}
6249 }
6250 
6251 /*
6252  *
6253  *  Procedure:
6254  *      update_transparent_menu_bg - set the background of the menu to
6255  *      match a forseen move of a menu. If the background is updated
6256  *	for the target position before the move is done, and repainted
6257  *	after the move, the move will look more seamless.
6258  *
6259  *	This method should be folleowd by a call to repaint_transparent_menu
6260  *	with the same step_x, stey_y, end_x and any_y, with is_bg_set True
6261  */
update_transparent_menu_bg(MenuRepaintTransparentParameters * prtm,int current_x,int current_y,int step_x,int step_y,int end_x,int end_y)6262 void update_transparent_menu_bg(
6263 	MenuRepaintTransparentParameters *prtm,
6264 	int current_x, int current_y, int step_x, int step_y,
6265 	int end_x, int end_y)
6266 {
6267 	MenuRoot *mr;
6268 	MenuStyle *ms;
6269 	Bool last = False;
6270 
6271 	mr = prtm->mr;
6272 	ms = MR_STYLE(mr);
6273 	if (step_x == end_x && step_y == end_y)
6274 	{
6275 		last = True;
6276 	}
6277 	if (!last && CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
6278 	{
6279 		/* too slow ... */
6280 		return;
6281 	}
6282 	SetWindowBackgroundWithOffset(
6283 		dpy, MR_WINDOW(mr), step_x - current_x, step_y - current_y,
6284 		MR_WIDTH(mr), MR_HEIGHT(mr),
6285 		&Colorset[ST_CSET_MENU(ms)], Pdepth,
6286 		FORE_GC(MST_MENU_INACTIVE_GCS(mr)), False);
6287 }
6288 
6289 
6290 /*
6291  *
6292  *  Procedure:
6293  *      repaint_transparent_menu - repaint the menu background if it is
6294  *      tranparent during an animated move. Called in move_resize.c
6295  *      (AnimatedMoveAnyWindow). Performance improvement Welcome!
6296  *  ideas: - write the foreground into a pixmap and a mask the first time
6297  *          this function is called. Then use these pixmaps to draw
6298  *	    the items
6299  *         - Use a Buffer if !IS_TRANSPARENT_PR_PURE and if we do not have one
6300  *           already
6301  */
repaint_transparent_menu(MenuRepaintTransparentParameters * prtm,Bool first,int x,int y,int end_x,int end_y,Bool is_bg_set)6302 void repaint_transparent_menu(
6303 	MenuRepaintTransparentParameters *prtm,
6304 	Bool first, int x, int y, int end_x, int end_y, Bool is_bg_set)
6305 {
6306 	MenuItem *mi;
6307 	MenuRoot *mr;
6308 	MenuStyle *ms;
6309 	int h = 0;
6310 	int s_h = 0;
6311 	int e_h = 0;
6312 	Bool last = False;
6313 
6314 	mr = prtm->mr;
6315 	ms = MR_STYLE(mr);
6316 	if (x == end_x && y == end_y)
6317 	{
6318 		last = True;
6319 	}
6320 	if (!last && CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
6321 	{
6322 		/* too slow ... */
6323 		return;
6324 	}
6325 	if (!is_bg_set)
6326 	{
6327 		SetWindowBackground(
6328 			dpy, MR_WINDOW(mr), MR_WIDTH(mr), MR_HEIGHT(mr),
6329 			&Colorset[ST_CSET_MENU(ms)], Pdepth,
6330 			FORE_GC(MST_MENU_INACTIVE_GCS(mr)), False);
6331 	}
6332 	/* redraw the background of non active item */
6333 	for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
6334 	{
6335 		if (mi == MR_SELECTED_ITEM(mr) && MST_DO_HILIGHT_BACK(mr))
6336 		{
6337 			int left;
6338 
6339 			left = MR_HILIGHT_X_OFFSET(mr) - MR_ITEM_X_OFFSET(mr);
6340 			if (left > 0)
6341 			{
6342 				XClearArea(
6343 					dpy, MR_WINDOW(mr),
6344 					MR_ITEM_X_OFFSET(mr), MI_Y_OFFSET(mi),
6345 					left, MI_HEIGHT(mi) +
6346 					MST_RELIEF_THICKNESS(mr), 0);
6347 			}
6348 			h = MI_HEIGHT(mi);
6349 			continue;
6350 		}
6351 		if (h == 0)
6352 		{
6353 			s_h += MI_HEIGHT(mi);
6354 		}
6355 		else
6356 		{
6357 			e_h += MI_HEIGHT(mi);
6358 		}
6359 	}
6360 	XClearArea(
6361 		dpy, MR_WINDOW(mr), MR_ITEM_X_OFFSET(mr), MST_BORDER_WIDTH(mr),
6362 		MR_ITEM_WIDTH(mr), s_h, 0);
6363 	if (e_h != 0)
6364 	{
6365 		XClearArea(
6366 			dpy, MR_WINDOW(mr), MR_ITEM_X_OFFSET(mr),
6367 			s_h + h + MST_RELIEF_THICKNESS(mr) +
6368 			MST_BORDER_WIDTH(mr), MR_ITEM_WIDTH(mr), e_h, 0);
6369 	}
6370 
6371 	/* now redraw the items */
6372 	for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
6373 	{
6374 		MenuPaintItemParameters mpip;
6375 
6376 		if (mi == MR_SELECTED_ITEM(mr) && MST_DO_HILIGHT_BACK(mr) &&
6377 		    !CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
6378 		{
6379 			continue;
6380 		}
6381 		get_menu_paint_item_parameters(
6382 			&mpip, mr, NULL, prtm->fw, NULL, True);
6383 		mpip.flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
6384 		menuitem_paint(mi, &mpip);
6385 	}
6386 
6387 	/* if we have a side pic and no side colors we shound repaint the side
6388 	 * pic */
6389 	if ((MR_SIDEPIC(mr) || MST_SIDEPIC(mr)) &&
6390 	    !MR_HAS_SIDECOLOR(mr) && !MST_HAS_SIDE_COLOR(mr))
6391 	{
6392 		FvwmPicture *sidePic;
6393 		if (MR_SIDEPIC(mr))
6394 		{
6395 			sidePic = MR_SIDEPIC(mr);
6396 		}
6397 		else if (MST_SIDEPIC(mr))
6398 		{
6399 			sidePic = MST_SIDEPIC(mr);
6400 		}
6401 		else
6402 		{
6403 			return;
6404 		}
6405 		XClearArea(
6406 			dpy, MR_WINDOW(mr), MR_SIDEPIC_X_OFFSET(mr),
6407 			MST_BORDER_WIDTH(mr), sidePic->width,
6408 			MR_HEIGHT(mr) - 2 * MST_BORDER_WIDTH(mr), 0);
6409 		paint_side_pic(mr, NULL);
6410 	}
6411 }
6412 
DestroyMenu(MenuRoot * mr,Bool do_recreate,Bool is_command_request)6413 Bool DestroyMenu(MenuRoot *mr, Bool do_recreate, Bool is_command_request)
6414 {
6415 	MenuItem *mi,*tmp2;
6416 	MenuRoot *tmp, *prev;
6417 	Bool in_list = True;
6418 
6419 	if (mr == NULL)
6420 	{
6421 		return False;
6422 	}
6423 
6424 	/* seek menu in master list */
6425 	tmp = Menus.all;
6426 	prev = NULL;
6427 	while (tmp && tmp != mr)
6428 	{
6429 		prev = tmp;
6430 		tmp = MR_NEXT_MENU(tmp);
6431 	}
6432 	if (tmp != mr)
6433 	{
6434 		/* no such menu */
6435 		in_list = False;
6436 	}
6437 
6438 	if (MR_MAPPED_COPIES(mr) > 0 &&
6439 	    (is_command_request || MR_COPIES(mr) == 1))
6440 	{
6441 		/* can't destroy a menu while in use */
6442 		fvwm_msg(ERR, "DestroyMenu", "Menu %s is in use", MR_NAME(mr));
6443 		return False;
6444 	}
6445 
6446 	if (in_list && MR_COPIES(mr) > 1 && MR_ORIGINAL_MENU(mr) == mr)
6447 	{
6448 		MenuRoot *m;
6449 		MenuRoot *new_orig;
6450 
6451 		/* find a new 'original' menu */
6452 		for (m = Menus.all, new_orig = NULL; m; m = MR_NEXT_MENU(m))
6453 		{
6454 			if (m != mr && MR_ORIGINAL_MENU(m) == mr)
6455 			{
6456 				if (new_orig == NULL)
6457 				{
6458 					new_orig = m;
6459 				}
6460 				MR_ORIGINAL_MENU(m) = new_orig;
6461 			}
6462 		}
6463 		MR_ORIGINAL_MENU(mr) = new_orig;
6464 		/* now dump old original menu */
6465 	}
6466 
6467 	MR_COPIES(mr)--;
6468 	if (MR_STORED_ITEM(mr).stored)
6469 	{
6470 		XFreePixmap(dpy, MR_STORED_ITEM(mr).stored);
6471 	}
6472 	if (MR_COPIES(mr) > 0)
6473 	{
6474 		do_recreate = False;
6475 	}
6476 	else
6477 	{
6478 		/* free all items */
6479 		mi = MR_FIRST_ITEM(mr);
6480 		while (mi != NULL)
6481 		{
6482 			tmp2 = MI_NEXT_ITEM(mi);
6483 			menuitem_free(mi);
6484 			mi = tmp2;
6485 		}
6486 		if (do_recreate)
6487 		{
6488 			/* just dump the menu items but keep the menu itself */
6489 			MR_COPIES(mr)++;
6490 			MR_FIRST_ITEM(mr) = NULL;
6491 			MR_LAST_ITEM(mr) = NULL;
6492 			MR_SELECTED_ITEM(mr) = NULL;
6493 			MR_CONTINUATION_MENU(mr) = NULL;
6494 			MR_PARENT_MENU(mr) = NULL;
6495 			MR_ITEMS(mr) = 0;
6496 			memset(&(MR_STORED_ITEM(mr)), 0 ,
6497 			       sizeof(MR_STORED_ITEM(mr)));
6498 			MR_IS_UPDATED(mr) = 1;
6499 			return True;
6500 		}
6501 	}
6502 
6503 	/* unlink menu from list */
6504 	if (in_list)
6505 	{
6506 		if (prev == NULL)
6507 		{
6508 			Menus.all = MR_NEXT_MENU(mr);
6509 		}
6510 		else
6511 		{
6512 			MR_NEXT_MENU(prev) = MR_NEXT_MENU(mr);
6513 		}
6514 	}
6515 	/* destroy the window and the display */
6516 	if (MR_WINDOW(mr) != None)
6517 	{
6518 		XDeleteContext(dpy, MR_WINDOW(mr), MenuContext);
6519 		XFlush(dpy);
6520 		XDestroyWindow(MR_CREATE_DPY(mr), MR_WINDOW(mr));
6521 		MR_WINDOW(mr) = None;
6522 		XFlush(MR_CREATE_DPY(mr));
6523 	}
6524 	if (MR_CREATE_DPY(mr) != NULL && MR_CREATE_DPY(mr) != dpy)
6525 	{
6526 		XCloseDisplay(MR_CREATE_DPY(mr));
6527 		MR_CREATE_DPY(mr) = NULL;
6528 	}
6529 
6530 	if (MR_COPIES(mr) == 0)
6531 	{
6532 		if (MR_POPUP_ACTION(mr))
6533 		{
6534 			free(MR_POPUP_ACTION(mr));
6535 		}
6536 		if (MR_POPDOWN_ACTION(mr))
6537 		{
6538 			free(MR_POPDOWN_ACTION(mr));
6539 		}
6540 		if (MR_MISSING_SUBMENU_FUNC(mr))
6541 		{
6542 			free(MR_MISSING_SUBMENU_FUNC(mr));
6543 		}
6544 		free(MR_NAME(mr));
6545 		if (MR_SIDEPIC(mr))
6546 		{
6547 			PDestroyFvwmPicture(dpy, MR_SIDEPIC(mr));
6548 		}
6549 		memset(mr->s, 0, sizeof(*(mr->s)));
6550 		free(mr->s);
6551 	}
6552 	memset(mr->d, 0, sizeof(*(mr->d)));
6553 	free(mr->d);
6554 	memset(mr, 0, sizeof(*mr));
6555 	free(mr);
6556 
6557 	return True;
6558 }
6559 
6560 /* FollowMenuContinuations
6561  * Given an menu root, return the menu root to add to by
6562  * following continuation links until there are no more
6563  */
FollowMenuContinuations(MenuRoot * mr,MenuRoot ** pmrPrior)6564 MenuRoot *FollowMenuContinuations(MenuRoot *mr, MenuRoot **pmrPrior )
6565 {
6566 	*pmrPrior = NULL;
6567 	while ((mr != NULL) &&
6568 	       (MR_CONTINUATION_MENU(mr) != NULL))
6569 	{
6570 		*pmrPrior = mr;
6571 		mr = MR_CONTINUATION_MENU(mr);
6572 	}
6573 
6574 	return mr;
6575 }
6576 
6577 /*
6578  *
6579  *  Procedure:
6580  *      AddToMenu - add an item to a root menu
6581  *
6582  *  Returned Value:
6583  *      (MenuItem *)
6584  *
6585  *  Inputs:
6586  *      menu    - pointer to the root menu to add the item
6587  *      item    - the text to appear in the menu
6588  *      action  - the string to possibly execute
6589  *
6590  * ckh - need to add boolean to say whether or not to expand for pixmaps,
6591  *       so built in window list can handle windows w/ * and % in title.
6592  *
6593  */
AddToMenu(MenuRoot * mr,char * item,char * action,Bool fPixmapsOk,Bool fNoPlus,Bool is_continuation_item)6594 void AddToMenu(
6595 	MenuRoot *mr, char *item, char *action, Bool fPixmapsOk, Bool fNoPlus,
6596 	Bool is_continuation_item)
6597 {
6598 	MenuItem *tmp;
6599 	char *start;
6600 	char *end;
6601 	char *token = NULL;
6602 	char *option = NULL;
6603 	int i;
6604 	int is_empty;
6605 	Bool do_replace_title;
6606 
6607 	if (MR_MAPPED_COPIES(mr) > 0)
6608 	{
6609 		/* whoa, we can't handle *everything* */
6610 		return;
6611 	}
6612 	if ((item == NULL || *item == 0) && (action == NULL || *action == 0) &&
6613 	    fNoPlus)
6614 	{
6615 		return;
6616 	}
6617 	/* empty items screw up our menu when painted, so we replace them with
6618 	 * a separator */
6619 	if (item == NULL)
6620 		item = "";
6621 
6622 	/*
6623 	 * Handle dynamic actions
6624 	 */
6625 
6626 	if (StrEquals(item, "DynamicPopupAction"))
6627 	{
6628 		if (MR_POPUP_ACTION(mr))
6629 		{
6630 			free(MR_POPUP_ACTION(mr));
6631 		}
6632 		if (!action || *action == 0)
6633 		{
6634 			MR_POPUP_ACTION(mr) = NULL;
6635 		}
6636 		else
6637 		{
6638 			MR_POPUP_ACTION(mr) = stripcpy(action);
6639 		}
6640 		return;
6641 	}
6642 	else if (StrEquals(item, "DynamicPopdownAction"))
6643 	{
6644 		if (MR_POPDOWN_ACTION(mr))
6645 		{
6646 			free(MR_POPDOWN_ACTION(mr));
6647 		}
6648 		if (!action || *action == 0)
6649 		{
6650 			MR_POPDOWN_ACTION(mr) = NULL;
6651 		}
6652 		else
6653 		{
6654 			MR_POPDOWN_ACTION(mr) = stripcpy(action);
6655 		}
6656 		return;
6657 	}
6658 	else if (StrEquals(item, "MissingSubmenuFunction"))
6659 	{
6660 		if (MR_MISSING_SUBMENU_FUNC(mr))
6661 		{
6662 			free(MR_MISSING_SUBMENU_FUNC(mr));
6663 		}
6664 		if (!action || *action == 0)
6665 		{
6666 			MR_MISSING_SUBMENU_FUNC(mr) = NULL;
6667 		}
6668 		else
6669 		{
6670 			MR_MISSING_SUBMENU_FUNC(mr) = stripcpy(action);
6671 		}
6672 		return;
6673 	}
6674 
6675 	/*
6676 	 * Parse the action
6677 	 */
6678 
6679 	if (action == NULL || *action == 0)
6680 	{
6681 		action = "Nop";
6682 	}
6683 	GetNextToken(GetNextToken(action, &token), &option);
6684 	tmp = menuitem_create();
6685 	if (MR_FIRST_ITEM(mr) != NULL && StrEquals(token, "title") &&
6686 	    option != NULL && StrEquals(option, "top"))
6687 	{
6688 		do_replace_title = True;
6689 	}
6690 	else
6691 	{
6692 		do_replace_title = False;
6693 	}
6694 	append_item_to_menu(mr, tmp, do_replace_title);
6695 	if (token)
6696 	{
6697 		free(token);
6698 	}
6699 	if (option)
6700 	{
6701 		free(option);
6702 	}
6703 
6704 	MI_ACTION(tmp) = stripcpy(action);
6705 
6706 	/*
6707 	 * Parse the labels
6708 	 */
6709 
6710 	start = item;
6711 	end = item;
6712 	for (i = 0; i < MAX_MENU_ITEM_LABELS; i++, start = end)
6713 	{
6714 		/* Read label up to next tab. */
6715 		if (*end)
6716 		{
6717 			if (i < MAX_MENU_ITEM_LABELS - 1)
6718 			{
6719 				while (*end && *end != '\t')
6720 				{
6721 					/* seek next tab or end of string */
6722 					end++;
6723 				}
6724 			}
6725 			else
6726 			{
6727 				/* remove all tabs in last label */
6728 				while (*end)
6729 				{
6730 					if (*end == '\t')
6731 					{
6732 						*end = ' ';
6733 					}
6734 					end++;
6735 				}
6736 			}
6737 			/* Copy the label. */
6738 			MI_LABEL(tmp)[i] = safemalloc(end - start + 1);
6739 			strncpy(MI_LABEL(tmp)[i], start, end - start);
6740 			(MI_LABEL(tmp)[i])[end - start] = 0;
6741 			if (*end == '\t')
6742 			{
6743 				/* skip the tab */
6744 				end++;
6745 			}
6746 		}
6747 		else
6748 		{
6749 			MI_LABEL(tmp)[i] = NULL;
6750 		}
6751 
6752 		/* Parse the label. */
6753 		if (MI_LABEL(tmp)[i] != NULL)
6754 		{
6755 			if (fPixmapsOk)
6756 			{
6757 				string_def_t item_pixmaps[] = {
6758 					{'*', __scan_for_pixmap},
6759 					{'%', __scan_for_pixmap},
6760 					{'\0', NULL}};
6761 				string_context_t ctx;
6762 
6763 				SCTX_SET_MI(ctx,tmp);
6764 				scanForStrings(
6765 					MI_LABEL(tmp)[i], item_pixmaps, &ctx);
6766 			}
6767 			if (!MI_HAS_HOTKEY(tmp))
6768 			{
6769 				/* pete@tecc.co.uk */
6770 				scanForHotkeys(tmp, i);
6771 
6772 				if (!MI_HAS_HOTKEY(tmp) &&
6773 				    *MI_LABEL(tmp)[i] != 0)
6774 				{
6775 					MI_HOTKEY_COFFSET(tmp) = 0;
6776 					MI_HOTKEY_COLUMN(tmp) = i;
6777 					MI_HAS_HOTKEY(tmp) = 1;
6778 					MI_IS_HOTKEY_AUTOMATIC(tmp) = 1;
6779 				}
6780 			}
6781 			if (*(MI_LABEL(tmp)[i]))
6782 			{
6783 				MI_HAS_TEXT(tmp) = True;
6784 			}
6785 			else
6786 			{
6787 				free(MI_LABEL(tmp)[i]);
6788 				MI_LABEL(tmp)[i] = NULL;
6789 			}
6790 		}
6791 		MI_LABEL_STRLEN(tmp)[i] =
6792 			(MI_LABEL(tmp)[i]) ? strlen(MI_LABEL(tmp)[i]) : 0;
6793 	} /* for */
6794 
6795 	/*
6796 	 * Set the type flags
6797 	 */
6798 
6799 	if (is_continuation_item)
6800 	{
6801 		MI_IS_CONTINUATION(tmp) = True;
6802 	}
6803 	find_func_t(MI_ACTION(tmp), &(MI_FUNC_TYPE(tmp)), NULL);
6804 	switch (MI_FUNC_TYPE(tmp))
6805 	{
6806 	case F_POPUP:
6807 		MI_IS_POPUP(tmp) = True;
6808 	case F_WINDOWLIST:
6809 	case F_STAYSUP:
6810 		MI_IS_MENU(tmp) = True;
6811 		break;
6812 	case F_TITLE:
6813 		MI_IS_TITLE(tmp) = True;
6814 		break;
6815 	default:
6816 		break;
6817 	}
6818 	is_empty = (!MI_HAS_TEXT(tmp) && !MI_HAS_PICTURE(tmp));
6819 	if (is_empty)
6820 	{
6821 		if (MI_FUNC_TYPE(tmp) == F_TEARMENUOFF)
6822 		{
6823 			MI_IS_TEAR_OFF_BAR(tmp) = 1;
6824 			MI_IS_SEPARATOR(tmp) = 0;
6825 		}
6826 		else
6827 		{
6828 			MI_IS_TEAR_OFF_BAR(tmp) = 0;
6829 			MI_IS_SEPARATOR(tmp) = 1;
6830 		}
6831 	}
6832 	if (MI_IS_SEPARATOR(tmp))
6833 	{
6834 		/* An empty title is handled like a separator. */
6835 		MI_IS_TITLE(tmp) = False;
6836 	}
6837 	MI_IS_SELECTABLE(tmp) =
6838 		((MI_HAS_TEXT(tmp) || MI_HAS_PICTURE(tmp) ||
6839 		  MI_IS_TEAR_OFF_BAR(tmp)) && !MI_IS_TITLE(tmp));
6840 
6841 	/*
6842 	 * misc stuff
6843 	 */
6844 
6845 	MR_ITEMS(mr)++;
6846 	MR_IS_UPDATED(mr) = 1;
6847 
6848 	return;
6849 }
6850 
6851 /*
6852  *
6853  *  Procedure:
6854  *      NewMenuRoot - create a new menu root
6855  *
6856  *  Returned Value:
6857  *      (MenuRoot *)
6858  *
6859  *  Inputs:
6860  *      name    - the name of the menu root
6861  *
6862  */
NewMenuRoot(char * name)6863 MenuRoot *NewMenuRoot(char *name)
6864 {
6865 	MenuRoot *mr;
6866 	string_def_t root_strings[] = {
6867 		{'@', __scan_for_pixmap},
6868 		{'^', __scan_for_color},
6869 		{'\0', NULL}};
6870 	string_context_t ctx;
6871 
6872 	mr = (MenuRoot *)safemalloc(sizeof(MenuRoot));
6873 	mr->s = (MenuRootStatic *)safemalloc(sizeof(MenuRootStatic));
6874 	mr->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic));
6875 
6876 	memset(mr->s, 0, sizeof(MenuRootStatic));
6877 	memset(mr->d, 0, sizeof(MenuRootDynamic));
6878 	MR_NEXT_MENU(mr) = Menus.all;
6879 	MR_NAME(mr) = safestrdup(name);
6880 	MR_WINDOW(mr) = None;
6881 	SCTX_SET_MR(ctx,mr);
6882 	MR_HAS_SIDECOLOR(mr) = False;
6883 	scanForStrings(
6884 		MR_NAME(mr), root_strings, &ctx);
6885 	MR_STYLE(mr) = menustyle_get_default_style();
6886 	MR_ORIGINAL_MENU(mr) = mr;
6887 	MR_COPIES(mr) = 1;
6888 	MR_IS_UPDATED(mr) = 1;
6889 
6890 	Menus.all = mr;
6891 	return mr;
6892 }
6893 
SetMenuCursor(Cursor cursor)6894 void SetMenuCursor(Cursor cursor)
6895 {
6896 	MenuRoot *mr;
6897 
6898 	mr = Menus.all;
6899 	for (mr = Menus.all; mr; mr = MR_NEXT_MENU(mr))
6900 	{
6901 		if (MR_WINDOW(mr))
6902 		{
6903 			XDefineCursor(dpy, MR_WINDOW(mr), cursor);
6904 		}
6905 	}
6906 
6907 	return;
6908 }
6909 
UpdateAllMenuStyles(void)6910 void UpdateAllMenuStyles(void)
6911 {
6912 	MenuStyle *ms;
6913 
6914 	for (ms = menustyle_get_default_style(); ms; ms = ST_NEXT_STYLE(ms))
6915 	{
6916 		menustyle_update(ms);
6917 	}
6918 
6919 	return;
6920 }
6921 
UpdateMenuColorset(int cset)6922 void UpdateMenuColorset(int cset)
6923 {
6924 	MenuStyle *ms;
6925 	FvwmWindow *t;
6926 
6927 	for (ms = menustyle_get_default_style(); ms; ms = ST_NEXT_STYLE(ms))
6928 	{
6929 		if ((ST_HAS_MENU_CSET(ms) && ST_CSET_MENU(ms) == cset) ||
6930 		    (ST_HAS_ACTIVE_CSET(ms) && ST_CSET_ACTIVE(ms) == cset) ||
6931 		    (ST_HAS_GREYED_CSET(ms) && ST_CSET_GREYED(ms) == cset) ||
6932 		    (ST_HAS_TITLE_CSET(ms) && ST_CSET_TITLE(ms) == cset))
6933 		{
6934 			menustyle_update(ms);
6935 		}
6936 	}
6937 
6938 	for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
6939 	{
6940 		MenuRoot *mr = NULL;
6941 		MenuStyle *ms = NULL;
6942 
6943 		if (IS_TEAR_OFF_MENU(t) &&
6944 		    XFindContext(dpy, FW_W(t), MenuContext, (caddr_t *)&mr) !=
6945 		    XCNOENT &&
6946 		    (ms = MR_STYLE(mr)))
6947 		{
6948 			if   (ST_HAS_MENU_CSET(ms) &&
6949 			      ST_CSET_MENU(ms) == cset)
6950 			{
6951 				SetWindowBackground(
6952 					dpy, MR_WINDOW(mr), MR_WIDTH(mr),
6953 					MR_HEIGHT(mr),
6954 					&Colorset[ST_CSET_MENU(ms)],
6955 					Pdepth,
6956 					FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
6957 					True);
6958 			}
6959 			else if ((ST_HAS_ACTIVE_CSET(ms) &&
6960 				  ST_CSET_ACTIVE(ms) == cset) ||
6961 				 (ST_HAS_GREYED_CSET(ms) &&
6962 				  ST_CSET_GREYED(ms) == cset))
6963 			{
6964 				paint_menu(mr, NULL, NULL);
6965 			}
6966 		}
6967 	}
6968 
6969 	return;
6970 }
6971 
6972 /* This is called by the window list function */
change_mr_menu_style(MenuRoot * mr,char * stylename)6973 void change_mr_menu_style(MenuRoot *mr, char *stylename)
6974 {
6975 	MenuStyle *ms = NULL;
6976 
6977 	ms = menustyle_find(stylename);
6978 	if (ms == NULL)
6979 	{
6980 		return;
6981 	}
6982 	if (MR_MAPPED_COPIES(mr) != 0)
6983 	{
6984 		fvwm_msg(ERR,"ChangeMenuStyle", "menu %s is in use",
6985 			 MR_NAME(mr));
6986 	}
6987 	else
6988 	{
6989 		MR_STYLE(mr) = ms;
6990 		MR_IS_UPDATED(mr) = 1;
6991 	}
6992 
6993 	return;
6994 }
6995 
add_another_menu_item(char * action)6996 void add_another_menu_item(char *action)
6997 {
6998 	MenuRoot *mr;
6999 	MenuRoot *mrPrior;
7000 	char *rest,*item;
7001 
7002 	mr = FollowMenuContinuations(Scr.last_added_item.item, &mrPrior);
7003 	if (mr == NULL)
7004 	{
7005 		return;
7006 	}
7007 	if (MR_MAPPED_COPIES(mr) != 0)
7008 	{
7009 		fvwm_msg(ERR,"add_another_menu_item", "menu is in use");
7010 		return;
7011 	}
7012 	rest = GetNextToken(action,&item);
7013 	AddToMenu(mr, item, rest, True /* pixmap scan */, False, False);
7014 	if (item)
7015 	{
7016 		free(item);
7017 	}
7018 
7019 	return;
7020 }
7021 
7022 /*
7023  * get_menu_options is used for Menu, Popup and WindowList
7024  * It parses strings matching
7025  *
7026  *   [ [context-rectangle] x y ] [special-options] [other arguments]
7027  *
7028  * and returns a pointer to the first part of the input string that doesn't
7029  * match this syntax.
7030  *
7031  * See documentation for a detailed description.
7032  */
get_menu_options(char * action,Window w,FvwmWindow * fw,XEvent * e,MenuRoot * mr,MenuItem * mi,MenuOptions * pops)7033 char *get_menu_options(
7034 	char *action, Window w, FvwmWindow *fw, XEvent *e, MenuRoot *mr,
7035 	MenuItem *mi, MenuOptions *pops)
7036 {
7037 	char *tok = NULL;
7038 	char *naction = action;
7039 	char *taction;
7040 	int x;
7041 	int y;
7042 	int button;
7043 	int width;
7044 	int height;
7045 	int dummy_int;
7046 	float dummy_float;
7047 	Bool dummy_flag;
7048 	Window context_window = None;
7049 	Bool fHasContext, fUseItemOffset, fRectangleContext, fXineramaRoot;
7050 	Bool fValidPosHints =
7051 		last_saved_pos_hints.flags.is_last_menu_pos_hints_valid;
7052 	Bool is_action_empty = False;
7053 	Bool once_more = True;
7054 	Bool is_icon_context;
7055 	rectangle icon_g;
7056 
7057 	/* If this is set we may want to reverse the position hints, so don't
7058 	 * sum up the totals right now. This is useful for the SubmenusLeft
7059 	 * style. */
7060 
7061 	fXineramaRoot = False;
7062 	last_saved_pos_hints.flags.is_last_menu_pos_hints_valid = False;
7063 	if (pops == NULL)
7064 	{
7065 		fvwm_msg(ERR,
7066 			 "get_menu_options","no MenuOptions pointer passed");
7067 		return action;
7068 	}
7069 
7070 	taction = action;
7071 	memset(&(pops->flags), 0, sizeof(pops->flags));
7072 	pops->flags.has_poshints = 0;
7073 	if (!action || *action == 0)
7074 	{
7075 		is_action_empty = True;
7076 	}
7077 	while (action != NULL && *action != 0 && once_more)
7078 	{
7079 		/* ^ just to be able to jump to end of loop without 'goto' */
7080 		pops->pos_hints.is_relative = False;
7081 		pops->pos_hints.menu_width = 0;
7082 		pops->pos_hints.is_menu_relative = False;
7083 		/* parse context argument (if present) */
7084 		naction = GetNextToken(taction, &tok);
7085 		if (!tok)
7086 		{
7087 			/* no context string */
7088 			fHasContext = False;
7089 			is_action_empty = True;
7090 			break;
7091 		}
7092 		/* set to False for absolute hints! */
7093 		pops->pos_hints.is_relative = True;
7094 		fUseItemOffset = False;
7095 		fHasContext = True;
7096 		fRectangleContext = False;
7097 		is_icon_context = False;
7098 		if (StrEquals(tok, "context"))
7099 		{
7100 			if (mi && mr)
7101 			{
7102 				context_window = MR_WINDOW(mr);
7103 			}
7104 			else if (fw)
7105 			{
7106 				if (IS_ICONIFIED(fw))
7107 				{
7108 					is_icon_context = True;
7109 					get_icon_geometry(fw, &icon_g);
7110 					context_window = None;
7111 				}
7112 				else
7113 				{
7114 					context_window =
7115 						FW_W_FRAME(fw);
7116 				}
7117 			}
7118 			else
7119 			{
7120 				context_window = w;
7121 			}
7122 		}
7123 		else if (StrEquals(tok,"menu"))
7124 		{
7125 			if (mr)
7126 			{
7127 				context_window = MR_WINDOW(mr);
7128 				pops->pos_hints.is_menu_relative = True;
7129 				pops->pos_hints.menu_width = MR_WIDTH(mr);
7130 			}
7131 		}
7132 		else if (StrEquals(tok,"item"))
7133 		{
7134 			if (mi && mr)
7135 			{
7136 				context_window = MR_WINDOW(mr);
7137 				fUseItemOffset = True;
7138 				pops->pos_hints.is_menu_relative = True;
7139 				pops->pos_hints.menu_width = MR_WIDTH(mr);
7140 			}
7141 		}
7142 		else if (StrEquals(tok,"icon"))
7143 		{
7144 			if (fw && IS_ICONIFIED(fw))
7145 			{
7146 				is_icon_context = True;
7147 				get_icon_geometry(fw, &icon_g);
7148 				context_window = None;
7149 			}
7150 		}
7151 		else if (StrEquals(tok,"window"))
7152 		{
7153 			if (fw && !IS_ICONIFIED(fw))
7154 			{
7155 				context_window = FW_W_FRAME(fw);
7156 			}
7157 		}
7158 		else if (StrEquals(tok,"interior"))
7159 		{
7160 			if (fw && !IS_ICONIFIED(fw))
7161 			{
7162 				context_window = FW_W(fw);
7163 			}
7164 		}
7165 		else if (StrEquals(tok,"title"))
7166 		{
7167 			if (fw)
7168 			{
7169 				if (IS_ICONIFIED(fw))
7170 				{
7171 					context_window =
7172 						FW_W_ICON_TITLE(fw);
7173 				}
7174 				else
7175 				{
7176 					context_window = FW_W_TITLE(fw);
7177 				}
7178 			}
7179 		}
7180 		else if (strncasecmp(tok,"button",6) == 0)
7181 		{
7182 			if (sscanf(&(tok[6]),"%d",&button) != 1 ||
7183 			    tok[6] == '+' || tok[6] == '-' || button < 0 ||
7184 			    button > 9)
7185 			{
7186 				fHasContext = False;
7187 			}
7188 			else if (fw && !IS_ICONIFIED(fw))
7189 			{
7190 				button = BUTTON_INDEX(button);
7191 				context_window = FW_W_BUTTON(fw, button);
7192 			}
7193 		}
7194 		else if (StrEquals(tok,"root"))
7195 		{
7196 			context_window = Scr.Root;
7197 			pops->pos_hints.is_relative = False;
7198 		}
7199 		else if (StrEquals(tok,"xineramaroot"))
7200 		{
7201 			context_window = Scr.Root;
7202 			pops->pos_hints.is_relative = False;
7203 			fXineramaRoot = True;
7204 		}
7205 		else if (StrEquals(tok,"mouse"))
7206 		{
7207 			context_window = None;
7208 		}
7209 		else if (StrEquals(tok,"rectangle"))
7210 		{
7211 			int flags;
7212 			int screen;
7213 			int sx;
7214 			int sy;
7215 			int sw;
7216 			int sh;
7217 
7218 			/* parse the rectangle */
7219 			free(tok);
7220 			naction = GetNextToken(naction, &tok);
7221 			if (tok == NULL)
7222 			{
7223 				fvwm_msg(ERR, "get_menu_options",
7224 					 "missing rectangle geometry");
7225 				if (!pops->pos_hints.has_screen_origin)
7226 				{
7227 					/* xinerama: emergency fallback */
7228 					pops->pos_hints.has_screen_origin = 1;
7229 					pops->pos_hints.screen_origin_x = 0;
7230 					pops->pos_hints.screen_origin_y = 0;
7231 				}
7232 				return action;
7233 			}
7234 			flags = FScreenParseGeometryWithScreen(
7235 				tok, &x, &y,
7236 				(unsigned int*)&width,
7237 				(unsigned int*)&height, &screen);
7238 			if ((flags & (XValue | YValue)) != (XValue | YValue))
7239 			{
7240 				free(tok);
7241 				fvwm_msg(ERR, "get_menu_options",
7242 					 "invalid rectangle geometry");
7243 				if (!pops->pos_hints.has_screen_origin)
7244 				{
7245 					/* xinerama: emergency fallback */
7246 					pops->pos_hints.has_screen_origin = 1;
7247 					pops->pos_hints.screen_origin_x = 0;
7248 					pops->pos_hints.screen_origin_y = 0;
7249 				}
7250 				return action;
7251 			}
7252 			pops->pos_hints.has_screen_origin = 1;
7253 			FScreenGetScrRect(NULL, screen, &sx, &sy, &sw, &sh);
7254 			pops->pos_hints.screen_origin_x = sx;
7255 			pops->pos_hints.screen_origin_y = sy;
7256 			if (!(flags & WidthValue))
7257 			{
7258 				width = 1;
7259 			}
7260 			if (!(flags & HeightValue))
7261 			{
7262 				height = 1;
7263 			}
7264 			x += sx;
7265 			y += sy;
7266 			if (flags & XNegative)
7267 			{
7268 				/* x is negative */
7269 				x = sx + sw + x;
7270 			}
7271 			if (flags & YNegative)
7272 			{
7273 				/* y is negative */
7274 				y = sy + sh + y;
7275 			}
7276 			pops->pos_hints.is_relative = False;
7277 			fRectangleContext = True;
7278 		}
7279 		else if (StrEquals(tok,"this"))
7280 		{
7281 			MenuRoot *dummy_mr;
7282 
7283 			context_window = w;
7284 			if (XFindContext(
7285 				    dpy, w, MenuContext,
7286 				    (caddr_t *)&dummy_mr) != XCNOENT)
7287 			{
7288 				if (mr)
7289 				{
7290 					/* the parent menu */
7291 					pops->pos_hints.is_menu_relative =
7292 						True;
7293 					pops->pos_hints.menu_width =
7294 						MR_WIDTH(mr);
7295 				}
7296 			}
7297 		}
7298 		else
7299 		{
7300 			/* no context string */
7301 			fHasContext = False;
7302 		}
7303 
7304 		if (tok)
7305 		{
7306 			free(tok);
7307 		}
7308 		if (fHasContext)
7309 		{
7310 			taction = naction;
7311 		}
7312 		else
7313 		{
7314 			naction = action;
7315 		}
7316 
7317 		if (fRectangleContext)
7318 		{
7319 			if (!pops->pos_hints.has_screen_origin)
7320 			{
7321 				/* xinerama: use global screen as reference */
7322 				pops->pos_hints.has_screen_origin = 1;
7323 				pops->pos_hints.screen_origin_x = -1;
7324 				pops->pos_hints.screen_origin_y = -1;
7325 			}
7326 			/* nothing else to do */
7327 		}
7328 		else if (!is_icon_context &&
7329 			 (!fHasContext || !context_window ||
7330 			  !XGetGeometry(
7331 				  dpy, context_window, &JunkRoot, &JunkX,
7332 				  &JunkY, (unsigned int*)&width,
7333 				  (unsigned int*)&height,
7334 				  (unsigned int*)&JunkBW,
7335 				  (unsigned int*)&JunkDepth) ||
7336 			  !XTranslateCoordinates(
7337 				  dpy, context_window, Scr.Root, 0, 0, &x, &y,
7338 				  &JunkChild)))
7339 		{
7340 			/* no window or could not get geometry */
7341 			if (FQueryPointer(
7342 				    dpy,Scr.Root, &JunkRoot, &JunkChild, &x,
7343 				    &y, &JunkX, &JunkY, &JunkMask) == False)
7344 			{
7345 				/* pointer is on a different screen - that's
7346 				 * okay here */
7347 				x = 0;
7348 				y = 0;
7349 			}
7350 			width = height = 1;
7351 			if (!pops->pos_hints.has_screen_origin)
7352 			{
7353 				/* xinerama: use screen with pinter as
7354 				 * reference */
7355 				pops->pos_hints.has_screen_origin = 1;
7356 				pops->pos_hints.screen_origin_x = x;
7357 				pops->pos_hints.screen_origin_y = y;
7358 			}
7359 		}
7360 		else
7361 		{
7362 			if (is_icon_context)
7363 			{
7364 				x = icon_g.x;
7365 				y = icon_g.y;
7366 				width = icon_g.width;
7367 				height = icon_g.height;
7368 			}
7369 			/* we have a context window */
7370 			if (fUseItemOffset)
7371 			{
7372 				y += MI_Y_OFFSET(mi);
7373 				height = MI_HEIGHT(mi);
7374 			}
7375 			if (!pops->pos_hints.has_screen_origin)
7376 			{
7377 				pops->pos_hints.has_screen_origin = 1;
7378 				if (fXineramaRoot)
7379 				{
7380 					/* use whole screen */
7381 					pops->pos_hints.screen_origin_x = -1;
7382 					pops->pos_hints.screen_origin_y = -1;
7383 				}
7384 				else if (context_window == Scr.Root)
7385 				{
7386 					/* xinerama: use screen that contains
7387 					 * the window center as reference */
7388 					if (!fev_get_evpos_or_query(
7389 						    dpy, context_window, e,
7390 						    &pops->pos_hints.
7391 						    screen_origin_x,
7392 						    &pops->pos_hints.
7393 						    screen_origin_y))
7394 					{
7395 						pops->pos_hints.
7396 							screen_origin_x = 0;
7397 						pops->pos_hints.
7398 							screen_origin_y = 0;
7399 					}
7400 					else
7401 					{
7402 						fscreen_scr_arg fscr;
7403 
7404 						fscr.xypos.x = pops->pos_hints.
7405 							screen_origin_x;
7406 						fscr.xypos.y = pops->pos_hints.
7407 							screen_origin_y;
7408 						FScreenGetScrRect(
7409 							&fscr, FSCREEN_XYPOS,
7410 							&x, &y, &width,
7411 							&height);
7412 					}
7413 				}
7414 				else
7415 				{
7416 					/* xinerama: use screen that contains
7417 					 * the window center as reference */
7418 					pops->pos_hints.screen_origin_x =
7419 						JunkX + width / 2;
7420 					pops->pos_hints.screen_origin_y =
7421 						JunkY + height / 2;
7422 				}
7423 			}
7424 		}
7425 
7426 		/* parse position arguments */
7427 		taction = get_one_menu_position_argument(
7428 			naction, x, width, &(pops->pos_hints.x),
7429 			&(pops->pos_hints.x_offset),
7430 			&(pops->pos_hints.x_factor),
7431 			&(pops->pos_hints.context_x_factor),
7432 			&pops->pos_hints.is_menu_relative);
7433 		if (pops->pos_hints.is_menu_relative)
7434 		{
7435 			pops->pos_hints.x = x;
7436 			if (pops->pos_hints.menu_width == 0 && mr)
7437 			{
7438 				pops->pos_hints.menu_width = MR_WIDTH(mr);
7439 			}
7440 		}
7441 		naction = get_one_menu_position_argument(
7442 			taction, y, height, &(pops->pos_hints.y), &dummy_int,
7443 			&(pops->pos_hints.y_factor), &dummy_float,
7444 			&dummy_flag);
7445 		if (naction == taction)
7446 		{
7447 			/* argument is missing or invalid */
7448 			if (fHasContext)
7449 			{
7450 				fvwm_msg(ERR, "get_menu_options",
7451 					 "invalid position arguments");
7452 			}
7453 			naction = action;
7454 			taction = action;
7455 			break;
7456 		}
7457 		taction = naction;
7458 		pops->flags.has_poshints = 1;
7459 		if (fValidPosHints == True &&
7460 		    pops->pos_hints.is_relative == True)
7461 		{
7462 			pops->pos_hints = last_saved_pos_hints.pos_hints;
7463 		}
7464 		/* we want to do this only once */
7465 		once_more = False;
7466 	} /* while */
7467 	if (is_action_empty)
7468 	{
7469 		if (!pops->pos_hints.has_screen_origin)
7470 		{
7471 			pops->pos_hints.has_screen_origin = 1;
7472 			if (!fev_get_evpos_or_query(
7473 				    dpy, Scr.Root, e,
7474 				    &pops->pos_hints.screen_origin_x,
7475 				    &pops->pos_hints.screen_origin_y))
7476 			{
7477 				pops->pos_hints.screen_origin_x = 0;
7478 				pops->pos_hints.screen_origin_y = 0;
7479 			}
7480 		}
7481 	}
7482 
7483 	if (!pops->flags.has_poshints && fValidPosHints)
7484 	{
7485 		pops->flags.has_poshints = 1;
7486 		pops->pos_hints = last_saved_pos_hints.pos_hints;
7487 		pops->pos_hints.is_relative = False;
7488 	}
7489 
7490 	action = naction;
7491 	/* to keep Purify silent */
7492 	pops->flags.do_select_in_place = 0;
7493 	/* parse additional options */
7494 	while (naction && *naction)
7495 	{
7496 		naction = GetNextToken(action, &tok);
7497 		if (!tok)
7498 		{
7499 			break;
7500 		}
7501 		if (StrEquals(tok, "WarpTitle"))
7502 		{
7503 			pops->flags.do_warp_title = 1;
7504 			pops->flags.do_not_warp = 0;
7505 		}
7506 		else if (StrEquals(tok, "NoWarp"))
7507 		{
7508 			pops->flags.do_warp_title = 0;
7509 			pops->flags.do_not_warp = 1;
7510 		}
7511 		else if (StrEquals(tok, "Fixed"))
7512 		{
7513 			pops->flags.is_fixed = 1;
7514 		}
7515 		else if (StrEquals(tok, "SelectInPlace"))
7516 		{
7517 			pops->flags.do_select_in_place = 1;
7518 		}
7519 		else if (StrEquals(tok, "SelectWarp"))
7520 		{
7521 			pops->flags.do_warp_on_select = 1;
7522 		}
7523 		else if (StrEquals(tok, "TearOffImmediately"))
7524 		{
7525 			pops->flags.do_tear_off_immediately = 1;
7526 		}
7527 		else
7528 		{
7529 			free (tok);
7530 			break;
7531 		}
7532 		action = naction;
7533 		free (tok);
7534 	}
7535 	if (!pops->flags.do_select_in_place)
7536 	{
7537 		pops->flags.do_warp_on_select = 0;
7538 	}
7539 
7540 	return action;
7541 }
7542 
7543 /* ---------------------------- new menu loop code ------------------------- */
7544 
7545 #if 0
7546 /*!!!*/
7547 typedef enum
7548 {
7549 	MTR_XEVENT = 0x1,
7550 	MTR_FAKE_ENTER_ITEM = 0x2,
7551 	MTR_PROPAGATE_XEVENT_UP = 0x4,
7552 	MTR_PROPAGATE_XEVENT_DOWN = 0x8,
7553 	MTR_POPUP_TIMEOUT = 0x10,
7554 	MTR_POPDOWN_TIMEOUT = 0x20
7555 } mloop_trigger_type_t;
7556 
7557 typedef_struct
7558 {
7559 	mloop_trigger_type_t type;
7560 	XEvent trigger_ev;
7561 	int ticks_passed;
7562 } mloop_trigger_t;
7563 
7564 typedef_struct
7565 {
7566 	int popup_10ms;
7567 	int popdown_10ms;
7568 } mloop_timeouts_t;
7569 
7570 typedef struct
7571 {
7572 	MenuRoot *current_menu;
7573 	XEvent *in_ev;
7574 	struct
7575 	{
7576 		unsigned do_fake_enter_item : 1;
7577 		unsigned do_propagete_event_up : 1;
7578 		unsigned do_propagete_event_down : 1;
7579 	} flags;
7580 } mloop_get_trigger_input_t;
7581 
7582 /*!!!static*/
7583 mloop_trigger_type_t __mloop_get_trigger(
7584 	mloop_trigger_t *ret_trigger, mloop_timeouts_t *io_timeouts,
7585 	const mloop_get_trigger_input_t * const in,
7586 	{
7587 		if (in_out->in_flags->do_propagate_event_down)
7588 		{
7589 			return MTR_PROPAGATE_XEVENT_DOWN;
7590 		}
7591 		else if (in_out->in_flags->do_propagate_event_up)
7592 		{
7593 			if (a != b)
7594 			{
7595 				return MTR_PROPAGATE_XEVENT_UP;
7596 			}
7597 			else
7598 			{
7599 			}
7600 			/*!!!return propagate up*/
7601 			/*!!!*/
7602 		}
7603 		/*!!!read event or wait for timeout*/
7604 		while (0/*!!!not finished*/)
7605 		{
7606 			/*!!!rc = 0*/
7607 			if (0/*!!!wait for tiomeout*/)
7608 			{
7609 				/*!!!check for event*/
7610 			}
7611 			else
7612 			{
7613 				/*!!!block for event*/
7614 			}
7615 			if (0/*got event*/)
7616 			{
7617 				/*!!!rc = MTR_XEVENT*/
7618 			}
7619 			if (0/*!!!popup timed out;break*/)
7620 			{
7621 				/*!!!rc = MTR_POPUP;break*/
7622 			}
7623 			if (0/*!!!popdown timed out;break*/)
7624 			{
7625 				/*!!!rc = MTR_POPDOWN;break*/
7626 			}
7627 			/*!!!sleep*/
7628 		}
7629 		if (0/*!!!rc == MTR_XEVENT && evtype == MotionNotify*/)
7630 		{
7631 			/*!!!eat up further MotionNotify events*/
7632 		}
7633 
7634 		return 0/*!!!rc*/;
7635 	}
7636 
7637 	/*!!!static*/
7638 	void __menu_loop_new(
7639 		MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp)
7640 	{
7641 		mloop_evh_input_t mei;
7642 #if 0
7643 		mloop_ret_code_t mloop_ret;
7644 #endif
7645 		mloop_evh_data_t med;
7646 		mloop_static_info_t msi;
7647 		MenuOptions mops;
7648 		Bool is_finished;
7649 
7650 		/*!!!init menu loop*/
7651 		__mloop_init(pmp, pmret, &mei, &med, &msi, &mops);
7652 		for (is_finished = False; !is_finished; )
7653 		{
7654 			mloop_trigger_type_t mtr;
7655 
7656 			mtr = __mloop_get_trigger(
7657 				pmp, pmret, &mei, &med, &msi);
7658 			switch (mtr)
7659 			{
7660 			case MTR_XEVENT:
7661 				/*!!!handle event*/
7662 				break;
7663 			case MTR_FAKE_ENTER_ITEM:
7664 				/*!!!fake enter item*/
7665 				break;
7666 			case MTR_PROPAGATE_XEVENT_UP:
7667 				/*!!!handle propagation*/
7668 				break;
7669 			case MTR_PROPAGATE_XEVENT_DOWN:
7670 				/*!!!handle propagation*/
7671 				break;
7672 			case MTR_POPUP_TIMEOUT:
7673 				/*!!!handle popup*/
7674 				break;
7675 			case MTR_POPDOWN_TIMEOUT:
7676 				/*!!!handle popdown*/
7677 				break;
7678 			}
7679 
7680 
7681 
7682 
7683 
7684 #if 0
7685 			mloop_ret = __mloop_handle_event(
7686 				pmp, pmret, pdkp, &mei, &med, &msi);
7687 			switch (mloop_ret)
7688 			{
7689 			case MENU_MLOOP_RET_LOOP:
7690 				continue;
7691 			case MENU_MLOOP_RET_END:
7692 				is_finished = True;
7693 				break;
7694 			default:
7695 				break;
7696 			}
7697 			/* Now handle new menu items, whether it is from a
7698 			 * keypress or a pointer motion event. */
7699 			if (med.mi != NULL)
7700 			{
7701 				mloop_ret = __mloop_handle_action_with_mi(
7702 					pmp, pmret, pdkp, &mei, &med, &msi,
7703 					&mops);
7704 			}
7705 			else
7706 			{
7707 				mloop_ret = __mloop_handle_action_without_mi(
7708 					pmp, pmret, pdkp, &mei, &med, &msi,
7709 					&mops);
7710 			}
7711 			if (mloop_ret == MENU_MLOOP_RET_END)
7712 			{
7713 				is_finished = True;
7714 			}
7715 			XFlush(dpy);
7716 #endif
7717 		}
7718 		__mloop_exit(pmp, pmret, pdkp, &mei, &med, &msi, &mops);
7719 
7720 		return;
7721 	}
7722 #endif
7723