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 ---------------------- */
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"
32 #include "execcontext.h"
37 #include "move_resize.h"
39 #include "menustyle.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
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
)
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
))
89 is_last_selectable
= 1;
91 else if (is_last_selectable
== 1)
94 is_last_selectable
= 0;
109 static MenuItem
*get_selectable_item_from_index(MenuRoot
*mr
, int index
)
113 MenuItem
*mi_last_ok
= NULL
;
115 for (mi
= MR_FIRST_ITEM(mr
); mi
&& (i
< index
|| mi_last_ok
== NULL
);
118 if (MI_IS_SELECTABLE(mi
))
128 static MenuItem
*get_selectable_item_from_section(MenuRoot
*mr
, int section
)
132 MenuItem
*mi_last_ok
= NULL
;
133 int is_last_selectable
= 0;
136 mi
= MR_FIRST_ITEM(mr
);
137 mi
&& (i
<= section
|| mi_last_ok
== NULL
);
140 if (MI_IS_SELECTABLE(mi
))
142 if (!is_last_selectable
)
145 is_last_selectable
= 1;
148 else if (is_last_selectable
)
151 is_last_selectable
= 0;
158 static int get_selectable_item_count(MenuRoot
*mr
, int *ret_sections
)
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
)))
171 static Binding
*__menu_binding_is_mouse(
172 Binding
*blist
, XEvent
* event
, int context
)
177 real_mod
= event
->xbutton
.state
- (1 << (7 + event
->xbutton
.button
));
178 for (b
= blist
; b
!= NULL
; b
= b
->NextBinding
)
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
))
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
)
203 "MenuEnterContinuation",
222 options
= GetNextTokenIndex((char *)action
, optlist
, 0, &index
);
225 case 0: /* MenuClose */
228 case 1: /* MenuEnterContinuation */
229 *saction
= (MR_CONTINUATION_MENU(mr
) != NULL
) ?
230 SA_CONTINUE
: SA_ENTER
;
232 case 2: /* MenuEnterSubmenu */
235 case 3: /* MenuLeaveSubmenu */
238 case 4: /* MenuMoveCursor */
239 num
= GetSuffixedIntegerArguments(options
, NULL
, count
, 2,
243 if (suffix
[0] != 0 || count
[0] != 0)
245 fvwm_msg(ERR
, "parse_menu_action",
246 "invalid MenuMoveCursor arguments "
254 *items_to_move
= 1 + count
[1];
259 *items_to_move
= count
[1];
264 *do_skip_section
= 1;
269 *saction
= SA_MOVE_ITEMS
;
270 *items_to_move
= count
[0];
273 *do_skip_section
= 1;
278 fvwm_msg(ERR
, "parse_menu_action",
279 "invalid MenuMoveCursor arguments '%s'",
285 case 5: /* MenuCursorLeft */
286 *saction
= (MST_USE_LEFT_SUBMENUS(mr
)) ? SA_ENTER
: SA_LEAVE
;
288 case 6: /* MenuCursorRight */
289 *saction
= (MST_USE_LEFT_SUBMENUS(mr
)) ? SA_LEAVE
: SA_ENTER
;
291 case 7: /* MenuSelectItem */
292 *saction
= SA_SELECT
;
294 case 8: /* MenuScroll */
295 if (MST_MOUSE_WHEEL(mr
) == MMW_OFF
)
297 *saction
= SA_SELECT
;
301 num
= GetSuffixedIntegerArguments(options
, NULL
, count
,
305 *saction
= SA_SCROLL
;
306 *items_to_move
= count
[0];
309 *do_skip_section
= 1;
314 fvwm_msg(ERR
, "parse_menu_action",
315 "invalid MenuScroll arguments '%s'",
322 case 9: /* MenuTearOff */
323 *saction
= SA_TEAROFF
;
325 case 10: /* MenuCloseAndExecute */
326 *saction
= SA_EXEC_CMD
;
331 ERR
, "parse_menu_action", "unknown action '%s'",
339 static Binding
*__menu_binding_is_key(
340 Binding
*blist
, XEvent
* event
, int context
)
344 for (b
= blist
; b
!= NULL
; b
= b
->NextBinding
)
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
))
362 /* ---------------------------- interface functions ------------------------ */
364 void menu_bindings_startup_complete(void)
366 menu_bindings
= &menu_bindings_regular
;
371 Binding
*menu_binding_is_mouse(XEvent
* event
, int context
)
375 b
= __menu_binding_is_mouse(menu_bindings_regular
, event
, context
);
378 b
= __menu_binding_is_mouse(
379 menu_bindings_fallback
, event
, context
);
385 Binding
*menu_binding_is_key(XEvent
* event
, int context
)
389 b
= __menu_binding_is_key(menu_bindings_regular
, event
, context
);
392 b
= __menu_binding_is_key(
393 menu_bindings_fallback
, event
, context
);
400 Display
*dpy
, binding_t type
, int button
, KeySym keysym
,
401 int context
, int modifier
, char *action
, char *menu_style
)
406 if (menu_bindings
== NULL
)
408 menu_bindings
= &menu_bindings_fallback
;
411 if (~(~context
| C_MENU
| C_TITLE
| C_MENU_ITEM
| C_SIDEBAR
) != 0)
415 "invalid context in combination with menu context.");
419 if (menu_style
!= NULL
)
421 /*!!! fixme - make either match a menu style or a menu name */
423 ERR
, "menu_binding", "a window name may not be"
424 " specified without a menu context.");
429 * Remove the "old" bindings if any
433 dpy
, menu_bindings
, &rmlist
, type
, STROKE_ARG(NULL
)
434 button
, keysym
, modifier
, context
, menu_style
);
437 FreeBindingList(rmlist
);
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. */
446 WARN
, "menu_binding",
447 "The syntax for disabling the tear off button has "
450 if (strcmp(action
,"-") == 0)
455 if ((modifier
& AnyModifier
) && (modifier
& (~AnyModifier
)))
458 WARN
, "menu_binding", "Binding specified AnyModifier"
459 " and other modifers too. Excess modifiers are"
461 modifier
= AnyModifier
;
463 /* Warn about Mouse n M N TearOff. */
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.");
474 dpy
, menu_bindings
, type
, STROKE_ARG(NULL
) button
, keysym
,
475 NULL
, modifier
, context
, (void *)action
, NULL
, menu_style
);
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
)
492 MenuItem
*mi_current
;
502 menu_shortcut_action saction
;
505 int is_geometry_known
;
510 mi_current
= pmi_current
? *pmi_current
: NULL
;
514 is_geometry_known
= 0;
519 if (MI_IS_TITLE(mi_current
))
525 /* menu item context, use I (icon) for it */
526 context
|= C_MENU_ITEM
;
533 mr
, &JunkRoot
, &menu_x
, &menu_y
, &menu_width
,
534 &menu_height
, &JunkBW
, &JunkDepth
))
536 is_geometry_known
= 1;
539 dpy
, Scr
.Root
, &JunkRoot
, &JunkChild
,
540 &mx
, &my
, &JunkX
, &JunkY
, &JunkMask
) ==
543 /* pointer is on a different screen */
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
553 if (my
< menu_y
+ MST_BORDER_WIDTH(mr
))
555 /* upper border context (-)*/
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
;
576 mx
>= menu_x
+ MR_ITEM_X_OFFSET(mr
) +
579 /* right sidepic or right border */
580 context
|= C_SB_RIGHT
;
583 mx
>= menu_x
+ menu_width
-
584 MST_BORDER_WIDTH(mr
))
586 /* right border context (])*/
587 context
|= C_SB_RIGHT
;
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
;
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
);
623 mr
, binding
->Action
, &saction
, &items_to_move
,
624 &do_skip_section
, &command
);
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
)
645 pmret
->rc
= MENU_DOUBLE_CLICKED
;
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
,
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 */
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
);
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
)];
697 if (++countHotkey
== 1)
703 mi
= (mi
== MR_LAST_ITEM(mr
)) ?
704 MR_FIRST_ITEM(mr
) : MI_NEXT_ITEM(mi
);
708 /* For multiple instances of a single hotkey, just move the
712 *pmi_current
= new_item
;
713 pmret
->rc
= MENU_NEWITEM
;
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
;
727 pmret
->rc
= MENU_SELECTED
;
732 /* MMH mikehan@best.com 2/7/99 */
735 /*** now determine the action to take ***/
737 /** handle menu key bindings **/
739 event
->type
== KeyPress
&& keysym
== XK_Escape
&&
740 with_control
== 0 && with_shift
== 0 &&
743 /* Don't allow override of Escape with no modifiers */
747 else if (event
->type
== KeyPress
)
749 binding
= menu_binding_is_key(event
, context
);
753 mr
, binding
->Action
, &saction
, &items_to_move
,
754 &do_skip_section
, &command
);
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
))
769 else if (my
> menu_y
+ menu_height
-
770 MST_BORDER_WIDTH(mr
))
776 saction
= SA_WARPBACK
;
785 /*** execute the necessary actions ***/
790 if (mi_current
&& MI_IS_POPUP(mi_current
))
792 pmret
->rc
= MENU_POPUP
;
796 pmret
->rc
= MENU_NOP
;
802 (MR_IS_TEAR_OFF_MENU(mr
)) ? MENU_NOP
: MENU_POPDOWN
;
807 *pmi_current
= get_selectable_item_from_section(
812 *pmi_current
= get_selectable_item_from_index(
815 if (*pmi_current
!= NULL
)
817 pmret
->rc
= MENU_NEWITEM
;
821 pmret
->rc
= MENU_NOP
;
827 get_selectable_item_count(mr
, &index
);
828 index
+= items_to_move
;
833 *pmi_current
= get_selectable_item_from_section(
835 if (*pmi_current
!= NULL
)
837 pmret
->rc
= MENU_NEWITEM
;
841 pmret
->rc
= MENU_NOP
;
846 index
= get_selectable_item_count(mr
, NULL
);
849 index
+= items_to_move
;
854 *pmi_current
= get_selectable_item_from_index(
856 pmret
->rc
= (*pmi_current
) ?
857 MENU_NEWITEM
: MENU_NOP
;
861 pmret
->rc
= MENU_NOP
;
871 get_selectable_item_count(mr
, &count
);
872 get_selectable_item_index(mr
, mi_current
, §ion
);
873 section
+= items_to_move
;
876 else if (section
> count
)
880 else if (items_to_move
< 0)
882 index
= get_selectable_item_index(
883 mr
, mi_current
, NULL
);
886 index
= get_selectable_item_count(mr
, NULL
);
889 index
+= items_to_move
;
894 index
= get_selectable_item_index(
895 mr
, mi_current
, NULL
) +
897 /* correct for the case that we're between items */
898 if (!MI_IS_SELECTABLE(mi_current
))
905 new_item
= get_selectable_item_from_section(mr
, index
);
909 new_item
= get_selectable_item_from_index(mr
, index
);
910 if (items_to_move
> 0 && new_item
== mi_current
)
913 get_selectable_item_from_index(mr
, 0);
918 *pmi_current
= new_item
;
919 pmret
->rc
= MENU_NEWITEM
;
923 pmret
->rc
= MENU_NOP
;
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
;
936 *pmi_current
= mi_current
;
937 pmret
->rc
= MENU_NOP
;
941 /* Warp the pointer back into the menu. */
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
;
948 pmret
->rc
= MENU_SELECTED
;
952 (MR_IS_TEAR_OFF_MENU(mr
)) ?
953 MENU_KILL_TEAR_OFF_MENU
: MENU_ABORTED
;
957 (MR_IS_TEAR_OFF_MENU(mr
)) ? MENU_NOP
: MENU_TEAR_OFF
;
960 if (MST_MOUSE_WHEEL(mr
) == MMW_MENU
)
965 !menu_get_outer_geometry(
966 mr
, pmp
, &JunkRoot
, &menu_x
, &menu_y
,
967 &JunkWidth
, &menu_height
,
968 &JunkBW
, &JunkDepth
))
971 ERR
, "menu_shortcuts",
972 "can't get geometry of menu %s", MR_NAME(mr
));
979 get_selectable_item_count(mr
, &count
);
980 get_selectable_item_index(mr
, mi_current
, &index
);
981 index
+= items_to_move
;
986 else if (index
> count
)
990 new_item
= get_selectable_item_from_section(mr
, index
);
994 index
= get_selectable_item_index(
995 mr
, mi_current
, NULL
);
996 if (items_to_move
> 0 && !MI_IS_SELECTABLE(mi_current
))
1000 index
+= items_to_move
;
1001 new_item
= get_selectable_item_from_index(mr
, index
);
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 */
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 */
1023 dpy
, MR_WINDOW(mr
), &JunkRoot
,
1024 &JunkChild
, &JunkX
, &JunkY
, &mx
, &my
,
1027 /* This should not happen */
1032 if (MST_MOUSE_WHEEL(mr
) == MMW_POINTER
)
1034 if (event
->type
== ButtonRelease
)
1038 dpy
, 0, 0, 0, 0, 0, 0, 0,
1040 menuitem_middle_y_offset(
1044 /* pointer wrapped elsewhere for key events */
1050 menu_y
+= my
- menuitem_middle_y_offset(
1051 new_item
, MR_STYLE(mr
));
1054 !MST_SCROLL_OFF_PAGE(mr
) &&
1055 menu_height
< MR_SCREEN_HEIGHT(mr
))
1059 FWarpPointer(dpy
, 0, 0, 0, 0,
1065 menu_y
+ menu_height
>
1066 MR_SCREEN_HEIGHT(mr
))
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
) -
1077 if (old_y
!= menu_y
)
1079 pmret
->rc
= MENU_NEWITEM_MOVEMENU
;
1080 *ret_menu_x
= menu_x
;
1081 *ret_menu_y
= menu_y
;
1085 pmret
->rc
= MENU_NEWITEM
;
1091 pmret
->rc
= MENU_NOP
;
1095 pmret
->rc
= MENU_EXEC_CMD
;
1096 *pmp
->ret_paction
= command
;
1100 pmret
->rc
= MENU_NOP
;
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
))
1111 ERR
, "menu_shortcuts",
1112 "can't get geometry of menu %s", MR_NAME(mr
));
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
;