Add NEWS bug entry for previous FvwmForm commit.
[fvwm.git] / fvwm / menus.c
blob918f366e350e489524552ec3c0fe8dcfb1bba152
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
18 * Copyright 1993, Robert Nation
19 * You may use this code for any purpose, as long as the original
20 * copyright remains in the source code and all documentation
23 /* IMPORTANT NOTE: Do *not* use any constant numbers in this file. All values
24 * have to be #defined in the section below or in defaults.h to ensure full
25 * control over the menus. */
27 /* ---------------------------- included header files ---------------------- */
29 #include "config.h"
31 #include <stdio.h>
32 #include <assert.h>
33 #include <X11/keysym.h>
35 #include "libs/ftime.h"
36 #include "libs/fvwmlib.h"
37 #include "libs/FScreen.h"
38 #include "libs/Grab.h"
39 #include "libs/Parse.h"
40 #include "libs/ColorUtils.h"
41 #include "libs/Picture.h"
42 #include "libs/PictureUtils.h"
43 #include "libs/Graphics.h"
44 #include "libs/PictureGraphics.h"
45 #include "libs/charmap.h"
46 #include "libs/wcontext.h"
47 #include "fvwm.h"
48 #include "externs.h"
49 #include "execcontext.h"
50 #include "events.h"
51 #include "eventhandler.h"
52 #include "eventmask.h"
53 #include "cursor.h"
54 #include "functions.h"
55 #include "commands.h"
56 #include "misc.h"
57 #include "screen.h"
58 #include "colormaps.h"
59 #include "geometry.h"
60 #include "move_resize.h"
61 #include "menudim.h"
62 #include "menuitem.h"
63 #include "menuroot.h"
64 #include "menustyle.h"
65 #include "bindings.h"
66 #include "menubindings.h"
67 #include "menugeometry.h"
68 #include "menuparameters.h"
69 #include "menus.h"
70 #include "libs/FGettext.h"
72 /* ---------------------------- local definitions -------------------------- */
74 /* used in float to int arithmetic */
75 #define ROUNDING_ERROR_TOLERANCE 0.005
77 /* ---------------------------- local macros ------------------------------- */
79 #define SCTX_SET_MI(ctx,item) ((ctx).type = SCTX_MENU_ITEM, \
80 (ctx).menu_item.menu_item = (item))
81 #define SCTX_GET_MI(ctx) ((ctx).type == SCTX_MENU_ITEM ? \
82 (ctx).menu_item.menu_item : NULL)
83 #define SCTX_SET_MR(ctx,root) ((ctx).type = SCTX_MENU_ROOT, \
84 (ctx).menu_root.menu_root = (root))
85 #define SCTX_GET_MR(ctx) ((ctx).type == SCTX_MENU_ROOT ? \
86 (ctx).menu_root.menu_root : NULL)
88 /* ---------------------------- imports ------------------------------------ */
90 /* This external is safe. It's written only during startup. */
91 extern XContext MenuContext;
93 /* ---------------------------- included code files ------------------------ */
95 /* ---------------------------- local types -------------------------------- */
97 /* patch to pass the last popups position hints to popup_func */
98 typedef struct
100 struct
102 unsigned is_last_menu_pos_hints_valid : 1;
103 unsigned do_ignore_pos_hints : 1;
104 unsigned do_warp_title : 1;
105 } flags;
106 struct MenuPosHints pos_hints;
107 } saved_pos_hints;
109 typedef struct MenuInfo
111 MenuRoot *all;
112 int n_destroyed_menus;
113 } MenuInfo;
115 typedef struct MenuSizingParameters
117 MenuRoot *menu;
118 /* number of item labels present in the item format */
119 int used_item_labels;
120 /* same for mini icons */
121 int used_mini_icons;
122 struct
124 int sidepic_width;
125 MenuItemPartSizesT i;
126 } max;
127 struct
129 unsigned is_popup_indicator_used : 1;
130 } flags;
131 } MenuSizingParameters;
133 typedef enum
135 SCTX_MENU_ROOT,
136 SCTX_MENU_ITEM
137 } string_context_type_t;
139 typedef union
141 string_context_type_t type;
142 struct
144 string_context_type_t type;
145 MenuRoot *menu_root;
146 } menu_root;
147 struct
149 string_context_type_t type;
150 MenuItem *menu_item;
151 } menu_item;
152 } string_context_t;
154 typedef struct
156 char delimiter;
157 Bool (*string_handler)(
158 char *string, char delimiter,
159 string_context_t *user_data);
160 } string_def_t;
162 /* ---------------------------- menu loop types ---------------------------- */
164 typedef enum
166 MENU_MLOOP_RET_NORMAL,
167 MENU_MLOOP_RET_LOOP,
168 MENU_MLOOP_RET_END
169 } mloop_ret_code_t;
171 typedef struct
173 unsigned do_popup_immediately : 1;
174 /* used for delay popups, to just popup the menu */
175 unsigned do_popup_now : 1;
176 unsigned do_popdown_now : 1;
177 /* used for keystrokes, to popup and move to that menu */
178 unsigned do_popup_and_warp : 1;
179 unsigned do_force_reposition : 1;
180 unsigned do_force_popup : 1;
181 unsigned do_popdown : 1;
182 unsigned do_popup : 1;
183 unsigned do_menu : 1;
184 unsigned do_recycle_event : 1;
185 unsigned do_propagate_event_into_submenu : 1;
186 unsigned has_mouse_moved : 1;
187 unsigned is_off_menu_allowed : 1;
188 unsigned is_key_press : 1;
189 unsigned is_item_entered_by_key_press : 1;
190 unsigned is_motion_faked : 1;
191 unsigned is_popped_up_by_timeout : 1;
192 unsigned is_pointer_in_active_item_area : 1;
193 unsigned is_motion_first : 1;
194 unsigned is_release_first : 1;
195 unsigned is_submenu_mapped : 1;
196 unsigned was_item_unposted : 1;
197 unsigned is_button_release : 1;
198 } mloop_flags_t;
200 typedef struct
202 MenuItem *mi;
203 MenuRoot *mrMi;
204 int x_offset;
205 int popdown_delay_10ms;
206 int popup_delay_10ms;
207 } mloop_evh_data_t;
209 typedef struct
211 MenuRoot *mrPopup;
212 MenuRoot *mrPopdown;
213 mloop_flags_t mif;
214 /* used to reduce network traffic with delayed popup/popdown */
215 MenuItem *mi_with_popup;
216 MenuItem *mi_wants_popup;
217 MenuItem *miRemovedSubmenu;
218 } mloop_evh_input_t;
220 /* values that are set once when the menu loop is entered */
221 typedef struct mloop_static_info_t
223 int x_init;
224 int y_init;
225 Time t0;
226 unsigned int event_mask;
227 } mloop_static_info_t;
229 /* ---------------------------- forward declarations ----------------------- */
231 /* ---------------------------- local variables ---------------------------- */
233 /* This global is saved and restored every time a function is called that
234 * might modify them, so we can safely let it live outside a function. */
235 static saved_pos_hints last_saved_pos_hints =
237 { False, False },
238 { 0, 0, 0.0, 0.0, 0, 0, False, False, False, False }
241 /* structures for menus */
242 static MenuInfo Menus;
244 /* ---------------------------- exported variables (globals) --------------- */
246 /* ---------------------------- local functions ---------------------------- */
248 static void __menu_execute_function(const exec_context_t **pexc, char *action)
250 const exec_context_t *exc;
251 exec_context_changes_t ecc;
252 int old_emf;
254 ecc.w.w = ((*pexc)->w.fw) ? FW_W((*pexc)->w.fw) : None;
255 exc = exc_clone_context(*pexc, &ecc, ECC_W);
256 old_emf = Scr.flags.is_executing_menu_function;
257 Scr.flags.is_executing_menu_function = 1;
258 execute_function(NULL, exc, action, FUNC_DONT_EXPAND_COMMAND);
259 Scr.flags.is_executing_menu_function = old_emf;
260 exc_destroy_context(exc);
261 /* See if the window has been deleted */
262 if (!check_if_fvwm_window_exists((*pexc)->w.fw))
264 ecc.w.fw = NULL;
265 ecc.w.w = None;
266 ecc.w.wcontext = 0;
267 exc = exc_clone_context(
268 *pexc, &ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
269 exc_destroy_context(*pexc);
270 *pexc = exc;
273 return;
276 static Bool pointer_in_active_item_area(int x_offset, MenuRoot *mr)
278 float ratio = (float)MST_ACTIVE_AREA_PERCENT(mr) / 100.0;
280 if (MST_ACTIVE_AREA_PERCENT(mr) >= 100)
282 return False;
284 if (MST_USE_LEFT_SUBMENUS(mr))
286 return (x_offset <=
287 MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) -
288 MR_ITEM_WIDTH(mr) * ratio);
290 else
292 return (x_offset >=
293 MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) * ratio);
297 static Bool pointer_in_passive_item_area(int x_offset, MenuRoot *mr)
299 float ratio = (float)MST_ACTIVE_AREA_PERCENT(mr) / 100.0;
301 if (MST_ACTIVE_AREA_PERCENT(mr) >= 100)
303 return False;
305 if (MST_USE_LEFT_SUBMENUS(mr))
307 return (x_offset >=
308 MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) * ratio);
310 else
312 return (x_offset <=
313 MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) -
314 MR_ITEM_WIDTH(mr) * ratio);
319 * warping functions
322 static void warp_pointer_to_title(MenuRoot *mr)
324 FWarpPointer(
325 dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0,
326 menudim_middle_x_offset(&MR_DIM(mr)),
327 menuitem_middle_y_offset(MR_FIRST_ITEM(mr), MR_STYLE(mr)));
330 static MenuItem *warp_pointer_to_item(
331 MenuRoot *mr, MenuItem *mi, Bool do_skip_title)
333 if (do_skip_title)
335 while (MI_NEXT_ITEM(mi) != NULL &&
336 (!MI_IS_SELECTABLE(mi) || MI_IS_TEAR_OFF_BAR(mi)))
338 /* skip separators, titles and tear off bars until the
339 * first 'real' item is found */
340 mi = MI_NEXT_ITEM(mi);
343 if (mi == NULL)
345 mi = MR_LAST_ITEM(mr);
347 if (mi == NULL)
349 return mi;
351 FWarpPointer(
352 dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0,
353 menudim_middle_x_offset(&MR_DIM(mr)),
354 menuitem_middle_y_offset(mi, MR_STYLE(mr)));
356 return mi;
360 * menu animation functions
363 /* prepares the parameters to be passed to AnimatedMoveOfWindow
364 * mr - the menu instance that holds the menu item
365 * fw - the FvwmWindow structure to check against allowed functions */
366 static void get_menu_repaint_transparent_parameters(
367 MenuRepaintTransparentParameters *pmrtp, MenuRoot *mr, FvwmWindow *fw)
369 pmrtp->mr = mr;
370 pmrtp->fw = fw;
372 return;
376 /* Undo the animation of a menu */
377 static void animated_move_back(
378 MenuRoot *mr, Bool do_warp_pointer, FvwmWindow *fw)
380 MenuRepaintTransparentParameters mrtp;
381 int act_x;
382 int act_y;
384 if (MR_XANIMATION(mr) == 0)
386 return;
388 if (menu_get_geometry(
389 mr, &JunkRoot, &act_x, &act_y, &JunkWidth, &JunkHeight,
390 &JunkBW, &JunkDepth))
392 Bool transparent_bg = False;
394 /* move it back */
395 if (ST_HAS_MENU_CSET(MR_STYLE(mr)) &&
396 CSET_IS_TRANSPARENT(ST_CSET_MENU(MR_STYLE(mr))))
398 transparent_bg = True;
399 get_menu_repaint_transparent_parameters(
400 &mrtp, mr, fw);
402 AnimatedMoveOfWindow(
403 MR_WINDOW(mr), act_x, act_y, act_x - MR_XANIMATION(mr),
404 act_y, do_warp_pointer, -1, NULL,
405 (transparent_bg)? &mrtp:NULL);
406 MR_XANIMATION(mr) = 0;
409 return;
412 /* move a menu or a tear-off menu preserving transparency.
413 * tear-off menus are moved with their frame coordinates. */
414 static void move_any_menu(
415 MenuRoot *mr, MenuParameters *pmp, int endX, int endY)
417 if (MR_IS_TEAR_OFF_MENU(mr))
419 float fFull = 1.0;
421 /* this moves the tearoff menu, updating of transparency
422 * will not be as good as if menu repaint parameters
423 * are used. */
424 AnimatedMoveFvwmWindow(
425 pmp->tear_off_root_menu_window,
426 FW_W_FRAME(pmp->tear_off_root_menu_window),
427 -1, -1, endX, endY, False, 0, &fFull);
429 else
431 int x;
432 int y;
433 int JunkDept;
435 menu_get_geometry(mr, &JunkRoot, &x, &y, &JunkWidth,
436 &JunkHeight, &JunkBW, &JunkDept);
437 if (x == endX && y == endY)
439 return;
441 if (ST_HAS_MENU_CSET(MR_STYLE(mr)) &&
442 CSET_IS_TRANSPARENT(ST_CSET_MENU(MR_STYLE(mr))))
444 MenuRepaintTransparentParameters mrtp;
446 get_menu_repaint_transparent_parameters(
447 &mrtp, mr, (*pmp->pexc)->w.fw);
448 update_transparent_menu_bg(
449 &mrtp, x, y, endX, endY, endX, endY);
450 XMoveWindow(dpy, MR_WINDOW(mr), endX, endY);
451 repaint_transparent_menu(
452 &mrtp, False, endX,endY, endX, endY, True);
454 else
456 XMoveWindow(dpy, MR_WINDOW(mr), endX, endY);
461 /* ---------------------------- submenu function --------------------------- */
463 /* Search for a submenu that was popped up by the given item in the given
464 * instance of the menu. */
465 static MenuRoot *seek_submenu_instance(
466 MenuRoot *parent_menu, MenuItem *parent_item)
468 MenuRoot *mr;
470 for (mr = Menus.all; mr != NULL; mr = MR_NEXT_MENU(mr))
472 if (MR_PARENT_MENU(mr) == parent_menu &&
473 MR_PARENT_ITEM(mr) == parent_item)
475 /* here it is */
476 break;
480 return mr;
483 static Bool is_submenu_mapped(MenuRoot *parent_menu, MenuItem *parent_item)
485 XWindowAttributes win_attribs;
486 MenuRoot *mr;
488 mr = seek_submenu_instance(parent_menu, parent_item);
489 if (mr == NULL)
491 return False;
494 if (MR_WINDOW(mr) == None)
496 return False;
498 if (!XGetWindowAttributes(dpy, MR_WINDOW(mr), &win_attribs))
500 return False;
503 return (win_attribs.map_state == IsViewable);
506 /* Returns the menu root that a given menu item pops up */
507 static MenuRoot *mr_popup_for_mi(MenuRoot *mr, MenuItem *mi)
509 char *menu_name;
510 MenuRoot *menu = NULL;
512 /* This checks if mi is != NULL too */
513 if (!mi || !MI_IS_POPUP(mi))
515 return NULL;
518 /* first look for a menu that is aleady mapped */
519 menu = seek_submenu_instance(mr, mi);
520 if (menu)
522 return menu;
525 /* just look past "Popup " in the action, and find that menu root */
526 menu_name = PeekToken(SkipNTokens(MI_ACTION(mi), 1), NULL);
527 menu = menus_find_menu(menu_name);
529 return menu;
532 /* ---------------------------- item handling ------------------------------ */
535 * find_entry()
537 * Returns the menu item the pointer is over and optionally the offset
538 * from the left side of the menu entry (if px_offset is != NULL) and
539 * the MenuRoot the pointer is over (if pmr is != NULL).
541 static MenuItem *find_entry(
542 MenuParameters *pmp,
543 int *px_offset /*NULL means don't return this value */,
544 MenuRoot **pmr /*NULL means don't return this value */,
545 /* values passed in from caller it FQueryPointer was already called
546 * there */
547 Window p_child, int p_rx, int p_ry)
549 MenuItem *mi;
550 MenuRoot *mr;
551 int root_x, root_y;
552 int x, y;
553 Window Child;
554 int r;
556 /* x_offset returns the x offset of the pointer in the found menu item
558 if (px_offset)
560 *px_offset = 0;
562 if (pmr)
564 *pmr = NULL;
566 /* get the pointer position */
567 if (p_rx < 0)
569 if (!FQueryPointer(
570 dpy, Scr.Root, &JunkRoot, &Child,
571 &root_x, &root_y, &JunkX, &JunkY, &JunkMask))
573 /* pointer is on a different screen */
574 return NULL;
577 else
579 root_x = p_rx;
580 root_y = p_ry;
581 Child = p_child;
583 /* find out the menu the pointer is in */
584 if (pmp->tear_off_root_menu_window != NULL &&
585 Child == FW_W_FRAME(pmp->tear_off_root_menu_window))
587 /* we're in the top level torn off menu */
588 Child = FW_W(pmp->tear_off_root_menu_window);
590 if (XFindContext(dpy, Child, MenuContext, (caddr_t *)&mr) == XCNOENT)
592 return NULL;
594 /* get position in that child window */
595 if (!XTranslateCoordinates(
596 dpy, Scr.Root, MR_WINDOW(mr), root_x, root_y, &x, &y,
597 &JunkChild))
599 return NULL;
601 if (x < 0 || y < 0 || x >= MR_WIDTH(mr) || y >= MR_HEIGHT(mr))
603 return NULL;
605 if (pmr)
607 *pmr = mr;
609 r = MST_RELIEF_THICKNESS(mr);
610 /* look for the entry that the mouse is in */
611 for (mi = MR_FIRST_ITEM(mr); mi; mi = MI_NEXT_ITEM(mi))
613 int a;
614 int b;
616 a = (MI_PREV_ITEM(mi) &&
617 MI_IS_SELECTABLE(MI_PREV_ITEM(mi))) ? r / 2 : 0;
618 if (!MI_IS_SELECTABLE(mi))
620 b = 0;
622 else if (MI_NEXT_ITEM(mi) &&
623 MI_IS_SELECTABLE(MI_NEXT_ITEM(mi)))
625 b = r / 2;
627 else
629 b = r;
631 if (y >= MI_Y_OFFSET(mi) - a &&
632 y < MI_Y_OFFSET(mi) + MI_HEIGHT(mi) + b)
634 break;
637 if (x < MR_ITEM_X_OFFSET(mr) ||
638 x >= MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) - 1)
640 mi = NULL;
642 if (mi && px_offset)
644 *px_offset = x;
647 return mi;
650 /* ---------------------------- keyboard shortcuts ------------------------- */
652 static Bool is_double_click(
653 Time t0, MenuItem *mi, MenuParameters *pmp, MenuReturn *pmret,
654 double_keypress *pdkp, Bool has_mouse_moved)
656 if ((*pmp->pexc)->x.elast->type == KeyPress)
658 return False;
660 if (fev_get_evtime() - t0 >= MST_DOUBLE_CLICK_TIME(pmp->menu))
662 return False;
664 if (has_mouse_moved)
666 return False;
668 if (!pmp->flags.has_default_action &&
669 (mi && mi == MR_FIRST_ITEM(pmp->menu) && MI_IS_SELECTABLE(mi)))
671 return False;
673 if (pmp->flags.is_submenu)
675 return False;
677 if (pmp->flags.is_invoked_by_key_press && pdkp->timestamp == 0)
679 return False;
682 return True;
685 /* ---------------------------- item label parsing ------------------------- */
688 * Procedure:
689 * scanForHotkeys - Look for hotkey markers in a MenuItem
690 * (pete@tecc.co.uk)
692 * Inputs:
693 * it - MenuItem to scan
694 * column - The column number in which to look for a hotkey.
697 static void scanForHotkeys(
698 MenuItem *it, int column)
700 char *start;
701 char *s;
702 char *t;
704 /* Get start of string */
705 start = MI_LABEL(it)[column];
706 /* Scan whole string */
707 for (s = start; *s != '\0'; s++)
709 if (*s != '&')
711 continue;
713 if (s[1] != '&')
715 /* found a hotkey - only one hotkey per item */
716 break;
718 /* Just an escaped '&'; copy the string down over it */
719 for (t = s; *t != '\0'; t++)
721 t[0] = t[1];
725 if (*s != 0)
727 /* It's a hot key marker - work out the offset value */
728 MI_HOTKEY_COFFSET(it) = s - start;
729 MI_HOTKEY_COLUMN(it) = column;
730 MI_HAS_HOTKEY(it) = (s[1] != '\0');
731 MI_IS_HOTKEY_AUTOMATIC(it) = 0;
732 for ( ; *s != '\0'; s++)
734 /* Copy down.. */
735 s[0] = s[1];
739 return;
742 static void __copy_down(char *remove_from, char *remove_to)
744 char *t1;
745 char *t2;
747 for (t1 = remove_from, t2 = remove_to; *t2 != '\0'; t2++, t1++)
749 *t1 = *t2;
751 *t1 = '\0';
753 return;
756 static int __check_for_delimiter(char *s, const string_def_t *string_defs)
758 int type;
760 for (type = 0; string_defs[type].delimiter != '\0'; type++)
762 if (s[0] == string_defs[type].delimiter)
764 if (s[1] != string_defs[type].delimiter)
766 return type;
768 else
770 /* escaped delimiter, copy the
771 * string down over it */
772 __copy_down(s, s+1);
774 return -1;
779 return -1;
782 /* This scans for strings within delimiters and calls a callback based
783 * on the delimiter found for each found string */
784 static void scanForStrings(
785 char *instring, const string_def_t *string_defs,
786 string_context_t *context)
788 char *s;
789 int type;
790 char *string;
792 type = -1;
793 /* string is set whenever type >= 0, and unused otherwise
794 * set to NULL to supress compiler warning */
795 string = NULL;
796 for (s = instring; *s != '\0'; s++)
798 if (type < 0)
800 /* look for starting delimiters */
801 type = __check_for_delimiter(s, string_defs);
802 if (type >= 0)
804 /* start of a string */
805 string = s + 1;
808 else if (
809 s[0] == string_defs[type].delimiter &&
810 s[1] != string_defs[type].delimiter)
812 /* found ending delimiter */
813 Bool is_valid;
815 /* terminate the string pointer */
816 s[0] = '\0';
817 is_valid = string_defs[type].string_handler(
818 string, string_defs[type].delimiter, context);
819 /* restore the string */
820 s[0] = string_defs[type].delimiter;
822 if (is_valid)
824 /* the string was OK, remove it from
825 * instring */
826 __copy_down(string - 1, s + 1);
827 /* continue next iteration at the
828 * first character after the string */
829 s = string - 2;
832 type = -1;
834 else if (s[0] == string_defs[type].delimiter)
836 /* escaped delimiter, copy the string down over
837 * it */
838 __copy_down(s, s + 1);
843 /* Side picture support: this scans for a color int the menu name
844 for colorization */
845 static Bool __scan_for_color(
846 char *name, char type, string_context_t *context)
848 if (type != '^' || SCTX_GET_MR(*context) == NULL)
850 abort();
853 if (MR_HAS_SIDECOLOR(SCTX_GET_MR(*context)))
855 return False;
858 MR_SIDECOLOR(SCTX_GET_MR(*context)) = GetColor(name);
859 MR_HAS_SIDECOLOR(SCTX_GET_MR(*context)) = True;
861 return True;
864 static Bool __scan_for_pixmap(
865 char *name, char type, string_context_t *context)
867 FvwmPicture *p;
868 FvwmPictureAttributes fpa;
869 int current_mini_icon;
871 /* check that more pictures are allowed before trying to load the
872 * picture */
873 current_mini_icon = -999999999;
874 switch (type)
876 case '@':
877 if (SCTX_GET_MR(*context) == NULL)
879 abort();
881 if (MR_SIDEPIC(SCTX_GET_MR(*context)))
883 return False;
885 break;
886 case '*':
887 /* menu item picture, requires menu item */
888 if (SCTX_GET_MI(*context) == NULL)
890 abort();
892 if (MI_PICTURE(SCTX_GET_MI(*context)))
894 return False;
896 break;
897 case '%':
898 /* mini icon - look for next free spot */
899 if (SCTX_GET_MI(*context) == NULL)
901 abort();
903 current_mini_icon = 0;
904 while (current_mini_icon < MAX_MENU_ITEM_MINI_ICONS)
906 if (
907 MI_MINI_ICON(SCTX_GET_MI(*context))
908 [current_mini_icon])
910 current_mini_icon++;
912 else
914 break;
917 if (current_mini_icon == MAX_MENU_ITEM_MINI_ICONS)
919 return False;
921 break;
922 default:
923 abort();
926 fpa.mask = 0;
927 p = PCacheFvwmPicture(
928 dpy, Scr.NoFocusWin, NULL, name, fpa);
929 if (!p)
931 fvwm_msg(WARN, "scanForPixmap",
932 "Couldn't load image from %s", name);
934 /* return true to make missing pictures not appear in the
935 * label/name */
936 return True;
939 switch (type)
941 case '@':
942 MR_SIDEPIC(SCTX_GET_MR(*context)) = p;
944 break;
945 case '*':
946 MI_PICTURE(SCTX_GET_MI(*context)) = p;
947 MI_HAS_PICTURE(SCTX_GET_MI(*context)) = True;
949 break;
950 case '%':
951 MI_MINI_ICON(SCTX_GET_MI(*context))[current_mini_icon] = p;
952 MI_HAS_PICTURE(SCTX_GET_MI(*context)) = True;
954 break;
957 return True;
960 /* ---------------------------- item list handling ------------------------- */
962 static void unlink_item_from_menu(
963 MenuRoot *mr, MenuItem *mi)
965 MenuItem *next;
966 MenuItem *prev;
968 next = MI_NEXT_ITEM(mi);
969 prev = MI_PREV_ITEM(mi);
970 if (next != NULL)
972 MI_PREV_ITEM(next) = prev;
974 else
976 MR_LAST_ITEM(mr) = prev;
978 if (prev != NULL)
980 MI_NEXT_ITEM(prev) = next;
982 else
984 MR_FIRST_ITEM(mr) = next;
986 MI_NEXT_ITEM(mi) = NULL;
987 MI_PREV_ITEM(mi) = NULL;
988 MR_ITEMS(mr)--;
990 return;
993 /* Add the given menu item to the menu. If the first item of the menu is a
994 * title, and the do_replace_title flag is True, the old title is deleted and
995 * replaced by the new item. Otherwise the item is appended at the end of the
996 * menu. */
997 static void append_item_to_menu(
998 MenuRoot *mr, MenuItem *mi, Bool do_replace_title)
1000 if (MR_FIRST_ITEM(mr) == NULL)
1002 MR_FIRST_ITEM(mr) = mi;
1003 MR_LAST_ITEM(mr) = mi;
1004 MI_NEXT_ITEM(mi) = NULL;
1005 MI_PREV_ITEM(mi) = NULL;
1007 else if (do_replace_title)
1009 if (MI_IS_TITLE(MR_FIRST_ITEM(mr)))
1011 if (MR_FIRST_ITEM(mr) == MR_LAST_ITEM(mr))
1013 MR_LAST_ITEM(mr) = mi;
1015 if (MI_NEXT_ITEM(MR_FIRST_ITEM(mr)) != NULL)
1017 MI_PREV_ITEM(MI_NEXT_ITEM(
1018 MR_FIRST_ITEM(mr))) = mi;
1020 MI_NEXT_ITEM(mi) = MI_NEXT_ITEM(MR_FIRST_ITEM(mr));
1021 menuitem_free(MR_FIRST_ITEM(mr));
1023 else
1025 MI_PREV_ITEM(MR_FIRST_ITEM(mr)) = mi;
1026 MI_NEXT_ITEM(mi) = MR_FIRST_ITEM(mr);
1028 MI_PREV_ITEM(mi) = NULL;
1029 MR_FIRST_ITEM(mr) = mi;
1031 else
1033 MI_NEXT_ITEM(MR_LAST_ITEM(mr)) = mi;
1034 MI_PREV_ITEM(mi) = MR_LAST_ITEM(mr);
1035 MR_LAST_ITEM(mr) = mi;
1038 return;
1041 static void clone_menu_item_list(
1042 MenuRoot *dest_mr, MenuRoot *src_mr)
1044 MenuItem *mi;
1045 MenuItem *cloned_mi;
1046 MenuRoot *mr;
1048 MR_FIRST_ITEM(dest_mr) = NULL;
1049 MR_LAST_ITEM(dest_mr) = NULL;
1050 /* traverse the menu and all its continuations */
1051 for (mr = src_mr; mr != NULL; mr = MR_CONTINUATION_MENU(mr))
1053 /* duplicate all items in the current menu */
1054 for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
1056 if (MI_IS_CONTINUATION(mi))
1058 /* skip this item */
1059 continue;
1061 cloned_mi = menuitem_clone(mi);
1062 append_item_to_menu(dest_mr, cloned_mi, False);
1066 return;
1069 /* ---------------------------- MenuRoot maintenance functions ------------- */
1071 /* Extract interesting values from the item format string that are needed by
1072 * the size_menu_... functions. */
1073 static void calculate_item_sizes(MenuSizingParameters *msp)
1075 MenuItem *mi;
1076 MenuItemPartSizesT mipst;
1077 int i;
1078 Bool do_reverse_icon_order =
1079 (MST_USE_LEFT_SUBMENUS(msp->menu)) ? True : False;
1081 memset(&(msp->max), 0, sizeof(msp->max));
1082 /* Calculate the widths for all columns of all items. */
1083 for (mi = MR_FIRST_ITEM(msp->menu); mi != NULL; mi = MI_NEXT_ITEM(mi))
1085 if (MI_IS_TITLE(mi))
1087 menuitem_get_size(
1088 mi, &mipst, MST_PTITLEFONT(msp->menu),
1089 do_reverse_icon_order);
1091 else
1093 menuitem_get_size(
1094 mi, &mipst, MST_PSTDFONT(msp->menu),
1095 do_reverse_icon_order);
1097 /* adjust maximums */
1098 if (msp->max.i.triangle_width < mipst.triangle_width)
1100 msp->max.i.triangle_width = mipst.triangle_width;
1102 if (msp->max.i.title_width < mipst.title_width)
1104 msp->max.i.title_width = mipst.title_width;
1106 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1108 if (msp->max.i.label_width[i] < mipst.label_width[i])
1110 msp->max.i.label_width[i] =
1111 mipst.label_width[i];
1114 if (msp->max.i.picture_width < mipst.picture_width)
1116 msp->max.i.picture_width = mipst.picture_width;
1118 for (i = 0; i < MAX_MENU_ITEM_MINI_ICONS; i++)
1120 if (msp->max.i.icon_width[i] < mipst.icon_width[i])
1122 msp->max.i.icon_width[i] = mipst.icon_width[i];
1126 if (MR_SIDEPIC(msp->menu))
1128 msp->max.sidepic_width = MR_SIDEPIC(msp->menu)->width;
1130 else if (MST_SIDEPIC(msp->menu))
1132 msp->max.sidepic_width = MST_SIDEPIC(msp->menu)->width;
1135 return;
1140 * Calculate the positions of the columns in the menu.
1141 * Called by make_menu().
1144 static void size_menu_horizontally(MenuSizingParameters *msp)
1146 MenuItem *mi;
1147 Bool sidepic_is_left = True;
1148 int total_width;
1149 int sidepic_space = 0;
1150 int label_offset[MAX_MENU_ITEM_LABELS];
1151 char lcr_column[MAX_MENU_ITEM_LABELS];
1152 int i;
1153 int d;
1154 int relief_thickness = MST_RELIEF_THICKNESS(msp->menu);
1155 int *item_order[
1156 MAX_MENU_ITEM_LABELS + MAX_MENU_ITEM_MINI_ICONS +
1157 1 /* triangle */ + 2 /* relief markers */];
1158 int used_objects = 0;
1159 int left_objects = 0;
1160 int right_objects = 0;
1161 int x;
1162 unsigned char icons_placed = 0;
1163 Bool sidepic_placed = False;
1164 Bool triangle_placed = False;
1165 Bool relief_begin_placed = False;
1166 Bool relief_end_placed = False;
1167 char *format;
1168 Bool first = True;
1169 Bool done = False;
1170 Bool is_last_object_left = True;
1171 unsigned char columns_placed = 0;
1172 int relief_gap = 0;
1173 int gap_left;
1174 int gap_right;
1175 int chars;
1177 memset(item_order, 0, sizeof(item_order));
1178 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1180 lcr_column[i] = 'l';
1183 /* Now calculate the offsets for the columns. */
1184 format = MST_ITEM_FORMAT(msp->menu);
1185 if (!format)
1187 format = (MST_USE_LEFT_SUBMENUS(msp->menu)) ?
1188 DEFAULT_LEFT_MENU_ITEM_FORMAT :
1189 DEFAULT_MENU_ITEM_FORMAT;
1191 /* Place the individual items off the menu in case they are not
1192 * set in the format string. */
1193 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1195 label_offset[i] = 2 * Scr.MyDisplayWidth;
1198 x = MST_BORDER_WIDTH(msp->menu);
1199 while (*format && !done)
1201 switch (*format)
1203 case '%':
1204 format++;
1205 chars = 0;
1206 gap_left = 0;
1207 gap_right = 0;
1209 /* Insert a gap of %d pixels. */
1210 if (sscanf(format, "%d.%d%n", &gap_left,
1211 &gap_right, &chars) >= 2 ||
1212 (sscanf(format, "%d.%n", &gap_left,
1213 &chars) >= 1 && chars > 0) ||
1214 sscanf(format, "%d%n", &gap_left,
1215 &chars) >= 1 ||
1216 sscanf(format, ".%d%n", &gap_right,
1217 &chars) >= 1)
1219 if (gap_left > MR_SCREEN_WIDTH(msp->menu) ||
1220 gap_left < -MR_SCREEN_WIDTH(msp->menu))
1222 gap_left = 0;
1224 if (gap_right > MR_SCREEN_HEIGHT(msp->menu) ||
1225 gap_right < -MR_SCREEN_HEIGHT(msp->menu))
1227 gap_right = 0;
1229 /* Skip the number. */
1230 format += chars;
1232 else if (*format == '.')
1234 /* Skip a dot without values */
1235 format++;
1237 if (!*format)
1239 break;
1241 switch (*format)
1243 case 'l':
1244 case 'c':
1245 case 'r':
1246 /* A left, center or right aligned column. */
1247 if (columns_placed >=
1248 MAX_MENU_ITEM_LABELS)
1250 break;
1252 if (
1253 msp->max.i.label_width[columns_placed]
1254 <= 0)
1256 columns_placed++;
1257 break;
1259 lcr_column[columns_placed] = *format;
1260 x += gap_left;
1261 label_offset[columns_placed] = x;
1262 x += msp->max.i.label_width[columns_placed] +
1263 gap_right;
1264 item_order[used_objects++] =
1265 &(label_offset[columns_placed]);
1266 if (is_last_object_left && (*format == 'l'))
1268 left_objects++;
1270 else
1272 is_last_object_left = False;
1273 if (*format == 'r')
1275 right_objects++;
1277 else
1279 right_objects = 0;
1282 columns_placed++;
1283 break;
1284 case 's':
1285 /* the sidepic */
1286 if (sidepic_placed)
1288 break;
1290 sidepic_placed = True;
1291 if (msp->max.sidepic_width <= 0)
1293 break;
1295 x += gap_left;
1296 MR_SIDEPIC_X_OFFSET(msp->menu) = x;
1297 sidepic_is_left = first;
1298 sidepic_space = msp->max.sidepic_width +
1299 ((sidepic_is_left) ?
1300 gap_left : gap_right);
1301 x += msp->max.sidepic_width + gap_right;
1302 break;
1303 case 'i':
1304 /* a mini icon */
1305 if (icons_placed >=
1306 MAX_MENU_ITEM_MINI_ICONS)
1308 break;
1310 if (msp->max.i.icon_width[icons_placed] > 0)
1312 x += gap_left;
1313 MR_ICON_X_OFFSET(msp->menu)
1314 [icons_placed] = x;
1315 x += msp->max.i.icon_width
1316 [icons_placed] + gap_right;
1317 item_order[used_objects++] =
1318 &(MR_ICON_X_OFFSET(msp->menu)
1319 [icons_placed]);
1320 if (is_last_object_left)
1322 left_objects++;
1324 else
1326 right_objects++;
1329 icons_placed++;
1330 break;
1331 case '|':
1332 if (!relief_begin_placed)
1334 relief_begin_placed = True;
1335 x += gap_left;
1336 MR_HILIGHT_X_OFFSET(msp->menu) =
1338 x += relief_thickness +
1339 gap_right;
1340 relief_gap += gap_right;
1341 item_order[used_objects++] =
1342 &(MR_HILIGHT_X_OFFSET(
1343 msp->menu));
1344 if (is_last_object_left)
1346 left_objects++;
1348 else
1350 right_objects = 0;
1353 else if (!relief_end_placed)
1355 relief_end_placed = True;
1356 x += relief_thickness + gap_left;
1357 /* This is a hack: for now we record
1358 * the x coordinate of the end of the
1359 * hilight area, but later we'll place
1360 * the width in here. */
1361 MR_HILIGHT_WIDTH(msp->menu) = x;
1362 x += gap_right;
1363 relief_gap += gap_left;
1364 item_order[used_objects++] =
1365 &(MR_HILIGHT_WIDTH(msp->menu));
1366 right_objects++;
1368 break;
1369 case '>':
1370 case '<':
1371 /* the triangle for popup menus */
1372 if (triangle_placed)
1374 break;
1376 triangle_placed = True;
1377 if (msp->max.i.triangle_width > 0)
1379 x += gap_left;
1380 MR_TRIANGLE_X_OFFSET(
1381 msp->menu) = x;
1382 MR_IS_LEFT_TRIANGLE(msp->menu) =
1383 (*format == '<');
1384 x += msp->max.i.triangle_width +
1385 gap_right;
1386 item_order[used_objects++] =
1387 &(MR_TRIANGLE_X_OFFSET(
1388 msp->menu));
1389 if (is_last_object_left &&
1390 *format == '<')
1392 left_objects++;
1394 else
1396 is_last_object_left = False;
1397 right_objects++;
1400 break;
1401 case 'p':
1402 /* Simply add a gap. */
1403 x += gap_right + gap_left;
1404 break;
1405 case '\t':
1406 x += MENU_TAB_WIDTH * FlocaleTextWidth(
1407 MST_PSTDFONT(
1408 msp->menu), " ", 1);
1409 break;
1410 case ' ':
1411 /* Advance the x position. */
1412 x += FlocaleTextWidth(
1413 MST_PSTDFONT(msp->menu), format,
1415 break;
1416 default:
1417 /* Ignore unknown characters. */
1418 break;
1419 } /* switch (*format) */
1420 break;
1421 case '\t':
1422 x += MENU_TAB_WIDTH * FlocaleTextWidth(
1423 MST_PSTDFONT(msp->menu), " ", 1);
1424 break;
1425 case ' ':
1426 /* Advance the x position. */
1427 x += FlocaleTextWidth(
1428 MST_PSTDFONT(msp->menu), format, 1);
1429 break;
1430 default:
1431 /* Ignore unknown characters. */
1432 break;
1433 } /* switch (*format) */
1434 format++;
1435 first = False;
1436 } /* while (*format) */
1437 /* stored for vertical sizing */
1438 msp->used_item_labels = columns_placed;
1439 msp->used_mini_icons = icons_placed;
1441 /* Hide unplaced parts of the menu. */
1442 if (!sidepic_placed)
1444 MR_SIDEPIC_X_OFFSET(msp->menu) =
1445 2 * MR_SCREEN_WIDTH(msp->menu);
1447 for (i = icons_placed; i < MAX_MENU_ITEM_MINI_ICONS; i++)
1449 MR_ICON_X_OFFSET(msp->menu)[i] =
1450 2 * MR_SCREEN_WIDTH(msp->menu);
1452 if (!triangle_placed)
1454 MR_TRIANGLE_X_OFFSET(msp->menu) =
1455 2 * MR_SCREEN_WIDTH(msp->menu);
1457 msp->flags.is_popup_indicator_used = triangle_placed;
1459 total_width = x - MST_BORDER_WIDTH(msp->menu);
1460 d = (sidepic_space + 2 * relief_thickness +
1461 max(msp->max.i.title_width, msp->max.i.picture_width)) -
1462 total_width;
1463 if (d > 0)
1465 int m = 1 - left_objects;
1466 int n = 1 + used_objects - left_objects - right_objects;
1468 /* The title is larger than all menu items. Stretch the
1469 * gaps between the items up to the total width of the
1470 * title. */
1471 for (i = 0; i < used_objects; i++)
1473 if (i < left_objects)
1475 continue;
1477 if (i >= used_objects - right_objects)
1479 /* Right aligned item. */
1480 *(item_order[i]) += d;
1482 else
1484 /* Neither left nor right aligned item.
1485 * Divide the overhead gap evenly
1486 * between the items. */
1487 *(item_order[i]) += d * (m + i) / n;
1490 total_width += d;
1491 if (!sidepic_is_left)
1493 MR_SIDEPIC_X_OFFSET(msp->menu) += d;
1495 } /* if (d > 0) */
1496 MR_WIDTH(msp->menu) =
1497 total_width + 2 * MST_BORDER_WIDTH(msp->menu);
1498 MR_ITEM_WIDTH(msp->menu) = total_width - sidepic_space;
1499 MR_ITEM_X_OFFSET(msp->menu) = MST_BORDER_WIDTH(msp->menu);
1500 if (sidepic_is_left)
1502 MR_ITEM_X_OFFSET(msp->menu) += sidepic_space;
1504 if (!relief_begin_placed)
1506 MR_HILIGHT_X_OFFSET(msp->menu) = MR_ITEM_X_OFFSET(msp->menu);
1508 if (relief_end_placed)
1510 MR_HILIGHT_WIDTH(msp->menu) =
1511 MR_HILIGHT_WIDTH(msp->menu) -
1512 MR_HILIGHT_X_OFFSET(msp->menu);
1514 else
1516 MR_HILIGHT_WIDTH(msp->menu) =
1517 MR_ITEM_WIDTH(msp->menu) +
1518 MR_ITEM_X_OFFSET(msp->menu) -
1519 MR_HILIGHT_X_OFFSET(msp->menu);
1522 /* Now calculate the offsets for the individual labels. */
1523 for (mi = MR_FIRST_ITEM(msp->menu); mi != NULL; mi = MI_NEXT_ITEM(mi))
1525 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1527 if (MI_LABEL(mi)[i] == NULL)
1529 continue;
1531 if (!MI_IS_TITLE(mi) || !MI_IS_TITLE_CENTERED(mi))
1533 switch (lcr_column[i])
1535 case 'l':
1536 MI_LABEL_OFFSET(mi)[i] =
1537 label_offset[i];
1538 break;
1539 case 'c':
1540 MI_LABEL_OFFSET(mi)[i] =
1541 label_offset[i] +
1542 (msp->max.i.label_width[i] -
1543 MI_LABEL_OFFSET(mi)[i]) / 2;
1544 break;
1545 case 'r':
1546 MI_LABEL_OFFSET(mi)[i] =
1547 label_offset[i] +
1548 msp->max.i.label_width[i] -
1549 MI_LABEL_OFFSET(mi)[i];
1550 break;
1553 else
1555 /* This is a centered title item (indicated by
1556 * negative width). */
1557 MI_LABEL_OFFSET(mi)[i] =
1558 menudim_middle_x_offset(
1559 &MR_DIM(msp->menu)) -
1560 MI_LABEL_OFFSET(mi)[i] / 2;
1562 } /* for */
1563 } /* for */
1565 return;
1568 static int calc_more_item_height(MenuSizingParameters *msp)
1570 int height;
1572 height =
1573 MST_PSTDFONT(msp->menu)->height +
1574 MST_ITEM_GAP_ABOVE(msp->menu) +
1575 MST_ITEM_GAP_BELOW(msp->menu) +
1576 MST_RELIEF_THICKNESS(msp->menu);
1578 return height;
1581 static int calc_normal_item_height(MenuSizingParameters *msp, MenuItem *mi)
1583 int height;
1585 height =
1586 MST_ITEM_GAP_ABOVE(msp->menu) +
1587 MST_ITEM_GAP_BELOW(msp->menu) +
1588 MST_RELIEF_THICKNESS(msp->menu);
1589 /* Normal text entry or an entry with a sub menu triangle */
1590 if (
1591 (MI_HAS_TEXT(mi) && msp->used_item_labels) ||
1592 (MI_IS_POPUP(mi) &&
1593 msp->flags.is_popup_indicator_used))
1595 height += MST_PSTDFONT(msp->menu)->height;
1598 return height;
1603 * Calculate the positions of the columns in the menu.
1604 * Called by make_menu().
1607 static Bool size_menu_vertically(MenuSizingParameters *msp)
1609 MenuItem *mi;
1610 int y;
1611 int cItems;
1612 int relief_thickness = MST_RELIEF_THICKNESS(msp->menu);
1613 int i;
1614 Bool has_continuation_menu = False;
1616 MR_ITEM_TEXT_Y_OFFSET(msp->menu) =
1617 MST_PSTDFONT(msp->menu)->ascent + relief_thickness +
1618 MST_ITEM_GAP_ABOVE(msp->menu);
1620 /* mi_prev trails one behind mi, since we need to move that
1621 into a newly-made menu if we run out of space */
1622 y = MST_BORDER_WIDTH(msp->menu) + MST_VERTICAL_MARGIN_TOP(msp->menu);
1623 for (
1624 cItems = 0, mi = MR_FIRST_ITEM(msp->menu); mi != NULL;
1625 mi = MI_NEXT_ITEM(mi), cItems++)
1627 Bool last_item_has_relief =
1628 (MI_PREV_ITEM(mi)) ?
1629 MI_IS_SELECTABLE(MI_PREV_ITEM(mi)) : False;
1630 Bool has_mini_icon = False;
1631 int separator_height;
1632 int menu_height;
1634 separator_height = (last_item_has_relief) ?
1635 MENU_SEPARATOR_HEIGHT + relief_thickness :
1636 MENU_SEPARATOR_TOTAL_HEIGHT;
1637 MI_Y_OFFSET(mi) = y;
1638 if (MI_IS_TITLE(mi))
1640 MI_HEIGHT(mi) = MST_PTITLEFONT(msp->menu)->height +
1641 MST_TITLE_GAP_ABOVE(msp->menu) +
1642 MST_TITLE_GAP_BELOW(msp->menu);
1644 else if (MI_IS_SEPARATOR(mi))
1646 /* Separator */
1647 MI_HEIGHT(mi) = separator_height;
1649 else if (MI_IS_TEAR_OFF_BAR(mi))
1651 /* Tear off bar */
1652 MI_HEIGHT(mi) = relief_thickness +
1653 MENU_TEAR_OFF_BAR_HEIGHT;
1655 else
1657 MI_HEIGHT(mi) = calc_normal_item_height(msp, mi);
1659 if (MI_IS_TITLE(mi))
1661 /* add space for the underlines */
1662 switch (MST_TITLE_UNDERLINES(msp->menu))
1664 case 0:
1665 if (last_item_has_relief)
1666 MI_HEIGHT(mi) += relief_thickness;
1667 break;
1668 case 1:
1669 if (mi != MR_FIRST_ITEM(msp->menu))
1671 /* Space to draw the separator plus a
1672 * gap above */
1673 MI_HEIGHT(mi) += separator_height;
1675 if (MI_NEXT_ITEM(mi) != NULL)
1677 /* Space to draw the separator */
1678 MI_HEIGHT(mi) += MENU_SEPARATOR_HEIGHT;
1680 break;
1681 default:
1682 /* Space to draw n underlines. */
1683 MI_HEIGHT(mi) +=
1684 MENU_UNDERLINE_HEIGHT *
1685 MST_TITLE_UNDERLINES(msp->menu);
1686 if (last_item_has_relief)
1687 MI_HEIGHT(mi) += relief_thickness;
1688 break;
1691 for (i = 0; i < msp->used_mini_icons; i++)
1693 if (MI_MINI_ICON(mi)[i])
1695 has_mini_icon = True;
1697 if (MI_MINI_ICON(mi)[i] &&
1698 MI_HEIGHT(mi) <
1699 MI_MINI_ICON(mi)[i]->height + relief_thickness)
1701 MI_HEIGHT(mi) = MI_MINI_ICON(mi)[i]->height +
1702 relief_thickness;
1705 if (MI_PICTURE(mi))
1707 if ((MI_HAS_TEXT(mi) && msp->used_item_labels) ||
1708 has_mini_icon)
1710 MI_HEIGHT(mi) += MI_PICTURE(mi)->height;
1712 else
1714 MI_HEIGHT(mi) = MI_PICTURE(mi)->height +
1715 relief_thickness;
1718 y += MI_HEIGHT(mi);
1719 /* this item would have to be the last item, or else
1720 * we need to add a "More..." entry pointing to a new menu */
1721 menu_height =
1722 y + MST_BORDER_WIDTH(msp->menu) +
1723 MST_VERTICAL_MARGIN_BOTTOM(msp->menu) +
1724 ((MI_IS_SELECTABLE(mi)) ? relief_thickness : 0);
1725 if (menu_height > MR_SCREEN_HEIGHT(msp->menu))
1727 /* Item does not fit on screen anymore. */
1728 char *t;
1729 char *tempname;
1730 MenuRoot *menuContinuation;
1731 int more_item_height;
1733 more_item_height = calc_more_item_height(msp);
1734 /* Remove items form the menu until it fits (plus a
1735 * 'More' entry). */
1736 while (
1737 MI_PREV_ITEM(mi) != NULL &&
1738 menu_height > MR_SCREEN_HEIGHT(msp->menu))
1740 /* Remove current item. */
1741 y -= MI_HEIGHT(mi);
1742 mi = MI_PREV_ITEM(mi);
1743 cItems--;
1744 menu_height =
1745 y + MST_BORDER_WIDTH(msp->menu) +
1746 more_item_height + relief_thickness +
1747 MST_VERTICAL_MARGIN_BOTTOM(msp->menu);
1749 if (
1750 MI_PREV_ITEM(mi) == NULL ||
1751 menu_height > MR_SCREEN_HEIGHT(msp->menu))
1753 fvwm_msg(ERR, "size_menu_vertically",
1754 "Menu entry does not fit on screen");
1755 /* leave a coredump */
1756 abort();
1757 exit(1);
1760 t = EscapeString(MR_NAME(msp->menu), "\"", '\\');
1761 tempname = (char *)safemalloc(
1762 (10 + strlen(t)) * sizeof(char));
1763 strcpy(tempname, "Popup \"");
1764 strcat(tempname, t);
1765 strcat(tempname, "$\"");
1766 free(t);
1767 /* NewMenuRoot inserts at the head of the list of menus
1768 but, we need it at the end. (Give it just the name,
1769 * which is 6 chars past the action since
1770 * strlen("Popup ")==6 ) */
1771 t = (char *)safemalloc(strlen(MR_NAME(msp->menu)) + 2);
1772 strcpy(t, MR_NAME(msp->menu));
1773 strcat(t, "$");
1774 menuContinuation = NewMenuRoot(t);
1775 free(t);
1776 MR_CONTINUATION_MENU(msp->menu) = menuContinuation;
1778 /* Now move this item and the remaining items into the
1779 * new menu */
1780 MR_FIRST_ITEM(menuContinuation) = MI_NEXT_ITEM(mi);
1781 MR_LAST_ITEM(menuContinuation) =
1782 MR_LAST_ITEM(msp->menu);
1783 MR_ITEMS(menuContinuation) =
1784 MR_ITEMS(msp->menu) - cItems;
1785 MI_PREV_ITEM(MI_NEXT_ITEM(mi)) = NULL;
1787 /* mi_prev is now the last item in the parent menu */
1788 MR_LAST_ITEM(msp->menu) = mi;
1789 MR_ITEMS(msp->menu) = cItems;
1790 MI_NEXT_ITEM(mi) = NULL;
1792 /* use the same style for the submenu */
1793 MR_STYLE(menuContinuation) = MR_STYLE(msp->menu);
1794 MR_IS_LEFT_TRIANGLE(menuContinuation) =
1795 MR_IS_LEFT_TRIANGLE(msp->menu);
1796 /* migo: propagate missing_submenu_func */
1797 if (MR_MISSING_SUBMENU_FUNC(msp->menu))
1799 MR_MISSING_SUBMENU_FUNC(menuContinuation) =
1800 safestrdup(MR_MISSING_SUBMENU_FUNC(
1801 msp->menu));
1803 /* don't propagate sidepic, sidecolor, popup and
1804 * popdown actions */
1805 /* And add the entry pointing to the new menu */
1806 AddToMenu(
1807 msp->menu, gettext("More&..."), tempname,
1808 False /* no pixmap scan */, False, True);
1809 free(tempname);
1810 has_continuation_menu = True;
1812 } /* for */
1813 /* The menu may be empty here! */
1814 if (MR_LAST_ITEM(msp->menu) != NULL &&
1815 MI_IS_SELECTABLE(MR_LAST_ITEM(msp->menu)))
1817 y += relief_thickness;
1819 MR_HEIGHT(msp->menu) =
1820 y + MST_BORDER_WIDTH(msp->menu) +
1821 MST_VERTICAL_MARGIN_BOTTOM(msp->menu);
1823 return has_continuation_menu;
1828 * Merge menu continuations back into the original menu.
1829 * Called by make_menu().
1832 static void merge_continuation_menus(MenuRoot *mr)
1834 /* merge menu continuations into one menu again - needed when changing
1835 * the font size of a long menu. */
1836 while (MR_CONTINUATION_MENU(mr) != NULL)
1838 MenuRoot *cont = MR_CONTINUATION_MENU(mr);
1840 /* link first item of continuation to item before 'more...' */
1841 MI_NEXT_ITEM(MI_PREV_ITEM(MR_LAST_ITEM(mr))) =
1842 MR_FIRST_ITEM(cont);
1843 MI_PREV_ITEM(MR_FIRST_ITEM(cont)) =
1844 MI_PREV_ITEM(MR_LAST_ITEM(mr));
1845 menuitem_free(MR_LAST_ITEM(mr));
1846 MR_LAST_ITEM(mr) = MR_LAST_ITEM(cont);
1847 MR_CONTINUATION_MENU(mr) = MR_CONTINUATION_MENU(cont);
1848 /* fake an empty menu so that DestroyMenu does not destroy the
1849 * items. */
1850 MR_FIRST_ITEM(cont) = NULL;
1851 DestroyMenu(cont, False, False);
1854 return;
1859 * Creates the window for the menu.
1862 static void make_menu_window(MenuRoot *mr, Bool is_tear_off)
1864 unsigned long valuemask;
1865 XSetWindowAttributes attributes;
1866 int w;
1867 int h;
1868 unsigned int evmask;
1870 w = MR_WIDTH(mr);
1871 if (w == 0)
1873 w = 1;
1875 h = MR_HEIGHT(mr);
1876 if (h == 0)
1878 h = 1;
1881 attributes.background_pixel = (MST_HAS_MENU_CSET(mr)) ?
1882 Colorset[MST_CSET_MENU(mr)].bg : MST_MENU_COLORS(mr).back;
1883 if (MR_WINDOW(mr) != None)
1885 /* just resize the existing window */
1886 XResizeWindow(dpy, MR_WINDOW(mr), w, h);
1887 /* and change the background color */
1888 valuemask = CWBackPixel | CWCursor;
1889 XChangeWindowAttributes(
1890 dpy, MR_WINDOW(mr), valuemask, &attributes);
1892 else
1894 /* create a new window */
1895 valuemask = CWBackPixel | CWEventMask | CWCursor | CWColormap
1896 | CWBorderPixel | CWSaveUnder;
1897 attributes.border_pixel = 0;
1898 attributes.colormap = Pcmap;
1899 evmask = XEVMASK_MENUW;
1900 attributes.event_mask = 0;
1901 attributes.cursor = Scr.FvwmCursors[CRS_MENU];
1902 attributes.save_under = True;
1904 /* Create a display used to create the window. Can't use the
1905 * normal display because 'xkill' would kill the window
1906 * manager if used on a tear off menu. The display can't be
1907 * deleted right now because that would either destroy the new
1908 * window or leave it as an orphan if fvwm dies or is
1909 * restarted. */
1910 if (is_tear_off)
1912 MR_CREATE_DPY(mr) = XOpenDisplay(display_name);
1913 if (MR_CREATE_DPY(mr) == NULL)
1915 /* Doh. Use the standard display instead. */
1916 MR_CREATE_DPY(mr) = dpy;
1919 else
1921 MR_CREATE_DPY(mr) = dpy;
1923 MR_WINDOW(mr) = XCreateWindow(
1924 MR_CREATE_DPY(mr), Scr.Root, 0, 0, w, h,
1925 0, Pdepth, InputOutput, Pvisual, valuemask,
1926 &attributes);
1927 if (MR_CREATE_DPY(mr) != dpy)
1929 /* We *must* synchronize the display here. Otherwise
1930 * the request will never be processed. */
1931 XSync(MR_CREATE_DPY(mr), 1);
1933 if (MR_WINDOW(mr) != None)
1935 /* select events for the window from the standard
1936 * display */
1937 XSelectInput(dpy, MR_WINDOW(mr), evmask);
1939 XSaveContext(dpy, MR_WINDOW(mr), MenuContext,(caddr_t)mr);
1942 return;
1948 * Generates the window for a menu
1951 static void make_menu(MenuRoot *mr, Bool is_tear_off)
1953 MenuSizingParameters msp;
1954 Bool has_continuation_menu = False;
1956 if (MR_MAPPED_COPIES(mr) > 0)
1958 return;
1960 merge_continuation_menus(mr);
1963 memset(&msp, 0, sizeof(MenuSizingParameters));
1964 msp.menu = mr;
1965 calculate_item_sizes(&msp);
1966 /* Call size_menu_horizontally first because it calculated
1967 * some values used by size_menu_vertically. */
1968 size_menu_horizontally(&msp);
1969 has_continuation_menu = size_menu_vertically(&msp);
1970 /* repeat this step if the menu was split */
1971 } while (has_continuation_menu);
1972 MR_USED_MINI_ICONS(mr) = msp.used_mini_icons;
1974 MR_XANIMATION(mr) = 0;
1975 memset(&(MR_DYNAMIC_FLAGS(mr)), 0, sizeof(MR_DYNAMIC_FLAGS(mr)));
1977 /* create a new window for the menu */
1978 make_menu_window(mr, is_tear_off);
1979 MR_IS_UPDATED(mr) = 0;
1981 return;
1984 /* Make sure the menu is properly rebuilt when the style or the menu has
1985 * changed. */
1986 static void update_menu(MenuRoot *mr, MenuParameters *pmp)
1988 int sw;
1989 int sh;
1990 Bool has_screen_size_changed = False;
1991 fscreen_scr_arg fscr;
1993 if (MST_IS_UPDATED(mr))
1995 /* The menu style has changed. */
1996 MenuRoot *menu;
1998 for (menu = Menus.all; menu; menu = MR_NEXT_MENU(menu))
2000 if (MR_STYLE(menu) == MR_STYLE(mr))
2002 /* Mark all other menus with the same style as
2003 * changed. */
2004 MR_IS_UPDATED(menu) = 1;
2007 MST_IS_UPDATED(mr) = 0;
2009 fscr.xypos.x = pmp->screen_origin_x;
2010 fscr.xypos.y = pmp->screen_origin_y;
2011 FScreenGetScrRect(&fscr, FSCREEN_XYPOS, &JunkX, &JunkY, &sw, &sh);
2012 if (sw != MR_SCREEN_WIDTH(mr) || sh != MR_SCREEN_HEIGHT(mr))
2014 has_screen_size_changed = True;
2015 MR_SCREEN_WIDTH(mr) = sw;
2016 MR_SCREEN_HEIGHT(mr) = sh;
2018 if (MR_IS_UPDATED(mr) || has_screen_size_changed)
2020 /* The menu or the screen dimensions have changed. We have to
2021 * re-make it. */
2022 make_menu(mr, False);
2025 return;
2030 * Procedure:
2031 * copy_menu_root - creates a new instance of an existing menu
2033 * Returned Value:
2034 * (MenuRoot *)
2036 * Inputs:
2037 * mr - the MenuRoot structure of the existing menu
2040 static MenuRoot *copy_menu_root(MenuRoot *mr)
2042 MenuRoot *tmp;
2044 if (!mr || MR_COPIES(mr) >= MAX_MENU_COPIES)
2046 return NULL;
2048 tmp = (MenuRoot *)safemalloc(sizeof(MenuRoot));
2049 tmp->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic));
2050 memset(tmp->d, 0, sizeof(MenuRootDynamic));
2051 tmp->s = mr->s;
2053 MR_COPIES(mr)++;
2054 MR_ORIGINAL_MENU(tmp) = MR_ORIGINAL_MENU(mr);
2055 MR_CONTINUATION_MENU(tmp) = MR_CONTINUATION_MENU(mr);
2056 MR_NEXT_MENU(tmp) = MR_NEXT_MENU(mr);
2057 MR_NEXT_MENU(mr) = tmp;
2058 MR_WINDOW(tmp) = None;
2059 memset(&(MR_DYNAMIC_FLAGS(tmp)), 0, sizeof(MR_DYNAMIC_FLAGS(tmp)));
2061 return tmp;
2066 * Procedure:
2067 * clone_menu - duplicates an existing menu in newly allocated
2068 memory. The new menu is independent of the original.
2070 * Returned Value:
2071 * (MenuRoot *)
2073 * Inputs:
2074 * mr - the MenuRoot structure of the existing menu
2077 static void clone_menu_root_static(
2078 MenuRoot *dest_mr, MenuRoot *src_mr)
2080 dest_mr->s = (MenuRootStatic *)safemalloc(sizeof(MenuRootStatic));
2081 /* copy everything */
2082 memcpy(dest_mr->s, src_mr->s, sizeof(MenuRootStatic));
2083 /* special treatment for a few parts */
2084 if (MR_NAME(src_mr) != NULL)
2086 MR_NAME(dest_mr) = safestrdup(MR_NAME(src_mr));
2088 MR_COPIES(dest_mr) = 1;
2089 MR_MAPPED_COPIES(dest_mr) = 0;
2090 MR_POPUP_ACTION(dest_mr) = NULL;
2091 MR_POPDOWN_ACTION(dest_mr) = NULL;
2092 if (MR_MISSING_SUBMENU_FUNC(src_mr))
2094 MR_MISSING_SUBMENU_FUNC(dest_mr) =
2095 safestrdup(MR_MISSING_SUBMENU_FUNC(src_mr));
2097 if (MR_HAS_SIDECOLOR(src_mr))
2099 MR_SIDECOLOR(dest_mr) =
2100 fvwmlib_clone_color(MR_SIDECOLOR(src_mr));
2102 MR_SIDEPIC(dest_mr) = PCloneFvwmPicture(MR_SIDEPIC(src_mr));
2103 clone_menu_item_list(dest_mr, src_mr);
2105 return;
2108 static MenuRoot *clone_menu(MenuRoot *mr)
2110 MenuRoot *new_mr;
2112 new_mr = (MenuRoot *)safemalloc(sizeof(MenuRoot));
2113 new_mr->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic));
2114 memset(new_mr->d, 0, sizeof(MenuRootDynamic));
2115 clone_menu_root_static(new_mr, mr);
2117 return new_mr;
2120 /* ---------------------------- position hints ----------------------------- */
2122 static int float_to_int_with_tolerance(float f)
2124 int low;
2126 if (f < 0)
2128 low = (int)(f - ROUNDING_ERROR_TOLERANCE);
2130 else
2132 low = (int)(f + ROUNDING_ERROR_TOLERANCE);
2134 if ((int)f != low)
2136 return low;
2138 else
2140 return (int)(f);
2144 static void get_xy_from_position_hints(
2145 struct MenuPosHints *ph, int width, int height, int context_width,
2146 Bool do_reverse_x, int *ret_x, int *ret_y)
2148 float x_add;
2149 float y_add;
2151 *ret_x = ph->x;
2152 *ret_y = ph->y;
2153 if (ph->is_menu_relative)
2155 if (do_reverse_x)
2157 *ret_x -= ph->x_offset;
2158 x_add = width * (-1.0 - ph->x_factor) +
2159 ph->menu_width * (1.0 - ph->context_x_factor);
2161 else
2163 *ret_x += ph->x_offset;
2164 x_add = width * ph->x_factor +
2165 ph->menu_width * ph->context_x_factor;
2167 y_add = height * ph->y_factor;
2169 else
2171 x_add = width * ph->x_factor;
2172 y_add = height * ph->y_factor;
2174 *ret_x += float_to_int_with_tolerance(x_add);
2175 *ret_y += float_to_int_with_tolerance(y_add);
2177 return;
2181 * Used by get_menu_options
2183 * The vars are named for the x-direction, but this is used for both x and y
2185 static char *get_one_menu_position_argument(
2186 char *action, int x, int w, int *pFinalX, int *x_offset,
2187 float *width_factor, float *context_width_factor,
2188 Bool *is_menu_relative)
2190 char *token, *orgtoken, *naction;
2191 char c;
2192 int val;
2193 int chars;
2194 float fval;
2195 float factor = (float)w/100;
2196 float x_add = 0;
2198 naction = GetNextToken(action, &token);
2199 if (token == NULL)
2201 return action;
2203 orgtoken = token;
2204 *pFinalX = x;
2205 *x_offset = 0;
2206 *width_factor = 0.0;
2207 *context_width_factor = 0.0;
2208 if (sscanf(token,"o%d%n", &val, &chars) >= 1)
2210 fval = val;
2211 token += chars;
2212 x_add += fval*factor;
2213 *width_factor -= fval / 100.0;
2214 *context_width_factor += fval / 100.0;
2216 else if (token[0] == 'c')
2218 token++;
2219 x_add += ((float)w) / 2.0;
2220 *width_factor -= 0.5;
2221 *context_width_factor += 0.5;
2223 while (*token != 0)
2225 if (sscanf(token,"%d%n", &val, &chars) < 1)
2227 naction = action;
2228 break;
2230 fval = (float)val;
2231 token += chars;
2232 if (sscanf(token,"%c", &c) == 1)
2234 switch (c)
2236 case 'm':
2237 token++;
2238 *width_factor += fval / 100.0;
2239 *is_menu_relative = True;
2240 break;
2241 case 'p':
2242 token++;
2243 x_add += val;
2244 *x_offset += val;
2245 break;
2246 default:
2247 x_add += fval * factor;
2248 *context_width_factor += fval / 100.0;
2249 break;
2252 else
2254 x_add += fval * factor;
2255 *context_width_factor += fval / 100.0;
2258 *pFinalX += float_to_int_with_tolerance(x_add);
2259 free(orgtoken);
2261 return naction;
2264 /* Returns the menu options for the menu that a given menu item pops up */
2265 static void get_popup_options(
2266 MenuParameters *pmp, MenuItem *mi, MenuOptions *pops)
2268 if (!mi)
2270 return;
2272 pops->flags.has_poshints = 0;
2273 pops->pos_hints.has_screen_origin = True;
2274 pops->pos_hints.screen_origin_x = pmp->screen_origin_x;
2275 pops->pos_hints.screen_origin_y = pmp->screen_origin_y;
2276 /* just look past "Popup <name>" in the action */
2277 get_menu_options(
2278 SkipNTokens(MI_ACTION(mi), 2), MR_WINDOW(pmp->menu), NULL,
2279 NULL, pmp->menu, mi, pops);
2281 return;
2284 /* ---------------------------- menu painting functions --------------------- */
2286 static void clear_expose_menu_area(Window win, XEvent *e)
2288 if (e == NULL)
2290 XClearWindow(dpy, win);
2292 else
2294 XClearArea(
2295 dpy, win, e->xexpose.x, e->xexpose.y, e->xexpose.width,
2296 e->xexpose.height, False);
2299 return;
2304 * Draws a picture on the left side of the menu
2305 * What about a SidePic Colorset ? (olicha 2002-08-21)
2308 static void paint_side_pic(MenuRoot *mr, XEvent *pevent)
2310 GC gc;
2311 FvwmPicture *sidePic;
2312 int ys;
2313 int yt;
2314 int h;
2315 int bw = MST_BORDER_WIDTH(mr);
2317 if (MR_SIDEPIC(mr))
2319 sidePic = MR_SIDEPIC(mr);
2321 else if (MST_SIDEPIC(mr))
2323 sidePic = MST_SIDEPIC(mr);
2325 else
2327 return;
2329 if (Pdepth < 2)
2331 /* ? */
2332 gc = SHADOW_GC(MST_MENU_INACTIVE_GCS(mr));
2334 else
2336 gc = FORE_GC(MST_MENU_INACTIVE_GCS(mr));
2338 if (sidePic->height > MR_HEIGHT(mr) - 2 * bw)
2340 h = MR_HEIGHT(mr) - 2 * bw;
2341 ys = sidePic->height - h;
2342 yt = bw;
2344 else
2346 h = sidePic->height;
2347 ys = 0;
2348 yt = MR_HEIGHT(mr) - bw - sidePic->height;
2350 if (pevent != NULL && pevent->type == Expose)
2352 if (
2353 pevent->xexpose.x + pevent->xexpose.width <
2354 MR_SIDEPIC_X_OFFSET(mr) ||
2355 pevent->xexpose.x >=
2356 MR_SIDEPIC_X_OFFSET(mr) + sidePic->width)
2358 /* out of x-range for side bar */
2359 return;
2361 if (
2362 pevent->xexpose.y + pevent->xexpose.height < bw ||
2363 pevent->xexpose.y >= bw + MR_HEIGHT(mr))
2365 /* in the border */
2366 return;
2368 if (
2369 !(MR_HAS_SIDECOLOR(mr) || MST_HAS_SIDE_COLOR(mr)) &&
2370 pevent->xexpose.y + pevent->xexpose.height < yt)
2372 /* outside picture and no background */
2373 return;
2377 if (MR_HAS_SIDECOLOR(mr))
2379 Globalgcv.foreground = MR_SIDECOLOR(mr);
2381 else if (MST_HAS_SIDE_COLOR(mr))
2383 Globalgcv.foreground = MST_SIDE_COLOR(mr);
2385 if (MR_HAS_SIDECOLOR(mr) || MST_HAS_SIDE_COLOR(mr))
2387 Globalgcm = GCForeground;
2388 XChangeGC(dpy, Scr.ScratchGC1, Globalgcm, &Globalgcv);
2389 XFillRectangle(
2390 dpy, MR_WINDOW(mr), Scr.ScratchGC1,
2391 MR_SIDEPIC_X_OFFSET(mr), bw, sidePic->width,
2392 MR_HEIGHT(mr) - 2 * bw);
2394 else if (sidePic->alpha != None)
2396 XClearArea(
2397 dpy, MR_WINDOW(mr),
2398 MR_SIDEPIC_X_OFFSET(mr), yt, sidePic->width, h, False);
2400 PGraphicsRenderPicture(
2401 dpy, MR_WINDOW(mr), sidePic, 0, MR_WINDOW(mr),
2402 gc, Scr.MonoGC, Scr.AlphaGC,
2403 0, ys, sidePic->width, h,
2404 MR_SIDEPIC_X_OFFSET(mr), yt, sidePic->width, h, False);
2406 return;
2409 static Bool paint_menu_gradient_background(
2410 MenuRoot *mr, XEvent *pevent)
2412 MenuStyle *ms = MR_STYLE(mr);
2413 int bw = MST_BORDER_WIDTH(mr);
2414 XRectangle bounds;
2415 Pixmap pmap;
2416 GC pmapgc;
2417 XGCValues gcv;
2418 unsigned long gcm = GCLineWidth;
2419 int switcher = -1;
2420 Bool do_clear = False;
2422 gcv.line_width = 1;
2423 bounds.x = bw;
2424 bounds.y = bw;
2425 bounds.width = MR_WIDTH(mr) - bw;
2426 bounds.height = MR_HEIGHT(mr) - bw;
2427 /* H, V, D and B gradients are optimized and have
2428 * their own code here. (if no dither) */
2429 if (!ST_FACE(ms).u.grad.do_dither)
2431 switcher = ST_FACE(ms).gradient_type;
2433 switch (switcher)
2435 case H_GRADIENT:
2436 if (MR_IS_BACKGROUND_SET(mr) == False)
2438 register int i;
2439 register int dw;
2441 pmap = XCreatePixmap(
2442 dpy, MR_WINDOW(mr), MR_WIDTH(mr),
2443 DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS,
2444 Pdepth);
2445 pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv);
2446 dw = (float)
2447 (bounds.width / ST_FACE(ms).u.grad.npixels)
2448 + 1;
2449 for (i = 0; i < ST_FACE(ms).u.grad.npixels; i++)
2451 int x;
2453 x = i * bounds.width /
2454 ST_FACE(ms).u.grad.npixels;
2455 XSetForeground(
2456 dpy, pmapgc,
2457 ST_FACE(ms).u.grad.xcs[i].pixel);
2458 XFillRectangle(
2459 dpy, pmap, pmapgc, x, 0, dw,
2460 DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS);
2462 XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap);
2463 XFreeGC(dpy,pmapgc);
2464 XFreePixmap(dpy,pmap);
2465 MR_IS_BACKGROUND_SET(mr) = True;
2467 do_clear = True;
2468 break;
2469 case V_GRADIENT:
2470 if (MR_IS_BACKGROUND_SET(mr) == False)
2472 register int i;
2473 register int dh;
2474 static int best_tile_width = 0;
2475 int junk;
2477 if (best_tile_width == 0)
2479 int tw = DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS;
2481 if (!XQueryBestTile(
2482 dpy, Scr.screen, tw, tw,
2483 (unsigned int*)&best_tile_width,
2484 (unsigned int*)&junk))
2486 /* call failed, use default and risk a
2487 * screwed up tile */
2488 best_tile_width = tw;
2491 pmap = XCreatePixmap(
2492 dpy, MR_WINDOW(mr), best_tile_width,
2493 MR_HEIGHT(mr), Pdepth);
2494 pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv);
2495 dh = (float) (bounds.height /
2496 ST_FACE(ms).u.grad.npixels) + 1;
2497 for (i = 0; i < ST_FACE(ms).u.grad.npixels; i++)
2499 int y;
2501 y = i * bounds.height /
2502 ST_FACE(ms).u.grad.npixels;
2503 XSetForeground(
2504 dpy, pmapgc,
2505 ST_FACE(ms).u.grad.xcs[i].pixel);
2506 XFillRectangle(
2507 dpy, pmap, pmapgc, 0, y,
2508 best_tile_width, dh);
2510 XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap);
2511 XFreeGC(dpy,pmapgc);
2512 XFreePixmap(dpy,pmap);
2513 MR_IS_BACKGROUND_SET(mr) = True;
2515 do_clear = True;
2516 break;
2517 case D_GRADIENT:
2518 case B_GRADIENT:
2520 register int i = 0, numLines;
2521 int cindex = -1;
2522 XRectangle r;
2523 FvwmPicture *sidePic = NULL;
2525 if (MR_SIDEPIC(mr))
2527 sidePic = MR_SIDEPIC(mr);
2529 else if (MST_SIDEPIC(mr))
2531 sidePic = MST_SIDEPIC(mr);
2533 if (pevent)
2535 r.x = pevent->xexpose.x;
2536 r.y = pevent->xexpose.y;
2537 r.width = pevent->xexpose.width;
2538 r.height = pevent->xexpose.height;
2540 else
2542 r.x = bw;
2543 r.y = bw;
2544 r.width = MR_WIDTH(mr) - 2 * bw;
2545 r.height = MR_HEIGHT(mr) - 2 * bw;
2547 XSetClipRectangles(
2548 dpy, Scr.TransMaskGC, 0, 0, &r, 1, Unsorted);
2549 numLines = MR_WIDTH(mr) + MR_HEIGHT(mr) - 2 * bw;
2550 for (i = 0; i < numLines; i++)
2552 if ((int)(i * ST_FACE(ms).u.grad.npixels / numLines) >
2553 cindex)
2555 /* pick the next colour (skip if necc.) */
2556 cindex = i * ST_FACE(ms).u.grad.npixels /
2557 numLines;
2558 XSetForeground(
2559 dpy, Scr.TransMaskGC,
2560 ST_FACE(ms).u.grad.xcs[cindex].pixel);
2562 if (ST_FACE(ms).gradient_type == D_GRADIENT)
2564 XDrawLine(dpy, MR_WINDOW(mr),
2565 Scr.TransMaskGC, 0, i, i, 0);
2567 else /* B_GRADIENT */
2569 XDrawLine(dpy, MR_WINDOW(mr), Scr.TransMaskGC,
2570 0, MR_HEIGHT(mr) - 1 - i, i,
2571 MR_HEIGHT(mr) - 1);
2575 XSetClipMask(dpy, Scr.TransMaskGC, None);
2576 break;
2577 default:
2578 if (MR_IS_BACKGROUND_SET(mr) == False)
2580 int g_width;
2581 int g_height;
2583 /* let library take care of all other gradients */
2584 pmap = XCreatePixmap(
2585 dpy, MR_WINDOW(mr), MR_WIDTH(mr),
2586 MR_HEIGHT(mr), Pdepth);
2587 pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv);
2589 /* find out the size the pixmap should be */
2590 CalculateGradientDimensions(
2591 dpy, MR_WINDOW(mr), ST_FACE(ms).u.grad.npixels,
2592 ST_FACE(ms).gradient_type,
2593 ST_FACE(ms).u.grad.do_dither, &g_width,
2594 &g_height);
2595 /* draw the gradient directly into the window */
2596 CreateGradientPixmap(
2597 dpy, MR_WINDOW(mr), pmapgc,
2598 ST_FACE(ms).gradient_type, g_width, g_height,
2599 ST_FACE(ms).u.grad.npixels,
2600 ST_FACE(ms).u.grad.xcs,
2601 ST_FACE(ms).u.grad.do_dither,
2602 &(MR_STORED_PIXELS(mr).d_pixels),
2603 &(MR_STORED_PIXELS(mr).d_npixels),
2604 pmap, bw, bw,
2605 MR_WIDTH(mr) - bw, MR_HEIGHT(mr) - bw, NULL);
2606 XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap);
2607 XFreeGC(dpy, pmapgc);
2608 XFreePixmap(dpy, pmap);
2609 MR_IS_BACKGROUND_SET(mr) = True;
2611 do_clear = True;
2612 break;
2614 return do_clear;
2617 static Bool paint_menu_pixmap_background(
2618 MenuRoot *mr, XEvent *pevent)
2620 MenuStyle *ms = MR_STYLE(mr);
2621 int width, height, x, y;
2622 int bw = MST_BORDER_WIDTH(mr);
2623 FvwmPicture *p;
2625 p = ST_FACE(ms).u.p;
2626 width = MR_WIDTH(mr) - 2 * bw;
2627 height = MR_HEIGHT(mr) - 2 * bw;
2628 y = (int)(height - p->height) / 2;
2629 x = (int)(width - p->width) / 2;
2630 if (x < bw)
2632 x = bw;
2634 if (y < bw)
2636 y = bw;
2638 if (width > p->width)
2640 width = p->width;
2642 if (height > p->height)
2644 height = p->height;
2646 if (width > MR_WIDTH(mr) - x - bw)
2648 width = MR_WIDTH(mr) - x - bw;
2650 if (height > MR_HEIGHT(mr) - y - bw)
2652 height = MR_HEIGHT(mr) - y - bw;
2654 XSetClipMask(dpy, Scr.TransMaskGC, p->mask);
2655 XSetClipOrigin(dpy, Scr.TransMaskGC, x, y);
2656 XCopyArea(
2657 dpy, p->picture, MR_WINDOW(mr), Scr.TransMaskGC,
2658 bw, bw, width, height, x, y);
2660 return False;
2665 * Procedure:
2666 * get_menu_paint_item_parameters - prepares the parameters to be
2667 * passed to menuitem_paint().
2669 * mr - the menu instance that holds the menu item
2670 * mi - the menu item to redraw
2671 * fw - the FvwmWindow structure to check against allowed functions
2674 static void get_menu_paint_item_parameters(
2675 MenuPaintItemParameters *mpip, MenuRoot *mr, MenuItem *mi,
2676 FvwmWindow *fw, XEvent *pevent, Bool do_redraw_menu_border)
2678 mpip->ms = MR_STYLE(mr);
2679 mpip->w = MR_WINDOW(mr);
2680 mpip->selected_item = MR_SELECTED_ITEM(mr);
2681 mpip->dim = &MR_DIM(mr);
2682 mpip->fw = fw;
2683 mpip->used_mini_icons = MR_USED_MINI_ICONS(mr);
2684 mpip->cb_mr = mr;
2685 mpip->cb_reset_bg = paint_menu_gradient_background;
2686 mpip->flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
2687 mpip->flags.is_left_triangle = MR_IS_LEFT_TRIANGLE(mr);
2688 mpip->ev = pevent;
2690 return;
2695 * Procedure:
2696 * paint_menu - draws the entire menu
2699 static void paint_menu(
2700 MenuRoot *mr, XEvent *pevent, FvwmWindow *fw)
2702 MenuItem *mi;
2703 MenuStyle *ms = MR_STYLE(mr);
2704 int bw = MST_BORDER_WIDTH(mr);
2705 XGCValues gcv;
2706 int relief_thickness = ST_RELIEF_THICKNESS(MR_STYLE(mr));
2708 gcv.line_width = 3;
2709 if (fw && !check_if_fvwm_window_exists(fw))
2711 fw = NULL;
2713 if (MR_IS_PAINTED(mr) && pevent &&
2714 (pevent->xexpose.x >= MR_WIDTH(mr) - bw ||
2715 pevent->xexpose.x + pevent->xexpose.width <= bw ||
2716 pevent->xexpose.y >= MR_HEIGHT(mr) - bw ||
2717 pevent->xexpose.y + pevent->xexpose.height <= bw))
2719 /* Only the border was obscured. Redraw it centrally instead of
2720 * redrawing several menu items. */
2721 RelieveRectangle(
2722 dpy, MR_WINDOW(mr), 0, 0, MR_WIDTH(mr) - 1,
2723 MR_HEIGHT(mr) - 1, (Pdepth < 2) ?
2724 SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)) :
2725 HILIGHT_GC(MST_MENU_INACTIVE_GCS(mr)),
2726 SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)), bw);
2728 return;
2731 MR_IS_PAINTED(mr) = 1;
2732 /* paint the menu background */
2733 if (ms && ST_HAS_MENU_CSET(ms))
2735 if (MR_IS_BACKGROUND_SET(mr) == False)
2737 SetWindowBackground(
2738 dpy, MR_WINDOW(mr), MR_WIDTH(mr),
2739 MR_HEIGHT(mr), &Colorset[ST_CSET_MENU(ms)],
2740 Pdepth, FORE_GC(ST_MENU_INACTIVE_GCS(ms)),
2741 True);
2742 MR_IS_BACKGROUND_SET(mr) = True;
2745 else if (ms)
2747 int type;
2748 Bool do_clear = False;
2750 type = ST_FACE(ms).type;
2751 switch(type)
2753 case SolidMenu:
2754 XSetWindowBackground(
2755 dpy, MR_WINDOW(mr), MST_FACE(mr).u.back);
2756 do_clear = True;
2757 break;
2758 case GradientMenu:
2759 do_clear = paint_menu_gradient_background(mr, pevent);
2760 break;
2761 case PixmapMenu:
2762 do_clear = paint_menu_pixmap_background(mr, pevent);
2763 break;
2764 case TiledPixmapMenu:
2765 XSetWindowBackgroundPixmap(
2766 dpy, MR_WINDOW(mr), ST_FACE(ms).u.p->picture);
2767 do_clear = True;
2768 break;
2769 } /* switch(type) */
2770 if (do_clear == True)
2772 clear_expose_menu_area(MR_WINDOW(mr), pevent);
2774 } /* if (ms) */
2775 /* draw the relief */
2776 RelieveRectangle(dpy, MR_WINDOW(mr), 0, 0, MR_WIDTH(mr) - 1,
2777 MR_HEIGHT(mr) - 1, (Pdepth < 2) ?
2778 SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)) :
2779 HILIGHT_GC(MST_MENU_INACTIVE_GCS(mr)),
2780 SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)), bw);
2781 /* paint the menu items */
2782 for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
2784 int do_draw = 0;
2786 /* be smart about handling the expose, redraw only the entries
2787 * that we need to */
2788 if (pevent == NULL)
2790 do_draw = True;
2792 else
2794 register int b_offset;
2796 b_offset = MI_Y_OFFSET(mi) + MI_HEIGHT(mi);
2797 if (MR_SELECTED_ITEM(mr) == mi)
2799 b_offset += relief_thickness;
2801 if (pevent->xexpose.y < b_offset &&
2802 (pevent->xexpose.y + pevent->xexpose.height) >
2803 MI_Y_OFFSET(mi))
2805 do_draw = True;
2808 if (do_draw)
2810 MenuPaintItemParameters mpip;
2812 get_menu_paint_item_parameters(
2813 &mpip, mr, NULL, fw, pevent, True);
2814 mpip.flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
2815 menuitem_paint(mi, &mpip);
2818 paint_side_pic(mr, pevent);
2819 XFlush(dpy);
2821 return;
2824 /* Set the selected-ness state of the menuitem passed in */
2825 static void select_menu_item(
2826 MenuRoot *mr, MenuItem *mi, Bool select, FvwmWindow *fw)
2828 if (select == True && MR_SELECTED_ITEM(mr) != NULL &&
2829 MR_SELECTED_ITEM(mr) != mi)
2831 select_menu_item(mr, MR_SELECTED_ITEM(mr), False, fw);
2833 else if (select == False && MR_SELECTED_ITEM(mr) == NULL)
2835 return;
2837 else if (select == True && MR_SELECTED_ITEM(mr) == mi)
2839 return;
2841 if (!MI_IS_SELECTABLE(mi))
2843 return;
2846 if (select == False)
2848 MI_WAS_DESELECTED(mi) = True;
2851 if (!MST_HAS_MENU_CSET(mr))
2853 switch (MST_FACE(mr).type)
2855 case GradientMenu:
2856 if (select == True)
2858 int iy;
2859 int ih;
2860 int mw;
2861 XEvent e;
2863 if (!MR_IS_PAINTED(mr))
2865 flush_expose(MR_WINDOW(mr));
2866 paint_menu(mr, NULL, fw);
2868 iy = MI_Y_OFFSET(mi);
2869 ih = MI_HEIGHT(mi) +
2870 (MI_IS_SELECTABLE(mi) ?
2871 MST_RELIEF_THICKNESS(mr) : 0);
2872 if (iy < 0)
2874 ih += iy;
2875 iy = 0;
2877 mw = MR_WIDTH(mr) - 2 * MST_BORDER_WIDTH(mr);
2878 if (iy + ih > MR_HEIGHT(mr))
2879 ih = MR_HEIGHT(mr) - iy;
2880 /* grab image */
2881 MR_STORED_ITEM(mr).stored =
2882 XCreatePixmap(
2883 dpy, Scr.NoFocusWin, mw, ih,
2884 Pdepth);
2885 XSetGraphicsExposures(
2886 dpy,
2887 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2888 True);
2889 XSync(dpy, 0);
2890 while (FCheckTypedEvent(dpy, NoExpose, &e))
2892 /* nothing to do here */
2894 XCopyArea(
2895 dpy, MR_WINDOW(mr),
2896 MR_STORED_ITEM(mr).stored,
2897 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2898 MST_BORDER_WIDTH(mr), iy, mw, ih, 0,
2900 XSync(dpy, 0);
2901 if (FCheckTypedEvent(dpy, NoExpose, &e))
2903 MR_STORED_ITEM(mr).y = iy;
2904 MR_STORED_ITEM(mr).width = mw;
2905 MR_STORED_ITEM(mr).height = ih;
2907 else
2909 XFreePixmap(
2910 dpy,
2911 MR_STORED_ITEM(mr).stored);
2912 MR_STORED_ITEM(mr).stored = None;
2913 MR_STORED_ITEM(mr).width = 0;
2914 MR_STORED_ITEM(mr).height = 0;
2915 MR_STORED_ITEM(mr).y = 0;
2917 XSetGraphicsExposures(
2918 dpy,
2919 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2920 False);
2922 else if (select == False &&
2923 MR_STORED_ITEM(mr).width != 0)
2925 /* ungrab image */
2926 XCopyArea(
2927 dpy, MR_STORED_ITEM(mr).stored,
2928 MR_WINDOW(mr),
2929 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2930 0, 0, MR_STORED_ITEM(mr).width,
2931 MR_STORED_ITEM(mr).height,
2932 MST_BORDER_WIDTH(mr),
2933 MR_STORED_ITEM(mr).y);
2935 if (MR_STORED_ITEM(mr).stored != None)
2937 XFreePixmap(
2938 dpy, MR_STORED_ITEM(mr).stored);
2940 MR_STORED_ITEM(mr).stored = None;
2941 MR_STORED_ITEM(mr).width = 0;
2942 MR_STORED_ITEM(mr).height = 0;
2943 MR_STORED_ITEM(mr).y = 0;
2945 else if (select == False)
2947 XEvent e;
2948 FvwmPicture *sidePic = NULL;
2950 e.type = Expose;
2951 e.xexpose.x = MST_BORDER_WIDTH(mr);
2952 e.xexpose.y = MI_Y_OFFSET(mi);
2953 e.xexpose.width = MR_WIDTH(mr) - 2 *
2954 MST_BORDER_WIDTH(mr);
2955 e.xexpose.height = MI_HEIGHT(mi) +
2956 (MI_IS_SELECTABLE(mi) ?
2957 MST_RELIEF_THICKNESS(mr) : 0);
2958 if (MR_SIDEPIC(mr))
2960 sidePic = MR_SIDEPIC(mr);
2962 else if (MST_SIDEPIC(mr))
2964 sidePic = MST_SIDEPIC(mr);
2966 if (sidePic)
2968 e.xexpose.width -= sidePic->width;
2969 if (MR_SIDEPIC_X_OFFSET(mr) ==
2970 MST_BORDER_WIDTH(mr))
2972 e.xexpose.x += sidePic->width;
2975 MR_SELECTED_ITEM(mr) = (select) ? mi : NULL;
2976 paint_menu(mr, &e, fw);
2978 break;
2979 default:
2980 if (MR_STORED_ITEM(mr).width != 0)
2982 if (MR_STORED_ITEM(mr).stored != None)
2984 XFreePixmap(
2985 dpy,
2986 MR_STORED_ITEM(mr).stored);
2988 MR_STORED_ITEM(mr).stored = None;
2989 MR_STORED_ITEM(mr).width = 0;
2990 MR_STORED_ITEM(mr).height = 0;
2991 MR_STORED_ITEM(mr).y = 0;
2993 break;
2994 } /* switch */
2995 } /* if */
2997 if (fw && !check_if_fvwm_window_exists(fw))
2999 fw = NULL;
3001 MR_SELECTED_ITEM(mr) = (select) ? mi : NULL;
3002 if (MR_IS_PAINTED(mr))
3004 MenuPaintItemParameters mpip;
3006 get_menu_paint_item_parameters(&mpip, mr, mi, fw, NULL, False);
3007 menuitem_paint(mi, &mpip);
3010 return;
3013 /* ---------------------------- popping menu up or down -------------------- */
3015 static int get_left_popup_x_position(MenuRoot *mr, MenuRoot *submenu, int x)
3017 if (MST_USE_LEFT_SUBMENUS(mr))
3019 return (x - MST_POPUP_OFFSET_ADD(mr) - MR_WIDTH(submenu) +
3020 MR_WIDTH(mr) *
3021 (100 - MST_POPUP_OFFSET_PERCENT(mr)) / 100);
3023 else
3025 return (x - MR_WIDTH(submenu) + MST_BORDER_WIDTH(mr));
3029 static int get_right_popup_x_position(MenuRoot *mr, MenuRoot *submenu, int x)
3031 if (MST_USE_LEFT_SUBMENUS(mr))
3033 return (x + MR_WIDTH(mr) - MST_BORDER_WIDTH(mr));
3035 else
3037 return (x + MR_WIDTH(mr) * MST_POPUP_OFFSET_PERCENT(mr) / 100 +
3038 MST_POPUP_OFFSET_ADD(mr));
3042 static void get_prefered_popup_position(
3043 MenuRoot *mr, MenuRoot *submenu, int *px, int *py,
3044 Bool *pprefer_left_submenus)
3046 int menu_x, menu_y;
3047 MenuItem *mi = NULL;
3049 if (!menu_get_geometry(
3050 mr, &JunkRoot, &menu_x, &menu_y, &JunkWidth, &JunkHeight,
3051 &JunkBW, &JunkDepth))
3053 *px = 0;
3054 *py = 0;
3055 *pprefer_left_submenus = False;
3056 fvwm_msg(ERR, "get_prefered_popup_position",
3057 "can't get geometry of menu %s", MR_NAME(mr));
3058 return;
3060 /* set the direction flag */
3061 *pprefer_left_submenus =
3062 (MR_HAS_POPPED_UP_LEFT(mr) ||
3063 (MST_USE_LEFT_SUBMENUS(mr) &&
3064 !MR_HAS_POPPED_UP_RIGHT(mr)));
3065 /* get the x position */
3066 if (*pprefer_left_submenus)
3068 *px = get_left_popup_x_position(mr, submenu, menu_x);
3070 else
3072 *px = get_right_popup_x_position(mr, submenu, menu_x);
3074 /* get the y position */
3075 if (MR_SELECTED_ITEM(mr))
3077 mi = MR_SELECTED_ITEM(mr);
3079 if (mi)
3081 *py = menu_y + MI_Y_OFFSET(mi) -
3082 MST_BORDER_WIDTH(mr) + MST_RELIEF_THICKNESS(mr);
3084 else
3086 *py = menu_y;
3090 static int do_menus_overlap(
3091 MenuRoot *mr, int x, int y, int width, int height, int h_tolerance,
3092 int v_tolerance, int s_tolerance, Bool allow_popup_offset_tolerance)
3094 int prior_x, prior_y, x_overlap;
3095 int prior_width, prior_height;
3097 if (mr == NULL)
3099 return 0;
3101 if (!menu_get_geometry(
3102 mr, &JunkRoot,&prior_x,&prior_y, &prior_width,
3103 &prior_height, &JunkBW, &JunkDepth))
3105 return 0;
3107 x_overlap = 0;
3108 if (allow_popup_offset_tolerance)
3110 if (MST_POPUP_OFFSET_ADD(mr) < 0)
3112 s_tolerance = -MST_POPUP_OFFSET_ADD(mr);
3114 if (MST_USE_LEFT_SUBMENUS(mr))
3116 prior_x += (100 - MST_POPUP_OFFSET_PERCENT(mr)) / 100;
3118 else
3120 prior_width *=
3121 (float)(MST_POPUP_OFFSET_PERCENT(mr)) / 100.0;
3124 if (MST_USE_LEFT_SUBMENUS(mr))
3126 int t = s_tolerance;
3127 s_tolerance = h_tolerance;
3128 h_tolerance = t;
3130 if (y < prior_y + prior_height - v_tolerance &&
3131 prior_y < y + height - v_tolerance &&
3132 x < prior_x + prior_width - s_tolerance &&
3133 prior_x < x + width - h_tolerance)
3135 x_overlap = x - prior_x;
3136 if (x <= prior_x)
3138 x_overlap--;
3142 return x_overlap;
3147 * Procedure:
3148 * pop_menu_up - pop up a pull down menu
3150 * Inputs:
3151 * x, y - location of upper left of menu
3152 * do_warp_to_item - warp pointer to the first item after title
3153 * pops - pointer to the menu options for new menu
3156 static int pop_menu_up(
3157 MenuRoot **pmenu, MenuParameters *pmp, MenuRoot *parent_menu,
3158 MenuItem *parent_item, const exec_context_t **pexc, int x, int y,
3159 Bool prefer_left_submenu, Bool do_warp_to_item, MenuOptions *pops,
3160 Bool *ret_overlap, Window popdown_window)
3162 Bool do_warp_to_title = False;
3163 int x_overlap = 0;
3164 int x_clipped_overlap;
3165 MenuRoot *mrMi;
3166 MenuRoot *mr;
3167 FvwmWindow *fw;
3168 int context;
3169 int bw = 0;
3170 int bwp = 0;
3171 int prev_x;
3172 int prev_y;
3173 int prev_width;
3174 int prev_height;
3175 unsigned int event_mask;
3176 int scr_x, scr_y;
3177 int scr_w, scr_h;
3179 mr = *pmenu;
3180 if (!mr ||
3181 (MR_MAPPED_COPIES(mr) > 0 && MR_COPIES(mr) >= MAX_MENU_COPIES))
3183 return False;
3187 * handle dynamic menu actions
3190 /* First of all, execute the popup action (if defined). */
3191 if (MR_POPUP_ACTION(mr))
3193 char *menu_name;
3194 saved_pos_hints pos_hints;
3195 Bool is_busy_grabbed = False;
3196 int mapped_copies = MR_MAPPED_COPIES(mr);
3198 /* save variables that we still need but that may be
3199 * overwritten */
3200 menu_name = safestrdup(MR_NAME(mr));
3201 pos_hints = last_saved_pos_hints;
3202 if (Scr.BusyCursor & BUSY_DYNAMICMENU)
3204 is_busy_grabbed = GrabEm(CRS_WAIT, GRAB_BUSYMENU);
3206 /* Execute the action */
3207 __menu_execute_function(pmp->pexc, MR_POPUP_ACTION(mr));
3208 if (is_busy_grabbed)
3210 UngrabEm(GRAB_BUSYMENU);
3212 /* restore the stuff we saved */
3213 last_saved_pos_hints = pos_hints;
3214 if (mapped_copies == 0)
3216 /* Now let's see if the menu still exists. It may have
3217 * been destroyed and recreated, so we have to look for
3218 * a menu with the saved name. menus_find_menu() always
3219 * returns the original menu, not one of its copies, so
3220 * below logic would fail miserably if used with a menu
3221 * copy. On the other hand, menu copies can't be
3222 * deleted within a dynamic popup action, so just
3223 * ignore this case. */
3224 *pmenu = menus_find_menu(menu_name);
3225 if (menu_name)
3227 free(menu_name);
3229 mr = *pmenu;
3232 if (mr)
3234 update_menu(mr, pmp);
3236 if (mr == NULL || MR_FIRST_ITEM(mr) == NULL || MR_ITEMS(mr) == 0)
3238 /* The menu deleted itself or all its items or it has been
3239 * empty from the start. */
3240 return False;
3242 fw = (*pexc)->w.fw;
3243 if (fw && !check_if_fvwm_window_exists(fw))
3245 fw = NULL;
3247 context = (*pexc)->w.wcontext;
3250 * Create a new menu instance (if necessary)
3253 if (MR_MAPPED_COPIES(mr) > 0)
3255 /* create a new instance of the menu */
3256 *pmenu = copy_menu_root(*pmenu);
3257 if (!*pmenu)
3259 return False;
3261 make_menu_window(*pmenu, False);
3262 mr = *pmenu;
3266 * Evaluate position hints
3269 /* calculate position from position hints if available */
3270 if (pops->flags.has_poshints &&
3271 !last_saved_pos_hints.flags.do_ignore_pos_hints)
3273 get_xy_from_position_hints(
3274 &(pops->pos_hints), MR_WIDTH(mr), MR_HEIGHT(mr),
3275 (parent_menu) ? MR_WIDTH(parent_menu) : 0,
3276 prefer_left_submenu, &x, &y);
3280 * Initialise new menu
3283 MR_PARENT_MENU(mr) = parent_menu;
3284 MR_PARENT_ITEM(mr) = parent_item;
3285 MR_IS_PAINTED(mr) = 0;
3286 MR_IS_LEFT(mr) = 0;
3287 MR_IS_RIGHT(mr) = 0;
3288 MR_IS_UP(mr) = 0;
3289 MR_IS_DOWN(mr) = 0;
3290 MR_XANIMATION(mr) = 0;
3291 InstallFvwmColormap();
3294 * Handle popups from button clicks on buttons in the title bar,
3295 * or the title bar itself. Position hints override this.
3298 if (!pops->flags.has_poshints && fw && parent_menu == NULL &&
3299 pmp->flags.is_first_root_menu)
3301 int cx;
3302 int cy;
3303 int gx;
3304 int gy;
3305 Bool has_context;
3306 rectangle button_g;
3308 has_context = get_title_button_geometry(
3309 fw, &button_g, context);
3310 if (has_context)
3312 fscreen_scr_arg fscr;
3314 get_title_gravity_factors(fw, &gx, &gy);
3315 cx = button_g.x + button_g.width / 2;
3316 cy = button_g.y + button_g.height / 2;
3317 if (gx != 0)
3319 x = button_g.x +
3320 (gx == 1) * button_g.width -
3321 (gx == -1) * MR_WIDTH(mr);
3323 if (gy != 0)
3325 y = button_g.y +
3326 (gy == 1) * button_g.height -
3327 (gy == -1) * MR_HEIGHT(mr);
3329 if (gx == 0 && x < button_g.x)
3331 x = button_g.x;
3333 else if (gx == 0 && x + MR_WIDTH(mr) >=
3334 button_g.x + button_g.width)
3336 x = button_g.x + button_g.width - MR_WIDTH(mr);
3338 if (gy == 0 && y < button_g.y)
3340 y = button_g.y;
3342 else if (gy == 0 && y + MR_HEIGHT(mr) >=
3343 button_g.y + button_g.height)
3345 y = button_g.y + button_g.height -
3346 MR_HEIGHT(mr);
3348 pops->pos_hints.has_screen_origin = True;
3349 fscr.xypos.x = cx;
3350 fscr.xypos.y = cy;
3351 if (FScreenGetScrRect(
3352 &fscr, FSCREEN_XYPOS, &JunkX, &JunkY,
3353 &JunkWidth, &JunkHeight))
3355 /* use current cx/cy */
3357 else if (FQueryPointer(
3358 dpy, Scr.Root, &JunkRoot, &JunkChild,
3359 &cx, &cy, &JunkX, &JunkY, &JunkMask))
3361 /* use pointer's position */
3363 else
3365 cx = -1;
3366 cy = -1;
3368 pops->pos_hints.screen_origin_x = cx;
3369 pops->pos_hints.screen_origin_y = cy;
3371 } /* if (pops->flags.has_poshints) */
3374 * Clip to screen
3378 fscreen_scr_arg fscr;
3380 fscr.xypos.x = pops->pos_hints.screen_origin_x;
3381 fscr.xypos.y = pops->pos_hints.screen_origin_y;
3382 /* clip to screen */
3383 FScreenClipToScreen(
3384 &fscr, FSCREEN_XYPOS, &x, &y, MR_WIDTH(mr),
3385 MR_HEIGHT(mr));
3386 /* "this" screen is defined -- so get its coords for future
3387 * reference */
3388 FScreenGetScrRect(
3389 &fscr, FSCREEN_XYPOS, &scr_x, &scr_y, &scr_w, &scr_h);
3392 if (parent_menu)
3394 bw = MST_BORDER_WIDTH(mr);
3395 bwp = MST_BORDER_WIDTH(parent_menu);
3396 x_overlap = do_menus_overlap(
3397 parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr), bwp,
3398 bwp, bwp, True);
3402 * Calculate position and animate menus
3405 if (parent_menu == NULL ||
3406 !menu_get_geometry(
3407 parent_menu, &JunkRoot, &prev_x, &prev_y, &prev_width,
3408 &prev_height, &JunkBW, &JunkDepth))
3410 MR_HAS_POPPED_UP_LEFT(mr) = 0;
3411 MR_HAS_POPPED_UP_RIGHT(mr) = 0;
3413 else
3415 int left_x;
3416 int right_x;
3417 Bool use_left_submenus = MST_USE_LEFT_SUBMENUS(mr);
3418 MenuRepaintTransparentParameters mrtp;
3419 Bool transparent_bg = False;
3421 /* check if menus overlap */
3422 x_clipped_overlap =
3423 do_menus_overlap(
3424 parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr),
3425 bwp, bwp, bwp, True);
3426 if (x_clipped_overlap &&
3427 (!pops->flags.has_poshints ||
3428 pops->pos_hints.is_relative == False ||
3429 x_overlap == 0))
3431 /* menus do overlap, but do not reposition if overlap
3432 * was caused by relative positioning hints */
3433 left_x = get_left_popup_x_position(
3434 parent_menu, mr, prev_x);
3435 right_x = get_right_popup_x_position(
3436 parent_menu, mr, prev_x);
3437 if (use_left_submenus)
3439 if (left_x + MR_WIDTH(mr) < prev_x + bwp)
3441 left_x = prev_x + bwp - MR_WIDTH(mr);
3444 else
3446 if (right_x > prev_x + prev_width - bwp)
3448 right_x = prev_x + prev_width - bwp;
3453 * Animate parent menu
3456 if (MST_IS_ANIMATED(mr))
3458 /* animate previous out of the way */
3459 int a_left_x;
3460 int a_right_x;
3461 int end_x;
3462 Window w;
3464 if (use_left_submenus)
3466 if (prev_x - left_x < MR_WIDTH(mr))
3468 a_right_x =
3469 x + (prev_x - left_x);
3471 else
3473 a_right_x =
3474 x + MR_WIDTH(mr) - bw;
3476 a_left_x = x - MR_WIDTH(parent_menu);
3478 else
3480 if (right_x - prev_x < prev_width)
3482 a_left_x =
3483 x + (prev_x - right_x);
3485 else
3487 a_left_x =
3488 x - prev_width + bw;
3490 a_right_x = x + MR_WIDTH(mr);
3492 if (prefer_left_submenu)
3494 /* popup menu is left of old menu, try
3495 * to move prior menu right */
3496 if (a_right_x + prev_width <=
3497 scr_x + scr_w)
3499 end_x = a_right_x;
3501 else if (a_left_x >= scr_x)
3503 end_x = a_left_x;
3505 else
3507 end_x = scr_x + scr_w -
3508 prev_width;
3511 else
3513 /* popup menu is right of old menu, try
3514 * to move prior menu left */
3515 if (a_left_x >= scr_x)
3517 end_x = a_left_x;
3519 else if (a_right_x + prev_width <=
3520 scr_x + scr_w)
3522 end_x = a_right_x;
3524 else
3526 end_x = scr_x;
3529 if (end_x == a_left_x || end_x == 0)
3531 MR_HAS_POPPED_UP_LEFT(mr) = 0;
3532 MR_HAS_POPPED_UP_RIGHT(mr) = 1;
3534 else
3536 MR_HAS_POPPED_UP_LEFT(mr) = 1;
3537 MR_HAS_POPPED_UP_RIGHT(mr) = 0;
3539 MR_XANIMATION(parent_menu) += end_x - prev_x;
3540 if (ST_HAS_MENU_CSET(MR_STYLE(parent_menu)) &&
3541 CSET_IS_TRANSPARENT(
3542 ST_CSET_MENU(
3543 MR_STYLE(parent_menu))))
3545 transparent_bg = True;
3546 get_menu_repaint_transparent_parameters(
3547 &mrtp, parent_menu, fw);
3550 if (MR_IS_TEAR_OFF_MENU(parent_menu))
3552 int cx;
3553 int cy;
3555 w = FW_W_FRAME(
3556 pmp->tear_off_root_menu_window
3558 if (XGetGeometry(
3559 dpy, w, &JunkRoot, &cx,
3560 &cy,
3561 (unsigned int*)&JunkWidth,
3562 (unsigned int*)&JunkHeight,
3563 (unsigned int*)&JunkBW,
3564 (unsigned int*)&JunkDepth))
3566 end_x += (cx - prev_x );
3567 prev_x = cx;
3568 prev_y = cy;
3571 else
3573 w = MR_WINDOW(parent_menu);
3575 AnimatedMoveOfWindow(
3576 w, prev_x, prev_y, end_x, prev_y, True,
3577 -1, NULL,
3578 (transparent_bg)? &mrtp:NULL);
3579 } /* if (MST_IS_ANIMATED(mr)) */
3582 * Try the other side of the parent menu
3585 else if (!pops->flags.is_fixed)
3587 Bool may_use_left;
3588 Bool may_use_right;
3589 Bool do_use_left;
3590 Bool use_left_as_last_resort;
3592 use_left_as_last_resort =
3593 (left_x > scr_x + scr_w - right_x -
3594 MR_WIDTH(mr));
3595 may_use_left = (left_x >= scr_x);
3596 may_use_right = (right_x + MR_WIDTH(mr) <=
3597 scr_x + scr_w);
3598 if (!may_use_left && !may_use_right)
3600 /* If everything goes wrong, put the
3601 * submenu on the side with more free
3602 * space. */
3603 do_use_left = use_left_as_last_resort;
3605 else if (may_use_left &&
3606 (prefer_left_submenu ||
3607 !may_use_right))
3609 do_use_left = True;
3611 else
3613 do_use_left = False;
3615 x = (do_use_left) ? left_x : right_x;
3616 MR_HAS_POPPED_UP_LEFT(mr) = do_use_left;
3617 MR_HAS_POPPED_UP_RIGHT(mr) = !do_use_left;
3619 /* Force the menu onto the screen, but leave at
3620 * least PARENT_MENU_FORCE_VISIBLE_WIDTH pixels
3621 * of the parent menu visible */
3622 if (x + MR_WIDTH(mr) > scr_x + scr_w)
3624 int d = x + MR_WIDTH(mr) -
3625 (scr_x + scr_w);
3626 int c;
3628 if (prev_width >=
3629 PARENT_MENU_FORCE_VISIBLE_WIDTH)
3631 c = PARENT_MENU_FORCE_VISIBLE_WIDTH +
3632 prev_x;
3634 else
3636 c = prev_x + prev_width;
3639 if (x - c >= d || x <= prev_x)
3641 x -= d;
3643 else if (x > c)
3645 x = c;
3648 if (x < scr_x)
3650 int c = prev_width -
3651 PARENT_MENU_FORCE_VISIBLE_WIDTH;
3653 if (c < 0)
3655 c = 0;
3657 if (scr_x - x > c)
3659 x += c;
3661 else
3663 x = scr_x;
3666 } /* else if (non-overlapping menu style) */
3667 } /* if (x_clipped_overlap && ...) */
3668 else
3670 MR_HAS_POPPED_UP_LEFT(mr) = prefer_left_submenu;
3671 MR_HAS_POPPED_UP_RIGHT(mr) = !prefer_left_submenu;
3674 if (x < prev_x)
3676 MR_IS_LEFT(mr) = 1;
3678 if (x + MR_WIDTH(mr) > prev_x + prev_width)
3680 MR_IS_RIGHT(mr) = 1;
3682 if (y < prev_y)
3684 MR_IS_UP(mr) = 1;
3686 if (y + MR_HEIGHT(mr) > prev_y + prev_height)
3688 MR_IS_DOWN(mr) = 1;
3690 if (!MR_IS_LEFT(mr) && !MR_IS_RIGHT(mr))
3692 MR_IS_LEFT(mr) = 1;
3693 MR_IS_RIGHT(mr) = 1;
3695 } /* if (parent_menu) */
3698 * Make sure we have the correct events selected
3701 if (pmp->tear_off_root_menu_window == NULL)
3703 /* normal menus and sub menus */
3704 event_mask = XEVMASK_MENUW;
3706 else if (parent_menu == NULL)
3708 /* tear off menu needs more events */
3709 event_mask = XEVMASK_TEAR_OFF_MENUW;
3711 else
3713 /* sub menus of tear off menus need LeaveNotify */
3714 event_mask = XEVMASK_TEAR_OFF_SUBMENUW;
3718 * Pop up the menu
3721 XMoveWindow(dpy, MR_WINDOW(mr), x, y);
3722 XSelectInput(dpy, MR_WINDOW(mr), event_mask);
3723 XMapRaised(dpy, MR_WINDOW(mr));
3724 if (popdown_window)
3725 XUnmapWindow(dpy, popdown_window);
3726 XFlush(dpy);
3727 MR_MAPPED_COPIES(mr)++;
3728 MST_USAGE_COUNT(mr)++;
3729 if (ret_overlap)
3731 *ret_overlap =
3732 do_menus_overlap(
3733 parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr),
3734 0, 0, 0, False) ? True : False;
3738 * Warp the pointer
3741 if (!do_warp_to_item && parent_menu != NULL)
3743 MenuItem *mi;
3745 mi = find_entry(pmp, NULL, &mrMi, None, -1, -1);
3746 if (mi && mrMi == mr && mi != MR_FIRST_ITEM(mrMi))
3748 /* pointer is on an item of the popup */
3749 if (MST_DO_WARP_TO_TITLE(mr))
3751 /* warp pointer if not on a root menu */
3752 do_warp_to_title = True;
3755 } /* if (!do_warp_to_item) */
3757 if (pops->flags.do_not_warp)
3759 do_warp_to_title = False;
3761 else if (pops->flags.do_warp_title)
3763 do_warp_to_title = True;
3765 if (pops->flags.has_poshints &&
3766 !last_saved_pos_hints.flags.do_ignore_pos_hints &&
3767 pops->flags.do_warp_title)
3769 do_warp_to_title = True;
3771 if (do_warp_to_item)
3773 /* also warp */
3774 MR_SELECTED_ITEM(mr) = NULL;
3775 warp_pointer_to_item(
3776 mr, MR_FIRST_ITEM(mr), True /* skip Title */);
3777 select_menu_item(mr, MR_SELECTED_ITEM(mr), True, fw);
3779 else if (do_warp_to_title)
3781 /* Warp pointer to middle of top line, since we don't
3782 * want the user to come up directly on an option */
3783 warp_pointer_to_title(mr);
3786 return True;
3791 * Procedure:
3792 * pop_menu_down - unhighlight the current menu selection and
3793 * take down the menus
3795 * mr - menu to pop down; this pointer is invalid after the function
3796 * returns. Don't use it anymore!
3797 * parent - the menu that has spawned mr (may be NULL). this is
3798 * used to see if mr was spawned by itself on some level.
3799 * this is a hack to allow specifying 'Popup foo' within
3800 * menu foo. You must use the MenuRoot that is currently
3801 * being processed here. DO NOT USE MR_PARENT_MENU(mr) here!
3804 static void pop_menu_down(MenuRoot **pmr, MenuParameters *pmp)
3806 MenuItem *mi;
3808 assert(*pmr);
3809 memset(&(MR_DYNAMIC_FLAGS(*pmr)), 0, sizeof(MR_DYNAMIC_FLAGS(*pmr)));
3810 XUnmapWindow(dpy, MR_WINDOW(*pmr));
3811 MR_MAPPED_COPIES(*pmr)--;
3812 MST_USAGE_COUNT(*pmr)--;
3813 UninstallFvwmColormap();
3814 XFlush(dpy);
3815 if ((mi = MR_SELECTED_ITEM(*pmr)) != NULL)
3817 select_menu_item(*pmr, mi, False, (*pmp->pexc)->w.fw);
3819 if (MR_STORED_PIXELS(*pmr).d_pixels != NULL)
3821 PictureFreeColors(
3822 dpy, Pcmap, MR_STORED_PIXELS(*pmr).d_pixels,
3823 MR_STORED_PIXELS(*pmr).d_npixels, 0, False);
3824 free(MR_STORED_PIXELS(*pmr).d_pixels);
3825 MR_STORED_PIXELS(*pmr).d_pixels = NULL;
3827 if (MR_COPIES(*pmr) > 1)
3829 /* delete this instance of the menu */
3830 DestroyMenu(*pmr, False, False);
3832 else if (MR_POPDOWN_ACTION(*pmr))
3834 /* Finally execute the popdown action (if defined). */
3835 saved_pos_hints pos_hints;
3837 /* save variables that we still need but that may be
3838 * overwritten */
3839 pos_hints = last_saved_pos_hints;
3840 /* Execute the action */
3841 __menu_execute_function(pmp->pexc, MR_POPDOWN_ACTION(*pmr));
3842 /* restore the stuff we saved */
3843 last_saved_pos_hints = pos_hints;
3846 return;
3851 * Procedure:
3852 * pop_menu_down_and_repaint_parent - Pops down a menu and repaints the
3853 * overlapped portions of the parent menu. This is done only if
3854 * *fSubmenuOverlaps is True. *fSubmenuOverlaps is set to False
3855 * afterwards.
3858 static void pop_menu_down_and_repaint_parent(
3859 MenuRoot **pmr, Bool *fSubmenuOverlaps, MenuParameters *pmp)
3861 MenuRoot *parent = MR_PARENT_MENU(*pmr);
3862 Window win;
3863 XEvent event;
3864 int mr_x;
3865 int mr_y;
3866 int mr_width;
3867 int mr_height;
3868 int parent_x;
3869 int parent_y;
3870 int parent_width;
3871 int parent_height;
3873 if (*fSubmenuOverlaps && parent)
3875 /* popping down the menu may destroy the menu via the dynamic
3876 * popdown action! Thus we must not access *pmr afterwards. */
3877 win = MR_WINDOW(*pmr);
3879 /* Create a fake event to pass into paint_menu */
3880 event.type = Expose;
3881 if (!menu_get_geometry(
3882 *pmr, &JunkRoot, &mr_x, &mr_y, &mr_width,
3883 &mr_height, &JunkBW, &JunkDepth) ||
3884 !menu_get_geometry(
3885 parent, &JunkRoot, &parent_x, &parent_y,
3886 &parent_width, &parent_height, &JunkBW,
3887 &JunkDepth))
3889 pop_menu_down(pmr, pmp);
3890 paint_menu(parent, NULL, (*pmp->pexc)->w.fw);
3892 else
3894 pop_menu_down(pmr, pmp);
3895 event.xexpose.x = mr_x - parent_x;
3896 event.xexpose.width = mr_width;
3897 if (event.xexpose.x < 0)
3899 event.xexpose.width += event.xexpose.x;
3900 event.xexpose.x = 0;
3902 if (event.xexpose.x + event.xexpose.width >
3903 parent_width)
3905 event.xexpose.width =
3906 parent_width - event.xexpose.x;
3908 event.xexpose.y = mr_y - parent_y;
3909 event.xexpose.height = mr_height;
3910 if (event.xexpose.y < 0)
3912 event.xexpose.height += event.xexpose.y;
3913 event.xexpose.y = 0;
3915 if (event.xexpose.y + event.xexpose.height >
3916 parent_height)
3918 event.xexpose.height =
3919 parent_height - event.xexpose.y;
3921 flush_accumulate_expose(MR_WINDOW(parent), &event);
3922 paint_menu(parent, &event, (*pmp->pexc)->w.fw);
3925 else
3927 /* popping down the menu may destroy the menu via the dynamic
3928 * popdown action! Thus we must not access *pmr afterwards. */
3929 pop_menu_down(pmr, pmp);
3931 *fSubmenuOverlaps = False;
3933 return;
3936 /* ---------------------------- menu main loop ------------------------------ */
3938 static void __mloop_init(
3939 MenuParameters *pmp, MenuReturn *pmret,
3940 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
3941 MenuOptions *pops)
3943 memset(in, 0, sizeof(*in));
3944 in->mif.do_force_reposition = 1;
3945 memset(med, 0, sizeof(*med));
3946 msi->t0 = fev_get_evtime();
3947 pmret->rc = MENU_NOP;
3948 memset(pops, 0, sizeof(*pops));
3949 /* remember where the pointer was so we can tell if it has moved */
3950 if (FQueryPointer(
3951 dpy, Scr.Root, &JunkRoot, &JunkChild, &(msi->x_init),
3952 &(msi->y_init), &JunkX, &JunkY, &JunkMask) == False)
3954 /* pointer is on a different screen */
3955 msi->x_init = 0;
3956 msi->y_init = 0;
3958 /* get the event mask right */
3959 msi->event_mask = (pmp->tear_off_root_menu_window == NULL) ?
3960 XEVMASK_MENU : XEVMASK_TEAR_OFF_MENU;
3962 return;
3965 static void __mloop_get_event_timeout_loop(
3966 MenuParameters *pmp,
3967 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
3969 XEvent e = *(*pmp->pexc)->x.elast;
3971 while (!FPending(dpy) || !FCheckMaskEvent(dpy, msi->event_mask, &e))
3973 Bool is_popup_timed_out =
3974 (MST_POPUP_DELAY(pmp->menu) > 0 &&
3975 med->popup_delay_10ms++ >=
3976 MST_POPUP_DELAY(pmp->menu) + 1);
3977 Bool is_popdown_timed_out =
3978 (MST_POPDOWN_DELAY(pmp->menu) > 0 && in->mrPopdown &&
3979 med->popdown_delay_10ms++ >=
3980 MST_POPDOWN_DELAY(pmp->menu) + 1);
3981 Bool do_fake_motion = False;
3983 if (is_popup_timed_out)
3985 med->popup_delay_10ms = MST_POPUP_DELAY(pmp->menu);
3987 if (is_popdown_timed_out)
3989 med->popdown_delay_10ms = MST_POPDOWN_DELAY(pmp->menu);
3991 if (
3992 in->mif.do_force_popup ||
3993 (is_popup_timed_out &&
3994 (is_popdown_timed_out ||
3995 in->mif.is_item_entered_by_key_press ||
3996 !in->mrPopdown ||
3997 MST_DO_POPDOWN_IMMEDIATELY(pmp->menu))))
3999 in->mif.do_popup_now = True;
4000 in->mif.do_force_popup = False;
4001 do_fake_motion = True;
4002 in->mif.is_popped_up_by_timeout = True;
4003 is_popdown_timed_out = True;
4005 if ((in->mrPopdown || in->mrPopup) &&
4006 (is_popdown_timed_out ||
4007 MST_DO_POPDOWN_IMMEDIATELY(pmp->menu)))
4009 MenuRoot *m = (in->mrPopdown) ?
4010 in->mrPopdown : in->mrPopup;
4012 if (!m || med->mi != MR_PARENT_ITEM(m))
4014 in->mif.do_popdown_now = True;
4015 do_fake_motion = True;
4016 if (MST_DO_POPUP_IMMEDIATELY(pmp->menu))
4018 in->mif.do_popup_now = True;
4019 in->mif.is_popped_up_by_timeout = True;
4021 else if (
4022 !MST_DO_POPUP_IMMEDIATELY(pmp->menu) &&
4023 in->mif.is_pointer_in_active_item_area)
4025 in->mif.do_popup_now = True;
4026 in->mif.is_popped_up_by_timeout = True;
4028 else if (
4029 !MST_DO_POPUP_IMMEDIATELY(pmp->menu) &&
4030 !MST_DO_POPDOWN_IMMEDIATELY(pmp->menu)
4031 && MST_POPUP_DELAY(pmp->menu) <=
4032 MST_POPDOWN_DELAY(pmp->menu) &&
4033 med->popup_delay_10ms ==
4034 med->popdown_delay_10ms)
4036 in->mif.do_popup_now = True;
4037 in->mif.is_popped_up_by_timeout = True;
4041 if (in->mif.do_popup_now && med->mi == in->miRemovedSubmenu &&
4042 !in->mif.is_key_press)
4044 /* prevent popping up the menu again with
4045 * RemoveSubemenus */
4046 in->mif.do_popup_now = False;
4047 in->mif.do_force_popup = False;
4048 do_fake_motion = in->mif.do_popdown_now;
4049 in->mif.is_popped_up_by_timeout = False;
4052 if (do_fake_motion)
4054 /* fake a motion event, and set in->mif.do_popup_now */
4055 e.type = MotionNotify;
4056 e.xmotion.time = fev_get_evtime();
4057 fev_fake_event(&e);
4058 in->mif.is_motion_faked = True;
4059 break;
4061 usleep(10000 /* 10 ms*/);
4064 return;
4067 static mloop_ret_code_t __mloop_get_event(
4068 MenuParameters *pmp, MenuReturn *pmret,
4069 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
4071 XEvent e = *(*pmp->pexc)->x.elast;
4073 in->mif.do_popup_and_warp = False;
4074 in->mif.do_popup_now = False;
4075 in->mif.do_popdown_now = False;
4076 in->mif.do_propagate_event_into_submenu = False;
4077 in->mif.is_key_press = False;
4078 in->mif.is_button_release = 0;
4079 if (pmp->event_propagate_to_submenu)
4081 /* handle an event that was passed in from the parent menu */
4082 fev_fake_event(pmp->event_propagate_to_submenu);
4083 pmp->event_propagate_to_submenu = NULL;
4085 else if (in->mif.do_recycle_event)
4087 in->mif.is_popped_up_by_timeout = False;
4088 in->mif.do_recycle_event = 0;
4089 if (pmp->menu != pmret->target_menu)
4091 /* the event is for a previous menu, just close this
4092 * one */
4093 pmret->rc = MENU_PROPAGATE_EVENT;
4094 return MENU_MLOOP_RET_END;
4096 if ((*pmp->pexc)->x.elast->type == KeyPress)
4098 /* since the pointer has been warped since the key was
4099 * pressed, fake a different key press position */
4100 if (FQueryPointer(
4101 dpy, Scr.Root, &JunkRoot, &JunkChild,
4102 &e.xkey.x_root, &e.xkey.y_root,
4103 &JunkX, &JunkY, &e.xkey.state) == False)
4105 /* pointer is on a different screen */
4106 e.xkey.x_root = 0;
4107 e.xkey.y_root = 0;
4109 fev_fake_event(&e);
4110 med->mi = MR_SELECTED_ITEM(pmp->menu);
4113 else if (pmp->tear_off_root_menu_window != NULL &&
4114 FCheckTypedWindowEvent(
4115 dpy, FW_W_PARENT(pmp->tear_off_root_menu_window),
4116 ClientMessage, &e))
4118 /* Got a ClientMessage for the tear out menu */
4120 else
4122 if (in->mif.do_force_reposition)
4124 e.type = MotionNotify;
4125 e.xmotion.time = fev_get_evtime();
4126 in->mif.is_motion_faked = True;
4127 in->mif.do_force_reposition = False;
4128 in->mif.is_popped_up_by_timeout = False;
4129 fev_fake_event(&e);
4131 else if (!FCheckMaskEvent(dpy, ExposureMask, &e))
4133 Bool is_popdown_timer_active = False;
4134 Bool is_popup_timer_active = False;
4136 if (MST_POPDOWN_DELAY(pmp->menu) > 0 &&
4137 in->mi_with_popup != NULL &&
4138 in->mi_with_popup != MR_SELECTED_ITEM(pmp->menu))
4140 is_popdown_timer_active = True;
4142 if (MST_POPUP_DELAY(pmp->menu) > 0 &&
4143 !in->mif.is_popped_up_by_timeout &&
4144 in->mi_wants_popup != NULL)
4146 is_popup_timer_active = True;
4148 /* handle exposure events first */
4149 if (in->mif.do_force_popup ||
4150 in->mif.is_pointer_in_active_item_area ||
4151 is_popdown_timer_active || is_popup_timer_active)
4153 __mloop_get_event_timeout_loop(
4154 pmp, in, med, msi);
4156 else
4158 /* block until there is an event */
4159 FMaskEvent(dpy, msi->event_mask, &e);
4160 in->mif.is_popped_up_by_timeout = False;
4163 else
4165 in->mif.is_popped_up_by_timeout = False;
4169 in->mif.is_pointer_in_active_item_area = False;
4170 if (e.type == MotionNotify)
4172 /* discard any extra motion events before a release */
4173 while (FCheckMaskEvent(
4174 dpy, ButtonMotionMask | ButtonReleaseMask,
4175 &e) && (e.type != ButtonRelease))
4177 /* nothing */
4181 return MENU_MLOOP_RET_NORMAL;
4184 static mloop_ret_code_t __mloop_handle_event(
4185 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
4186 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
4188 MenuRoot *tmrMi;
4189 Bool rc;
4191 pmret->rc = MENU_NOP;
4192 switch ((*pmp->pexc)->x.elast->type)
4194 case ButtonRelease:
4195 in->mif.is_button_release = 1;
4196 med->mi = find_entry(
4197 pmp, &med->x_offset, &med->mrMi,
4198 (*pmp->pexc)->x.elast->xbutton.subwindow,
4199 (*pmp->pexc)->x.elast->xbutton.x_root,
4200 (*pmp->pexc)->x.elast->xbutton.y_root);
4201 /* hold the menu up when the button is released
4202 * for the first time if released OFF of the menu */
4203 if (pmp->flags.is_sticky && !in->mif.is_motion_first)
4205 in->mif.is_release_first = True;
4206 pmp->flags.is_sticky = False;
4207 return MENU_MLOOP_RET_LOOP;
4209 if (med->mrMi != NULL)
4211 int menu_x;
4212 int menu_y;
4214 menu_shortcuts(
4215 med->mrMi, pmp, pmret, (*pmp->pexc)->x.elast,
4216 &med->mi, pdkp, &menu_x, &menu_y);
4217 if (pmret->rc == MENU_NEWITEM_MOVEMENU)
4219 move_any_menu(med->mrMi, pmp, menu_x, menu_y);
4220 pmret->rc = MENU_NEWITEM;
4222 else if (pmret->rc == MENU_NEWITEM_FIND)
4224 med->mi = find_entry(
4225 pmp, NULL, NULL, None, -1, -1);
4226 pmret->rc = MENU_NEWITEM;
4229 else
4231 pmret->rc = MENU_SELECTED;
4233 switch (pmret->rc)
4235 case MENU_NOP:
4236 return MENU_MLOOP_RET_LOOP;
4237 case MENU_POPDOWN:
4238 case MENU_ABORTED:
4239 case MENU_DOUBLE_CLICKED:
4240 case MENU_TEAR_OFF:
4241 case MENU_KILL_TEAR_OFF_MENU:
4242 case MENU_EXEC_CMD:
4243 return MENU_MLOOP_RET_END;
4244 case MENU_POPUP:
4245 /* Allow for MoveLeft/MoveRight action to work with
4246 * Mouse */
4247 in->mif.do_popup_and_warp = True;
4248 case MENU_NEWITEM:
4249 /* unpost the menu if posted */
4250 pmret->flags.is_menu_posted = 0;
4251 return MENU_MLOOP_RET_NORMAL;
4252 case MENU_SELECTED:
4253 in->mif.was_item_unposted = 0;
4254 if (pmret->flags.is_menu_posted && med->mrMi != NULL)
4256 if (pmret->flags.do_unpost_submenu)
4258 pmret->flags.do_unpost_submenu = 0;
4259 pmret->flags.is_menu_posted = 0;
4260 /* just ignore the event */
4261 return MENU_MLOOP_RET_LOOP;
4263 else if (med->mi && MI_IS_POPUP(med->mi)
4264 && med->mrMi == pmp->menu)
4266 /* post menu - done below */
4268 else if (MR_PARENT_ITEM(pmp->menu) &&
4269 med->mi == MR_PARENT_ITEM(pmp->menu))
4271 /* propagate back to parent menu
4272 * and unpost current menu */
4273 pmret->flags.do_unpost_submenu = 1;
4274 pmret->rc = MENU_PROPAGATE_EVENT;
4275 pmret->target_menu = med->mrMi;
4276 return MENU_MLOOP_RET_END;
4278 else if (med->mrMi != pmp->menu &&
4279 med->mrMi != in->mrPopup)
4281 /* unpost and propagate back to
4282 * ancestor */
4283 pmret->flags.is_menu_posted = 0;
4284 pmret->rc = MENU_PROPAGATE_EVENT;
4285 pmret->target_menu = med->mrMi;
4286 return MENU_MLOOP_RET_END;
4288 else if (in->mrPopup && med->mrMi ==
4289 in->mrPopup)
4291 /* unpost and propagate into
4292 * submenu */
4293 in->mif.was_item_unposted = 1;
4294 in->mif.do_propagate_event_into_submenu
4295 = True;
4296 break;
4298 else
4300 /* simply unpost the menu */
4301 pmret->flags.is_menu_posted = 0;
4304 if (is_double_click(
4305 msi->t0, med->mi, pmp, pmret, pdkp,
4306 in->mif.has_mouse_moved))
4308 pmret->rc = MENU_DOUBLE_CLICKED;
4309 return MENU_MLOOP_RET_END;
4311 if (med->mi == NULL)
4313 pmret->rc = MENU_ABORTED;
4315 else if (MI_IS_POPUP(med->mi))
4317 switch (MST_DO_POPUP_AS(pmp->menu))
4319 case MDP_POST_MENU:
4320 if (in->mif.was_item_unposted)
4322 pmret->flags.is_menu_posted =
4324 pmret->rc = MENU_UNPOST;
4325 pmret->target_menu = NULL;
4326 in->mif.do_popup_now = False;
4328 else
4330 pmret->flags.is_menu_posted =
4332 pmret->rc = MENU_POST;
4333 pmret->target_menu = NULL;
4334 in->mif.do_popup_now = True;
4335 if ((in->mrPopup ||
4336 in->mrPopdown)
4337 && med->mi !=
4338 MR_SELECTED_ITEM(
4339 pmp->menu))
4341 in->mif.do_popdown_now
4342 = True;
4345 return MENU_MLOOP_RET_NORMAL;
4346 case MDP_IGNORE:
4347 pmret->rc = MENU_NOP;
4348 return MENU_MLOOP_RET_NORMAL;
4349 case MDP_CLOSE:
4350 pmret->rc = MENU_ABORTED;
4351 break;
4352 case MDP_ROOT_MENU:
4353 default:
4354 break;
4357 break;
4358 default:
4359 break;
4361 pdkp->timestamp = 0;
4363 return MENU_MLOOP_RET_END;
4365 case ButtonPress:
4366 /* if the first event is a button press allow the release to
4367 * select something */
4368 pmp->flags.is_sticky = False;
4369 return MENU_MLOOP_RET_LOOP;
4371 case VisibilityNotify:
4372 return MENU_MLOOP_RET_LOOP;
4374 case KeyRelease:
4375 if ((*pmp->pexc)->x.elast->xkey.keycode !=
4376 MST_SELECT_ON_RELEASE_KEY(pmp->menu))
4378 return MENU_MLOOP_RET_LOOP;
4380 /* fall through to KeyPress */
4382 case KeyPress:
4383 /* Handle a key press events to allow mouseless operation */
4384 in->mif.is_key_press = True;
4385 med->x_offset = menudim_middle_x_offset(&MR_DIM(pmp->menu));
4387 /* if there is a posted menu we may have to move back into a
4388 * previous menu or possibly ignore the mouse position */
4389 if (pmret->flags.is_menu_posted)
4391 MenuItem *l_mi;
4392 MenuRoot *l_mrMi;
4393 int l_x_offset;
4394 XEvent e;
4396 pmret->flags.is_menu_posted = 0;
4397 l_mi = find_entry(
4398 pmp, &l_x_offset, &l_mrMi, None, -1, -1);
4399 if (l_mrMi != NULL)
4401 if (pmp->menu != l_mrMi)
4403 /* unpost the menu and propagate the
4404 * event to the correct menu */
4405 pmret->rc = MENU_PROPAGATE_EVENT;
4406 pmret->target_menu = l_mrMi;
4407 return MENU_MLOOP_RET_END;
4410 med->mi = MR_SELECTED_ITEM(pmp->menu);
4411 e = *(*pmp->pexc)->x.elast;
4412 e.xkey.x_root = med->x_offset;
4413 e.xkey.y_root = menuitem_middle_y_offset(
4414 med->mi, MR_STYLE(pmp->menu));
4415 fev_fake_event(&e);
4418 /* now handle the actual key press */
4420 int menu_x;
4421 int menu_y;
4423 menu_shortcuts(
4424 pmp->menu, pmp, pmret, (*pmp->pexc)->x.elast,
4425 &med->mi, pdkp, &menu_x, &menu_y);
4426 if (pmret->rc == MENU_NEWITEM_MOVEMENU)
4428 move_any_menu(pmp->menu, pmp, menu_x, menu_y);
4429 pmret->rc = MENU_NEWITEM;
4431 else if (pmret->rc == MENU_NEWITEM_FIND)
4433 med->mi = find_entry(
4434 pmp, NULL, NULL, None, -1, -1);
4435 pmret->rc = MENU_NEWITEM;
4438 if (pmret->rc != MENU_NOP)
4440 /* using a key 'unposts' the posted menu */
4441 pmret->flags.is_menu_posted = 0;
4443 switch (pmret->rc)
4445 case MENU_SELECTED:
4446 if (med->mi && MI_IS_POPUP(med->mi))
4448 switch (MST_DO_POPUP_AS(pmp->menu))
4450 case MDP_POST_MENU:
4451 pmret->rc = MENU_POPUP;
4452 break;
4453 case MDP_IGNORE:
4454 pmret->rc = MENU_NOP;
4455 return MENU_MLOOP_RET_NORMAL;
4456 case MDP_CLOSE:
4457 pmret->rc = MENU_ABORTED;
4458 return MENU_MLOOP_RET_END;
4459 case MDP_ROOT_MENU:
4460 default:
4461 return MENU_MLOOP_RET_END;
4463 break;
4465 return MENU_MLOOP_RET_END;
4466 case MENU_POPDOWN:
4467 case MENU_ABORTED:
4468 case MENU_DOUBLE_CLICKED:
4469 case MENU_TEAR_OFF:
4470 case MENU_KILL_TEAR_OFF_MENU:
4471 case MENU_EXEC_CMD:
4472 return MENU_MLOOP_RET_END;
4473 case MENU_NEWITEM:
4474 case MENU_POPUP:
4475 if (med->mrMi == NULL)
4477 /* Set the MenuRoot of the current item in case
4478 * we have just warped to the menu from the
4479 * void or unposted a popup menu. */
4480 med->mrMi = pmp->menu;
4482 /*tmrMi = med->mrMi;*/
4483 break;
4484 default:
4485 break;
4487 /* now warp to the new menu item, if any */
4488 if (pmret->rc == MENU_NEWITEM && med->mi)
4490 warp_pointer_to_item(med->mrMi, med->mi, False);
4492 if (pmret->rc == MENU_POPUP && med->mi && MI_IS_POPUP(med->mi))
4494 in->mif.do_popup_and_warp = True;
4495 break;
4497 break;
4499 case MotionNotify:
4501 int p_rx = -1;
4502 int p_ry = -1;
4503 Window p_child = None;
4505 if (in->mif.has_mouse_moved == False)
4507 if (FQueryPointer(
4508 dpy, Scr.Root, &JunkRoot, &p_child, &p_rx,
4509 &p_ry, &JunkX, &JunkY, &JunkMask) == False)
4511 /* pointer is on a different screen */
4512 p_rx = 0;
4513 p_ry = 0;
4515 if (p_rx - msi->x_init > Scr.MoveThreshold ||
4516 msi->x_init - p_rx > Scr.MoveThreshold ||
4517 p_ry - msi->y_init > Scr.MoveThreshold ||
4518 msi->y_init - p_ry > Scr.MoveThreshold)
4520 /* remember that this isn't just a click any
4521 * more since the pointer moved */
4522 in->mif.has_mouse_moved = True;
4525 med->mi = find_entry(
4526 pmp, &med->x_offset, &med->mrMi, p_child, p_rx, p_ry);
4527 if (pmret->flags.is_menu_posted &&
4528 med->mrMi != pmret->target_menu)
4530 /* ignore mouse movement outside a posted menu */
4531 med->mrMi = NULL;
4532 med->mi = NULL;
4534 if (!in->mif.is_release_first && !in->mif.is_motion_faked &&
4535 in->mif.has_mouse_moved)
4537 in->mif.is_motion_first = True;
4539 in->mif.is_motion_faked = False;
4540 break;
4543 case Expose:
4544 /* grab our expose events, let the rest go through */
4545 XFlush(dpy);
4546 rc = menu_expose((*pmp->pexc)->x.elast, (*pmp->pexc)->w.fw);
4547 /* we want to dispatch this too so that icons and maybe tear
4548 * off get redrawn after being obscured by menus. */
4549 if (rc == False)
4551 dispatch_event((*pmp->pexc)->x.elast);
4553 return MENU_MLOOP_RET_LOOP;
4555 case ClientMessage:
4556 if ((*pmp->pexc)->x.elast->xclient.format == 32 &&
4557 (*pmp->pexc)->x.elast->xclient.data.l[0] ==
4558 _XA_WM_DELETE_WINDOW &&
4559 pmp->tear_off_root_menu_window != NULL &&
4560 (*pmp->pexc)->x.elast->xclient.window == FW_W_PARENT(
4561 pmp->tear_off_root_menu_window))
4563 /* handle deletion of tear out menus */
4564 pmret->rc = MENU_KILL_TEAR_OFF_MENU;
4565 return MENU_MLOOP_RET_END;
4567 break;
4569 case EnterNotify:
4570 /* ignore EnterNotify events */
4571 break;
4573 case LeaveNotify:
4574 if (pmp->tear_off_root_menu_window != NULL &&
4575 find_entry(pmp, NULL, &tmrMi, None, -1, -1) == NULL &&
4576 tmrMi == NULL)
4578 /* handle deletion of tear out menus */
4579 pmret->rc = MENU_ABORTED;
4580 return MENU_MLOOP_RET_END;
4582 /* ignore it */
4583 return MENU_MLOOP_RET_LOOP;
4585 case UnmapNotify:
4586 /* should never happen, but does not hurt */
4587 if (pmp->tear_off_root_menu_window != NULL &&
4588 (*pmp->pexc)->x.elast->xunmap.window ==
4589 FW_W(pmp->tear_off_root_menu_window))
4591 /* handle deletion of tear out menus */
4592 pmret->rc = MENU_KILL_TEAR_OFF_MENU;
4593 /* extra safety: pass event back to main event loop to
4594 * make sure the window is destroyed */
4595 FPutBackEvent(dpy, (*pmp->pexc)->x.elast);
4596 return MENU_MLOOP_RET_END;
4598 break;
4600 default:
4601 /* We must not dispatch events here. There is no guarantee
4602 * that dispatch_event doesn't destroy a window stored in the
4603 * menu structures. Anyway, no events should ever get here
4604 * except to tear off menus and these must be handled
4605 * individually. */
4606 #if 0
4607 dispatch_event((*pmp->pexc)->x.elast);
4608 #endif
4609 break;
4612 return MENU_MLOOP_RET_NORMAL;
4615 static void __mloop_select_item(
4616 MenuParameters *pmp, mloop_evh_input_t *in, mloop_evh_data_t *med,
4617 Bool does_submenu_overlap, Bool *pdoes_popdown_submenu_overlap)
4619 in->mif.is_item_entered_by_key_press = in->mif.is_key_press;
4620 med->popup_delay_10ms = 0;
4621 /* we're on the same menu, but a different item, so we need to unselect
4622 * the old item */
4623 if (MR_SELECTED_ITEM(pmp->menu))
4625 /* something else was already selected on this menu. We have
4626 * to pop down the menu before unselecting the item in case we
4627 * are using gradient menus. The recalled image would paint
4628 * over the submenu. */
4629 if (in->mrPopup && in->mrPopup != in->mrPopdown)
4631 in->mrPopdown = in->mrPopup;
4632 med->popdown_delay_10ms = 0;
4633 *pdoes_popdown_submenu_overlap = does_submenu_overlap;
4634 in->mrPopup = NULL;
4636 select_menu_item(
4637 pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
4638 (*pmp->pexc)->w.fw);
4640 /* highlight the new item; sets MR_SELECTED_ITEM(pmp->menu) too */
4641 select_menu_item(pmp->menu, med->mi, True, (*pmp->pexc)->w.fw);
4643 return;
4646 static void __mloop_wants_popup(
4647 MenuParameters *pmp, mloop_evh_input_t *in, mloop_evh_data_t *med,
4648 MenuRoot *mrMiPopup)
4650 Bool do_it_now = False;
4652 in->mi_wants_popup = med->mi;
4653 if (in->mif.do_popup_now)
4655 do_it_now = True;
4657 else if (MST_DO_POPUP_IMMEDIATELY(pmp->menu) &&
4658 med->mi != in->miRemovedSubmenu)
4660 if (in->mif.is_key_press ||
4661 MST_DO_POPDOWN_IMMEDIATELY(pmp->menu) || !in->mrPopdown)
4663 do_it_now = True;
4666 else if (pointer_in_active_item_area(med->x_offset, med->mrMi))
4668 if (in->mif.is_key_press || med->mi == in->miRemovedSubmenu ||
4669 MST_DO_POPDOWN_IMMEDIATELY(pmp->menu) || !in->mrPopdown)
4671 do_it_now = True;
4673 else
4675 in->mif.is_pointer_in_active_item_area = 1;
4678 if (do_it_now)
4680 in->miRemovedSubmenu = NULL;
4681 /* must create a new menu or popup */
4682 if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
4684 if (in->mif.do_popup_now)
4686 in->mif.do_popup = True;
4688 else
4690 /* pop up in next pass through loop */
4691 in->mif.do_force_popup = True;
4694 else if (in->mif.do_popup_and_warp)
4696 warp_pointer_to_item(
4697 in->mrPopup, MR_FIRST_ITEM(in->mrPopup), True);
4701 return;
4704 static mloop_ret_code_t __mloop_make_popup(
4705 MenuParameters *pmp, MenuReturn *pmret,
4706 mloop_evh_input_t *in, mloop_evh_data_t *med,
4707 MenuOptions *pops, Bool *pdoes_submenu_overlap)
4709 /* create a popup menu */
4710 if (!is_submenu_mapped(pmp->menu, med->mi))
4712 /* We want to pop prepop menus so it doesn't *have* to be
4713 unpopped; do_menu pops down any menus it pops up, but we
4714 want to be able to popdown w/o actually removing the menu */
4715 int x;
4716 int y;
4717 Bool prefer_left_submenus;
4719 /* Make sure we are using the latest style and menu layout. */
4720 update_menu(in->mrPopup, pmp);
4722 get_prefered_popup_position(
4723 pmp->menu, in->mrPopup, &x, &y, &prefer_left_submenus);
4724 /* Note that we don't care if popping up the menu works. If it
4725 * doesn't we'll catch it below. */
4726 pop_menu_up(
4727 &in->mrPopup, pmp, pmp->menu, med->mi, pmp->pexc, x, y,
4728 prefer_left_submenus, in->mif.do_popup_and_warp, pops,
4729 pdoes_submenu_overlap,
4730 (in->mrPopdown) ? MR_WINDOW(in->mrPopdown) : None);
4731 in->mi_with_popup = med->mi;
4732 in->mi_wants_popup = NULL;
4733 if (in->mrPopup == NULL)
4735 /* the menu deleted itself when execution the dynamic
4736 * popup * action */
4737 pmret->rc = MENU_ERROR;
4738 return MENU_MLOOP_RET_END;
4740 MR_SUBMENU_ITEM(pmp->menu) = med->mi;
4743 return MENU_MLOOP_RET_NORMAL;
4746 static mloop_ret_code_t __mloop_get_mi_actions(
4747 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
4748 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
4749 MenuRoot *mrMiPopup, Bool *pdoes_submenu_overlap,
4750 Bool *pdoes_popdown_submenu_overlap)
4752 in->mif.do_popdown = False;
4753 in->mif.do_popup = False;
4754 in->mif.do_menu = False;
4755 in->mif.is_submenu_mapped = False;
4756 if (!in->mrPopup && in->mrPopdown &&
4757 med->mi == MR_PARENT_ITEM(in->mrPopdown))
4759 /* We're again on the item that we left before.
4760 * Deschedule popping it down. */
4761 in->mrPopup = in->mrPopdown;
4762 in->mrPopdown = NULL;
4764 if (med->mrMi == in->mrPopup)
4766 /* must make current popup menu a real menu */
4767 in->mif.do_menu = True;
4768 in->mif.is_submenu_mapped = True;
4770 else if (pmret->rc == MENU_POST)
4772 /* must create a real menu and warp into it */
4773 if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
4775 in->mif.do_popup = True;
4777 in->mif.do_menu = True;
4778 in->mif.is_submenu_mapped = True;
4780 else if (in->mif.do_popup_and_warp)
4782 /* must create a real menu and warp into it */
4783 if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
4785 in->mif.do_popup = True;
4787 else
4789 XRaiseWindow(dpy, MR_WINDOW(in->mrPopup));
4790 warp_pointer_to_item(
4791 in->mrPopup, MR_FIRST_ITEM(in->mrPopup), True);
4792 in->mif.do_menu = True;
4793 in->mif.is_submenu_mapped = True;
4796 else if (med->mi && MI_IS_POPUP(med->mi))
4798 if ((*pmp->pexc)->x.elast->type == ButtonPress &&
4799 is_double_click(
4800 msi->t0, med->mi, pmp, pmret, pdkp,
4801 in->mif.has_mouse_moved))
4803 pmret->rc = MENU_DOUBLE_CLICKED;
4804 return MENU_MLOOP_RET_END;
4806 else if (!in->mrPopup || in->mrPopup != mrMiPopup ||
4807 in->mif.do_popup_and_warp)
4809 __mloop_wants_popup(pmp, in, med, mrMiPopup);
4812 if (in->mif.do_popdown_now)
4814 in->mif.do_popdown = True;
4815 in->mif.do_popdown_now = False;
4817 else if (in->mif.do_popup)
4819 if (in->mrPopup && in->mrPopup != mrMiPopup)
4821 in->mif.do_popdown = True;
4822 in->mrPopdown = in->mrPopup;
4823 med->popdown_delay_10ms = 0;
4824 *pdoes_popdown_submenu_overlap =
4825 *pdoes_submenu_overlap;
4827 else if (in->mrPopdown && in->mrPopdown != mrMiPopup)
4829 in->mif.do_popdown = True;
4831 else if (in->mrPopdown && in->mrPopdown == mrMiPopup)
4833 in->mrPopup = in->mrPopdown;
4834 *pdoes_submenu_overlap =
4835 *pdoes_popdown_submenu_overlap;
4836 in->mrPopdown = NULL;
4837 in->mif.do_popup = False;
4838 in->mif.do_popdown = False;
4842 return MENU_MLOOP_RET_NORMAL;
4845 static void __mloop_do_popdown(
4846 MenuParameters *pmp, mloop_evh_input_t *in,
4847 Bool *pdoes_popdown_submenu_overlap)
4849 if (in->mrPopdown)
4851 pop_menu_down_and_repaint_parent(
4852 &in->mrPopdown, pdoes_popdown_submenu_overlap, pmp);
4853 in->mi_with_popup = NULL;
4854 MR_SUBMENU_ITEM(pmp->menu) = NULL;
4855 if (in->mrPopup == in->mrPopdown)
4857 in->mrPopup = NULL;
4859 in->mrPopdown = NULL;
4861 in->mif.do_popdown = False;
4863 return;
4866 static mloop_ret_code_t __mloop_do_popup(
4867 MenuParameters *pmp, MenuReturn *pmret,
4868 mloop_evh_input_t *in, mloop_evh_data_t *med,
4869 MenuOptions *pops, MenuRoot *mrMiPopup, Bool *pdoes_submenu_overlap,
4870 Bool *pdoes_popdown_submenu_overlap)
4872 if (!MR_IS_PAINTED(pmp->menu))
4874 /* draw the parent menu if it is not already drawn */
4875 flush_expose(MR_WINDOW(pmp->menu));
4876 paint_menu(pmp->menu, NULL, (*pmp->pexc)->w.fw);
4878 /* get pos hints for item's action */
4879 get_popup_options(pmp, med->mi, pops);
4880 if (med->mrMi == pmp->menu && mrMiPopup == NULL &&
4881 MI_IS_POPUP(med->mi) && MR_MISSING_SUBMENU_FUNC(pmp->menu))
4883 /* We're on a submenu item, but the submenu does not exist.
4884 * The user defined missing_submenu_action may create it. */
4885 Bool is_complex_function;
4886 Bool is_busy_grabbed = False;
4887 char *menu_name;
4888 char *action;
4889 char *missing_action = MR_MISSING_SUBMENU_FUNC(pmp->menu);
4891 menu_name = PeekToken(
4892 SkipNTokens(MI_ACTION(med->mi), 1), NULL);
4893 if (!menu_name)
4895 menu_name = "";
4897 is_complex_function =
4898 functions_is_complex_function(missing_action);
4899 if (is_complex_function)
4901 char *action_ptr;
4902 action =
4903 safemalloc(
4904 strlen("Function") + 3 +
4905 strlen(missing_action) * 2 + 3 +
4906 strlen(menu_name) * 2 + 1);
4907 strcpy(action, "Function ");
4908 action_ptr = action + strlen(action);
4909 action_ptr = QuoteString(action_ptr, missing_action);
4910 *action_ptr++ = ' ';
4911 action_ptr = QuoteString(action_ptr, menu_name);
4913 else
4915 action = MR_MISSING_SUBMENU_FUNC(pmp->menu);
4917 if (Scr.BusyCursor & BUSY_DYNAMICMENU)
4919 is_busy_grabbed = GrabEm(CRS_WAIT, GRAB_BUSYMENU);
4921 /* Execute the action */
4922 __menu_execute_function(pmp->pexc, action);
4923 if (is_complex_function)
4925 free(action);
4927 if (is_busy_grabbed)
4929 UngrabEm(GRAB_BUSYMENU);
4931 /* Let's see if the menu exists now. */
4932 mrMiPopup = mr_popup_for_mi(pmp->menu, med->mi);
4933 } /* run MISSING_SUBMENU_FUNCTION */
4934 in->mrPopup = mrMiPopup;
4935 if (!in->mrPopup)
4937 in->mif.do_menu = False;
4938 pmret->flags.is_menu_posted = 0;
4940 else
4942 if (__mloop_make_popup(
4943 pmp, pmret, in, med, pops, pdoes_submenu_overlap)
4944 == MENU_MLOOP_RET_END)
4946 return MENU_MLOOP_RET_END;
4948 } /* else (in->mrPopup) */
4949 if (in->mif.do_popdown)
4951 if (in->mrPopdown)
4953 if (in->mrPopdown != in->mrPopup)
4955 if (in->mi_with_popup ==
4956 MR_PARENT_ITEM(in->mrPopdown))
4958 in->mi_with_popup = NULL;
4960 pop_menu_down_and_repaint_parent(
4961 &in->mrPopdown,
4962 pdoes_popdown_submenu_overlap, pmp);
4964 in->mrPopdown = NULL;
4966 in->mif.do_popdown = False;
4968 if (in->mrPopup)
4970 if (MR_PARENT_MENU(in->mrPopup) == pmp->menu)
4972 med->mi = find_entry(
4973 pmp, NULL, &med->mrMi, None, -1, -1);
4974 if (med->mi && med->mrMi == in->mrPopup)
4976 in->mif.do_menu = True;
4977 in->mif.is_submenu_mapped = True;
4980 else
4982 /* This menu must be already mapped somewhere else, so
4983 * ignore it completely. This can only happen if we
4984 * have reached the maximum allowed number of menu
4985 * copies. */
4986 in->mif.do_menu = False;
4987 in->mif.do_popdown = False;
4988 in->mrPopup = NULL;
4992 return MENU_MLOOP_RET_NORMAL;
4995 static mloop_ret_code_t __mloop_do_menu(
4996 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
4997 mloop_evh_input_t *in, mloop_evh_data_t *med, MenuOptions *pops,
4998 Bool *pdoes_submenu_overlap)
5000 MenuParameters mp;
5001 XEvent e;
5003 memset(&mp, 0, sizeof(mp));
5004 mp.menu = in->mrPopup;
5005 mp.pexc = pmp->pexc;
5006 mp.parent_menu = pmp->menu;
5007 mp.parent_item = med->mi;
5008 mp.tear_off_root_menu_window = pmp->tear_off_root_menu_window;
5009 MR_IS_TEAR_OFF_MENU(in->mrPopup) = 0;
5010 mp.flags.has_default_action = False;
5011 mp.flags.is_already_mapped = in->mif.is_submenu_mapped;
5012 mp.flags.is_sticky = False;
5013 mp.flags.is_submenu = True;
5014 mp.flags.is_triggered_by_keypress = !!in->mif.do_popup_and_warp;
5015 mp.pops = pops;
5016 mp.ret_paction = pmp->ret_paction;
5017 mp.screen_origin_x = pmp->screen_origin_x;
5018 mp.screen_origin_y = pmp->screen_origin_y;
5019 if (in->mif.do_propagate_event_into_submenu)
5021 e = *(*pmp->pexc)->x.elast;
5022 mp.event_propagate_to_submenu = &e;
5024 else
5026 mp.event_propagate_to_submenu = NULL;
5029 /* recursively do the new menu we've moved into */
5030 do_menu(&mp, pmret);
5032 in->mif.do_propagate_event_into_submenu = False;
5033 if (pmret->rc == MENU_PROPAGATE_EVENT)
5035 in->mif.do_recycle_event = 1;
5036 return MENU_MLOOP_RET_LOOP;
5038 if (IS_MENU_RETURN(pmret->rc))
5040 pdkp->timestamp = 0;
5041 return MENU_MLOOP_RET_END;
5043 if (MST_DO_UNMAP_SUBMENU_ON_POPDOWN(pmp->menu) &&
5044 pmret->flags.is_key_press)
5046 in->miRemovedSubmenu = MR_PARENT_ITEM(in->mrPopup);
5047 pop_menu_down_and_repaint_parent(
5048 &in->mrPopup, pdoes_submenu_overlap, pmp);
5049 in->mi_with_popup = NULL;
5050 MR_SUBMENU_ITEM(pmp->menu) = NULL;
5051 if (in->mrPopup == in->mrPopdown)
5053 in->mrPopdown = NULL;
5055 in->mrPopup = NULL;
5057 if (pmret->rc == MENU_POPDOWN)
5059 med->popup_delay_10ms = 0;
5060 in->mif.do_force_reposition = True;
5063 return MENU_MLOOP_RET_NORMAL;
5066 static mloop_ret_code_t __mloop_handle_action_with_mi(
5067 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
5068 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
5069 MenuOptions *pops, Bool *pdoes_submenu_overlap,
5070 Bool *pdoes_popdown_submenu_overlap)
5072 MenuItem *tmi;
5073 MenuRoot *tmrMi;
5074 MenuRoot *mrMiPopup = NULL;
5076 pmret->flags.do_unpost_submenu = 0;
5077 /* we're on a menu item */
5078 in->mif.is_off_menu_allowed = False;
5079 if (med->mrMi == pmp->menu && med->mi != in->miRemovedSubmenu)
5081 in->miRemovedSubmenu = NULL;
5083 if (med->mrMi != pmp->menu && med->mrMi != in->mrPopup &&
5084 med->mrMi != in->mrPopdown)
5086 /* we're on an item from a prior menu */
5087 if (med->mrMi != MR_PARENT_MENU(pmp->menu))
5089 /* the event is for a previous menu, just close
5090 * this one */
5091 pmret->rc = MENU_PROPAGATE_EVENT;
5092 pmret->target_menu = med->mrMi;
5094 else
5096 pmret->rc = MENU_POPDOWN;
5098 pdkp->timestamp = 0;
5099 return MENU_MLOOP_RET_END;
5101 if (med->mi != MR_SELECTED_ITEM(pmp->menu) && med->mrMi == pmp->menu)
5103 /* new item of the same menu */
5104 __mloop_select_item(
5105 pmp, in, med, *pdoes_submenu_overlap,
5106 pdoes_popdown_submenu_overlap);
5108 else if (med->mi != MR_SELECTED_ITEM(pmp->menu) && med->mrMi &&
5109 med->mrMi == in->mrPopdown)
5111 /* we're on the popup menu of a different menu item of this
5112 * menu */
5113 med->mi = MR_PARENT_ITEM(in->mrPopdown);
5114 in->mrPopup = in->mrPopdown;
5115 in->mrPopdown = NULL;
5116 *pdoes_submenu_overlap = *pdoes_popdown_submenu_overlap;
5117 select_menu_item(pmp->menu, med->mi, True, (*pmp->pexc)->w.fw);
5119 mrMiPopup = mr_popup_for_mi(pmp->menu, med->mi);
5120 /* check what has to be done with the item */
5121 if (__mloop_get_mi_actions(
5122 pmp, pmret, pdkp, in, med, msi, mrMiPopup,
5123 pdoes_submenu_overlap, pdoes_popdown_submenu_overlap) ==
5124 MENU_MLOOP_RET_END)
5126 return MENU_MLOOP_RET_END;
5128 /* do what needs to be done */
5129 if (in->mif.do_popdown && !in->mif.do_popup)
5131 /* popdown previous popup */
5132 __mloop_do_popdown(pmp, in, pdoes_popdown_submenu_overlap);
5134 if (in->mif.do_popup)
5136 if (__mloop_do_popup(
5137 pmp, pmret, in, med, pops, mrMiPopup,
5138 pdoes_submenu_overlap,
5139 pdoes_popdown_submenu_overlap) ==
5140 MENU_MLOOP_RET_END)
5142 return MENU_MLOOP_RET_END;
5145 /* remember the 'posted' menu */
5146 if (pmret->flags.is_menu_posted && in->mrPopup != NULL &&
5147 pmret->target_menu == NULL)
5149 pmret->target_menu = in->mrPopup;
5151 else if (pmret->flags.is_menu_posted && in->mrPopup == NULL)
5153 pmret->flags.is_menu_posted = 0;
5155 if (in->mif.do_menu)
5157 mloop_ret_code_t rc;
5159 rc = __mloop_do_menu(
5160 pmp, pmret, pdkp, in, med, pops,
5161 pdoes_submenu_overlap);
5162 if (rc != MENU_MLOOP_RET_NORMAL)
5164 return rc;
5167 /* Now check whether we can animate the current popup menu back to the
5168 * original place to unobscure the current menu; this happens only
5169 * when using animation */
5170 if (in->mrPopup && MR_XANIMATION(in->mrPopup) &&
5171 (tmi = find_entry(pmp, NULL, &tmrMi, None, -1, -1)) &&
5172 (tmi == MR_SELECTED_ITEM(pmp->menu) || tmrMi != pmp->menu))
5174 animated_move_back(in->mrPopup, False, (*pmp->pexc)->w.fw);
5176 /* now check whether we should animate the current real menu
5177 * over to the right to unobscure the prior menu; only a very
5178 * limited case where this might be helpful and not too disruptive */
5179 /* but this cause terrible back-and-forth under certain circonstance,
5180 * I think we should disable this ... 2002-09-17 olicha */
5181 if (in->mrPopup == NULL && pmp->parent_menu != NULL &&
5182 MR_XANIMATION(pmp->menu) != 0 &&
5183 pointer_in_passive_item_area(med->x_offset, med->mrMi))
5185 /* we have to see if we need menu to be moved */
5186 animated_move_back(pmp->menu, True, (*pmp->pexc)->w.fw);
5187 if (in->mrPopdown)
5189 if (in->mrPopdown != in->mrPopup)
5191 pop_menu_down_and_repaint_parent(
5192 &in->mrPopdown,
5193 pdoes_popdown_submenu_overlap, pmp);
5194 in->mi_with_popup = NULL;
5196 in->mrPopdown = NULL;
5198 in->mif.do_popdown = False;
5201 return MENU_MLOOP_RET_NORMAL;
5204 static mloop_ret_code_t __mloop_handle_action_without_mi(
5205 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
5206 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
5207 MenuOptions *pops, Bool *pdoes_submenu_overlap,
5208 Bool *pdoes_popdown_submenu_overlap)
5210 pmret->flags.do_unpost_submenu = 0;
5211 /* moved off menu, deselect selected item... */
5212 if (!MR_SELECTED_ITEM(pmp->menu) ||
5213 in->mif.is_off_menu_allowed == True || pmret->flags.is_menu_posted)
5215 /* nothing to do */
5216 return MENU_MLOOP_RET_NORMAL;
5218 if (in->mrPopup)
5220 int x, y, mx, my;
5221 int mw, mh;
5223 if (FQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
5224 &x, &y, &JunkX, &JunkY, &JunkMask) == False)
5226 /* pointer is on a different screen */
5227 x = 0;
5228 y = 0;
5230 if (menu_get_geometry(
5231 pmp->menu, &JunkRoot, &mx, &my, &mw, &mh, &JunkBW,
5232 &JunkDepth) &&
5233 ((!MR_IS_LEFT(in->mrPopup) && x < mx) ||
5234 (!MR_IS_RIGHT(in->mrPopup) && x > mx + mw) ||
5235 (!MR_IS_UP(in->mrPopup) && y < my) ||
5236 (!MR_IS_DOWN(in->mrPopup) && y > my + mh)))
5238 select_menu_item(
5239 pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
5240 (*pmp->pexc)->w.fw);
5241 pop_menu_down_and_repaint_parent(
5242 &in->mrPopup, pdoes_submenu_overlap, pmp);
5243 in->mi_with_popup = NULL;
5244 MR_SUBMENU_ITEM(pmp->menu) = NULL;
5245 if (in->mrPopup == in->mrPopdown)
5247 in->mrPopdown = NULL;
5249 in->mrPopup = NULL;
5251 else if (x < mx || x >= mx + mw || y < my || y >= my + mh)
5253 /* pointer is outside the menu but do not pop down */
5254 in->mif.is_off_menu_allowed = True;
5256 else
5258 /* Pointer is still in the menu. Postpone the decision
5259 * if we have to pop down. */
5261 } /* if (in->mrPopup) */
5262 else
5264 select_menu_item(
5265 pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
5266 (*pmp->pexc)->w.fw);
5269 return MENU_MLOOP_RET_NORMAL;
5272 static void __mloop_exit_warp_back(MenuParameters *pmp)
5274 MenuItem *tmi;
5275 MenuRoot *tmrMi;
5277 if (pmp->parent_menu && MR_SELECTED_ITEM(pmp->parent_menu))
5279 warp_pointer_to_item(
5280 pmp->parent_menu, MR_SELECTED_ITEM(pmp->parent_menu),
5281 False);
5282 tmi = find_entry(pmp, NULL, &tmrMi, None, -1, -1);
5283 if (pmp->parent_menu != tmrMi && MR_XANIMATION(pmp->menu) == 0)
5285 /* Warping didn't take us to the correct menu, i.e. the
5286 * spot we want to warp to is obscured. So raise our
5287 * window first. */
5288 XRaiseWindow(dpy, MR_WINDOW(pmp->parent_menu));
5292 return;
5295 static void __mloop_exit_select_in_place(
5296 MenuParameters *pmp, mloop_evh_data_t *med, MenuOptions *pops)
5298 MenuRoot *submenu;
5299 XWindowAttributes win_attribs;
5301 last_saved_pos_hints.flags.is_last_menu_pos_hints_valid = True;
5302 last_saved_pos_hints.pos_hints.x_offset = 0;
5303 last_saved_pos_hints.pos_hints.x_factor = 0;
5304 last_saved_pos_hints.pos_hints.context_x_factor = 0;
5305 last_saved_pos_hints.pos_hints.y_factor = 0;
5306 last_saved_pos_hints.pos_hints.is_relative = False;
5307 last_saved_pos_hints.pos_hints.is_menu_relative = False;
5308 submenu = mr_popup_for_mi(pmp->menu, med->mi);
5309 if (submenu && MR_WINDOW(submenu) != None &&
5310 XGetWindowAttributes(dpy, MR_WINDOW(submenu), &win_attribs) &&
5311 win_attribs.map_state == IsViewable &&
5312 menu_get_geometry(
5313 submenu, &JunkRoot, &last_saved_pos_hints.pos_hints.x,
5314 &last_saved_pos_hints.pos_hints.y, &JunkWidth, &JunkHeight,
5315 &JunkBW, &JunkDepth))
5317 /* The submenu is mapped, just take its position and put it in
5318 * the position hints. */
5320 else if (pops->flags.has_poshints)
5322 last_saved_pos_hints.pos_hints = pops->pos_hints;
5324 else
5326 Bool dummy;
5328 get_prefered_popup_position(
5329 pmp->menu, submenu, &last_saved_pos_hints. pos_hints.x,
5330 &last_saved_pos_hints. pos_hints.y, &dummy);
5332 if (pops->flags.do_warp_on_select)
5334 last_saved_pos_hints.flags.do_warp_title = 1;
5337 return;
5340 static void __mloop_exit_selected(
5341 MenuParameters *pmp, MenuReturn *pmret, mloop_evh_data_t *med,
5342 MenuOptions *pops)
5344 /* save action to execute so that the menu may be destroyed now */
5345 if (pmp->ret_paction)
5347 if (pmret->rc == MENU_EXEC_CMD)
5349 *pmp->ret_paction = safestrdup(*pmp->ret_paction);
5351 else
5353 if (*pmp->ret_paction)
5355 free(*pmp->ret_paction);
5357 *pmp->ret_paction = (med->mi) ?
5358 safestrdup(MI_ACTION(med->mi)) : NULL;
5361 if (
5362 pmp->ret_paction && *pmp->ret_paction &&
5363 med->mi && MI_IS_POPUP(med->mi))
5365 get_popup_options(pmp, med->mi, pops);
5366 if (pops->flags.do_select_in_place)
5368 __mloop_exit_select_in_place(pmp, med, pops);
5370 else
5372 last_saved_pos_hints.flags.do_ignore_pos_hints = True;
5373 } /* pops->flags.do_select_in_place */
5374 last_saved_pos_hints.pos_hints.screen_origin_x =
5375 pmp->screen_origin_x;
5376 last_saved_pos_hints.pos_hints.screen_origin_y =
5377 pmp->screen_origin_y;
5380 return;
5383 static void __mloop_exit(
5384 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
5385 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
5386 MenuOptions *pops)
5388 Bool no = False;
5389 Bool do_deselect = False;
5391 if (in->mrPopdown)
5393 pop_menu_down_and_repaint_parent(&in->mrPopdown, &no, pmp);
5394 MR_SUBMENU_ITEM(pmp->menu) = NULL;
5396 if (
5397 pmret->rc == MENU_SELECTED && is_double_click(
5398 msi->t0, med->mi, pmp, pmret, pdkp,
5399 in->mif.has_mouse_moved))
5401 pmret->rc = MENU_DOUBLE_CLICKED;
5403 if (
5404 pmret->rc == MENU_SELECTED && med->mi &&
5405 MI_FUNC_TYPE(med->mi) == F_TEARMENUOFF)
5407 pmret->rc = (MR_IS_TEAR_OFF_MENU(pmp->menu)) ?
5408 MENU_KILL_TEAR_OFF_MENU : MENU_TEAR_OFF;
5410 switch (pmret->rc)
5412 case MENU_POPDOWN:
5413 case MENU_PROPAGATE_EVENT:
5414 case MENU_DOUBLE_CLICKED:
5415 do_deselect = True;
5416 /* Allow popdown to warp back pointer to main menu with mouse
5417 button control. (MoveLeft/MoveRight on a mouse binding) */
5418 if (((pmret->rc == MENU_POPDOWN && in->mif.is_button_release)
5419 || in->mif.is_key_press) &&
5420 pmret->rc != MENU_DOUBLE_CLICKED)
5422 if (!pmp->flags.is_submenu)
5424 /* abort a root menu rather than pop it down */
5425 pmret->rc = MENU_ABORTED;
5427 __mloop_exit_warp_back(pmp);
5429 break;
5430 case MENU_ABORTED:
5431 case MENU_TEAR_OFF:
5432 case MENU_KILL_TEAR_OFF_MENU:
5433 do_deselect = True;
5434 break;
5435 case MENU_SELECTED:
5436 case MENU_EXEC_CMD:
5437 __mloop_exit_selected(pmp, pmret, med, pops);
5438 pmret->rc = MENU_DONE;
5439 break;
5440 default:
5441 break;
5443 if (do_deselect && MR_SELECTED_ITEM(pmp->menu))
5445 select_menu_item(
5446 pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
5447 (*pmp->pexc)->w.fw);
5449 if (pmret->rc == MENU_SUBMENU_TORN_OFF)
5451 in->mrPopup = NULL;
5452 MR_SUBMENU_ITEM(pmp->menu) = NULL;
5454 if (in->mrPopup)
5456 pop_menu_down_and_repaint_parent(&in->mrPopup, &no, pmp);
5457 MR_SUBMENU_ITEM(pmp->menu) = NULL;
5459 pmret->flags.is_key_press = in->mif.is_key_press;
5461 return;
5464 static void __menu_loop(
5465 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp)
5467 mloop_evh_input_t mei;
5468 mloop_ret_code_t mloop_ret;
5469 mloop_evh_data_t med;
5470 mloop_static_info_t msi;
5471 MenuOptions mops;
5472 Bool is_finished;
5473 Bool does_submenu_overlap = False;
5474 Bool does_popdown_submenu_overlap = False;
5476 __mloop_init(pmp, pmret, &mei, &med, &msi, &mops);
5477 for (is_finished = False; !is_finished; )
5479 mloop_ret = __mloop_get_event(pmp, pmret, &mei, &med, &msi);
5480 switch (mloop_ret)
5482 case MENU_MLOOP_RET_END:
5483 is_finished = True;
5484 case MENU_MLOOP_RET_LOOP:
5485 continue;
5486 default:
5487 break;
5489 mloop_ret = __mloop_handle_event(
5490 pmp, pmret, pdkp, &mei, &med, &msi);
5491 switch (mloop_ret)
5493 case MENU_MLOOP_RET_END:
5494 is_finished = True;
5495 continue;
5496 case MENU_MLOOP_RET_LOOP:
5497 continue;
5498 default:
5499 break;
5501 /* Now handle new menu items, whether it is from a keypress or
5502 * a pointer motion event. */
5503 if (med.mi != NULL)
5505 mloop_ret = __mloop_handle_action_with_mi(
5506 pmp, pmret, pdkp, &mei, &med, &msi, &mops,
5507 &does_submenu_overlap,
5508 &does_popdown_submenu_overlap);
5510 else
5512 mloop_ret = __mloop_handle_action_without_mi(
5513 pmp, pmret, pdkp, &mei, &med, &msi, &mops,
5514 &does_submenu_overlap,
5515 &does_popdown_submenu_overlap);
5517 if (mloop_ret == MENU_MLOOP_RET_END)
5519 is_finished = True;
5521 XFlush(dpy);
5523 __mloop_exit(pmp, pmret, pdkp, &mei, &med, &msi, &mops);
5525 return;
5529 * Functions dealing with tear off menus
5532 static char *menu_strip_tear_off_title(MenuRoot *mr)
5534 MenuItem *mi;
5535 int i;
5536 int len;
5537 char *name;
5539 for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
5541 if (MI_IS_TITLE(mi))
5543 break;
5545 else if (!MI_IS_SEPARATOR(mi) && !MI_IS_TEAR_OFF_BAR(mi))
5547 /* a normal item, no title found */
5548 return NULL;
5550 /* skip separators and tear off bars */
5552 if (mi == NULL || !MI_HAS_TEXT(mi) || MI_NEXT_ITEM(mi) == NULL)
5554 return NULL;
5556 /* extract the window title from the labels */
5557 for (i = 0, len = 0; i < MAX_MENU_ITEM_LABELS; i++)
5559 if (MI_LABEL(mi)[i] != 0)
5561 len += strlen(MI_LABEL(mi)[i]) + 1;
5564 if (len == 0)
5566 return NULL;
5568 name = safemalloc(len + 1);
5569 *name = 0;
5570 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
5572 if (MI_LABEL(mi)[i] != 0)
5574 strcat(name, MI_LABEL(mi)[i]);
5575 strcat(name, " ");
5578 /* strip the last space */
5579 name[len - 1] = 0;
5580 /* unlink and destroy the item */
5581 unlink_item_from_menu(mr, mi);
5582 menuitem_free(mi);
5584 return name;
5587 static void menu_tear_off(MenuRoot *mr_to_copy)
5589 MenuRoot *mr;
5590 MenuStyle *ms;
5591 XEvent ev;
5592 XSizeHints menusizehints;
5593 XClassHint menuclasshints;
5594 XTextProperty menunametext;
5595 XWMHints menuwmhints;
5596 char *list[] ={ NULL, NULL };
5597 char *t;
5598 char *name = NULL;
5599 Atom protocols[1];
5600 int x = 0;
5601 int y = 0;
5602 unsigned int add_mask = 0;
5603 initial_window_options_t win_opts;
5604 evh_args_t ea;
5605 exec_context_changes_t ecc;
5606 char *buffer;
5607 char *action;
5608 cond_rc_t *cond_rc = NULL;
5609 const exec_context_t *exc = NULL;
5611 /* keep the menu open */
5612 if (MR_WINDOW(mr_to_copy) != None)
5614 discard_window_events(
5615 MR_WINDOW(mr_to_copy), SubstructureNotifyMask);
5617 mr = clone_menu(mr_to_copy);
5618 /* also dump the menu style */
5619 buffer = (char *)safemalloc(23);
5620 sprintf(buffer,"%lu",(unsigned long)mr);
5621 action = buffer;
5622 ms = menustyle_parse_style(F_PASS_ARGS);
5623 if (!ms)
5625 /* this must never happen */
5626 fvwm_msg(
5627 ERR, "menu_tear_off",
5628 "impossible to create %s menu style", buffer);
5629 free(buffer);
5630 DestroyMenu(mr, False, False);
5631 return;
5633 free(buffer);
5634 menustyle_copy(MR_STYLE(mr_to_copy),ms);
5635 MR_STYLE(mr) = ms;
5636 MST_USAGE_COUNT(mr) = 0;
5637 name = menu_strip_tear_off_title(mr);
5638 /* create the menu window and size the menu */
5639 make_menu(mr, True);
5640 /* set position */
5641 if (menu_get_geometry(
5642 mr_to_copy, &JunkRoot, &x, &y, &JunkWidth, &JunkHeight,
5643 &JunkBW, &JunkDepth))
5645 add_mask = PPosition;
5646 XMoveWindow(dpy, MR_WINDOW(mr), x, y);
5648 else
5650 add_mask = 0;
5652 /* focus policy */
5653 menuwmhints.flags = InputHint;
5654 menuwmhints.input = False;
5655 /* size hints */
5656 menusizehints.flags = PBaseSize | PMinSize | PMaxSize | add_mask;
5657 menusizehints.base_width = 0;
5658 menusizehints.base_height = 0;
5659 menusizehints.min_width = MR_WIDTH(mr);
5660 menusizehints.min_height = MR_HEIGHT(mr);
5661 menusizehints.max_width = MR_WIDTH(mr);
5662 menusizehints.max_height = MR_HEIGHT(mr);
5663 /* class, resource and names */
5664 menuclasshints.res_name =
5665 (name != NULL) ? name : safestrdup(MR_NAME(mr));
5666 menuclasshints.res_class = safestrdup("fvwm_menu");
5667 for (t = menuclasshints.res_name; t != NULL && *t != 0; t++)
5669 /* replace tabs in the title with spaces */
5670 if (*t == '\t')
5672 *t = ' ';
5675 list[0] = menuclasshints.res_name;
5676 menunametext.value = NULL;
5677 XStringListToTextProperty(list, 1, &menunametext);
5678 /* set all properties and hints */
5679 XSetWMProperties(
5680 dpy, MR_WINDOW(mr), &menunametext, &menunametext, NULL, 0,
5681 &menusizehints, NULL, &menuclasshints);
5682 XSetWMHints(dpy, MR_WINDOW(mr), &menuwmhints);
5683 protocols[0] = _XA_WM_DELETE_WINDOW;
5684 XSetWMProtocols(dpy, MR_WINDOW(mr), &(protocols[0]), 1);
5686 /* free memory */
5687 if (menunametext.value != NULL)
5689 XFree(menunametext.value);
5691 free(menuclasshints.res_class);
5692 free(menuclasshints.res_name);
5694 /* manage the window */
5695 memset(&win_opts, 0, sizeof(win_opts));
5696 win_opts.flags.is_menu = True;
5697 ev.type = MapRequest;
5698 ev.xmaprequest.send_event = True;
5699 ev.xmaprequest.display = dpy;
5700 ev.xmaprequest.parent = Scr.Root;
5701 ev.xmaprequest.window = MR_WINDOW(mr);
5702 fev_fake_event(&ev);
5703 ecc.type = EXCT_NULL;
5704 ecc.x.etrigger = &ev;
5705 ecc.w.w = MR_WINDOW(mr);
5706 ecc.w.wcontext = C_ROOT;
5707 ea.exc = exc_create_context(
5708 &ecc, ECC_TYPE | ECC_ETRIGGER | ECC_W | ECC_WCONTEXT);
5709 HandleMapRequestKeepRaised(&ea, None, NULL, &win_opts);
5710 exc_destroy_context(ea.exc);
5712 return;
5715 static void do_menu_close_tear_off_menu(MenuRoot *mr, MenuParameters mp)
5717 pop_menu_down(&mr, &mp);
5718 menustyle_free(MR_STYLE(mr));
5719 DestroyMenu(mr, False, False);
5722 /* ---------------------------- interface functions ------------------------- */
5724 void menus_init(void)
5726 memset((&Menus), 0, sizeof(MenuInfo));
5728 return;
5731 /* menus_find_menu expects a token as the input. Make sure you have used
5732 * GetNextToken before passing a menu name to remove quotes (if necessary) */
5733 MenuRoot *menus_find_menu(char *name)
5735 MenuRoot *mr;
5737 if (name == NULL)
5739 return NULL;
5741 for (mr = Menus.all; mr != NULL; mr = MR_NEXT_MENU(mr))
5743 if (!MR_IS_DESTROYED(mr) && mr == MR_ORIGINAL_MENU(mr) &&
5744 MR_NAME(mr) != NULL && StrEquals(name, MR_NAME(mr)))
5746 break;
5750 return mr;
5753 void menus_remove_style_from_menus(MenuStyle *ms)
5755 MenuRoot *mr;
5757 for (mr = Menus.all; mr; mr = MR_NEXT_MENU(mr))
5759 if (MR_STYLE(mr) == ms)
5761 MR_STYLE(mr) = menustyle_get_default_style();
5762 MR_IS_UPDATED(mr) = 1;
5766 return;
5770 * Functions dealing with tear off menus
5773 void menu_enter_tear_off_menu(const exec_context_t *exc)
5775 MenuRoot *mr;
5776 char *ret_action = NULL;
5777 MenuOptions mops;
5778 MenuParameters mp;
5779 MenuReturn mret;
5780 const exec_context_t *exc2;
5781 exec_context_changes_t ecc;
5783 if (XFindContext(
5784 dpy, FW_W(exc->w.fw), MenuContext,
5785 (caddr_t *)&mr) == XCNOENT)
5787 return;
5789 ecc.w.fw = NULL;
5790 ecc.w.w = None;
5791 ecc.w.wcontext = C_ROOT;
5792 exc2 = exc_clone_context(exc, &ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
5793 memset(&mops, 0, sizeof(mops));
5794 memset(&mret, 0, sizeof(MenuReturn));
5795 memset(&mp, 0, sizeof(mp));
5796 mp.menu = mr;
5797 mp.pexc = &exc2;
5798 mp.tear_off_root_menu_window = exc->w.fw;
5799 MR_IS_TEAR_OFF_MENU(mr) = 1;
5800 mp.flags.has_default_action = 0;
5801 mp.flags.is_already_mapped = True;
5802 mp.flags.is_sticky = False;
5803 mp.flags.is_submenu = False;
5804 mp.pops = &mops;
5805 mp.ret_paction = &ret_action;
5806 do_menu(&mp, &mret);
5807 exc_destroy_context(exc2);
5809 return;
5812 void menu_close_tear_off_menu(FvwmWindow *fw)
5814 MenuRoot *mr;
5815 MenuParameters mp;
5816 const exec_context_t *exc;
5817 exec_context_changes_t ecc;
5819 if (XFindContext(
5820 dpy, FW_W(fw), MenuContext, (caddr_t *)&mr) == XCNOENT)
5822 return;
5824 memset(&mp, 0, sizeof(mp));
5825 mp.menu = mr;
5826 ecc.w.fw = NULL;
5827 ecc.w.w = None;
5828 ecc.w.wcontext = C_ROOT;
5829 exc = exc_create_context(&ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
5830 mp.pexc = &exc;
5831 do_menu_close_tear_off_menu(mr, mp);
5832 exc_destroy_context(exc);
5834 return;
5837 Bool menu_redraw_transparent_tear_off_menu(FvwmWindow *fw, Bool pr_only)
5839 MenuRoot *mr;
5840 MenuStyle *ms = NULL;
5841 int cs;
5843 if (!(IS_TEAR_OFF_MENU(fw) &&
5844 XFindContext(dpy, FW_W(fw), MenuContext,(caddr_t *)&mr) !=
5845 XCNOENT &&
5846 (ms = MR_STYLE(mr)) &&
5847 ST_HAS_MENU_CSET(ms)))
5849 return False;
5852 cs = ST_CSET_MENU(ms);
5854 if (!CSET_IS_TRANSPARENT(cs) ||
5855 (pr_only && !CSET_IS_TRANSPARENT_PR(cs)))
5857 return False;
5860 return UpdateBackgroundTransparency(
5861 dpy, MR_WINDOW(mr), MR_WIDTH(mr),
5862 MR_HEIGHT(mr),
5863 &Colorset[ST_CSET_MENU(ms)],
5864 Pdepth,
5865 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
5866 True);
5871 * Initiates a menu pop-up
5873 * fStick = True = sticky menu, stays up on initial button release.
5874 * fStick = False = transient menu, drops on initial release.
5876 * eventp = 0: menu opened by mouse, do not warp
5877 * eventp > 1: root menu opened by keypress with 'Menu', warp pointer and
5878 * allow 'double-keypress'.
5879 * eventp = 1: menu opened by keypress, warp but forbid 'double-keypress'
5880 * this should always be used except in the call in 'staysup_func'
5881 * in builtin.c
5883 * Returns one of MENU_NOP, MENU_ERROR, MENU_ABORTED, MENU_DONE
5884 * do_menu() may destroy the *pmp->pexec member and replace it with a new
5885 * copy. Be sure not to rely on the original structure being kept intact
5886 * when calling do_menu().
5888 void do_menu(MenuParameters *pmp, MenuReturn *pmret)
5890 int x;
5891 int y;
5892 Bool fWasAlreadyPopped = False;
5893 Bool key_press;
5894 Bool is_pointer_grabbed = False;
5895 Bool is_pointer_ungrabbed = False;
5896 Bool do_menu_interaction;
5897 Bool do_check_pop_down;
5898 Bool do_warp;
5899 Time t0 = fev_get_evtime();
5900 XEvent tmpevent;
5901 double_keypress dkp;
5902 /* don't save these ones, we want them to work even within recursive
5903 * menus popped up by dynamic actions. */
5904 static int indirect_depth = 0;
5905 static int x_start;
5906 static int y_start;
5907 static Bool has_mouse_moved = False;
5908 int scr_x, scr_y;
5909 int scr_w, scr_h;
5911 pmret->rc = MENU_NOP;
5912 if (pmp->flags.is_sticky && !pmp->flags.is_submenu)
5914 FCheckTypedEvent(dpy, ButtonPressMask, &tmpevent);
5916 if (pmp->menu == NULL)
5918 pmret->rc = MENU_ERROR;
5919 return;
5921 key_press = pmp->flags.is_triggered_by_keypress;
5923 /* Try to pick a root-relative optimal x,y to
5924 * put the mouse right on the title w/o warping */
5925 if (FQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
5926 &x, &y, &JunkX, &JunkY, &JunkMask) == False)
5928 /* pointer is on a different screen */
5929 x = 0;
5930 y = 0;
5932 /* Save these - we want to warp back here if this is a top level
5933 * menu brought up by a keystroke */
5934 if (!pmp->flags.is_submenu)
5936 pmp->flags.is_invoked_by_key_press = key_press;
5937 pmp->flags.is_first_root_menu = !indirect_depth;
5939 else
5941 pmp->flags.is_first_root_menu = 0;
5943 if (!pmp->flags.is_submenu && indirect_depth == 0)
5945 if (key_press)
5947 x_start = x;
5948 y_start = y;
5950 else
5952 x_start = -1;
5953 y_start = -1;
5955 pmp->screen_origin_x = pmp->pops->pos_hints.screen_origin_x;
5956 pmp->screen_origin_y = pmp->pops->pos_hints.screen_origin_y;
5958 if (pmp->pops->flags.do_warp_title)
5960 do_warp = True;
5962 else if (pmp->pops->flags.do_not_warp)
5964 do_warp = False;
5966 else if (key_press)
5968 do_warp = True;
5970 else
5972 do_warp = False;
5974 /* Figure out where we should popup, if possible */
5975 if (!pmp->flags.is_already_mapped)
5977 Bool prefer_left_submenus = False;
5978 /* Make sure we are using the latest style and menu layout. */
5979 update_menu(pmp->menu, pmp);
5981 if (pmp->flags.is_submenu)
5983 /* this is a submenu popup */
5984 get_prefered_popup_position(
5985 pmp->parent_menu, pmp->menu, &x, &y,
5986 &prefer_left_submenus);
5988 else
5990 fscreen_scr_arg fscr;
5992 /* we're a top level menu */
5993 has_mouse_moved = False;
5994 if (!GrabEm(CRS_MENU, GRAB_MENU))
5996 /* GrabEm specifies the cursor to use */
5997 XBell(dpy, 0);
5998 pmret->rc = MENU_ABORTED;
5999 return;
6001 is_pointer_grabbed = True;
6002 /* Make the menu appear under the pointer rather than
6003 * warping */
6004 fscr.xypos.x = x;
6005 fscr.xypos.y = y;
6006 FScreenGetScrRect(
6007 &fscr, FSCREEN_XYPOS, &scr_x, &scr_y, &scr_w,
6008 &scr_h);
6009 x -= menudim_middle_x_offset(&MR_DIM(pmp->menu));
6010 y -= menuitem_middle_y_offset(
6011 MR_FIRST_ITEM(pmp->menu), MR_STYLE(pmp->menu));
6012 if (x < scr_x)
6014 x = scr_x;
6016 if (y < scr_y)
6018 y = scr_y;
6022 /* pop_menu_up may move the x,y to make it fit on screen more
6023 * nicely. It might also move parent_menu out of the way. */
6024 if (!pop_menu_up(
6025 &(pmp->menu), pmp, pmp->parent_menu, NULL,
6026 pmp->pexc, x, y, prefer_left_submenus, do_warp,
6027 pmp->pops, NULL, None))
6029 XBell(dpy, 0);
6030 UngrabEm(GRAB_MENU);
6031 pmret->rc = MENU_ERROR;
6032 return;
6035 else
6037 fWasAlreadyPopped = True;
6038 if (pmp->tear_off_root_menu_window != NULL)
6040 if (!GrabEm(CRS_MENU, GRAB_MENU))
6042 /* GrabEm specifies the cursor to use */
6043 XBell(dpy, 0);
6044 pmret->rc = MENU_ABORTED;
6045 return;
6047 is_pointer_grabbed = True;
6049 if (key_press)
6051 warp_pointer_to_item(
6052 pmp->menu, MR_FIRST_ITEM(pmp->menu),
6053 True /* skip Title */);
6057 /* Remember the key that popped up the root menu. */
6058 if (pmp->flags.is_submenu)
6060 dkp.timestamp = 0;
6062 else
6064 if (pmp->flags.is_triggered_by_keypress)
6066 /* we have a real key event */
6067 dkp.keystate = (*pmp->pexc)->x.etrigger->xkey.state;
6068 dkp.keycode = (*pmp->pexc)->x.etrigger->xkey.keycode;
6070 dkp.timestamp =
6071 (key_press && pmp->flags.has_default_action) ? t0 : 0;
6073 if (!pmp->flags.is_submenu && indirect_depth == 0)
6075 /* we need to grab the keyboard so we are sure no key presses
6076 * are lost */
6077 MyXGrabKeyboard(dpy);
6079 /* This may loop for tear off menus */
6080 for (do_menu_interaction = True; do_menu_interaction == True; )
6082 if (is_pointer_ungrabbed && !GrabEm(CRS_MENU, GRAB_MENU))
6084 /* re-grab the pointer in this cycle */
6085 XBell(dpy, 0);
6086 pmret->rc = MENU_ABORTED;
6087 break;
6089 do_menu_interaction = False;
6090 if (pmp->pops->flags.do_tear_off_immediately == 1)
6092 pmret->rc = MENU_TEAR_OFF;
6094 else
6096 if (!pmp->flags.is_submenu)
6098 XSelectInput(
6099 dpy, Scr.NoFocusWin, XEVMASK_MENUNFW);
6100 XFlush(dpy);
6102 __menu_loop(pmp, pmret, &dkp);
6103 if (!pmp->flags.is_submenu)
6105 XSelectInput(
6106 dpy, Scr.NoFocusWin, XEVMASK_NOFOCUSW);
6107 XFlush(dpy);
6110 do_check_pop_down = False;
6111 switch (pmret->rc)
6113 case MENU_TEAR_OFF:
6114 menu_tear_off(pmp->menu);
6115 pop_menu_down(&pmp->menu, pmp);
6116 break;
6117 case MENU_KILL_TEAR_OFF_MENU:
6118 if (MR_IS_TEAR_OFF_MENU(pmp->menu))
6120 /* kill the menu */
6121 do_menu_close_tear_off_menu(pmp->menu, *pmp);
6122 pmret->rc = MENU_ABORTED;
6124 else
6126 /* pass return code up to the torn off menu */
6128 break;
6129 case MENU_DOUBLE_CLICKED:
6130 case MENU_DONE:
6131 if (MR_IS_TEAR_OFF_MENU(pmp->menu))
6133 do_menu_interaction = True;
6135 else
6137 do_check_pop_down = True;
6139 break;
6140 case MENU_SUBMENU_TORN_OFF:
6141 pmret->rc = MENU_ABORTED;
6142 do_check_pop_down = True;
6143 break;
6144 default:
6145 do_check_pop_down = True;
6146 break;
6148 if (do_check_pop_down == True)
6150 /* popping down may destroy the menu via the dynamic
6151 * popdown action! */
6152 if (!MR_IS_TEAR_OFF_MENU(pmp->menu) &&
6153 fWasAlreadyPopped == False)
6155 pop_menu_down(&pmp->menu, pmp);
6158 XFlush(dpy);
6160 if (!pmp->flags.is_submenu && x_start >= 0 && y_start >= 0 &&
6161 pmret->flags.is_key_press && pmret->rc != MENU_TEAR_OFF)
6163 /* warp pointer back to where invoked if this was
6164 * brought up with a keypress and we're returning from
6165 * a top level menu, and a button release event didn't
6166 * end it */
6167 FWarpPointer(
6168 dpy, 0, Scr.Root, 0, 0, Scr.MyDisplayWidth,
6169 Scr.MyDisplayHeight, x_start, y_start);
6170 if ((*pmp->pexc)->x.elast->type == KeyPress)
6172 XEvent e = *(*pmp->pexc)->x.elast;
6174 e.xkey.x_root = x_start;
6175 e.xkey.y_root = y_start;
6176 fev_fake_event(&e);
6179 if (pmret->rc == MENU_TEAR_OFF)
6181 pmret->rc = MENU_SUBMENU_TORN_OFF;
6184 dkp.timestamp = 0;
6185 if (is_pointer_grabbed)
6187 UngrabEm(GRAB_MENU);
6188 WaitForButtonsUp(True);
6189 is_pointer_ungrabbed = True;
6191 if (!pmp->flags.is_submenu)
6193 if (pmret->rc == MENU_DONE)
6195 if (pmp->ret_paction && *pmp->ret_paction)
6197 indirect_depth++;
6198 /* Execute the action */
6199 __menu_execute_function(
6200 pmp->pexc, *pmp->ret_paction);
6201 indirect_depth--;
6202 free(*pmp->ret_paction);
6203 *pmp->ret_paction = NULL;
6205 last_saved_pos_hints.flags.
6206 do_ignore_pos_hints = False;
6207 last_saved_pos_hints.flags.
6208 is_last_menu_pos_hints_valid = False;
6210 if (indirect_depth == 0)
6212 last_saved_pos_hints.flags.
6213 do_ignore_pos_hints = False;
6214 last_saved_pos_hints.flags.
6215 is_last_menu_pos_hints_valid = False;
6219 if (!pmp->flags.is_submenu && indirect_depth == 0)
6221 /* release the keyboard when the last menu closes */
6222 MyXUngrabKeyboard(dpy);
6225 return;
6228 Bool menu_expose(XEvent *event, FvwmWindow *fw)
6230 MenuRoot *mr = NULL;
6232 if ((XFindContext(
6233 dpy, event->xany.window, MenuContext, (caddr_t *)&mr) !=
6234 XCNOENT))
6236 flush_accumulate_expose(event->xany.window, event);
6237 paint_menu(mr, event, fw);
6239 return True;
6241 else
6243 return False;
6249 * Procedure:
6250 * update_transparent_menu_bg - set the background of the menu to
6251 * match a forseen move of a menu. If the background is updated
6252 * for the target position before the move is done, and repainted
6253 * after the move, the move will look more seamless.
6255 * This method should be folleowd by a call to repaint_transparent_menu
6256 * with the same step_x, stey_y, end_x and any_y, with is_bg_set True
6258 void update_transparent_menu_bg(
6259 MenuRepaintTransparentParameters *prtm,
6260 int current_x, int current_y, int step_x, int step_y,
6261 int end_x, int end_y)
6263 MenuRoot *mr;
6264 MenuStyle *ms;
6265 Bool last = False;
6267 mr = prtm->mr;
6268 ms = MR_STYLE(mr);
6269 if (step_x == end_x && step_y == end_y)
6271 last = True;
6273 if (!last && CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
6275 /* too slow ... */
6276 return;
6278 SetWindowBackgroundWithOffset(
6279 dpy, MR_WINDOW(mr), step_x - current_x, step_y - current_y,
6280 MR_WIDTH(mr), MR_HEIGHT(mr),
6281 &Colorset[ST_CSET_MENU(ms)], Pdepth,
6282 FORE_GC(MST_MENU_INACTIVE_GCS(mr)), False);
6288 * Procedure:
6289 * repaint_transparent_menu - repaint the menu background if it is
6290 * tranparent during an animated move. Called in move_resize.c
6291 * (AnimatedMoveAnyWindow). Performance improvement Welcome!
6292 * ideas: - write the foreground into a pixmap and a mask the first time
6293 * this function is called. Then use these pixmaps to draw
6294 * the items
6295 * - Use a Buffer if !IS_TRANSPARENT_PR_PURE and if we do not have one
6296 * already
6298 void repaint_transparent_menu(
6299 MenuRepaintTransparentParameters *prtm,
6300 Bool first, int x, int y, int end_x, int end_y, Bool is_bg_set)
6302 MenuItem *mi;
6303 MenuRoot *mr;
6304 MenuStyle *ms;
6305 int h = 0;
6306 int s_h = 0;
6307 int e_h = 0;
6308 Bool last = False;
6310 mr = prtm->mr;
6311 ms = MR_STYLE(mr);
6312 if (x == end_x && y == end_y)
6314 last = True;
6316 if (!last && CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
6318 /* too slow ... */
6319 return;
6321 if (!is_bg_set)
6323 SetWindowBackground(
6324 dpy, MR_WINDOW(mr), MR_WIDTH(mr), MR_HEIGHT(mr),
6325 &Colorset[ST_CSET_MENU(ms)], Pdepth,
6326 FORE_GC(MST_MENU_INACTIVE_GCS(mr)), False);
6328 /* redraw the background of non active item */
6329 for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
6331 if (mi == MR_SELECTED_ITEM(mr) && MST_DO_HILIGHT_BACK(mr))
6333 int left;
6335 left = MR_HILIGHT_X_OFFSET(mr) - MR_ITEM_X_OFFSET(mr);
6336 if (left > 0)
6338 XClearArea(
6339 dpy, MR_WINDOW(mr),
6340 MR_ITEM_X_OFFSET(mr), MI_Y_OFFSET(mi),
6341 left, MI_HEIGHT(mi) +
6342 MST_RELIEF_THICKNESS(mr), 0);
6344 h = MI_HEIGHT(mi);
6345 continue;
6347 if (h == 0)
6349 s_h += MI_HEIGHT(mi);
6351 else
6353 e_h += MI_HEIGHT(mi);
6356 XClearArea(
6357 dpy, MR_WINDOW(mr), MR_ITEM_X_OFFSET(mr), MST_BORDER_WIDTH(mr),
6358 MR_ITEM_WIDTH(mr), s_h, 0);
6359 if (e_h != 0)
6361 XClearArea(
6362 dpy, MR_WINDOW(mr), MR_ITEM_X_OFFSET(mr),
6363 s_h + h + MST_RELIEF_THICKNESS(mr) +
6364 MST_BORDER_WIDTH(mr), MR_ITEM_WIDTH(mr), e_h, 0);
6367 /* now redraw the items */
6368 for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
6370 MenuPaintItemParameters mpip;
6372 if (mi == MR_SELECTED_ITEM(mr) && MST_DO_HILIGHT_BACK(mr) &&
6373 !CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
6375 continue;
6377 get_menu_paint_item_parameters(
6378 &mpip, mr, NULL, prtm->fw, NULL, True);
6379 mpip.flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
6380 menuitem_paint(mi, &mpip);
6383 /* if we have a side pic and no side colors we shound repaint the side
6384 * pic */
6385 if ((MR_SIDEPIC(mr) || MST_SIDEPIC(mr)) &&
6386 !MR_HAS_SIDECOLOR(mr) && !MST_HAS_SIDE_COLOR(mr))
6388 FvwmPicture *sidePic;
6389 if (MR_SIDEPIC(mr))
6391 sidePic = MR_SIDEPIC(mr);
6393 else if (MST_SIDEPIC(mr))
6395 sidePic = MST_SIDEPIC(mr);
6397 else
6399 return;
6401 XClearArea(
6402 dpy, MR_WINDOW(mr), MR_SIDEPIC_X_OFFSET(mr),
6403 MST_BORDER_WIDTH(mr), sidePic->width,
6404 MR_HEIGHT(mr) - 2 * MST_BORDER_WIDTH(mr), 0);
6405 paint_side_pic(mr, NULL);
6409 Bool DestroyMenu(MenuRoot *mr, Bool do_recreate, Bool is_command_request)
6411 MenuItem *mi,*tmp2;
6412 MenuRoot *tmp, *prev;
6413 Bool in_list = True;
6415 if (mr == NULL)
6417 return False;
6420 /* seek menu in master list */
6421 tmp = Menus.all;
6422 prev = NULL;
6423 while (tmp && tmp != mr)
6425 prev = tmp;
6426 tmp = MR_NEXT_MENU(tmp);
6428 if (tmp != mr)
6430 /* no such menu */
6431 in_list = False;
6434 if (MR_MAPPED_COPIES(mr) > 0 &&
6435 (is_command_request || MR_COPIES(mr) == 1))
6437 /* can't destroy a menu while in use */
6438 fvwm_msg(ERR, "DestroyMenu", "Menu %s is in use", MR_NAME(mr));
6439 return False;
6442 if (in_list && MR_COPIES(mr) > 1 && MR_ORIGINAL_MENU(mr) == mr)
6444 MenuRoot *m;
6445 MenuRoot *new_orig;
6447 /* find a new 'original' menu */
6448 for (m = Menus.all, new_orig = NULL; m; m = MR_NEXT_MENU(m))
6450 if (m != mr && MR_ORIGINAL_MENU(m) == mr)
6452 if (new_orig == NULL)
6454 new_orig = m;
6456 MR_ORIGINAL_MENU(m) = new_orig;
6459 MR_ORIGINAL_MENU(mr) = new_orig;
6460 /* now dump old original menu */
6463 MR_COPIES(mr)--;
6464 if (MR_STORED_ITEM(mr).stored)
6466 XFreePixmap(dpy, MR_STORED_ITEM(mr).stored);
6468 if (MR_COPIES(mr) > 0)
6470 do_recreate = False;
6472 else
6474 /* free all items */
6475 mi = MR_FIRST_ITEM(mr);
6476 while (mi != NULL)
6478 tmp2 = MI_NEXT_ITEM(mi);
6479 menuitem_free(mi);
6480 mi = tmp2;
6482 if (do_recreate)
6484 /* just dump the menu items but keep the menu itself */
6485 MR_COPIES(mr)++;
6486 MR_FIRST_ITEM(mr) = NULL;
6487 MR_LAST_ITEM(mr) = NULL;
6488 MR_SELECTED_ITEM(mr) = NULL;
6489 MR_CONTINUATION_MENU(mr) = NULL;
6490 MR_PARENT_MENU(mr) = NULL;
6491 MR_ITEMS(mr) = 0;
6492 memset(&(MR_STORED_ITEM(mr)), 0 ,
6493 sizeof(MR_STORED_ITEM(mr)));
6494 MR_IS_UPDATED(mr) = 1;
6495 return True;
6499 /* unlink menu from list */
6500 if (in_list)
6502 if (prev == NULL)
6504 Menus.all = MR_NEXT_MENU(mr);
6506 else
6508 MR_NEXT_MENU(prev) = MR_NEXT_MENU(mr);
6511 /* destroy the window and the display */
6512 if (MR_WINDOW(mr) != None)
6514 XDeleteContext(dpy, MR_WINDOW(mr), MenuContext);
6515 XFlush(dpy);
6516 XDestroyWindow(MR_CREATE_DPY(mr), MR_WINDOW(mr));
6517 MR_WINDOW(mr) = None;
6518 XFlush(MR_CREATE_DPY(mr));
6520 if (MR_CREATE_DPY(mr) != NULL && MR_CREATE_DPY(mr) != dpy)
6522 XCloseDisplay(MR_CREATE_DPY(mr));
6523 MR_CREATE_DPY(mr) = NULL;
6526 if (MR_COPIES(mr) == 0)
6528 if (MR_POPUP_ACTION(mr))
6530 free(MR_POPUP_ACTION(mr));
6532 if (MR_POPDOWN_ACTION(mr))
6534 free(MR_POPDOWN_ACTION(mr));
6536 if (MR_MISSING_SUBMENU_FUNC(mr))
6538 free(MR_MISSING_SUBMENU_FUNC(mr));
6540 free(MR_NAME(mr));
6541 if (MR_SIDEPIC(mr))
6543 PDestroyFvwmPicture(dpy, MR_SIDEPIC(mr));
6545 memset(mr->s, 0, sizeof(*(mr->s)));
6546 free(mr->s);
6548 memset(mr->d, 0, sizeof(*(mr->d)));
6549 free(mr->d);
6550 memset(mr, 0, sizeof(*mr));
6551 free(mr);
6553 return True;
6556 /* FollowMenuContinuations
6557 * Given an menu root, return the menu root to add to by
6558 * following continuation links until there are no more
6560 MenuRoot *FollowMenuContinuations(MenuRoot *mr, MenuRoot **pmrPrior )
6562 *pmrPrior = NULL;
6563 while ((mr != NULL) &&
6564 (MR_CONTINUATION_MENU(mr) != NULL))
6566 *pmrPrior = mr;
6567 mr = MR_CONTINUATION_MENU(mr);
6570 return mr;
6575 * Procedure:
6576 * AddToMenu - add an item to a root menu
6578 * Returned Value:
6579 * (MenuItem *)
6581 * Inputs:
6582 * menu - pointer to the root menu to add the item
6583 * item - the text to appear in the menu
6584 * action - the string to possibly execute
6586 * ckh - need to add boolean to say whether or not to expand for pixmaps,
6587 * so built in window list can handle windows w/ * and % in title.
6590 void AddToMenu(
6591 MenuRoot *mr, char *item, char *action, Bool fPixmapsOk, Bool fNoPlus,
6592 Bool is_continuation_item)
6594 MenuItem *tmp;
6595 char *start;
6596 char *end;
6597 char *token = NULL;
6598 char *option = NULL;
6599 int i;
6600 int is_empty;
6601 Bool do_replace_title;
6603 if (MR_MAPPED_COPIES(mr) > 0)
6605 /* whoa, we can't handle *everything* */
6606 return;
6608 if ((item == NULL || *item == 0) && (action == NULL || *action == 0) &&
6609 fNoPlus)
6611 return;
6613 /* empty items screw up our menu when painted, so we replace them with
6614 * a separator */
6615 if (item == NULL)
6616 item = "";
6619 * Handle dynamic actions
6622 if (StrEquals(item, "DynamicPopupAction"))
6624 if (MR_POPUP_ACTION(mr))
6626 free(MR_POPUP_ACTION(mr));
6628 if (!action || *action == 0)
6630 MR_POPUP_ACTION(mr) = NULL;
6632 else
6634 MR_POPUP_ACTION(mr) = stripcpy(action);
6636 return;
6638 else if (StrEquals(item, "DynamicPopdownAction"))
6640 if (MR_POPDOWN_ACTION(mr))
6642 free(MR_POPDOWN_ACTION(mr));
6644 if (!action || *action == 0)
6646 MR_POPDOWN_ACTION(mr) = NULL;
6648 else
6650 MR_POPDOWN_ACTION(mr) = stripcpy(action);
6652 return;
6654 else if (StrEquals(item, "MissingSubmenuFunction"))
6656 if (MR_MISSING_SUBMENU_FUNC(mr))
6658 free(MR_MISSING_SUBMENU_FUNC(mr));
6660 if (!action || *action == 0)
6662 MR_MISSING_SUBMENU_FUNC(mr) = NULL;
6664 else
6666 MR_MISSING_SUBMENU_FUNC(mr) = stripcpy(action);
6668 return;
6672 * Parse the action
6675 if (action == NULL || *action == 0)
6677 action = "Nop";
6679 GetNextToken(GetNextToken(action, &token), &option);
6680 tmp = menuitem_create();
6681 if (MR_FIRST_ITEM(mr) != NULL && StrEquals(token, "title") &&
6682 option != NULL && StrEquals(option, "top"))
6684 do_replace_title = True;
6686 else
6688 do_replace_title = False;
6690 append_item_to_menu(mr, tmp, do_replace_title);
6691 if (token)
6693 free(token);
6695 if (option)
6697 free(option);
6700 MI_ACTION(tmp) = stripcpy(action);
6703 * Parse the labels
6706 start = item;
6707 end = item;
6708 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++, start = end)
6710 /* Read label up to next tab. */
6711 if (*end)
6713 if (i < MAX_MENU_ITEM_LABELS - 1)
6715 while (*end && *end != '\t')
6717 /* seek next tab or end of string */
6718 end++;
6721 else
6723 /* remove all tabs in last label */
6724 while (*end)
6726 if (*end == '\t')
6728 *end = ' ';
6730 end++;
6733 /* Copy the label. */
6734 MI_LABEL(tmp)[i] = safemalloc(end - start + 1);
6735 strncpy(MI_LABEL(tmp)[i], start, end - start);
6736 (MI_LABEL(tmp)[i])[end - start] = 0;
6737 if (*end == '\t')
6739 /* skip the tab */
6740 end++;
6743 else
6745 MI_LABEL(tmp)[i] = NULL;
6748 /* Parse the label. */
6749 if (MI_LABEL(tmp)[i] != NULL)
6751 if (fPixmapsOk)
6753 string_def_t item_pixmaps[] = {
6754 {'*', __scan_for_pixmap},
6755 {'%', __scan_for_pixmap},
6756 {'\0', NULL}};
6757 string_context_t ctx;
6759 SCTX_SET_MI(ctx,tmp);
6760 scanForStrings(
6761 MI_LABEL(tmp)[i], item_pixmaps, &ctx);
6763 if (!MI_HAS_HOTKEY(tmp))
6765 /* pete@tecc.co.uk */
6766 scanForHotkeys(tmp, i);
6768 if (!MI_HAS_HOTKEY(tmp) &&
6769 *MI_LABEL(tmp)[i] != 0)
6771 MI_HOTKEY_COFFSET(tmp) = 0;
6772 MI_HOTKEY_COLUMN(tmp) = i;
6773 MI_HAS_HOTKEY(tmp) = 1;
6774 MI_IS_HOTKEY_AUTOMATIC(tmp) = 1;
6777 if (*(MI_LABEL(tmp)[i]))
6779 MI_HAS_TEXT(tmp) = True;
6781 else
6783 free(MI_LABEL(tmp)[i]);
6784 MI_LABEL(tmp)[i] = NULL;
6787 MI_LABEL_STRLEN(tmp)[i] =
6788 (MI_LABEL(tmp)[i]) ? strlen(MI_LABEL(tmp)[i]) : 0;
6789 } /* for */
6792 * Set the type flags
6795 if (is_continuation_item)
6797 MI_IS_CONTINUATION(tmp) = True;
6799 find_func_t(MI_ACTION(tmp), &(MI_FUNC_TYPE(tmp)), NULL);
6800 switch (MI_FUNC_TYPE(tmp))
6802 case F_POPUP:
6803 MI_IS_POPUP(tmp) = True;
6804 case F_WINDOWLIST:
6805 case F_STAYSUP:
6806 MI_IS_MENU(tmp) = True;
6807 break;
6808 case F_TITLE:
6809 MI_IS_TITLE(tmp) = True;
6810 break;
6811 default:
6812 break;
6814 is_empty = (!MI_HAS_TEXT(tmp) && !MI_HAS_PICTURE(tmp));
6815 if (is_empty)
6817 if (MI_FUNC_TYPE(tmp) == F_TEARMENUOFF)
6819 MI_IS_TEAR_OFF_BAR(tmp) = 1;
6820 MI_IS_SEPARATOR(tmp) = 0;
6822 else
6824 MI_IS_TEAR_OFF_BAR(tmp) = 0;
6825 MI_IS_SEPARATOR(tmp) = 1;
6828 if (MI_IS_SEPARATOR(tmp))
6830 /* An empty title is handled like a separator. */
6831 MI_IS_TITLE(tmp) = False;
6833 MI_IS_SELECTABLE(tmp) =
6834 ((MI_HAS_TEXT(tmp) || MI_HAS_PICTURE(tmp) ||
6835 MI_IS_TEAR_OFF_BAR(tmp)) && !MI_IS_TITLE(tmp));
6838 * misc stuff
6841 MR_ITEMS(mr)++;
6842 MR_IS_UPDATED(mr) = 1;
6844 return;
6849 * Procedure:
6850 * NewMenuRoot - create a new menu root
6852 * Returned Value:
6853 * (MenuRoot *)
6855 * Inputs:
6856 * name - the name of the menu root
6859 MenuRoot *NewMenuRoot(char *name)
6861 MenuRoot *mr;
6862 string_def_t root_strings[] = {
6863 {'@', __scan_for_pixmap},
6864 {'^', __scan_for_color},
6865 {'\0', NULL}};
6866 string_context_t ctx;
6868 mr = (MenuRoot *)safemalloc(sizeof(MenuRoot));
6869 mr->s = (MenuRootStatic *)safemalloc(sizeof(MenuRootStatic));
6870 mr->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic));
6872 memset(mr->s, 0, sizeof(MenuRootStatic));
6873 memset(mr->d, 0, sizeof(MenuRootDynamic));
6874 MR_NEXT_MENU(mr) = Menus.all;
6875 MR_NAME(mr) = safestrdup(name);
6876 MR_WINDOW(mr) = None;
6877 SCTX_SET_MR(ctx,mr);
6878 MR_HAS_SIDECOLOR(mr) = False;
6879 scanForStrings(
6880 MR_NAME(mr), root_strings, &ctx);
6881 MR_STYLE(mr) = menustyle_get_default_style();
6882 MR_ORIGINAL_MENU(mr) = mr;
6883 MR_COPIES(mr) = 1;
6884 MR_IS_UPDATED(mr) = 1;
6886 Menus.all = mr;
6887 return mr;
6890 void SetMenuCursor(Cursor cursor)
6892 MenuRoot *mr;
6894 mr = Menus.all;
6895 for (mr = Menus.all; mr; mr = MR_NEXT_MENU(mr))
6897 if (MR_WINDOW(mr))
6899 XDefineCursor(dpy, MR_WINDOW(mr), cursor);
6903 return;
6906 void UpdateAllMenuStyles(void)
6908 MenuStyle *ms;
6910 for (ms = menustyle_get_default_style(); ms; ms = ST_NEXT_STYLE(ms))
6912 menustyle_update(ms);
6915 return;
6918 void UpdateMenuColorset(int cset)
6920 MenuStyle *ms;
6921 FvwmWindow *t;
6923 for (ms = menustyle_get_default_style(); ms; ms = ST_NEXT_STYLE(ms))
6925 if ((ST_HAS_MENU_CSET(ms) && ST_CSET_MENU(ms) == cset) ||
6926 (ST_HAS_ACTIVE_CSET(ms) && ST_CSET_ACTIVE(ms) == cset) ||
6927 (ST_HAS_GREYED_CSET(ms) && ST_CSET_GREYED(ms) == cset) ||
6928 (ST_HAS_TITLE_CSET(ms) && ST_CSET_TITLE(ms) == cset))
6930 menustyle_update(ms);
6934 for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
6936 MenuRoot *mr = NULL;
6937 MenuStyle *ms = NULL;
6939 if (IS_TEAR_OFF_MENU(t) &&
6940 XFindContext(dpy, FW_W(t), MenuContext, (caddr_t *)&mr) !=
6941 XCNOENT &&
6942 (ms = MR_STYLE(mr)))
6944 if (ST_HAS_MENU_CSET(ms) &&
6945 ST_CSET_MENU(ms) == cset)
6947 SetWindowBackground(
6948 dpy, MR_WINDOW(mr), MR_WIDTH(mr),
6949 MR_HEIGHT(mr),
6950 &Colorset[ST_CSET_MENU(ms)],
6951 Pdepth,
6952 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
6953 True);
6955 else if ((ST_HAS_ACTIVE_CSET(ms) &&
6956 ST_CSET_ACTIVE(ms) == cset) ||
6957 (ST_HAS_GREYED_CSET(ms) &&
6958 ST_CSET_GREYED(ms) == cset))
6960 paint_menu(mr, NULL, NULL);
6965 return;
6968 /* This is called by the window list function */
6969 void change_mr_menu_style(MenuRoot *mr, char *stylename)
6971 MenuStyle *ms = NULL;
6973 ms = menustyle_find(stylename);
6974 if (ms == NULL)
6976 return;
6978 if (MR_MAPPED_COPIES(mr) != 0)
6980 fvwm_msg(ERR,"ChangeMenuStyle", "menu %s is in use",
6981 MR_NAME(mr));
6983 else
6985 MR_STYLE(mr) = ms;
6986 MR_IS_UPDATED(mr) = 1;
6989 return;
6992 void add_another_menu_item(char *action)
6994 MenuRoot *mr;
6995 MenuRoot *mrPrior;
6996 char *rest,*item;
6998 mr = FollowMenuContinuations(Scr.last_added_item.item, &mrPrior);
6999 if (mr == NULL)
7001 return;
7003 if (MR_MAPPED_COPIES(mr) != 0)
7005 fvwm_msg(ERR,"add_another_menu_item", "menu is in use");
7006 return;
7008 rest = GetNextToken(action,&item);
7009 AddToMenu(mr, item, rest, True /* pixmap scan */, False, False);
7010 if (item)
7012 free(item);
7015 return;
7019 * get_menu_options is used for Menu, Popup and WindowList
7020 * It parses strings matching
7022 * [ [context-rectangle] x y ] [special-options] [other arguments]
7024 * and returns a pointer to the first part of the input string that doesn't
7025 * match this syntax.
7027 * See documentation for a detailed description.
7029 char *get_menu_options(
7030 char *action, Window w, FvwmWindow *fw, XEvent *e, MenuRoot *mr,
7031 MenuItem *mi, MenuOptions *pops)
7033 char *tok = NULL;
7034 char *naction = action;
7035 char *taction;
7036 int x;
7037 int y;
7038 int button;
7039 int gflags;
7040 int width;
7041 int height;
7042 int dummy_int;
7043 float dummy_float;
7044 Bool dummy_flag;
7045 Window context_window = None;
7046 Bool fHasContext, fUseItemOffset, fRectangleContext, fXineramaRoot;
7047 Bool fValidPosHints =
7048 last_saved_pos_hints.flags.is_last_menu_pos_hints_valid;
7049 Bool is_action_empty = False;
7050 Bool once_more = True;
7051 Bool is_icon_context;
7052 rectangle icon_g;
7054 /* If this is set we may want to reverse the position hints, so don't
7055 * sum up the totals right now. This is useful for the SubmenusLeft
7056 * style. */
7058 fXineramaRoot = False;
7059 last_saved_pos_hints.flags.is_last_menu_pos_hints_valid = False;
7060 if (pops == NULL)
7062 fvwm_msg(ERR,
7063 "get_menu_options","no MenuOptions pointer passed");
7064 return action;
7067 taction = action;
7068 memset(&(pops->flags), 0, sizeof(pops->flags));
7069 pops->flags.has_poshints = 0;
7070 if (!action || *action == 0)
7072 is_action_empty = True;
7074 while (action != NULL && *action != 0 && once_more)
7076 /* ^ just to be able to jump to end of loop without 'goto' */
7077 gflags = NoValue;
7078 pops->pos_hints.is_relative = False;
7079 pops->pos_hints.menu_width = 0;
7080 pops->pos_hints.is_menu_relative = False;
7081 /* parse context argument (if present) */
7082 naction = GetNextToken(taction, &tok);
7083 if (!tok)
7085 /* no context string */
7086 fHasContext = False;
7087 is_action_empty = True;
7088 break;
7090 /* set to False for absolute hints! */
7091 pops->pos_hints.is_relative = True;
7092 fUseItemOffset = False;
7093 fHasContext = True;
7094 fRectangleContext = False;
7095 is_icon_context = False;
7096 if (StrEquals(tok, "context"))
7098 if (mi && mr)
7100 context_window = MR_WINDOW(mr);
7102 else if (fw)
7104 if (IS_ICONIFIED(fw))
7106 is_icon_context = True;
7107 get_icon_geometry(fw, &icon_g);
7108 context_window = None;
7110 else
7112 context_window =
7113 FW_W_FRAME(fw);
7116 else
7118 context_window = w;
7121 else if (StrEquals(tok,"menu"))
7123 if (mr)
7125 context_window = MR_WINDOW(mr);
7126 pops->pos_hints.is_menu_relative = True;
7127 pops->pos_hints.menu_width = MR_WIDTH(mr);
7130 else if (StrEquals(tok,"item"))
7132 if (mi && mr)
7134 context_window = MR_WINDOW(mr);
7135 fUseItemOffset = True;
7136 pops->pos_hints.is_menu_relative = True;
7137 pops->pos_hints.menu_width = MR_WIDTH(mr);
7140 else if (StrEquals(tok,"icon"))
7142 if (fw && IS_ICONIFIED(fw))
7144 is_icon_context = True;
7145 get_icon_geometry(fw, &icon_g);
7146 context_window = None;
7149 else if (StrEquals(tok,"window"))
7151 if (fw && !IS_ICONIFIED(fw))
7153 context_window = FW_W_FRAME(fw);
7156 else if (StrEquals(tok,"interior"))
7158 if (fw && !IS_ICONIFIED(fw))
7160 context_window = FW_W(fw);
7163 else if (StrEquals(tok,"title"))
7165 if (fw)
7167 if (IS_ICONIFIED(fw))
7169 context_window =
7170 FW_W_ICON_TITLE(fw);
7172 else
7174 context_window = FW_W_TITLE(fw);
7178 else if (strncasecmp(tok,"button",6) == 0)
7180 if (sscanf(&(tok[6]),"%d",&button) != 1 ||
7181 tok[6] == '+' || tok[6] == '-' || button < 0 ||
7182 button > 9)
7184 fHasContext = False;
7186 else if (fw && !IS_ICONIFIED(fw))
7188 button = BUTTON_INDEX(button);
7189 context_window = FW_W_BUTTON(fw, button);
7192 else if (StrEquals(tok,"root"))
7194 context_window = Scr.Root;
7195 pops->pos_hints.is_relative = False;
7197 else if (StrEquals(tok,"xineramaroot"))
7199 context_window = Scr.Root;
7200 pops->pos_hints.is_relative = False;
7201 fXineramaRoot = True;
7203 else if (StrEquals(tok,"mouse"))
7205 context_window = None;
7207 else if (StrEquals(tok,"rectangle"))
7209 int flags;
7210 int screen;
7211 int sx;
7212 int sy;
7213 int sw;
7214 int sh;
7216 /* parse the rectangle */
7217 free(tok);
7218 naction = GetNextToken(naction, &tok);
7219 if (tok == NULL)
7221 fvwm_msg(ERR, "get_menu_options",
7222 "missing rectangle geometry");
7223 if (!pops->pos_hints.has_screen_origin)
7225 /* xinerama: emergency fallback */
7226 pops->pos_hints.has_screen_origin = 1;
7227 pops->pos_hints.screen_origin_x = 0;
7228 pops->pos_hints.screen_origin_y = 0;
7230 return action;
7232 flags = FScreenParseGeometryWithScreen(
7233 tok, &x, &y,
7234 (unsigned int*)&width,
7235 (unsigned int*)&height, &screen);
7236 if ((flags & (XValue | YValue)) != (XValue | YValue))
7238 free(tok);
7239 fvwm_msg(ERR, "get_menu_options",
7240 "invalid rectangle geometry");
7241 if (!pops->pos_hints.has_screen_origin)
7243 /* xinerama: emergency fallback */
7244 pops->pos_hints.has_screen_origin = 1;
7245 pops->pos_hints.screen_origin_x = 0;
7246 pops->pos_hints.screen_origin_y = 0;
7248 return action;
7250 pops->pos_hints.has_screen_origin = 1;
7251 FScreenGetScrRect(NULL, screen, &sx, &sy, &sw, &sh);
7252 pops->pos_hints.screen_origin_x = sx;
7253 pops->pos_hints.screen_origin_y = sy;
7254 if (!(flags & WidthValue))
7256 width = 1;
7258 if (!(flags & HeightValue))
7260 height = 1;
7262 x += sx;
7263 y += sy;
7264 if (flags & XNegative)
7266 x = sx + sw - x - width;
7268 if (flags & YNegative)
7270 y = sy + sh - y - height;
7272 pops->pos_hints.is_relative = False;
7273 fRectangleContext = True;
7275 else if (StrEquals(tok,"this"))
7277 MenuRoot *dummy_mr;
7279 context_window = w;
7280 if (XFindContext(
7281 dpy, w, MenuContext,
7282 (caddr_t *)&dummy_mr) != XCNOENT)
7284 if (mr)
7286 /* the parent menu */
7287 pops->pos_hints.is_menu_relative =
7288 True;
7289 pops->pos_hints.menu_width =
7290 MR_WIDTH(mr);
7294 else
7296 /* no context string */
7297 fHasContext = False;
7300 if (tok)
7302 free(tok);
7304 if (fHasContext)
7306 taction = naction;
7308 else
7310 naction = action;
7313 if (fRectangleContext)
7315 if (!pops->pos_hints.has_screen_origin)
7317 /* xinerama: use global screen as reference */
7318 pops->pos_hints.has_screen_origin = 1;
7319 pops->pos_hints.screen_origin_x = -1;
7320 pops->pos_hints.screen_origin_y = -1;
7322 /* nothing else to do */
7324 else if (!is_icon_context &&
7325 (!fHasContext || !context_window ||
7326 !XGetGeometry(
7327 dpy, context_window, &JunkRoot, &JunkX,
7328 &JunkY, (unsigned int*)&width,
7329 (unsigned int*)&height,
7330 (unsigned int*)&JunkBW,
7331 (unsigned int*)&JunkDepth) ||
7332 !XTranslateCoordinates(
7333 dpy, context_window, Scr.Root, 0, 0, &x, &y,
7334 &JunkChild)))
7336 /* no window or could not get geometry */
7337 if (FQueryPointer(
7338 dpy,Scr.Root, &JunkRoot, &JunkChild, &x,
7339 &y, &JunkX, &JunkY, &JunkMask) == False)
7341 /* pointer is on a different screen - that's
7342 * okay here */
7343 x = 0;
7344 y = 0;
7346 width = height = 1;
7347 if (!pops->pos_hints.has_screen_origin)
7349 /* xinerama: use screen with pinter as
7350 * reference */
7351 pops->pos_hints.has_screen_origin = 1;
7352 pops->pos_hints.screen_origin_x = x;
7353 pops->pos_hints.screen_origin_y = y;
7356 else
7358 if (is_icon_context)
7360 x = icon_g.x;
7361 y = icon_g.y;
7362 width = icon_g.width;
7363 height = icon_g.height;
7365 /* we have a context window */
7366 if (fUseItemOffset)
7368 y += MI_Y_OFFSET(mi);
7369 height = MI_HEIGHT(mi);
7371 if (!pops->pos_hints.has_screen_origin)
7373 pops->pos_hints.has_screen_origin = 1;
7374 if (fXineramaRoot)
7376 /* use whole screen */
7377 pops->pos_hints.screen_origin_x = -1;
7378 pops->pos_hints.screen_origin_y = -1;
7380 else if (context_window == Scr.Root)
7382 /* xinerama: use screen that contains
7383 * the window center as reference */
7384 if (!fev_get_evpos_or_query(
7385 dpy, context_window, e,
7386 &pops->pos_hints.
7387 screen_origin_x,
7388 &pops->pos_hints.
7389 screen_origin_y))
7391 pops->pos_hints.
7392 screen_origin_x = 0;
7393 pops->pos_hints.
7394 screen_origin_y = 0;
7396 else
7398 fscreen_scr_arg fscr;
7400 fscr.xypos.x = pops->pos_hints.
7401 screen_origin_x;
7402 fscr.xypos.y = pops->pos_hints.
7403 screen_origin_y;
7404 FScreenGetScrRect(
7405 &fscr, FSCREEN_XYPOS,
7406 &x, &y, &width,
7407 &height);
7410 else
7412 /* xinerama: use screen that contains
7413 * the window center as reference */
7414 pops->pos_hints.screen_origin_x =
7415 JunkX + width / 2;
7416 pops->pos_hints.screen_origin_y =
7417 JunkY + height / 2;
7422 /* parse position arguments */
7423 taction = get_one_menu_position_argument(
7424 naction, x, width, &(pops->pos_hints.x),
7425 &(pops->pos_hints.x_offset),
7426 &(pops->pos_hints.x_factor),
7427 &(pops->pos_hints.context_x_factor),
7428 &pops->pos_hints.is_menu_relative);
7429 if (pops->pos_hints.is_menu_relative)
7431 pops->pos_hints.x = x;
7432 if (pops->pos_hints.menu_width == 0 && mr)
7434 pops->pos_hints.menu_width = MR_WIDTH(mr);
7437 naction = get_one_menu_position_argument(
7438 taction, y, height, &(pops->pos_hints.y), &dummy_int,
7439 &(pops->pos_hints.y_factor), &dummy_float,
7440 &dummy_flag);
7441 if (naction == taction)
7443 /* argument is missing or invalid */
7444 if (fHasContext)
7446 fvwm_msg(ERR, "get_menu_options",
7447 "invalid position arguments");
7449 naction = action;
7450 taction = action;
7451 break;
7453 taction = naction;
7454 pops->flags.has_poshints = 1;
7455 if (fValidPosHints == True &&
7456 pops->pos_hints.is_relative == True)
7458 pops->pos_hints = last_saved_pos_hints.pos_hints;
7460 /* we want to do this only once */
7461 once_more = False;
7462 } /* while */
7463 if (is_action_empty)
7465 if (!pops->pos_hints.has_screen_origin)
7467 pops->pos_hints.has_screen_origin = 1;
7468 if (!fev_get_evpos_or_query(
7469 dpy, Scr.Root, e,
7470 &pops->pos_hints.screen_origin_x,
7471 &pops->pos_hints.screen_origin_y))
7473 pops->pos_hints.screen_origin_x = 0;
7474 pops->pos_hints.screen_origin_y = 0;
7479 if (!pops->flags.has_poshints && fValidPosHints)
7481 pops->flags.has_poshints = 1;
7482 pops->pos_hints = last_saved_pos_hints.pos_hints;
7483 pops->pos_hints.is_relative = False;
7486 action = naction;
7487 /* to keep Purify silent */
7488 pops->flags.do_select_in_place = 0;
7489 /* parse additional options */
7490 while (naction && *naction)
7492 naction = GetNextToken(action, &tok);
7493 if (!tok)
7495 break;
7497 if (StrEquals(tok, "WarpTitle"))
7499 pops->flags.do_warp_title = 1;
7500 pops->flags.do_not_warp = 0;
7502 else if (StrEquals(tok, "NoWarp"))
7504 pops->flags.do_warp_title = 0;
7505 pops->flags.do_not_warp = 1;
7507 else if (StrEquals(tok, "Fixed"))
7509 pops->flags.is_fixed = 1;
7511 else if (StrEquals(tok, "SelectInPlace"))
7513 pops->flags.do_select_in_place = 1;
7515 else if (StrEquals(tok, "SelectWarp"))
7517 pops->flags.do_warp_on_select = 1;
7519 else if (StrEquals(tok, "TearOffImmediately"))
7521 pops->flags.do_tear_off_immediately = 1;
7523 else
7525 free (tok);
7526 break;
7528 action = naction;
7529 free (tok);
7531 if (!pops->flags.do_select_in_place)
7533 pops->flags.do_warp_on_select = 0;
7536 return action;
7539 /* ---------------------------- new menu loop code ------------------------- */
7541 #if 0
7542 /*!!!*/
7543 typedef enum
7545 MTR_XEVENT = 0x1,
7546 MTR_FAKE_ENTER_ITEM = 0x2,
7547 MTR_PROPAGATE_XEVENT_UP = 0x4,
7548 MTR_PROPAGATE_XEVENT_DOWN = 0x8,
7549 MTR_POPUP_TIMEOUT = 0x10,
7550 MTR_POPDOWN_TIMEOUT = 0x20
7551 } mloop_trigger_type_t;
7553 typedef_struct
7555 mloop_trigger_type_t type;
7556 XEvent trigger_ev;
7557 int ticks_passed;
7558 } mloop_trigger_t;
7560 typedef_struct
7562 int popup_10ms;
7563 int popdown_10ms;
7564 } mloop_timeouts_t;
7566 typedef struct
7568 MenuRoot *current_menu;
7569 XEvent *in_ev;
7570 struct
7572 unsigned do_fake_enter_item : 1;
7573 unsigned do_propagete_event_up : 1;
7574 unsigned do_propagete_event_down : 1;
7575 } flags;
7576 } mloop_get_trigger_input_t;
7578 /*!!!static*/
7579 mloop_trigger_type_t __mloop_get_trigger(
7580 mloop_trigger_t *ret_trigger, mloop_timeouts_t *io_timeouts,
7581 const mloop_get_trigger_input_t * const in,
7583 if (in_out->in_flags->do_propagate_event_down)
7585 return MTR_PROPAGATE_XEVENT_DOWN;
7587 else if (in_out->in_flags->do_propagate_event_up)
7589 if (a != b)
7591 return MTR_PROPAGATE_XEVENT_UP;
7593 else
7596 /*!!!return propagate up*/
7597 /*!!!*/
7599 /*!!!read event or wait for timeout*/
7600 while (0/*!!!not finished*/)
7602 /*!!!rc = 0*/
7603 if (0/*!!!wait for tiomeout*/)
7605 /*!!!check for event*/
7607 else
7609 /*!!!block for event*/
7611 if (0/*got event*/)
7613 /*!!!rc = MTR_XEVENT*/
7615 if (0/*!!!popup timed out;break*/)
7617 /*!!!rc = MTR_POPUP;break*/
7619 if (0/*!!!popdown timed out;break*/)
7621 /*!!!rc = MTR_POPDOWN;break*/
7623 /*!!!sleep*/
7625 if (0/*!!!rc == MTR_XEVENT && evtype == MotionNotify*/)
7627 /*!!!eat up further MotionNotify events*/
7630 return 0/*!!!rc*/;
7633 /*!!!static*/
7634 void __menu_loop_new(
7635 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp)
7637 mloop_evh_input_t mei;
7638 #if 0
7639 mloop_ret_code_t mloop_ret;
7640 #endif
7641 mloop_evh_data_t med;
7642 mloop_static_info_t msi;
7643 MenuOptions mops;
7644 Bool is_finished;
7646 /*!!!init menu loop*/
7647 __mloop_init(pmp, pmret, &mei, &med, &msi, &mops);
7648 for (is_finished = False; !is_finished; )
7650 mloop_trigger_type_t mtr;
7652 mtr = __mloop_get_trigger(
7653 pmp, pmret, &mei, &med, &msi);
7654 switch (mtr)
7656 case MTR_XEVENT:
7657 /*!!!handle event*/
7658 break;
7659 case MTR_FAKE_ENTER_ITEM:
7660 /*!!!fake enter item*/
7661 break;
7662 case MTR_PROPAGATE_XEVENT_UP:
7663 /*!!!handle propagation*/
7664 break;
7665 case MTR_PROPAGATE_XEVENT_DOWN:
7666 /*!!!handle propagation*/
7667 break;
7668 case MTR_POPUP_TIMEOUT:
7669 /*!!!handle popup*/
7670 break;
7671 case MTR_POPDOWN_TIMEOUT:
7672 /*!!!handle popdown*/
7673 break;
7680 #if 0
7681 mloop_ret = __mloop_handle_event(
7682 pmp, pmret, pdkp, &mei, &med, &msi);
7683 switch (mloop_ret)
7685 case MENU_MLOOP_RET_LOOP:
7686 continue;
7687 case MENU_MLOOP_RET_END:
7688 is_finished = True;
7689 break;
7690 default:
7691 break;
7693 /* Now handle new menu items, whether it is from a
7694 * keypress or a pointer motion event. */
7695 if (med.mi != NULL)
7697 mloop_ret = __mloop_handle_action_with_mi(
7698 pmp, pmret, pdkp, &mei, &med, &msi,
7699 &mops);
7701 else
7703 mloop_ret = __mloop_handle_action_without_mi(
7704 pmp, pmret, pdkp, &mei, &med, &msi,
7705 &mops);
7707 if (mloop_ret == MENU_MLOOP_RET_END)
7709 is_finished = True;
7711 XFlush(dpy);
7712 #endif
7714 __mloop_exit(pmp, pmret, pdkp, &mei, &med, &msi, &mops);
7716 return;
7718 #endif