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 /* ---------------------------- included header files ---------------------- */
17 
18 #include "config.h"
19 
20 #include <stdio.h>
21 #include <X11/keysym.h>
22 
23 #include "libs/Parse.h"
24 #include "libs/Bindings.h"
25 #include "libs/charmap.h"
26 #include "libs/Flocale.h"
27 #include "libs/wcontext.h"
28 #include "fvwm.h"
29 #include "fvwm.h"
30 #include "externs.h"
31 #include "execcontext.h"
32 #include "misc.h"
33 #include "screen.h"
34 #include "bindings.h"
35 
36 #include "move_resize.h"
37 #include "menudim.h"
38 #include "menustyle.h"
39 #include "menuitem.h"
40 #include "menuroot.h"
41 #include "menuparameters.h"
42 #include "menugeometry.h"
43 #include "menubindings.h"
44 
45 /* ---------------------------- local definitions -------------------------- */
46 
47 /* ---------------------------- local macros ------------------------------- */
48 
49 /* ---------------------------- imports ------------------------------------ */
50 
51 /* ---------------------------- included code files ------------------------ */
52 
53 /* ---------------------------- local types -------------------------------- */
54 
55 /* ---------------------------- forward declarations ----------------------- */
56 
57 /* ---------------------------- local variables ---------------------------- */
58 
59 /* menu bindings are kept in a local binding list separate from other
60  * bindings*/
61 static Binding *menu_bindings_regular = NULL;
62 static Binding *menu_bindings_fallback = NULL;
63 static Binding **menu_bindings = NULL;
64 
65 /* ---------------------------- exported variables (globals) --------------- */
66 
67 /* ---------------------------- local functions ---------------------------- */
68 
69 /*
70  * Returns the position of the item in the menu, but counts
71  * only items that can be selected (i.e. nor separators or
72  * titles). The count begins with 0.
73  */
get_selectable_item_index(MenuRoot * mr,MenuItem * mi_target,int * ret_sections)74 static int get_selectable_item_index(
75 	MenuRoot *mr, MenuItem *mi_target, int *ret_sections)
76 {
77 	int i = 0;
78 	int s = 0;
79 	MenuItem *mi;
80 	int is_last_selectable = 0;
81 
82 	for (mi = MR_FIRST_ITEM(mr); mi && mi != mi_target;
83 	     mi = MI_NEXT_ITEM(mi))
84 	{
85 		if (MI_IS_SELECTABLE(mi))
86 		{
87 			i++;
88 			is_last_selectable = 1;
89 		}
90 		else if (is_last_selectable == 1)
91 		{
92 			s++;
93 			is_last_selectable = 0;
94 		}
95 	}
96 	if (ret_sections)
97 	{
98 		*ret_sections = s;
99 	}
100 	if (mi == mi_target)
101 	{
102 		return i;
103 	}
104 
105 	return -1;
106 }
107 
get_selectable_item_from_index(MenuRoot * mr,int index)108 static MenuItem *get_selectable_item_from_index(MenuRoot *mr, int index)
109 {
110 	int i = -1;
111 	MenuItem *mi;
112 	MenuItem *mi_last_ok = NULL;
113 
114 	for (mi = MR_FIRST_ITEM(mr); mi && (i < index || mi_last_ok == NULL);
115 	     mi=MI_NEXT_ITEM(mi))
116 	{
117 		if (MI_IS_SELECTABLE(mi))
118 		{
119 			mi_last_ok = mi;
120 			i++;
121 		}
122 	}
123 
124 	return mi_last_ok;
125 }
126 
get_selectable_item_from_section(MenuRoot * mr,int section)127 static MenuItem *get_selectable_item_from_section(MenuRoot *mr, int section)
128 {
129 	int i = 0;
130 	MenuItem *mi;
131 	MenuItem *mi_last_ok = NULL;
132 	int is_last_selectable = 0;
133 
134 	for (
135 		mi = MR_FIRST_ITEM(mr);
136 		mi && (i <= section || mi_last_ok == NULL);
137 		mi=MI_NEXT_ITEM(mi))
138 	{
139 		if (MI_IS_SELECTABLE(mi))
140 		{
141 			if (!is_last_selectable)
142 			{
143 				mi_last_ok = mi;
144 				is_last_selectable = 1;
145 			}
146 		}
147 		else if (is_last_selectable)
148 		{
149 			i++;
150 			is_last_selectable = 0;
151 		}
152 	}
153 
154 	return mi_last_ok;
155 }
156 
get_selectable_item_count(MenuRoot * mr,int * ret_sections)157 static int get_selectable_item_count(MenuRoot *mr, int *ret_sections)
158 {
159 	int count;
160 
161 	count = get_selectable_item_index(mr, MR_LAST_ITEM(mr), ret_sections);
162 	if (MR_LAST_ITEM(mr) && MI_IS_SELECTABLE(MR_LAST_ITEM(mr)))
163 	{
164 		count++;
165 	}
166 
167 	return count;
168 }
169 
__menu_binding_is_mouse(Binding * blist,XEvent * event,int context)170 static Binding *__menu_binding_is_mouse(
171 	Binding *blist, XEvent* event, int context)
172 {
173 	Binding *b;
174 	int real_mod;
175 
176 	real_mod = event->xbutton.state - (1 << (7 + event->xbutton.button));
177 	for (b = blist; b != NULL; b = b->NextBinding)
178 	{
179 		if (
180 			BIND_IS_MOUSE_BINDING(b->type) &&
181 			(b->Button_Key == 0 ||
182 			 event->xbutton.button == b->Button_Key) &&
183 			(b->Modifier == AnyModifier ||
184 			 MaskUsedModifiers(b->Modifier) ==
185 			 MaskUsedModifiers(real_mod)) &&
186 			(b->Context == C_MENU ||
187 			 (b->Context & context) != C_MENU))
188 		{
189 			break;
190 		}
191 	}
192 
193 	return b;
194 }
195 
parse_menu_action(struct MenuRoot * mr,const char * action,menu_shortcut_action * saction,int * items_to_move,int * do_skip_section,char ** ret_cmd)196 static void parse_menu_action(
197 	struct MenuRoot *mr, const char *action, menu_shortcut_action *saction,
198 	int *items_to_move, int *do_skip_section, char **ret_cmd)
199 {
200 	char *optlist[] = {
201 		"MenuClose",
202 		"MenuEnterContinuation",
203 		"MenuEnterSubmenu",
204 		"MenuLeaveSubmenu",
205 		"MenuMoveCursor",
206 		"MenuCursorLeft",
207 		"MenuCursorRight",
208 		"MenuSelectItem",
209 		"MenuScroll",
210 		"MenuTearOff",
211 		"MenuCloseAndExec",
212 		NULL
213 	};
214 	int index;
215 	char *options;
216 	int num;
217 	int suffix[2];
218 	int count[2];
219 
220 	*ret_cmd = NULL;
221 	options = GetNextTokenIndex((char *)action, optlist, 0, &index);
222 	switch (index)
223 	{
224 	case 0: /* MenuClose */
225 		*saction = SA_ABORT;
226 		break;
227 	case 1: /* MenuEnterContinuation */
228 		*saction = (MR_CONTINUATION_MENU(mr) != NULL) ?
229 			SA_CONTINUE : SA_ENTER;
230 		break;
231 	case 2: /* MenuEnterSubmenu */
232 		*saction = SA_ENTER;
233 		break;
234 	case 3: /* MenuLeaveSubmenu */
235 		*saction = SA_LEAVE;
236 		break;
237 	case 4: /* MenuMoveCursor */
238 		num = GetSuffixedIntegerArguments(options, NULL, count, 2,
239 						  "s", suffix);
240 		if (num == 2)
241 		{
242 			if (suffix[0] != 0 || count[0] != 0)
243 			{
244 				fvwm_msg(ERR, "parse_menu_action",
245 					 "invalid MenuMoveCursor arguments "
246 					 "'%s'", options);
247 				*saction = SA_NONE;
248 				break;
249 			}
250 			if (count[1] < 0)
251 			{
252 				*saction = SA_LAST;
253 				*items_to_move = 1 + count[1];
254 			}
255 			else
256 			{
257 				*saction = SA_FIRST;
258 				*items_to_move = count[1];
259 			}
260 
261 			if (suffix[1] == 1)
262 			{
263 				*do_skip_section = 1;
264 			}
265 		}
266 		else if (num == 1)
267 		{
268 			*saction = SA_MOVE_ITEMS;
269 			*items_to_move = count[0];
270 			if (suffix[0] == 1)
271 			{
272 				*do_skip_section = 1;
273 			}
274 		}
275 		else
276 		{
277 			fvwm_msg(ERR, "parse_menu_action",
278 				 "invalid MenuMoveCursor arguments '%s'",
279 				 options);
280 			*saction = SA_NONE;
281 			break;
282 		}
283 		break;
284 	case 5: /* MenuCursorLeft */
285 		*saction = (MST_USE_LEFT_SUBMENUS(mr)) ? SA_ENTER : SA_LEAVE;
286 		break;
287 	case 6: /* MenuCursorRight */
288 		*saction = (MST_USE_LEFT_SUBMENUS(mr)) ? SA_LEAVE : SA_ENTER;
289 		break;
290 	case 7: /* MenuSelectItem */
291 		*saction = SA_SELECT;
292 		break;
293 	case 8: /* MenuScroll */
294 		if (MST_MOUSE_WHEEL(mr) == MMW_OFF)
295 		{
296 			*saction = SA_SELECT;
297 		}
298 		else
299 		{
300 			num = GetSuffixedIntegerArguments(options, NULL, count,
301 							  1, "s", suffix);
302 			if (num == 1)
303 			{
304 				*saction = SA_SCROLL;
305 				*items_to_move = count[0];
306 				if (suffix[0] == 1)
307 				{
308 					*do_skip_section = 1;
309 				}
310 			}
311 			else
312 			{
313 				fvwm_msg(ERR, "parse_menu_action",
314 					 "invalid MenuScroll arguments '%s'",
315 					 options);
316 				*saction = SA_NONE;
317 				break;
318 			}
319 		}
320 		break;
321 	case 9: /* MenuTearOff */
322 		*saction = SA_TEAROFF;
323 		break;
324 	case 10: /* MenuCloseAndExecute */
325 		*saction = SA_EXEC_CMD;
326 		*ret_cmd = options;
327 		break;
328 	default:
329 		fvwm_msg(
330 			ERR, "parse_menu_action", "unknown action '%s'",
331 			action);
332 		*saction = SA_NONE;
333 	}
334 
335 	return;
336 }
337 
__menu_binding_is_key(Binding * blist,XEvent * event,int context)338 static Binding *__menu_binding_is_key(
339 	Binding *blist, XEvent* event, int context)
340 {
341 	Binding *b;
342 
343 	for (b = blist; b != NULL; b = b->NextBinding)
344 	{
345 		if (
346 			BIND_IS_KEY_BINDING(b->type) &&
347 			event->xkey.keycode == b->Button_Key &&
348 			(b->Modifier == AnyModifier ||
349 			 MaskUsedModifiers(b->Modifier) ==
350 			 MaskUsedModifiers(event->xkey.state)) &&
351 			(b->Context == C_MENU ||
352 			 (b->Context & context) != C_MENU))
353 		{
354 			break;
355 		}
356 	}
357 
358 	return b;
359 }
360 
361 /* ---------------------------- interface functions ------------------------ */
362 
menu_bindings_startup_complete(void)363 void menu_bindings_startup_complete(void)
364 {
365 	menu_bindings = &menu_bindings_regular;
366 
367 	return;
368 }
369 
menu_binding_is_mouse(XEvent * event,int context)370 Binding *menu_binding_is_mouse(XEvent* event, int context)
371 {
372 	Binding *b;
373 
374 	b = __menu_binding_is_mouse(menu_bindings_regular, event, context);
375 	if (b == NULL)
376 	{
377 		b = __menu_binding_is_mouse(
378 			menu_bindings_fallback, event, context);
379 	}
380 
381 	return b;
382 }
383 
menu_binding_is_key(XEvent * event,int context)384 Binding *menu_binding_is_key(XEvent* event, int context)
385 {
386 	Binding *b;
387 
388 	b = __menu_binding_is_key(menu_bindings_regular, event, context);
389 	if (b == NULL)
390 	{
391 		b = __menu_binding_is_key(
392 			menu_bindings_fallback, event, context);
393 	}
394 
395 	return b;
396 }
397 
menu_binding(Display * dpy,binding_t type,int button,KeySym keysym,int context,int modifier,char * action,char * menu_style)398 int menu_binding(
399 	Display *dpy, binding_t type, int button, KeySym keysym,
400 	int context, int modifier, char *action, char *menu_style)
401 {
402 	Binding *rmlist;
403 	int rc;
404 	Bool dummy;
405 
406 	if (menu_bindings == NULL)
407 	{
408 		menu_bindings = &menu_bindings_fallback;
409 	}
410 	rmlist = NULL;
411 	if (~(~context | C_MENU | C_TITLE | C_MENU_ITEM | C_SIDEBAR) != 0)
412 	{
413 		fvwm_msg(
414 			ERR, "menu_binding",
415 			"invalid context in combination with menu context.");
416 
417 		return 1;
418 	}
419 	if (menu_style != NULL)
420 	{
421 		/*!!! fixme - make either match a menu style or a menu name */
422 		fvwm_msg(
423 			ERR, "menu_binding", "a window name may not be"
424 			" specified without a menu context.");
425 
426 		return 1;
427 	}
428 	/*
429 	 * Remove the "old" bindings if any
430 	 */
431 	/* BEGIN remove */
432 	CollectBindingList(
433 		dpy, menu_bindings, &rmlist, &dummy, type, STROKE_ARG(NULL)
434 		button, keysym, modifier, context, menu_style);
435 	if (rmlist != NULL)
436 	{
437 		FreeBindingList(rmlist);
438 	}
439 	else if (
440 		keysym == 0 && button != 0 && modifier == 0 &&
441 		strcmp(action,"-") == 0 && context == C_MENU)
442 	{
443 		/* Warn if Mouse n M N - occurs without removing any binding.
444 		 The user most likely want Mouse n MT A - instead. */
445 		fvwm_msg(
446 			WARN, "menu_binding",
447 			"The syntax for disabling the tear off button has "
448 			"changed.");
449 	}
450 	if (strcmp(action,"-") == 0)
451 	{
452 		return 0;
453 	}
454 	/* END remove */
455 	if ((modifier & AnyModifier) && (modifier & (~AnyModifier)))
456 	{
457 		fvwm_msg(
458 			WARN, "menu_binding", "Binding specified AnyModifier"
459 			" and other modifers too. Excess modifiers are"
460 			" ignored.");
461 		modifier = AnyModifier;
462 	}
463 	/* Warn about Mouse n M N TearOff. */
464 	if (
465 		keysym == 0 && button != 0 && modifier == 0 &&
466 		strcasecmp(action,"tearoff") == 0 && context == C_MENU)
467 	{
468 		fvwm_msg(OLD, "menu_binding",
469 			 "The syntax for disabling the tear off button has "
470 			 "changed. The TearOff action is no longer possible "
471 			 "in menu bindings.");
472 	}
473 	rc = AddBinding(
474 		dpy, menu_bindings, type, STROKE_ARG(NULL) button, keysym,
475 		NULL, modifier, context, (void *)action, NULL, menu_style);
476 
477 	return rc;
478 }
479 
menu_shortcuts(struct MenuRoot * mr,struct MenuParameters * pmp,struct MenuReturn * pmret,XEvent * event,struct MenuItem ** pmi_current,double_keypress * pdkp,int * ret_menu_x,int * ret_menu_y)480 void menu_shortcuts(
481 	struct MenuRoot *mr, struct MenuParameters *pmp,
482 	struct MenuReturn *pmret, XEvent *event, struct MenuItem **pmi_current,
483 	double_keypress *pdkp, int *ret_menu_x, int *ret_menu_y)
484 {
485 	int with_control;
486 	int with_shift;
487 	int with_meta;
488 	KeySym keysym;
489 	char ckeychar;
490 	int ikeychar;
491 	MenuItem *new_item;
492 	MenuItem *mi_current;
493 	int index;
494 	int mx;
495 	int my;
496 	int menu_x;
497 	int menu_y;
498 	int menu_width;
499 	int menu_height;
500 	int items_to_move;
501 	int do_skip_section;
502 	menu_shortcut_action saction;
503 	Binding *binding;
504 	int context;
505 	int is_geometry_known;
506 	char *command;
507 
508 	ckeychar = 0;
509 	new_item = NULL;
510 	mi_current = pmi_current ? *pmi_current : NULL;
511 	do_skip_section = 0;
512 	saction = SA_NONE;
513 	context = C_MENU;
514 	is_geometry_known = 0;
515 	context = C_MENU;
516 	command = 0;
517 	if (mi_current)
518 	{
519 		if (MI_IS_TITLE(mi_current))
520 		{
521 			context |= C_TITLE;
522 		}
523 		else
524 		{
525 			/* menu item context, use I (icon) for it */
526 			context |= C_MENU_ITEM;
527 		}
528 	}
529 	else
530 	{
531 		if (
532 			menu_get_geometry(
533 				mr, &JunkRoot, &menu_x, &menu_y, &menu_width,
534 				&menu_height, &JunkBW, &JunkDepth))
535 		{
536 			is_geometry_known = 1;
537 			if (
538 				FQueryPointer(
539 					dpy, Scr.Root, &JunkRoot, &JunkChild,
540 					&mx, &my, &JunkX, &JunkY, &JunkMask) ==
541 				0)
542 			{
543 				/* pointer is on a different screen */
544 				mx = 0;
545 				my = 0;
546 			}
547 			else if (
548 				mx >= menu_x && mx < menu_x + menu_width &&
549 				my >= menu_y && my < menu_y + menu_height)
550 			{
551 				/* pointer is on the meny somewhere not over
552 				 * an item */
553 				if (my < menu_y + MST_BORDER_WIDTH(mr))
554 				{
555 					/* upper border context (-)*/
556 					context |= C_SB_TOP;
557 				}
558 				else if (
559 					my >= menu_y + menu_height -
560 					MST_BORDER_WIDTH(mr))
561 				{
562 					/* lower border context (_) */
563 					context |= C_SB_BOTTOM;
564 				}
565 				else if (mx < menu_x + MR_ITEM_X_OFFSET(mr))
566 				{
567 					/* left border or left sidepic */
568 					context |= C_SB_LEFT;
569 				}
570 				else if (mx < menu_x + MST_BORDER_WIDTH(mr))
571 				{
572 					/* left border context ([)*/
573 					context |= C_SB_LEFT;
574 				}
575 				else if (
576 					mx >= menu_x + MR_ITEM_X_OFFSET(mr) +
577 					MR_ITEM_WIDTH(mr))
578 				{
579 					/* right sidepic or right border */
580 					context |= C_SB_RIGHT;
581 				}
582 				else if (
583 					mx >= menu_x + menu_width -
584 					MST_BORDER_WIDTH(mr))
585 				{
586 					/* right border context (])*/
587 					context |= C_SB_RIGHT;
588 				}
589 			}
590 		}
591 		else
592 		{
593 			fvwm_msg(
594 				ERR, "menu_shortcuts", "can't get geometry of"
595 				" menu %s", MR_NAME(mr));
596 		}
597 	}
598 
599 	if (event->type == KeyRelease)
600 	{
601 		/* This function is only called with a KeyRelease event if the
602 		 * user released the 'select' key (s)he configured. */
603 		pmret->rc = MENU_SELECTED;
604 
605 		return;
606 	}
607 
608 	items_to_move = 0;
609 	pmret->rc = MENU_NOP;
610 
611 	/*** handle mouse events ***/
612 	if (event->type == ButtonRelease)
613 	{
614 	        /*** Read the control keys stats ***/
615 		with_control = event->xbutton.state & ControlMask ? 1 : 0;
616 		with_shift = event->xbutton.state & ShiftMask ? 1: 0;
617 		with_meta = event->xbutton.state & Mod1Mask ? 1: 0;
618 		/** handle menu bindings **/
619 		binding = menu_binding_is_mouse(event, context);
620 		if (binding != NULL)
621 		{
622 			parse_menu_action(
623 				mr, binding->Action, &saction, &items_to_move,
624 				&do_skip_section, &command);
625 		}
626 		index = 0;
627 		ikeychar = 0;
628 	}
629 	else /* Should be KeyPressed */
630 	{
631 	        /*** Read the control keys stats ***/
632 		with_control = event->xkey.state & ControlMask? 1 : 0;
633 		with_shift = event->xkey.state & ShiftMask? 1: 0;
634 		with_meta = event->xkey.state & Mod1Mask? 1: 0;
635 
636 		/*** handle double-keypress ***/
637 
638 		if (pdkp->timestamp &&
639 		    fev_get_evtime() - pdkp->timestamp <
640 		    MST_DOUBLE_CLICK_TIME(pmp->menu) &&
641 		    event->xkey.state == pdkp->keystate &&
642 		    event->xkey.keycode == pdkp->keycode)
643 		{
644 			*pmi_current = NULL;
645 			pmret->rc = MENU_DOUBLE_CLICKED;
646 
647 			return;
648 		}
649 		pdkp->timestamp = 0;
650 
651 		/*** find out the key ***/
652 
653 		/* Is it okay to treat keysym-s as Ascii?
654 		 * No, because the keypad numbers don't work.
655 		 * Use XlookupString */
656 		index = XLookupString(&(event->xkey), &ckeychar, 1, &keysym,
657 				      NULL);
658 		ikeychar = (int)ckeychar;
659 	}
660 	/*** Try to match hot keys ***/
661 
662 	/* Need isascii here - isgraph might coredump! */
663 	if (index == 1 && isascii(ikeychar) && isgraph(ikeychar) &&
664 	    with_control == 0 && with_meta == 0)
665 	{
666 		/* allow any printable character to be a keysym, but be sure
667 		 * control isn't pressed */
668 		MenuItem *mi;
669 		MenuItem *mi1;
670 		int key;
671 		int countHotkey = 0;
672 
673 		/* if this is a letter set it to lower case */
674 		if (isupper(ikeychar))
675 		{
676 			ikeychar = tolower(ikeychar) ;
677 		}
678 
679 		/* MMH mikehan@best.com 2/7/99
680 		 * Multiple hotkeys per menu
681 		 * Search menu for matching hotkey;
682 		 * remember how many we found and where we found it */
683 		mi = (mi_current == NULL || mi_current == MR_LAST_ITEM(mr)) ?
684 			MR_FIRST_ITEM(mr) : MI_NEXT_ITEM(mi_current);
685 		mi1 = mi;
686 		do
687 		{
688 			if (MI_HAS_HOTKEY(mi) && !MI_IS_TITLE(mi) &&
689 			    (!MI_IS_HOTKEY_AUTOMATIC(mi) ||
690 			     MST_USE_AUTOMATIC_HOTKEYS(mr)))
691 			{
692 				key = (MI_LABEL(mi)[(int)MI_HOTKEY_COLUMN(mi)])
693 					[MI_HOTKEY_COFFSET(mi)];
694 				key = tolower(key);
695 				if (ikeychar == key)
696 				{
697 					if (++countHotkey == 1)
698 					{
699 						new_item = mi;
700 					}
701 				}
702 			}
703 			mi = (mi == MR_LAST_ITEM(mr)) ?
704 				MR_FIRST_ITEM(mr) : MI_NEXT_ITEM(mi);
705 		}
706 		while (mi != mi1);
707 
708 		/* For multiple instances of a single hotkey, just move the
709 		 * selection */
710 		/* TA:  2011-07-24:  But if the user has turned off
711 		 * "UniqueHotkeyActivatedImmediate", keep the menu open until
712 		 * the user has asked for that entry to be enacted.  This also
713 		 * implies the style "TitleWarpOff" and we're not over a popup
714 		 * item, in which case the pointer is warped to the submenu in
715 		 * the usual way.
716 		 */
717 		if ((countHotkey > 1) || (countHotkey >=1 && (
718 					  (!MST_DO_WARP_TO_TITLE(mr) ||
719 					   !MI_IS_POPUP(new_item))
720 					  ) &&
721 					  !MST_HOTKEY_ACTIVATES_IMMEDIATE(mr)))
722 		{
723 			*pmi_current = new_item;
724 			pmret->rc = MENU_NEWITEM;
725 
726 			return;
727 		}
728 		/* Do things the old way for unique hotkeys in the menu */
729 		else if (countHotkey == 1)
730 		{
731 			*pmi_current = new_item;
732 			if (new_item && MI_IS_POPUP(new_item))
733 			{
734 				pmret->rc = MENU_POPUP;
735 			}
736 			else
737 			{
738 				pmret->rc = MENU_SELECTED;
739 			}
740 
741 			return;
742 		}
743 		/* MMH mikehan@best.com 2/7/99 */
744 	}
745 
746 	/*** now determine the action to take ***/
747 
748 	/** handle menu key bindings **/
749 	if (
750 		event->type == KeyPress && keysym == XK_Escape &&
751 		with_control == 0 && with_shift == 0 &&
752 		with_meta == 0)
753 	{
754 		/* Don't allow override of Escape with no modifiers */
755 		saction = SA_ABORT;
756 
757 	}
758 	else if (event->type == KeyPress)
759 	{
760 		binding = menu_binding_is_key(event, context);
761 		if (binding != NULL)
762 		{
763 			parse_menu_action(
764 				mr, binding->Action, &saction, &items_to_move,
765 				&do_skip_section, &command);
766 		}
767 	}
768 
769 	if (
770 		!mi_current &&
771 		(saction == SA_ENTER || saction == SA_MOVE_ITEMS ||
772 		 saction == SA_SELECT || saction == SA_SCROLL))
773 	{
774 		if (is_geometry_known)
775 		{
776 			if (my < menu_y + MST_BORDER_WIDTH(mr))
777 			{
778 				saction = SA_FIRST;
779 			}
780 			else if (my > menu_y + menu_height -
781 				 MST_BORDER_WIDTH(mr))
782 			{
783 				saction = SA_LAST;
784 			}
785 			else
786 			{
787 				saction = SA_WARPBACK;
788 			}
789 		}
790 		else
791 		{
792 			saction = SA_FIRST;
793 		}
794 	}
795 
796 	/*** execute the necessary actions ***/
797 
798 	switch (saction)
799 	{
800 	case SA_ENTER:
801 		if (mi_current && MI_IS_POPUP(mi_current))
802 		{
803 			pmret->rc = MENU_POPUP;
804 		}
805 		else
806 		{
807 			pmret->rc = MENU_NOP;
808 		}
809 		break;
810 
811 	case SA_LEAVE:
812 		pmret->rc =
813 			(MR_IS_TEAR_OFF_MENU(mr)) ? MENU_NOP : MENU_POPDOWN;
814 		break;
815 	case SA_FIRST:
816 		if (do_skip_section)
817 		{
818 			*pmi_current = get_selectable_item_from_section(
819 				mr, items_to_move);
820 		}
821 		else
822 		{
823 			*pmi_current = get_selectable_item_from_index(
824 				mr, items_to_move);
825 		}
826 		if (*pmi_current != NULL)
827 		{
828 			pmret->rc = MENU_NEWITEM;
829 		}
830 		else
831 		{
832 			pmret->rc = MENU_NOP;
833 		}
834 		break;
835 	case SA_LAST:
836 		if (do_skip_section)
837 		{
838 			get_selectable_item_count(mr, &index);
839 			index += items_to_move;
840 			if (index < 0)
841 			{
842 				index = 0;
843 			}
844 			*pmi_current = get_selectable_item_from_section(
845 				mr, index);
846 			if (*pmi_current != NULL)
847 			{
848 				pmret->rc = MENU_NEWITEM;
849 			}
850 			else
851 			{
852 				pmret->rc = MENU_NOP;
853 			}
854 		}
855 		else
856 		{
857 			index = get_selectable_item_count(mr, NULL);
858 			if (index > 0)
859 			{
860 				index += items_to_move;
861 				if (index < 0)
862 				{
863 					index = 0;
864 				}
865 				*pmi_current = get_selectable_item_from_index(
866 					mr, index);
867 				pmret->rc = (*pmi_current) ?
868 					MENU_NEWITEM : MENU_NOP;
869 			}
870 			else
871 			{
872 				pmret->rc = MENU_NOP;
873 			}
874 		}
875 		break;
876 	case SA_MOVE_ITEMS:
877 		if (do_skip_section)
878 		{
879 			int section;
880 			int count;
881 
882 			get_selectable_item_count(mr, &count);
883 			get_selectable_item_index(mr, mi_current, &section);
884 			section += items_to_move;
885 			if (section < 0)
886 				section = count;
887 			else if (section > count)
888 				section = 0;
889 			index = section;
890 		}
891 		else if (items_to_move < 0)
892 		{
893 			index = get_selectable_item_index(
894 				mr, mi_current, NULL);
895 			if (index == 0)
896 				/* wraparound */
897 				index = get_selectable_item_count(mr, NULL);
898 			else
899 			{
900 				index += items_to_move;
901 			}
902 		}
903 		else
904 		{
905 			index = get_selectable_item_index(
906 				mr, mi_current, NULL) +
907 				items_to_move;
908 			/* correct for the case that we're between items */
909 			if (!MI_IS_SELECTABLE(mi_current))
910 			{
911 				index--;
912 			}
913 		}
914 		if (do_skip_section)
915 		{
916 			new_item = get_selectable_item_from_section(mr, index);
917 		}
918 		else
919 		{
920 			new_item = get_selectable_item_from_index(mr, index);
921 			if (items_to_move > 0 && new_item == mi_current)
922 			{
923 				new_item =
924 					get_selectable_item_from_index(mr, 0);
925 			}
926 		}
927 		if (new_item)
928 		{
929 			*pmi_current = new_item;
930 			pmret->rc = MENU_NEWITEM;
931 		}
932 		else
933 		{
934 			pmret->rc = MENU_NOP;
935 		}
936 		break;
937 	case SA_CONTINUE:
938 		*pmi_current = MR_LAST_ITEM(mr);
939 		if (*pmi_current && MI_IS_POPUP(*pmi_current))
940 		{
941 			/* enter the submenu */
942 			pmret->rc = MENU_POPUP;
943 		}
944 		else
945 		{
946 			/* do nothing */
947 			*pmi_current = mi_current;
948 			pmret->rc = MENU_NOP;
949 		}
950 		break;
951 	case SA_WARPBACK:
952 		/* Warp the pointer back into the menu. */
953 		FWarpPointer(
954 			dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0,
955 			menudim_middle_x_offset(&MR_DIM(mr)), my - menu_y);
956 		pmret->rc = MENU_NEWITEM_FIND;
957 		break;
958 	case SA_SELECT:
959 		pmret->rc = MENU_SELECTED;
960 		return;
961 	case SA_ABORT:
962 		pmret->rc =
963 			(MR_IS_TEAR_OFF_MENU(mr)) ?
964 			MENU_KILL_TEAR_OFF_MENU : MENU_ABORTED;
965 		return;
966 	case SA_TEAROFF:
967 		pmret->rc =
968 			(MR_IS_TEAR_OFF_MENU(mr)) ? MENU_NOP : MENU_TEAR_OFF;
969 		return;
970 	case SA_SCROLL:
971 		if (MST_MOUSE_WHEEL(mr) == MMW_MENU)
972 		{
973 			items_to_move *= -1;
974 		}
975 		if (
976 			!menu_get_outer_geometry(
977 				mr, pmp, &JunkRoot, &menu_x, &menu_y,
978 				&JunkWidth, &menu_height,
979 				&JunkBW, &JunkDepth))
980 		{
981 			fvwm_msg(
982 				ERR, "menu_shortcuts",
983 				"can't get geometry of menu %s", MR_NAME(mr));
984 			return;
985 		}
986 		if (do_skip_section)
987 		{
988 			int count;
989 
990 			get_selectable_item_count(mr, &count);
991 			get_selectable_item_index(mr, mi_current, &index);
992 			index += items_to_move;
993 			if (index < 0)
994 			{
995 				index = 0;
996 			}
997 			else if (index > count)
998 			{
999 				index = count;
1000 			}
1001 			new_item = get_selectable_item_from_section(mr, index);
1002 		}
1003 		else
1004 		{
1005 			index = get_selectable_item_index(
1006 				mr, mi_current, NULL);
1007 			if (items_to_move > 0 && !MI_IS_SELECTABLE(mi_current))
1008 			{
1009 				index--;
1010 			}
1011 			index += items_to_move;
1012 			new_item = get_selectable_item_from_index(mr, index);
1013 		}
1014 
1015 		if (
1016 			new_item &&
1017 			((items_to_move < 0 &&
1018 			  MI_Y_OFFSET(new_item) > MI_Y_OFFSET(mi_current)) ||
1019 			 (items_to_move > 0 &&
1020 			  MI_Y_OFFSET(new_item) < MI_Y_OFFSET(mi_current))))
1021 		{
1022 			/* never scroll in the "wrong" direction */
1023 			new_item = NULL;
1024 		}
1025 
1026 		if (new_item)
1027 		{
1028 			*pmi_current = new_item;
1029 			pmret->rc = MENU_NEWITEM;
1030 			/* Have to work with relative positions or tear off
1031 			 * menus will be hard to reposition */
1032 			if (
1033 				FQueryPointer(
1034 					dpy, MR_WINDOW(mr), &JunkRoot,
1035 					&JunkChild, &JunkX, &JunkY, &mx, &my,
1036 					&JunkMask) ==  0)
1037 			{
1038 				/* This should not happen */
1039 			    	mx = 0;
1040 				my = 0;
1041 			}
1042 
1043 			if (MST_MOUSE_WHEEL(mr) == MMW_POINTER)
1044 			{
1045 				if (event->type == ButtonRelease)
1046 				{
1047 
1048 				  	FWarpPointer(
1049 						dpy, 0, 0, 0, 0, 0, 0, 0,
1050 						-my +
1051 						menuitem_middle_y_offset(
1052 							new_item,
1053 							MR_STYLE(mr)));
1054 				}
1055 				/* pointer wrapped elsewhere for key events */
1056 			}
1057 			else
1058 			{
1059 				int old_y = menu_y;
1060 
1061 				menu_y += my - menuitem_middle_y_offset(
1062 					new_item, MR_STYLE(mr));
1063 
1064 				if (
1065 					!MST_SCROLL_OFF_PAGE(mr) &&
1066 					menu_height < MR_SCREEN_HEIGHT(mr))
1067 				{
1068 					if (menu_y < 0)
1069 					{
1070 						FWarpPointer(dpy, 0, 0, 0, 0,
1071 							     0, 0, 0,-menu_y);
1072 						menu_y=0;
1073 					}
1074 
1075 					if (
1076 						menu_y + menu_height >
1077 						MR_SCREEN_HEIGHT(mr))
1078 					{
1079 						FWarpPointer(
1080 							dpy, 0, 0, 0, 0, 0, 0,
1081 							0,
1082 							MR_SCREEN_HEIGHT(mr) -
1083 							menu_y - menu_height);
1084 						menu_y = MR_SCREEN_HEIGHT(mr) -
1085 							menu_height;
1086 					}
1087 				}
1088 				if (old_y != menu_y)
1089 				{
1090 					pmret->rc = MENU_NEWITEM_MOVEMENU;
1091 					*ret_menu_x = menu_x;
1092 					*ret_menu_y = menu_y;
1093 				}
1094 				else
1095 				{
1096 					pmret->rc = MENU_NEWITEM;
1097 				}
1098 			}
1099 		}
1100 		else
1101 		{
1102 			pmret->rc = MENU_NOP;
1103 		}
1104 		break;
1105 	case SA_EXEC_CMD:
1106 		pmret->rc = MENU_EXEC_CMD;
1107 		*pmp->ret_paction = command;
1108 		break;
1109 	case SA_NONE:
1110 	default:
1111 		pmret->rc = MENU_NOP;
1112 		break;
1113 	}
1114 	if (saction != SA_SCROLL && pmret->rc == MENU_NEWITEM)
1115 	{
1116 		if (!menu_get_outer_geometry(
1117 			    mr, pmp, &JunkRoot, &menu_x, &menu_y,
1118 			    &JunkWidth, &menu_height,
1119 			    &JunkBW, &JunkDepth))
1120 		{
1121 			fvwm_msg(
1122 				ERR, "menu_shortcuts",
1123 				"can't get geometry of menu %s", MR_NAME(mr));
1124 			return;
1125 		}
1126 		if (menu_y < 0 || menu_y + menu_height > MR_SCREEN_HEIGHT(mr))
1127 		{
1128 			menu_y = (menu_y < 0) ?
1129 				0 : MR_SCREEN_HEIGHT(mr) - menu_height;
1130 			pmret->rc = MENU_NEWITEM_MOVEMENU;
1131 			*ret_menu_x = menu_x;
1132 			*ret_menu_y = menu_y;
1133 		}
1134 	}
1135 
1136 	return;
1137 }
1138