Fix segfault setting MenuFace pixmap style for menus.
[fvwm.git] / fvwm / menubindings.c
blob62accf3f131053554fa976d84245d91b609b945b
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.
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.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 /* ---------------------------- included header files ---------------------- */
19 #include "config.h"
21 #include <stdio.h>
22 #include <X11/keysym.h>
24 #include "libs/Parse.h"
25 #include "libs/Bindings.h"
26 #include "libs/charmap.h"
27 #include "libs/Flocale.h"
28 #include "libs/wcontext.h"
29 #include "fvwm.h"
30 #include "fvwm.h"
31 #include "externs.h"
32 #include "execcontext.h"
33 #include "misc.h"
34 #include "screen.h"
35 #include "bindings.h"
37 #include "move_resize.h"
38 #include "menudim.h"
39 #include "menustyle.h"
40 #include "menuitem.h"
41 #include "menuroot.h"
42 #include "menuparameters.h"
43 #include "menugeometry.h"
44 #include "menubindings.h"
46 /* ---------------------------- local definitions -------------------------- */
48 /* ---------------------------- local macros ------------------------------- */
50 /* ---------------------------- imports ------------------------------------ */
52 /* ---------------------------- included code files ------------------------ */
54 /* ---------------------------- local types -------------------------------- */
56 /* ---------------------------- forward declarations ----------------------- */
58 /* ---------------------------- local variables ---------------------------- */
60 /* menu bindings are kept in a local binding list separate from other
61 * bindings*/
62 static Binding *menu_bindings_regular = NULL;
63 static Binding *menu_bindings_fallback = NULL;
64 static Binding **menu_bindings = NULL;
66 /* ---------------------------- exported variables (globals) --------------- */
68 /* ---------------------------- local functions ---------------------------- */
71 * Returns the position of the item in the menu, but counts
72 * only items that can be selected (i.e. nor separators or
73 * titles). The count begins with 0.
75 static int get_selectable_item_index(
76 MenuRoot *mr, MenuItem *mi_target, int *ret_sections)
78 int i = 0;
79 int s = 0;
80 MenuItem *mi;
81 int is_last_selectable = 0;
83 for (mi = MR_FIRST_ITEM(mr); mi && mi != mi_target;
84 mi = MI_NEXT_ITEM(mi))
86 if (MI_IS_SELECTABLE(mi))
88 i++;
89 is_last_selectable = 1;
91 else if (is_last_selectable == 1)
93 s++;
94 is_last_selectable = 0;
97 if (ret_sections)
99 *ret_sections = s;
101 if (mi == mi_target)
103 return i;
106 return -1;
109 static MenuItem *get_selectable_item_from_index(MenuRoot *mr, int index)
111 int i = -1;
112 MenuItem *mi;
113 MenuItem *mi_last_ok = NULL;
115 for (mi = MR_FIRST_ITEM(mr); mi && (i < index || mi_last_ok == NULL);
116 mi=MI_NEXT_ITEM(mi))
118 if (MI_IS_SELECTABLE(mi))
120 mi_last_ok = mi;
121 i++;
125 return mi_last_ok;
128 static MenuItem *get_selectable_item_from_section(MenuRoot *mr, int section)
130 int i = 0;
131 MenuItem *mi;
132 MenuItem *mi_last_ok = NULL;
133 int is_last_selectable = 0;
135 for (
136 mi = MR_FIRST_ITEM(mr);
137 mi && (i <= section || mi_last_ok == NULL);
138 mi=MI_NEXT_ITEM(mi))
140 if (MI_IS_SELECTABLE(mi))
142 if (!is_last_selectable)
144 mi_last_ok = mi;
145 is_last_selectable = 1;
148 else if (is_last_selectable)
150 i++;
151 is_last_selectable = 0;
155 return mi_last_ok;
158 static int get_selectable_item_count(MenuRoot *mr, int *ret_sections)
160 int count;
162 count = get_selectable_item_index(mr, MR_LAST_ITEM(mr), ret_sections);
163 if (MR_LAST_ITEM(mr) && MI_IS_SELECTABLE(MR_LAST_ITEM(mr)))
165 count++;
168 return count;
171 static Binding *__menu_binding_is_mouse(
172 Binding *blist, XEvent* event, int context)
174 Binding *b;
175 int real_mod;
177 real_mod = event->xbutton.state - (1 << (7 + event->xbutton.button));
178 for (b = blist; b != NULL; b = b->NextBinding)
180 if (
181 BIND_IS_MOUSE_BINDING(b->type) &&
182 (b->Button_Key == 0 ||
183 event->xbutton.button == b->Button_Key) &&
184 (b->Modifier == AnyModifier ||
185 MaskUsedModifiers(b->Modifier) ==
186 MaskUsedModifiers(real_mod)) &&
187 (b->Context == C_MENU ||
188 (b->Context & context) != C_MENU))
190 break;
194 return b;
197 static void parse_menu_action(
198 struct MenuRoot *mr, const char *action, menu_shortcut_action *saction,
199 int *items_to_move, int *do_skip_section, char **ret_cmd)
201 char *optlist[] = {
202 "MenuClose",
203 "MenuEnterContinuation",
204 "MenuEnterSubmenu",
205 "MenuLeaveSubmenu",
206 "MenuMoveCursor",
207 "MenuCursorLeft",
208 "MenuCursorRight",
209 "MenuSelectItem",
210 "MenuScroll",
211 "MenuTearOff",
212 "MenuCloseAndExec",
213 NULL
215 int index;
216 char *options;
217 int num;
218 int suffix[2];
219 int count[2];
221 *ret_cmd = NULL;
222 options = GetNextTokenIndex((char *)action, optlist, 0, &index);
223 switch (index)
225 case 0: /* MenuClose */
226 *saction = SA_ABORT;
227 break;
228 case 1: /* MenuEnterContinuation */
229 *saction = (MR_CONTINUATION_MENU(mr) != NULL) ?
230 SA_CONTINUE : SA_ENTER;
231 break;
232 case 2: /* MenuEnterSubmenu */
233 *saction = SA_ENTER;
234 break;
235 case 3: /* MenuLeaveSubmenu */
236 *saction = SA_LEAVE;
237 break;
238 case 4: /* MenuMoveCursor */
239 num = GetSuffixedIntegerArguments(options, NULL, count, 2,
240 "s", suffix);
241 if (num == 2)
243 if (suffix[0] != 0 || count[0] != 0)
245 fvwm_msg(ERR, "parse_menu_action",
246 "invalid MenuMoveCursor arguments "
247 "'%s'", options);
248 *saction = SA_NONE;
249 break;
251 if (count[1] < 0)
253 *saction = SA_LAST;
254 *items_to_move = 1 + count[1];
256 else
258 *saction = SA_FIRST;
259 *items_to_move = count[1];
262 if (suffix[1] == 1)
264 *do_skip_section = 1;
267 else if (num == 1)
269 *saction = SA_MOVE_ITEMS;
270 *items_to_move = count[0];
271 if (suffix[0] == 1)
273 *do_skip_section = 1;
276 else
278 fvwm_msg(ERR, "parse_menu_action",
279 "invalid MenuMoveCursor arguments '%s'",
280 options);
281 *saction = SA_NONE;
282 break;
284 break;
285 case 5: /* MenuCursorLeft */
286 *saction = (MST_USE_LEFT_SUBMENUS(mr)) ? SA_ENTER : SA_LEAVE;
287 break;
288 case 6: /* MenuCursorRight */
289 *saction = (MST_USE_LEFT_SUBMENUS(mr)) ? SA_LEAVE : SA_ENTER;
290 break;
291 case 7: /* MenuSelectItem */
292 *saction = SA_SELECT;
293 break;
294 case 8: /* MenuScroll */
295 if (MST_MOUSE_WHEEL(mr) == MMW_OFF)
297 *saction = SA_SELECT;
299 else
301 num = GetSuffixedIntegerArguments(options, NULL, count,
302 1, "s", suffix);
303 if (num == 1)
305 *saction = SA_SCROLL;
306 *items_to_move = count[0];
307 if (suffix[0] == 1)
309 *do_skip_section = 1;
312 else
314 fvwm_msg(ERR, "parse_menu_action",
315 "invalid MenuScroll arguments '%s'",
316 options);
317 *saction = SA_NONE;
318 break;
321 break;
322 case 9: /* MenuTearOff */
323 *saction = SA_TEAROFF;
324 break;
325 case 10: /* MenuCloseAndExecute */
326 *saction = SA_EXEC_CMD;
327 *ret_cmd = options;
328 break;
329 default:
330 fvwm_msg(
331 ERR, "parse_menu_action", "unknown action '%s'",
332 action);
333 *saction = SA_NONE;
336 return;
339 static Binding *__menu_binding_is_key(
340 Binding *blist, XEvent* event, int context)
342 Binding *b;
344 for (b = blist; b != NULL; b = b->NextBinding)
346 if (
347 BIND_IS_KEY_BINDING(b->type) &&
348 event->xkey.keycode == b->Button_Key &&
349 (b->Modifier == AnyModifier ||
350 MaskUsedModifiers(b->Modifier) ==
351 MaskUsedModifiers(event->xkey.state)) &&
352 (b->Context == C_MENU ||
353 (b->Context & context) != C_MENU))
355 break;
359 return b;
362 /* ---------------------------- interface functions ------------------------ */
364 void menu_bindings_startup_complete(void)
366 menu_bindings = &menu_bindings_regular;
368 return;
371 Binding *menu_binding_is_mouse(XEvent* event, int context)
373 Binding *b;
375 b = __menu_binding_is_mouse(menu_bindings_regular, event, context);
376 if (b == NULL)
378 b = __menu_binding_is_mouse(
379 menu_bindings_fallback, event, context);
382 return b;
385 Binding *menu_binding_is_key(XEvent* event, int context)
387 Binding *b;
389 b = __menu_binding_is_key(menu_bindings_regular, event, context);
390 if (b == NULL)
392 b = __menu_binding_is_key(
393 menu_bindings_fallback, event, context);
396 return b;
399 int menu_binding(
400 Display *dpy, binding_t type, int button, KeySym keysym,
401 int context, int modifier, char *action, char *menu_style)
403 Binding *rmlist;
404 int rc;
406 if (menu_bindings == NULL)
408 menu_bindings = &menu_bindings_fallback;
410 rmlist = NULL;
411 if (~(~context | C_MENU | C_TITLE | C_MENU_ITEM | C_SIDEBAR) != 0)
413 fvwm_msg(
414 ERR, "menu_binding",
415 "invalid context in combination with menu context.");
417 return 1;
419 if (menu_style != NULL)
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.");
426 return 1;
429 * Remove the "old" bindings if any
431 /* BEGIN remove */
432 CollectBindingList(
433 dpy, menu_bindings, &rmlist, type, STROKE_ARG(NULL)
434 button, keysym, modifier, context, menu_style);
435 if (rmlist != NULL)
437 FreeBindingList(rmlist);
439 else if (
440 keysym == 0 && button != 0 && modifier == 0 &&
441 strcmp(action,"-") == 0 && context == C_MENU)
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.");
450 if (strcmp(action,"-") == 0)
452 return 0;
454 /* END remove */
455 if ((modifier & AnyModifier) && (modifier & (~AnyModifier)))
457 fvwm_msg(
458 WARN, "menu_binding", "Binding specified AnyModifier"
459 " and other modifers too. Excess modifiers are"
460 " ignored.");
461 modifier = AnyModifier;
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)
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.");
473 rc = AddBinding(
474 dpy, menu_bindings, type, STROKE_ARG(NULL) button, keysym,
475 NULL, modifier, context, (void *)action, NULL, menu_style);
477 return rc;
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)
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;
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)
519 if (MI_IS_TITLE(mi_current))
521 context |= C_TITLE;
523 else
525 /* menu item context, use I (icon) for it */
526 context |= C_MENU_ITEM;
529 else
531 if (
532 menu_get_geometry(
533 mr, &JunkRoot, &menu_x, &menu_y, &menu_width,
534 &menu_height, &JunkBW, &JunkDepth))
536 is_geometry_known = 1;
537 if (
538 FQueryPointer(
539 dpy, Scr.Root, &JunkRoot, &JunkChild,
540 &mx, &my, &JunkX, &JunkY, &JunkMask) ==
543 /* pointer is on a different screen */
544 mx = 0;
545 my = 0;
547 else if (
548 mx >= menu_x && mx < menu_x + menu_width &&
549 my >= menu_y && my < menu_y + menu_height)
551 /* pointer is on the meny somewhere not over
552 * an item */
553 if (my < menu_y + MST_BORDER_WIDTH(mr))
555 /* upper border context (-)*/
556 context |= C_SB_TOP;
558 else if (
559 my >= menu_y + menu_height -
560 MST_BORDER_WIDTH(mr))
562 /* lower border context (_) */
563 context |= C_SB_BOTTOM;
565 else if (mx < menu_x + MR_ITEM_X_OFFSET(mr))
567 /* left border or left sidepic */
568 context |= C_SB_LEFT;
570 else if (mx < menu_x + MST_BORDER_WIDTH(mr))
572 /* left border context ([)*/
573 context |= C_SB_LEFT;
575 else if (
576 mx >= menu_x + MR_ITEM_X_OFFSET(mr) +
577 MR_ITEM_WIDTH(mr))
579 /* right sidepic or right border */
580 context |= C_SB_RIGHT;
582 else if (
583 mx >= menu_x + menu_width -
584 MST_BORDER_WIDTH(mr))
586 /* right border context (])*/
587 context |= C_SB_RIGHT;
591 else
593 fvwm_msg(
594 ERR, "menu_shortcuts", "can't get geometry of"
595 " menu %s", MR_NAME(mr));
599 if (event->type == KeyRelease)
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;
605 return;
608 items_to_move = 0;
609 pmret->rc = MENU_NOP;
611 /*** handle mouse events ***/
612 if (event->type == ButtonRelease)
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)
622 parse_menu_action(
623 mr, binding->Action, &saction, &items_to_move,
624 &do_skip_section, &command);
626 index = 0;
627 ikeychar = 0;
629 else /* Should be KeyPressed */
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;
636 /*** handle double-keypress ***/
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)
644 *pmi_current = NULL;
645 pmret->rc = MENU_DOUBLE_CLICKED;
647 return;
649 pdkp->timestamp = 0;
651 /*** find out the key ***/
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;
660 /*** Try to match hot keys ***/
662 /* Need isascii here - isgraph might coredump! */
663 if (index == 1 && isascii(ikeychar) && isgraph(ikeychar) &&
664 with_control == 0 && with_meta == 0)
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;
673 /* if this is a letter set it to lower case */
674 if (isupper(ikeychar))
676 ikeychar = tolower(ikeychar) ;
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;
688 if (MI_HAS_HOTKEY(mi) && !MI_IS_TITLE(mi) &&
689 (!MI_IS_HOTKEY_AUTOMATIC(mi) ||
690 MST_USE_AUTOMATIC_HOTKEYS(mr)))
692 key = (MI_LABEL(mi)[(int)MI_HOTKEY_COLUMN(mi)])
693 [MI_HOTKEY_COFFSET(mi)];
694 key = tolower(key);
695 if (ikeychar == key)
697 if (++countHotkey == 1)
699 new_item = mi;
703 mi = (mi == MR_LAST_ITEM(mr)) ?
704 MR_FIRST_ITEM(mr) : MI_NEXT_ITEM(mi);
706 while (mi != mi1);
708 /* For multiple instances of a single hotkey, just move the
709 * selection */
710 if (countHotkey > 1)
712 *pmi_current = new_item;
713 pmret->rc = MENU_NEWITEM;
715 return;
717 /* Do things the old way for unique hotkeys in the menu */
718 else if (countHotkey == 1)
720 *pmi_current = new_item;
721 if (new_item && MI_IS_POPUP(new_item))
723 pmret->rc = MENU_POPUP;
725 else
727 pmret->rc = MENU_SELECTED;
730 return;
732 /* MMH mikehan@best.com 2/7/99 */
735 /*** now determine the action to take ***/
737 /** handle menu key bindings **/
738 if (
739 event->type == KeyPress && keysym == XK_Escape &&
740 with_control == 0 && with_shift == 0 &&
741 with_meta == 0)
743 /* Don't allow override of Escape with no modifiers */
744 saction = SA_ABORT;
747 else if (event->type == KeyPress)
749 binding = menu_binding_is_key(event, context);
750 if (binding != NULL)
752 parse_menu_action(
753 mr, binding->Action, &saction, &items_to_move,
754 &do_skip_section, &command);
758 if (
759 !mi_current &&
760 (saction == SA_ENTER || saction == SA_MOVE_ITEMS ||
761 saction == SA_SELECT || saction == SA_SCROLL))
763 if (is_geometry_known)
765 if (my < menu_y + MST_BORDER_WIDTH(mr))
767 saction = SA_FIRST;
769 else if (my > menu_y + menu_height -
770 MST_BORDER_WIDTH(mr))
772 saction = SA_LAST;
774 else
776 saction = SA_WARPBACK;
779 else
781 saction = SA_FIRST;
785 /*** execute the necessary actions ***/
787 switch (saction)
789 case SA_ENTER:
790 if (mi_current && MI_IS_POPUP(mi_current))
792 pmret->rc = MENU_POPUP;
794 else
796 pmret->rc = MENU_NOP;
798 break;
800 case SA_LEAVE:
801 pmret->rc =
802 (MR_IS_TEAR_OFF_MENU(mr)) ? MENU_NOP : MENU_POPDOWN;
803 break;
804 case SA_FIRST:
805 if (do_skip_section)
807 *pmi_current = get_selectable_item_from_section(
808 mr, items_to_move);
810 else
812 *pmi_current = get_selectable_item_from_index(
813 mr, items_to_move);
815 if (*pmi_current != NULL)
817 pmret->rc = MENU_NEWITEM;
819 else
821 pmret->rc = MENU_NOP;
823 break;
824 case SA_LAST:
825 if (do_skip_section)
827 get_selectable_item_count(mr, &index);
828 index += items_to_move;
829 if (index < 0)
831 index = 0;
833 *pmi_current = get_selectable_item_from_section(
834 mr, index);
835 if (*pmi_current != NULL)
837 pmret->rc = MENU_NEWITEM;
839 else
841 pmret->rc = MENU_NOP;
844 else
846 index = get_selectable_item_count(mr, NULL);
847 if (index > 0)
849 index += items_to_move;
850 if (index < 0)
852 index = 0;
854 *pmi_current = get_selectable_item_from_index(
855 mr, index);
856 pmret->rc = (*pmi_current) ?
857 MENU_NEWITEM : MENU_NOP;
859 else
861 pmret->rc = MENU_NOP;
864 break;
865 case SA_MOVE_ITEMS:
866 if (do_skip_section)
868 int section;
869 int count;
871 get_selectable_item_count(mr, &count);
872 get_selectable_item_index(mr, mi_current, &section);
873 section += items_to_move;
874 if (section < 0)
875 section = count;
876 else if (section > count)
877 section = 0;
878 index = section;
880 else if (items_to_move < 0)
882 index = get_selectable_item_index(
883 mr, mi_current, NULL);
884 if (index == 0)
885 /* wraparound */
886 index = get_selectable_item_count(mr, NULL);
887 else
889 index += items_to_move;
892 else
894 index = get_selectable_item_index(
895 mr, mi_current, NULL) +
896 items_to_move;
897 /* correct for the case that we're between items */
898 if (!MI_IS_SELECTABLE(mi_current))
900 index--;
903 if (do_skip_section)
905 new_item = get_selectable_item_from_section(mr, index);
907 else
909 new_item = get_selectable_item_from_index(mr, index);
910 if (items_to_move > 0 && new_item == mi_current)
912 new_item =
913 get_selectable_item_from_index(mr, 0);
916 if (new_item)
918 *pmi_current = new_item;
919 pmret->rc = MENU_NEWITEM;
921 else
923 pmret->rc = MENU_NOP;
925 break;
926 case SA_CONTINUE:
927 *pmi_current = MR_LAST_ITEM(mr);
928 if (*pmi_current && MI_IS_POPUP(*pmi_current))
930 /* enter the submenu */
931 pmret->rc = MENU_POPUP;
933 else
935 /* do nothing */
936 *pmi_current = mi_current;
937 pmret->rc = MENU_NOP;
939 break;
940 case SA_WARPBACK:
941 /* Warp the pointer back into the menu. */
942 FWarpPointer(
943 dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0,
944 menudim_middle_x_offset(&MR_DIM(mr)), my - menu_y);
945 pmret->rc = MENU_NEWITEM_FIND;
946 break;
947 case SA_SELECT:
948 pmret->rc = MENU_SELECTED;
949 return;
950 case SA_ABORT:
951 pmret->rc =
952 (MR_IS_TEAR_OFF_MENU(mr)) ?
953 MENU_KILL_TEAR_OFF_MENU : MENU_ABORTED;
954 return;
955 case SA_TEAROFF:
956 pmret->rc =
957 (MR_IS_TEAR_OFF_MENU(mr)) ? MENU_NOP : MENU_TEAR_OFF;
958 return;
959 case SA_SCROLL:
960 if (MST_MOUSE_WHEEL(mr) == MMW_MENU)
962 items_to_move *= -1;
964 if (
965 !menu_get_outer_geometry(
966 mr, pmp, &JunkRoot, &menu_x, &menu_y,
967 &JunkWidth, &menu_height,
968 &JunkBW, &JunkDepth))
970 fvwm_msg(
971 ERR, "menu_shortcuts",
972 "can't get geometry of menu %s", MR_NAME(mr));
973 return;
975 if (do_skip_section)
977 int count;
979 get_selectable_item_count(mr, &count);
980 get_selectable_item_index(mr, mi_current, &index);
981 index += items_to_move;
982 if (index < 0)
984 index = 0;
986 else if (index > count)
988 index = count;
990 new_item = get_selectable_item_from_section(mr, index);
992 else
994 index = get_selectable_item_index(
995 mr, mi_current, NULL);
996 if (items_to_move > 0 && !MI_IS_SELECTABLE(mi_current))
998 index--;
1000 index += items_to_move;
1001 new_item = get_selectable_item_from_index(mr, index);
1004 if (
1005 new_item &&
1006 ((items_to_move < 0 &&
1007 MI_Y_OFFSET(new_item) > MI_Y_OFFSET(mi_current)) ||
1008 (items_to_move > 0 &&
1009 MI_Y_OFFSET(new_item) < MI_Y_OFFSET(mi_current))))
1011 /* never scroll in the "wrong" direction */
1012 new_item = NULL;
1015 if (new_item)
1017 *pmi_current = new_item;
1018 pmret->rc = MENU_NEWITEM;
1019 /* Have to work with relative positions or tear off
1020 * menus will be hard to reposition */
1021 if (
1022 FQueryPointer(
1023 dpy, MR_WINDOW(mr), &JunkRoot,
1024 &JunkChild, &JunkX, &JunkY, &mx, &my,
1025 &JunkMask) == 0)
1027 /* This should not happen */
1028 mx = 0;
1029 my = 0;
1032 if (MST_MOUSE_WHEEL(mr) == MMW_POINTER)
1034 if (event->type == ButtonRelease)
1037 FWarpPointer(
1038 dpy, 0, 0, 0, 0, 0, 0, 0,
1039 -my +
1040 menuitem_middle_y_offset(
1041 new_item,
1042 MR_STYLE(mr)));
1044 /* pointer wrapped elsewhere for key events */
1046 else
1048 int old_y = menu_y;
1050 menu_y += my - menuitem_middle_y_offset(
1051 new_item, MR_STYLE(mr));
1053 if (
1054 !MST_SCROLL_OFF_PAGE(mr) &&
1055 menu_height < MR_SCREEN_HEIGHT(mr))
1057 if (menu_y < 0)
1059 FWarpPointer(dpy, 0, 0, 0, 0,
1060 0, 0, 0,-menu_y);
1061 menu_y=0;
1064 if (
1065 menu_y + menu_height >
1066 MR_SCREEN_HEIGHT(mr))
1068 FWarpPointer(
1069 dpy, 0, 0, 0, 0, 0, 0,
1071 MR_SCREEN_HEIGHT(mr) -
1072 menu_y - menu_height);
1073 menu_y = MR_SCREEN_HEIGHT(mr) -
1074 menu_height;
1077 if (old_y != menu_y)
1079 pmret->rc = MENU_NEWITEM_MOVEMENU;
1080 *ret_menu_x = menu_x;
1081 *ret_menu_y = menu_y;
1083 else
1085 pmret->rc = MENU_NEWITEM;
1089 else
1091 pmret->rc = MENU_NOP;
1093 break;
1094 case SA_EXEC_CMD:
1095 pmret->rc = MENU_EXEC_CMD;
1096 *pmp->ret_paction = command;
1097 break;
1098 case SA_NONE:
1099 default:
1100 pmret->rc = MENU_NOP;
1101 break;
1103 if (saction != SA_SCROLL && pmret->rc == MENU_NEWITEM)
1105 if (!menu_get_outer_geometry(
1106 mr, pmp, &JunkRoot, &menu_x, &menu_y,
1107 &JunkWidth, &menu_height,
1108 &JunkBW, &JunkDepth))
1110 fvwm_msg(
1111 ERR, "menu_shortcuts",
1112 "can't get geometry of menu %s", MR_NAME(mr));
1113 return;
1115 if (menu_y < 0 || menu_y + menu_height > MR_SCREEN_HEIGHT(mr))
1117 menu_y = (menu_y < 0) ?
1118 0 : MR_SCREEN_HEIGHT(mr) - menu_height;
1119 pmret->rc = MENU_NEWITEM_MOVEMENU;
1120 *ret_menu_x = menu_x;
1121 *ret_menu_y = menu_y;
1125 return;