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, §ion);
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