1 /* -*-c-*- */
2 /* This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, see: <http://www.gnu.org/licenses/>
14 */
15
16 /* IMPORTANT NOTE: Do *not* use any constant numbers in this file. All values
17 * have to be #defined in the section below or in defaults.h to ensure full
18 * control over the menus. */
19
20 /* ---------------------------- included header files ---------------------- */
21
22 #include "config.h"
23
24 #include <stdio.h>
25 #include <assert.h>
26 #include <X11/keysym.h>
27
28 #include "libs/ftime.h"
29 #include "libs/fvwmlib.h"
30 #include "libs/FScreen.h"
31 #include "libs/Grab.h"
32 #include "libs/Parse.h"
33 #include "libs/ColorUtils.h"
34 #include "libs/Picture.h"
35 #include "libs/PictureUtils.h"
36 #include "libs/Graphics.h"
37 #include "libs/PictureGraphics.h"
38 #include "libs/charmap.h"
39 #include "libs/wcontext.h"
40 #include "fvwm.h"
41 #include "externs.h"
42 #include "execcontext.h"
43 #include "events.h"
44 #include "eventhandler.h"
45 #include "eventmask.h"
46 #include "cursor.h"
47 #include "functions.h"
48 #include "commands.h"
49 #include "misc.h"
50 #include "screen.h"
51 #include "colormaps.h"
52 #include "geometry.h"
53 #include "move_resize.h"
54 #include "menudim.h"
55 #include "menuitem.h"
56 #include "menuroot.h"
57 #include "menustyle.h"
58 #include "bindings.h"
59 #include "menubindings.h"
60 #include "menugeometry.h"
61 #include "menuparameters.h"
62 #include "menus.h"
63 #include "libs/FGettext.h"
64
65 /* ---------------------------- local definitions -------------------------- */
66
67 /* used in float to int arithmetic */
68 #define ROUNDING_ERROR_TOLERANCE 0.005
69
70 /* ---------------------------- local macros ------------------------------- */
71
72 #define SCTX_SET_MI(ctx,item) ((ctx).type = SCTX_MENU_ITEM, \
73 (ctx).menu_item.menu_item = (item))
74 #define SCTX_GET_MI(ctx) ((ctx).type == SCTX_MENU_ITEM ? \
75 (ctx).menu_item.menu_item : NULL)
76 #define SCTX_SET_MR(ctx,root) ((ctx).type = SCTX_MENU_ROOT, \
77 (ctx).menu_root.menu_root = (root))
78 #define SCTX_GET_MR(ctx) ((ctx).type == SCTX_MENU_ROOT ? \
79 (ctx).menu_root.menu_root : NULL)
80
81 /* ---------------------------- imports ------------------------------------ */
82
83 /* This external is safe. It's written only during startup. */
84 extern XContext MenuContext;
85
86 /* ---------------------------- included code files ------------------------ */
87
88 /* ---------------------------- local types -------------------------------- */
89
90 /* patch to pass the last popups position hints to popup_func */
91 typedef struct
92 {
93 struct
94 {
95 unsigned is_last_menu_pos_hints_valid : 1;
96 unsigned do_ignore_pos_hints : 1;
97 unsigned do_warp_title : 1;
98 } flags;
99 struct MenuPosHints pos_hints;
100 } saved_pos_hints;
101
102 typedef struct MenuInfo
103 {
104 MenuRoot *all;
105 int n_destroyed_menus;
106 } MenuInfo;
107
108 typedef struct MenuSizingParameters
109 {
110 MenuRoot *menu;
111 /* number of item labels present in the item format */
112 int used_item_labels;
113 /* same for mini icons */
114 int used_mini_icons;
115 struct
116 {
117 int sidepic_width;
118 MenuItemPartSizesT i;
119 } max;
120 struct
121 {
122 unsigned is_popup_indicator_used : 1;
123 } flags;
124 } MenuSizingParameters;
125
126 typedef enum
127 {
128 SCTX_MENU_ROOT,
129 SCTX_MENU_ITEM
130 } string_context_type_t;
131
132 typedef union
133 {
134 string_context_type_t type;
135 struct
136 {
137 string_context_type_t type;
138 MenuRoot *menu_root;
139 } menu_root;
140 struct
141 {
142 string_context_type_t type;
143 MenuItem *menu_item;
144 } menu_item;
145 } string_context_t;
146
147 typedef struct
148 {
149 char delimiter;
150 Bool (*string_handler)(
151 char *string, char delimiter,
152 string_context_t *user_data);
153 } string_def_t;
154
155 /* ---------------------------- menu loop types ---------------------------- */
156
157 typedef enum
158 {
159 MENU_MLOOP_RET_NORMAL,
160 MENU_MLOOP_RET_LOOP,
161 MENU_MLOOP_RET_END
162 } mloop_ret_code_t;
163
164 typedef struct
165 {
166 unsigned do_popup_immediately : 1;
167 /* used for delay popups, to just popup the menu */
168 unsigned do_popup_now : 1;
169 unsigned do_popdown_now : 1;
170 /* used for keystrokes, to popup and move to that menu */
171 unsigned do_popup_and_warp : 1;
172 unsigned do_force_reposition : 1;
173 unsigned do_force_popup : 1;
174 unsigned do_popdown : 1;
175 unsigned do_popup : 1;
176 unsigned do_menu : 1;
177 unsigned do_recycle_event : 1;
178 unsigned do_propagate_event_into_submenu : 1;
179 unsigned has_mouse_moved : 1;
180 unsigned is_off_menu_allowed : 1;
181 unsigned is_key_press : 1;
182 unsigned is_item_entered_by_key_press : 1;
183 unsigned is_motion_faked : 1;
184 unsigned is_popped_up_by_timeout : 1;
185 unsigned is_pointer_in_active_item_area : 1;
186 unsigned is_motion_first : 1;
187 unsigned is_release_first : 1;
188 unsigned is_submenu_mapped : 1;
189 unsigned was_item_unposted : 1;
190 unsigned is_button_release : 1;
191 } mloop_flags_t;
192
193 typedef struct
194 {
195 MenuItem *mi;
196 MenuRoot *mrMi;
197 int x_offset;
198 int popdown_delay_10ms;
199 int popup_delay_10ms;
200 } mloop_evh_data_t;
201
202 typedef struct
203 {
204 MenuRoot *mrPopup;
205 MenuRoot *mrPopdown;
206 mloop_flags_t mif;
207 /* used to reduce network traffic with delayed popup/popdown */
208 MenuItem *mi_with_popup;
209 MenuItem *mi_wants_popup;
210 MenuItem *miRemovedSubmenu;
211 } mloop_evh_input_t;
212
213 /* values that are set once when the menu loop is entered */
214 typedef struct mloop_static_info_t
215 {
216 int x_init;
217 int y_init;
218 Time t0;
219 unsigned int event_mask;
220 } mloop_static_info_t;
221
222 /* ---------------------------- forward declarations ----------------------- */
223
224 /* ---------------------------- local variables ---------------------------- */
225
226 /* This global is saved and restored every time a function is called that
227 * might modify them, so we can safely let it live outside a function. */
228 static saved_pos_hints last_saved_pos_hints;
229
230 /* structures for menus */
231 static MenuInfo Menus;
232
233 /* ---------------------------- exported variables (globals) --------------- */
234
235 /* ---------------------------- local functions ---------------------------- */
236
__menu_execute_function(const exec_context_t ** pexc,char * action)237 static void __menu_execute_function(const exec_context_t **pexc, char *action)
238 {
239 const exec_context_t *exc;
240 exec_context_changes_t ecc;
241 int old_emf;
242
243 ecc.w.w = ((*pexc)->w.fw) ? FW_W((*pexc)->w.fw) : None;
244 exc = exc_clone_context(*pexc, &ecc, ECC_W);
245 old_emf = Scr.flags.is_executing_menu_function;
246 Scr.flags.is_executing_menu_function = 1;
247 execute_function(NULL, exc, action, FUNC_DONT_EXPAND_COMMAND);
248 Scr.flags.is_executing_menu_function = old_emf;
249 exc_destroy_context(exc);
250 /* See if the window has been deleted */
251 if (!check_if_fvwm_window_exists((*pexc)->w.fw))
252 {
253 ecc.w.fw = NULL;
254 ecc.w.w = None;
255 ecc.w.wcontext = 0;
256 exc = exc_clone_context(
257 *pexc, &ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
258 exc_destroy_context(*pexc);
259 *pexc = exc;
260 }
261
262 return;
263 }
264
pointer_in_active_item_area(int x_offset,MenuRoot * mr)265 static Bool pointer_in_active_item_area(int x_offset, MenuRoot *mr)
266 {
267 float ratio = (float)MST_ACTIVE_AREA_PERCENT(mr) / 100.0;
268
269 if (MST_ACTIVE_AREA_PERCENT(mr) >= 100)
270 {
271 return False;
272 }
273 if (MST_USE_LEFT_SUBMENUS(mr))
274 {
275 return (x_offset <=
276 MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) -
277 MR_ITEM_WIDTH(mr) * ratio);
278 }
279 else
280 {
281 return (x_offset >=
282 MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) * ratio);
283 }
284 }
285
pointer_in_passive_item_area(int x_offset,MenuRoot * mr)286 static Bool pointer_in_passive_item_area(int x_offset, MenuRoot *mr)
287 {
288 float ratio = (float)MST_ACTIVE_AREA_PERCENT(mr) / 100.0;
289
290 if (MST_ACTIVE_AREA_PERCENT(mr) >= 100)
291 {
292 return False;
293 }
294 if (MST_USE_LEFT_SUBMENUS(mr))
295 {
296 return (x_offset >=
297 MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) * ratio);
298 }
299 else
300 {
301 return (x_offset <=
302 MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) -
303 MR_ITEM_WIDTH(mr) * ratio);
304 }
305 }
306
307 /*
308 * warping functions
309 */
310
warp_pointer_to_title(MenuRoot * mr)311 static void warp_pointer_to_title(MenuRoot *mr)
312 {
313 FWarpPointer(
314 dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0,
315 menudim_middle_x_offset(&MR_DIM(mr)),
316 menuitem_middle_y_offset(MR_FIRST_ITEM(mr), MR_STYLE(mr)));
317 }
318
warp_pointer_to_item(MenuRoot * mr,MenuItem * mi,Bool do_skip_title)319 static MenuItem *warp_pointer_to_item(
320 MenuRoot *mr, MenuItem *mi, Bool do_skip_title)
321 {
322 if (do_skip_title)
323 {
324 while (MI_NEXT_ITEM(mi) != NULL &&
325 (!MI_IS_SELECTABLE(mi) || MI_IS_TEAR_OFF_BAR(mi)))
326 {
327 /* skip separators, titles and tear off bars until the
328 * first 'real' item is found */
329 mi = MI_NEXT_ITEM(mi);
330 }
331 }
332 if (mi == NULL)
333 {
334 mi = MR_LAST_ITEM(mr);
335 }
336 if (mi == NULL)
337 {
338 return mi;
339 }
340 FWarpPointer(
341 dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0,
342 menudim_middle_x_offset(&MR_DIM(mr)),
343 menuitem_middle_y_offset(mi, MR_STYLE(mr)));
344
345 return mi;
346 }
347
348 /*
349 * menu animation functions
350 */
351
352 /* prepares the parameters to be passed to AnimatedMoveOfWindow
353 * mr - the menu instance that holds the menu item
354 * fw - the FvwmWindow structure to check against allowed functions */
get_menu_repaint_transparent_parameters(MenuRepaintTransparentParameters * pmrtp,MenuRoot * mr,FvwmWindow * fw)355 static void get_menu_repaint_transparent_parameters(
356 MenuRepaintTransparentParameters *pmrtp, MenuRoot *mr, FvwmWindow *fw)
357 {
358 pmrtp->mr = mr;
359 pmrtp->fw = fw;
360
361 return;
362 }
363
364
365 /* Undo the animation of a menu */
animated_move_back(MenuRoot * mr,Bool do_warp_pointer,FvwmWindow * fw)366 static void animated_move_back(
367 MenuRoot *mr, Bool do_warp_pointer, FvwmWindow *fw)
368 {
369 MenuRepaintTransparentParameters mrtp;
370 int act_x;
371 int act_y;
372
373 if (MR_XANIMATION(mr) == 0)
374 {
375 return;
376 }
377 if (menu_get_geometry(
378 mr, &JunkRoot, &act_x, &act_y, &JunkWidth, &JunkHeight,
379 &JunkBW, &JunkDepth))
380 {
381 Bool transparent_bg = False;
382
383 /* move it back */
384 if (ST_HAS_MENU_CSET(MR_STYLE(mr)) &&
385 CSET_IS_TRANSPARENT(ST_CSET_MENU(MR_STYLE(mr))))
386 {
387 transparent_bg = True;
388 get_menu_repaint_transparent_parameters(
389 &mrtp, mr, fw);
390 }
391 AnimatedMoveOfWindow(
392 MR_WINDOW(mr), act_x, act_y, act_x - MR_XANIMATION(mr),
393 act_y, do_warp_pointer, -1, NULL,
394 (transparent_bg)? &mrtp:NULL);
395 MR_XANIMATION(mr) = 0;
396 }
397
398 return;
399 }
400
401 /* move a menu or a tear-off menu preserving transparency.
402 * tear-off menus are moved with their frame coordinates. */
move_any_menu(MenuRoot * mr,MenuParameters * pmp,int endX,int endY)403 static void move_any_menu(
404 MenuRoot *mr, MenuParameters *pmp, int endX, int endY)
405 {
406 if (MR_IS_TEAR_OFF_MENU(mr))
407 {
408 float fFull = 1.0;
409
410 /* this moves the tearoff menu, updating of transparency
411 * will not be as good as if menu repaint parameters
412 * are used. */
413 AnimatedMoveFvwmWindow(
414 pmp->tear_off_root_menu_window,
415 FW_W_FRAME(pmp->tear_off_root_menu_window),
416 -1, -1, endX, endY, False, 0, &fFull);
417 }
418 else
419 {
420 int x;
421 int y;
422 int JunkDept;
423
424 menu_get_geometry(mr, &JunkRoot, &x, &y, &JunkWidth,
425 &JunkHeight, &JunkBW, &JunkDept);
426 if (x == endX && y == endY)
427 {
428 return;
429 }
430 if (ST_HAS_MENU_CSET(MR_STYLE(mr)) &&
431 CSET_IS_TRANSPARENT(ST_CSET_MENU(MR_STYLE(mr))))
432 {
433 MenuRepaintTransparentParameters mrtp;
434
435 get_menu_repaint_transparent_parameters(
436 &mrtp, mr, (*pmp->pexc)->w.fw);
437 update_transparent_menu_bg(
438 &mrtp, x, y, endX, endY, endX, endY);
439 XMoveWindow(dpy, MR_WINDOW(mr), endX, endY);
440 repaint_transparent_menu(
441 &mrtp, False, endX,endY, endX, endY, True);
442 }
443 else
444 {
445 XMoveWindow(dpy, MR_WINDOW(mr), endX, endY);
446 }
447 }
448 }
449
450 /* ---------------------------- submenu function --------------------------- */
451
452 /* Search for a submenu that was popped up by the given item in the given
453 * instance of the menu. */
seek_submenu_instance(MenuRoot * parent_menu,MenuItem * parent_item)454 static MenuRoot *seek_submenu_instance(
455 MenuRoot *parent_menu, MenuItem *parent_item)
456 {
457 MenuRoot *mr;
458
459 for (mr = Menus.all; mr != NULL; mr = MR_NEXT_MENU(mr))
460 {
461 if (MR_PARENT_MENU(mr) == parent_menu &&
462 MR_PARENT_ITEM(mr) == parent_item)
463 {
464 /* here it is */
465 break;
466 }
467 }
468
469 return mr;
470 }
471
is_submenu_mapped(MenuRoot * parent_menu,MenuItem * parent_item)472 static Bool is_submenu_mapped(MenuRoot *parent_menu, MenuItem *parent_item)
473 {
474 XWindowAttributes win_attribs;
475 MenuRoot *mr;
476
477 mr = seek_submenu_instance(parent_menu, parent_item);
478 if (mr == NULL)
479 {
480 return False;
481 }
482
483 if (MR_WINDOW(mr) == None)
484 {
485 return False;
486 }
487 if (!XGetWindowAttributes(dpy, MR_WINDOW(mr), &win_attribs))
488 {
489 return False;
490 }
491
492 return (win_attribs.map_state == IsViewable);
493 }
494
495 /* Returns the menu root that a given menu item pops up */
mr_popup_for_mi(MenuRoot * mr,MenuItem * mi)496 static MenuRoot *mr_popup_for_mi(MenuRoot *mr, MenuItem *mi)
497 {
498 char *menu_name;
499 MenuRoot *menu = NULL;
500
501 /* This checks if mi is != NULL too */
502 if (!mi || !MI_IS_POPUP(mi))
503 {
504 return NULL;
505 }
506
507 /* first look for a menu that is aleady mapped */
508 menu = seek_submenu_instance(mr, mi);
509 if (menu)
510 {
511 return menu;
512 }
513
514 /* just look past "Popup " in the action, and find that menu root */
515 menu_name = PeekToken(SkipNTokens(MI_ACTION(mi), 1), NULL);
516 menu = menus_find_menu(menu_name);
517
518 return menu;
519 }
520
521 /* ---------------------------- item handling ------------------------------ */
522
523 /*
524 * find_entry()
525 *
526 * Returns the menu item the pointer is over and optionally the offset
527 * from the left side of the menu entry (if px_offset is != NULL) and
528 * the MenuRoot the pointer is over (if pmr is != NULL).
529 */
find_entry(MenuParameters * pmp,int * px_offset,MenuRoot ** pmr,Window p_child,int p_rx,int p_ry)530 static MenuItem *find_entry(
531 MenuParameters *pmp,
532 int *px_offset /*NULL means don't return this value */,
533 MenuRoot **pmr /*NULL means don't return this value */,
534 /* values passed in from caller it FQueryPointer was already called
535 * there */
536 Window p_child, int p_rx, int p_ry)
537 {
538 MenuItem *mi;
539 MenuRoot *mr;
540 int root_x, root_y;
541 int x, y;
542 Window Child;
543 int r;
544
545 /* x_offset returns the x offset of the pointer in the found menu item
546 */
547 if (px_offset)
548 {
549 *px_offset = 0;
550 }
551 if (pmr)
552 {
553 *pmr = NULL;
554 }
555 /* get the pointer position */
556 if (p_rx < 0)
557 {
558 if (!FQueryPointer(
559 dpy, Scr.Root, &JunkRoot, &Child,
560 &root_x, &root_y, &JunkX, &JunkY, &JunkMask))
561 {
562 /* pointer is on a different screen */
563 return NULL;
564 }
565 }
566 else
567 {
568 root_x = p_rx;
569 root_y = p_ry;
570 Child = p_child;
571 }
572 /* find out the menu the pointer is in */
573 if (pmp->tear_off_root_menu_window != NULL &&
574 Child == FW_W_FRAME(pmp->tear_off_root_menu_window))
575 {
576 /* we're in the top level torn off menu */
577 Child = FW_W(pmp->tear_off_root_menu_window);
578 }
579 if (XFindContext(dpy, Child, MenuContext, (caddr_t *)&mr) == XCNOENT)
580 {
581 return NULL;
582 }
583 /* get position in that child window */
584 if (!XTranslateCoordinates(
585 dpy, Scr.Root, MR_WINDOW(mr), root_x, root_y, &x, &y,
586 &JunkChild))
587 {
588 return NULL;
589 }
590 if (x < 0 || y < 0 || x >= MR_WIDTH(mr) || y >= MR_HEIGHT(mr))
591 {
592 return NULL;
593 }
594 if (pmr)
595 {
596 *pmr = mr;
597 }
598 r = MST_RELIEF_THICKNESS(mr);
599 /* look for the entry that the mouse is in */
600 for (mi = MR_FIRST_ITEM(mr); mi; mi = MI_NEXT_ITEM(mi))
601 {
602 int a;
603 int b;
604
605 a = (MI_PREV_ITEM(mi) &&
606 MI_IS_SELECTABLE(MI_PREV_ITEM(mi))) ? r / 2 : 0;
607 if (!MI_IS_SELECTABLE(mi))
608 {
609 b = 0;
610 }
611 else if (MI_NEXT_ITEM(mi) &&
612 MI_IS_SELECTABLE(MI_NEXT_ITEM(mi)))
613 {
614 b = r / 2;
615 }
616 else
617 {
618 b = r;
619 }
620 if (y >= MI_Y_OFFSET(mi) - a &&
621 y < MI_Y_OFFSET(mi) + MI_HEIGHT(mi) + b)
622 {
623 break;
624 }
625 }
626 if (x < MR_ITEM_X_OFFSET(mr) ||
627 x >= MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) - 1)
628 {
629 mi = NULL;
630 }
631 if (mi && px_offset)
632 {
633 *px_offset = x;
634 }
635
636 return mi;
637 }
638
639 /* ---------------------------- keyboard shortcuts ------------------------- */
640
is_double_click(Time t0,MenuItem * mi,MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,Bool has_mouse_moved)641 static Bool is_double_click(
642 Time t0, MenuItem *mi, MenuParameters *pmp, MenuReturn *pmret,
643 double_keypress *pdkp, Bool has_mouse_moved)
644 {
645 if ((*pmp->pexc)->x.elast->type == KeyPress)
646 {
647 return False;
648 }
649 if (fev_get_evtime() - t0 >= MST_DOUBLE_CLICK_TIME(pmp->menu))
650 {
651 return False;
652 }
653 if (has_mouse_moved)
654 {
655 return False;
656 }
657 if (!pmp->flags.has_default_action &&
658 (mi && mi == MR_FIRST_ITEM(pmp->menu) && MI_IS_SELECTABLE(mi)))
659 {
660 return False;
661 }
662 if (pmp->flags.is_submenu)
663 {
664 return False;
665 }
666 if (pmp->flags.is_invoked_by_key_press && pdkp->timestamp == 0)
667 {
668 return False;
669 }
670
671 return True;
672 }
673
674 /* ---------------------------- item label parsing ------------------------- */
675
676 /*
677 * Procedure:
678 * scanForHotkeys - Look for hotkey markers in a MenuItem
679 * (pete@tecc.co.uk)
680 *
681 * Inputs:
682 * it - MenuItem to scan
683 * column - The column number in which to look for a hotkey.
684 *
685 */
scanForHotkeys(MenuItem * it,int column)686 static void scanForHotkeys(
687 MenuItem *it, int column)
688 {
689 char *start;
690 char *s;
691 char *t;
692
693 /* Get start of string */
694 start = MI_LABEL(it)[column];
695 /* Scan whole string */
696 for (s = start; *s != '\0'; s++)
697 {
698 if (*s != '&')
699 {
700 continue;
701 }
702 if (s[1] != '&')
703 {
704 /* found a hotkey - only one hotkey per item */
705 break;
706 }
707 /* Just an escaped '&'; copy the string down over it */
708 for (t = s; *t != '\0'; t++)
709 {
710 t[0] = t[1];
711 }
712
713 }
714 if (*s != 0)
715 {
716 /* It's a hot key marker - work out the offset value */
717 MI_HOTKEY_COFFSET(it) = s - start;
718 MI_HOTKEY_COLUMN(it) = column;
719 MI_HAS_HOTKEY(it) = (s[1] != '\0');
720 MI_IS_HOTKEY_AUTOMATIC(it) = 0;
721 for ( ; *s != '\0'; s++)
722 {
723 /* Copy down.. */
724 s[0] = s[1];
725 }
726 }
727
728 return;
729 }
730
__copy_down(char * remove_from,char * remove_to)731 static void __copy_down(char *remove_from, char *remove_to)
732 {
733 char *t1;
734 char *t2;
735
736 for (t1 = remove_from, t2 = remove_to; *t2 != '\0'; t2++, t1++)
737 {
738 *t1 = *t2;
739 }
740 *t1 = '\0';
741
742 return;
743 }
744
__check_for_delimiter(char * s,const string_def_t * string_defs)745 static int __check_for_delimiter(char *s, const string_def_t *string_defs)
746 {
747 int type;
748
749 for (type = 0; string_defs[type].delimiter != '\0'; type++)
750 {
751 if (s[0] == string_defs[type].delimiter)
752 {
753 if (s[1] != string_defs[type].delimiter)
754 {
755 return type;
756 }
757 else
758 {
759 /* escaped delimiter, copy the
760 * string down over it */
761 __copy_down(s, s+1);
762
763 return -1;
764 }
765 }
766 }
767
768 return -1;
769 }
770
771 /* This scans for strings within delimiters and calls a callback based
772 * on the delimiter found for each found string */
scanForStrings(char * instring,const string_def_t * string_defs,string_context_t * context)773 static void scanForStrings(
774 char *instring, const string_def_t *string_defs,
775 string_context_t *context)
776 {
777 char *s;
778 int type;
779 char *string;
780
781 type = -1;
782 /* string is set whenever type >= 0, and unused otherwise
783 * set to NULL to supress compiler warning */
784 string = NULL;
785 for (s = instring; *s != '\0'; s++)
786 {
787 if (type < 0)
788 {
789 /* look for starting delimiters */
790 type = __check_for_delimiter(s, string_defs);
791 if (type >= 0)
792 {
793 /* start of a string */
794 string = s + 1;
795 }
796 }
797 else if (
798 s[0] == string_defs[type].delimiter &&
799 s[1] != string_defs[type].delimiter)
800 {
801 /* found ending delimiter */
802 Bool is_valid;
803
804 /* terminate the string pointer */
805 s[0] = '\0';
806 is_valid = string_defs[type].string_handler(
807 string, string_defs[type].delimiter, context);
808 /* restore the string */
809 s[0] = string_defs[type].delimiter;
810
811 if (is_valid)
812 {
813 /* the string was OK, remove it from
814 * instring */
815 __copy_down(string - 1, s + 1);
816 /* continue next iteration at the
817 * first character after the string */
818 s = string - 2;
819 }
820
821 type = -1;
822 }
823 else if (s[0] == string_defs[type].delimiter)
824 {
825 /* escaped delimiter, copy the string down over
826 * it */
827 __copy_down(s, s + 1);
828 }
829 }
830 }
831
832 /* Side picture support: this scans for a color int the menu name
833 for colorization */
__scan_for_color(char * name,char type,string_context_t * context)834 static Bool __scan_for_color(
835 char *name, char type, string_context_t *context)
836 {
837 if (type != '^' || SCTX_GET_MR(*context) == NULL)
838 {
839 abort();
840 }
841
842 if (MR_HAS_SIDECOLOR(SCTX_GET_MR(*context)))
843 {
844 return False;
845 }
846
847 MR_SIDECOLOR(SCTX_GET_MR(*context)) = GetColor(name);
848 MR_HAS_SIDECOLOR(SCTX_GET_MR(*context)) = True;
849
850 return True;
851 }
852
__scan_for_pixmap(char * name,char type,string_context_t * context)853 static Bool __scan_for_pixmap(
854 char *name, char type, string_context_t *context)
855 {
856 FvwmPicture *p;
857 FvwmPictureAttributes fpa;
858 int current_mini_icon;
859
860 /* check that more pictures are allowed before trying to load the
861 * picture */
862 current_mini_icon = -999999999;
863 switch (type)
864 {
865 case '@':
866 if (SCTX_GET_MR(*context) == NULL)
867 {
868 abort();
869 }
870 if (MR_SIDEPIC(SCTX_GET_MR(*context)))
871 {
872 return False;
873 }
874 break;
875 case '*':
876 /* menu item picture, requires menu item */
877 if (SCTX_GET_MI(*context) == NULL)
878 {
879 abort();
880 }
881 if (MI_PICTURE(SCTX_GET_MI(*context)))
882 {
883 return False;
884 }
885 break;
886 case '%':
887 /* mini icon - look for next free spot */
888 if (SCTX_GET_MI(*context) == NULL)
889 {
890 abort();
891 }
892 current_mini_icon = 0;
893 while (current_mini_icon < MAX_MENU_ITEM_MINI_ICONS)
894 {
895 if (
896 MI_MINI_ICON(SCTX_GET_MI(*context))
897 [current_mini_icon])
898 {
899 current_mini_icon++;
900 }
901 else
902 {
903 break;
904 }
905 }
906 if (current_mini_icon == MAX_MENU_ITEM_MINI_ICONS)
907 {
908 return False;
909 }
910 break;
911 default:
912 abort();
913 }
914
915 fpa.mask = 0;
916 p = PCacheFvwmPicture(
917 dpy, Scr.NoFocusWin, NULL, name, fpa);
918 if (!p)
919 {
920 fvwm_msg(WARN, "scanForPixmap",
921 "Couldn't load image from %s", name);
922 fvwm_msg(WARN, "scanForPixmap",
923 "Check that FVWM has support for the filetype it's "
924 "being asked to load.");
925
926 /* return true to make missing pictures not appear in the
927 * label/name */
928 return True;
929 }
930
931 switch (type)
932 {
933 case '@':
934 MR_SIDEPIC(SCTX_GET_MR(*context)) = p;
935
936 break;
937 case '*':
938 MI_PICTURE(SCTX_GET_MI(*context)) = p;
939 MI_HAS_PICTURE(SCTX_GET_MI(*context)) = True;
940
941 break;
942 case '%':
943 MI_MINI_ICON(SCTX_GET_MI(*context))[current_mini_icon] = p;
944 MI_HAS_PICTURE(SCTX_GET_MI(*context)) = True;
945
946 break;
947 }
948
949 return True;
950 }
951
952 /* ---------------------------- item list handling ------------------------- */
953
unlink_item_from_menu(MenuRoot * mr,MenuItem * mi)954 static void unlink_item_from_menu(
955 MenuRoot *mr, MenuItem *mi)
956 {
957 MenuItem *next;
958 MenuItem *prev;
959
960 next = MI_NEXT_ITEM(mi);
961 prev = MI_PREV_ITEM(mi);
962 if (next != NULL)
963 {
964 MI_PREV_ITEM(next) = prev;
965 }
966 else
967 {
968 MR_LAST_ITEM(mr) = prev;
969 }
970 if (prev != NULL)
971 {
972 MI_NEXT_ITEM(prev) = next;
973 }
974 else
975 {
976 MR_FIRST_ITEM(mr) = next;
977 }
978 MI_NEXT_ITEM(mi) = NULL;
979 MI_PREV_ITEM(mi) = NULL;
980 MR_ITEMS(mr)--;
981
982 return;
983 }
984
985 /* Add the given menu item to the menu. If the first item of the menu is a
986 * title, and the do_replace_title flag is True, the old title is deleted and
987 * replaced by the new item. Otherwise the item is appended at the end of the
988 * menu. */
append_item_to_menu(MenuRoot * mr,MenuItem * mi,Bool do_replace_title)989 static void append_item_to_menu(
990 MenuRoot *mr, MenuItem *mi, Bool do_replace_title)
991 {
992 if (MR_FIRST_ITEM(mr) == NULL)
993 {
994 MR_FIRST_ITEM(mr) = mi;
995 MR_LAST_ITEM(mr) = mi;
996 MI_NEXT_ITEM(mi) = NULL;
997 MI_PREV_ITEM(mi) = NULL;
998 }
999 else if (do_replace_title)
1000 {
1001 if (MI_IS_TITLE(MR_FIRST_ITEM(mr)))
1002 {
1003 if (MR_FIRST_ITEM(mr) == MR_LAST_ITEM(mr))
1004 {
1005 MR_LAST_ITEM(mr) = mi;
1006 }
1007 if (MI_NEXT_ITEM(MR_FIRST_ITEM(mr)) != NULL)
1008 {
1009 MI_PREV_ITEM(MI_NEXT_ITEM(
1010 MR_FIRST_ITEM(mr))) = mi;
1011 }
1012 MI_NEXT_ITEM(mi) = MI_NEXT_ITEM(MR_FIRST_ITEM(mr));
1013 menuitem_free(MR_FIRST_ITEM(mr));
1014 }
1015 else
1016 {
1017 MI_PREV_ITEM(MR_FIRST_ITEM(mr)) = mi;
1018 MI_NEXT_ITEM(mi) = MR_FIRST_ITEM(mr);
1019 }
1020 MI_PREV_ITEM(mi) = NULL;
1021 MR_FIRST_ITEM(mr) = mi;
1022 }
1023 else
1024 {
1025 MI_NEXT_ITEM(MR_LAST_ITEM(mr)) = mi;
1026 MI_PREV_ITEM(mi) = MR_LAST_ITEM(mr);
1027 MR_LAST_ITEM(mr) = mi;
1028 }
1029
1030 return;
1031 }
1032
clone_menu_item_list(MenuRoot * dest_mr,MenuRoot * src_mr)1033 static void clone_menu_item_list(
1034 MenuRoot *dest_mr, MenuRoot *src_mr)
1035 {
1036 MenuItem *mi;
1037 MenuItem *cloned_mi;
1038 MenuRoot *mr;
1039
1040 MR_FIRST_ITEM(dest_mr) = NULL;
1041 MR_LAST_ITEM(dest_mr) = NULL;
1042 /* traverse the menu and all its continuations */
1043 for (mr = src_mr; mr != NULL; mr = MR_CONTINUATION_MENU(mr))
1044 {
1045 /* duplicate all items in the current menu */
1046 for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
1047 {
1048 if (MI_IS_CONTINUATION(mi))
1049 {
1050 /* skip this item */
1051 continue;
1052 }
1053 cloned_mi = menuitem_clone(mi);
1054 append_item_to_menu(dest_mr, cloned_mi, False);
1055 }
1056 }
1057
1058 return;
1059 }
1060
1061 /* ---------------------------- MenuRoot maintenance functions ------------- */
1062
1063 /* Extract interesting values from the item format string that are needed by
1064 * the size_menu_... functions. */
calculate_item_sizes(MenuSizingParameters * msp)1065 static void calculate_item_sizes(MenuSizingParameters *msp)
1066 {
1067 MenuItem *mi;
1068 MenuItemPartSizesT mipst;
1069 int i;
1070 Bool do_reverse_icon_order =
1071 (MST_USE_LEFT_SUBMENUS(msp->menu)) ? True : False;
1072
1073 memset(&(msp->max), 0, sizeof(msp->max));
1074 /* Calculate the widths for all columns of all items. */
1075 for (mi = MR_FIRST_ITEM(msp->menu); mi != NULL; mi = MI_NEXT_ITEM(mi))
1076 {
1077 if (MI_IS_TITLE(mi))
1078 {
1079 menuitem_get_size(
1080 mi, &mipst, MST_PTITLEFONT(msp->menu),
1081 do_reverse_icon_order);
1082 }
1083 else
1084 {
1085 menuitem_get_size(
1086 mi, &mipst, MST_PSTDFONT(msp->menu),
1087 do_reverse_icon_order);
1088 }
1089 /* adjust maximums */
1090 if (msp->max.i.triangle_width < mipst.triangle_width)
1091 {
1092 msp->max.i.triangle_width = mipst.triangle_width;
1093 }
1094 if (msp->max.i.title_width < mipst.title_width)
1095 {
1096 msp->max.i.title_width = mipst.title_width;
1097 }
1098 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1099 {
1100 if (msp->max.i.label_width[i] < mipst.label_width[i])
1101 {
1102 msp->max.i.label_width[i] =
1103 mipst.label_width[i];
1104 }
1105 }
1106 if (msp->max.i.picture_width < mipst.picture_width)
1107 {
1108 msp->max.i.picture_width = mipst.picture_width;
1109 }
1110 for (i = 0; i < MAX_MENU_ITEM_MINI_ICONS; i++)
1111 {
1112 if (msp->max.i.icon_width[i] < mipst.icon_width[i])
1113 {
1114 msp->max.i.icon_width[i] = mipst.icon_width[i];
1115 }
1116 }
1117 }
1118 if (MR_SIDEPIC(msp->menu))
1119 {
1120 msp->max.sidepic_width = MR_SIDEPIC(msp->menu)->width;
1121 }
1122 else if (MST_SIDEPIC(msp->menu))
1123 {
1124 msp->max.sidepic_width = MST_SIDEPIC(msp->menu)->width;
1125 }
1126
1127 return;
1128 }
1129
1130 /*
1131 *
1132 * Calculate the positions of the columns in the menu.
1133 * Called by make_menu().
1134 *
1135 */
size_menu_horizontally(MenuSizingParameters * msp)1136 static void size_menu_horizontally(MenuSizingParameters *msp)
1137 {
1138 MenuItem *mi;
1139 Bool sidepic_is_left = True;
1140 int total_width;
1141 int sidepic_space = 0;
1142 int label_offset[MAX_MENU_ITEM_LABELS];
1143 char lcr_column[MAX_MENU_ITEM_LABELS];
1144 int i;
1145 int d;
1146 int relief_thickness = MST_RELIEF_THICKNESS(msp->menu);
1147 int *item_order[
1148 MAX_MENU_ITEM_LABELS + MAX_MENU_ITEM_MINI_ICONS +
1149 1 /* triangle */ + 2 /* relief markers */];
1150 int used_objects = 0;
1151 int left_objects = 0;
1152 int right_objects = 0;
1153 int x;
1154 unsigned char icons_placed = 0;
1155 Bool sidepic_placed = False;
1156 Bool triangle_placed = False;
1157 Bool relief_begin_placed = False;
1158 Bool relief_end_placed = False;
1159 char *format;
1160 Bool first = True;
1161 Bool done = False;
1162 Bool is_last_object_left = True;
1163 unsigned char columns_placed = 0;
1164 int relief_gap = 0;
1165 int gap_left;
1166 int gap_right;
1167 int chars;
1168
1169 memset(item_order, 0, sizeof(item_order));
1170 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1171 {
1172 lcr_column[i] = 'l';
1173 }
1174
1175 /* Now calculate the offsets for the columns. */
1176 format = MST_ITEM_FORMAT(msp->menu);
1177 if (!format)
1178 {
1179 format = (MST_USE_LEFT_SUBMENUS(msp->menu)) ?
1180 DEFAULT_LEFT_MENU_ITEM_FORMAT :
1181 DEFAULT_MENU_ITEM_FORMAT;
1182 }
1183 /* Place the individual items off the menu in case they are not
1184 * set in the format string. */
1185 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1186 {
1187 label_offset[i] = 2 * Scr.MyDisplayWidth;
1188 }
1189
1190 x = MST_BORDER_WIDTH(msp->menu);
1191 while (*format && !done)
1192 {
1193 switch (*format)
1194 {
1195 case '%':
1196 format++;
1197 chars = 0;
1198 gap_left = 0;
1199 gap_right = 0;
1200
1201 /* Insert a gap of %d pixels. */
1202 if (sscanf(format, "%d.%d%n", &gap_left,
1203 &gap_right, &chars) >= 2 ||
1204 (sscanf(format, "%d.%n", &gap_left,
1205 &chars) >= 1 && chars > 0) ||
1206 sscanf(format, "%d%n", &gap_left,
1207 &chars) >= 1 ||
1208 sscanf(format, ".%d%n", &gap_right,
1209 &chars) >= 1)
1210 {
1211 if (gap_left > MR_SCREEN_WIDTH(msp->menu) ||
1212 gap_left < -MR_SCREEN_WIDTH(msp->menu))
1213 {
1214 gap_left = 0;
1215 }
1216 if (gap_right > MR_SCREEN_HEIGHT(msp->menu) ||
1217 gap_right < -MR_SCREEN_HEIGHT(msp->menu))
1218 {
1219 gap_right = 0;
1220 }
1221 /* Skip the number. */
1222 format += chars;
1223 }
1224 else if (*format == '.')
1225 {
1226 /* Skip a dot without values */
1227 format++;
1228 }
1229 if (!*format)
1230 {
1231 break;
1232 }
1233 switch (*format)
1234 {
1235 case 'l':
1236 case 'c':
1237 case 'r':
1238 /* A left, center or right aligned column. */
1239 if (columns_placed >=
1240 MAX_MENU_ITEM_LABELS)
1241 {
1242 break;
1243 }
1244 if (
1245 msp->max.i.label_width[columns_placed]
1246 <= 0)
1247 {
1248 columns_placed++;
1249 break;
1250 }
1251 lcr_column[columns_placed] = *format;
1252 x += gap_left;
1253 label_offset[columns_placed] = x;
1254 x += msp->max.i.label_width[columns_placed] +
1255 gap_right;
1256 item_order[used_objects++] =
1257 &(label_offset[columns_placed]);
1258 if (is_last_object_left && (*format == 'l'))
1259 {
1260 left_objects++;
1261 }
1262 else
1263 {
1264 is_last_object_left = False;
1265 if (*format == 'r')
1266 {
1267 right_objects++;
1268 }
1269 else
1270 {
1271 right_objects = 0;
1272 }
1273 }
1274 columns_placed++;
1275 break;
1276 case 's':
1277 /* the sidepic */
1278 if (sidepic_placed)
1279 {
1280 break;
1281 }
1282 sidepic_placed = True;
1283 if (msp->max.sidepic_width <= 0)
1284 {
1285 break;
1286 }
1287 x += gap_left;
1288 MR_SIDEPIC_X_OFFSET(msp->menu) = x;
1289 sidepic_is_left = first;
1290 sidepic_space = msp->max.sidepic_width +
1291 ((sidepic_is_left) ?
1292 gap_left : gap_right);
1293 x += msp->max.sidepic_width + gap_right;
1294 break;
1295 case 'i':
1296 /* a mini icon */
1297 if (icons_placed >=
1298 MAX_MENU_ITEM_MINI_ICONS)
1299 {
1300 break;
1301 }
1302 if (msp->max.i.icon_width[icons_placed] > 0)
1303 {
1304 x += gap_left;
1305 MR_ICON_X_OFFSET(msp->menu)
1306 [icons_placed] = x;
1307 x += msp->max.i.icon_width
1308 [icons_placed] + gap_right;
1309 item_order[used_objects++] =
1310 &(MR_ICON_X_OFFSET(msp->menu)
1311 [icons_placed]);
1312 if (is_last_object_left)
1313 {
1314 left_objects++;
1315 }
1316 else
1317 {
1318 right_objects++;
1319 }
1320 }
1321 icons_placed++;
1322 break;
1323 case '|':
1324 if (!relief_begin_placed)
1325 {
1326 relief_begin_placed = True;
1327 x += gap_left;
1328 MR_HILIGHT_X_OFFSET(msp->menu) =
1329 x;
1330 x += relief_thickness +
1331 gap_right;
1332 relief_gap += gap_right;
1333 item_order[used_objects++] =
1334 &(MR_HILIGHT_X_OFFSET(
1335 msp->menu));
1336 if (is_last_object_left)
1337 {
1338 left_objects++;
1339 }
1340 else
1341 {
1342 right_objects = 0;
1343 }
1344 }
1345 else if (!relief_end_placed)
1346 {
1347 relief_end_placed = True;
1348 x += relief_thickness + gap_left;
1349 /* This is a hack: for now we record
1350 * the x coordinate of the end of the
1351 * hilight area, but later we'll place
1352 * the width in here. */
1353 MR_HILIGHT_WIDTH(msp->menu) = x;
1354 x += gap_right;
1355 relief_gap += gap_left;
1356 item_order[used_objects++] =
1357 &(MR_HILIGHT_WIDTH(msp->menu));
1358 right_objects++;
1359 }
1360 break;
1361 case '>':
1362 case '<':
1363 /* the triangle for popup menus */
1364 if (triangle_placed)
1365 {
1366 break;
1367 }
1368 triangle_placed = True;
1369 if (msp->max.i.triangle_width > 0)
1370 {
1371 x += gap_left;
1372 MR_TRIANGLE_X_OFFSET(
1373 msp->menu) = x;
1374 MR_IS_LEFT_TRIANGLE(msp->menu) =
1375 (*format == '<');
1376 x += msp->max.i.triangle_width +
1377 gap_right;
1378 item_order[used_objects++] =
1379 &(MR_TRIANGLE_X_OFFSET(
1380 msp->menu));
1381 if (is_last_object_left &&
1382 *format == '<')
1383 {
1384 left_objects++;
1385 }
1386 else
1387 {
1388 is_last_object_left = False;
1389 right_objects++;
1390 }
1391 }
1392 break;
1393 case 'p':
1394 /* Simply add a gap. */
1395 x += gap_right + gap_left;
1396 break;
1397 case '\t':
1398 x += MENU_TAB_WIDTH * FlocaleTextWidth(
1399 MST_PSTDFONT(
1400 msp->menu), " ", 1);
1401 break;
1402 case ' ':
1403 /* Advance the x position. */
1404 x += FlocaleTextWidth(
1405 MST_PSTDFONT(msp->menu), format,
1406 1);
1407 break;
1408 default:
1409 /* Ignore unknown characters. */
1410 break;
1411 } /* switch (*format) */
1412 break;
1413 case '\t':
1414 x += MENU_TAB_WIDTH * FlocaleTextWidth(
1415 MST_PSTDFONT(msp->menu), " ", 1);
1416 break;
1417 case ' ':
1418 /* Advance the x position. */
1419 x += FlocaleTextWidth(
1420 MST_PSTDFONT(msp->menu), format, 1);
1421 break;
1422 default:
1423 /* Ignore unknown characters. */
1424 break;
1425 } /* switch (*format) */
1426 format++;
1427 first = False;
1428 } /* while (*format) */
1429 /* stored for vertical sizing */
1430 msp->used_item_labels = columns_placed;
1431 msp->used_mini_icons = icons_placed;
1432
1433 /* Hide unplaced parts of the menu. */
1434 if (!sidepic_placed)
1435 {
1436 MR_SIDEPIC_X_OFFSET(msp->menu) =
1437 2 * MR_SCREEN_WIDTH(msp->menu);
1438 }
1439 for (i = icons_placed; i < MAX_MENU_ITEM_MINI_ICONS; i++)
1440 {
1441 MR_ICON_X_OFFSET(msp->menu)[i] =
1442 2 * MR_SCREEN_WIDTH(msp->menu);
1443 }
1444 if (!triangle_placed)
1445 {
1446 MR_TRIANGLE_X_OFFSET(msp->menu) =
1447 2 * MR_SCREEN_WIDTH(msp->menu);
1448 }
1449 msp->flags.is_popup_indicator_used = triangle_placed;
1450
1451 total_width = x - MST_BORDER_WIDTH(msp->menu);
1452 d = (sidepic_space + 2 * relief_thickness +
1453 max(msp->max.i.title_width, msp->max.i.picture_width)) -
1454 total_width;
1455 if (d > 0)
1456 {
1457 int m = 1 - left_objects;
1458 int n = 1 + used_objects - left_objects - right_objects;
1459
1460 /* The title is larger than all menu items. Stretch the
1461 * gaps between the items up to the total width of the
1462 * title. */
1463 for (i = 0; i < used_objects; i++)
1464 {
1465 if (i < left_objects)
1466 {
1467 continue;
1468 }
1469 if (i >= used_objects - right_objects)
1470 {
1471 /* Right aligned item. */
1472 *(item_order[i]) += d;
1473 }
1474 else
1475 {
1476 /* Neither left nor right aligned item.
1477 * Divide the overhead gap evenly
1478 * between the items. */
1479 *(item_order[i]) += d * (m + i) / n;
1480 }
1481 }
1482 total_width += d;
1483 if (!sidepic_is_left)
1484 {
1485 MR_SIDEPIC_X_OFFSET(msp->menu) += d;
1486 }
1487 } /* if (d > 0) */
1488 MR_WIDTH(msp->menu) =
1489 total_width + 2 * MST_BORDER_WIDTH(msp->menu);
1490 MR_ITEM_WIDTH(msp->menu) = total_width - sidepic_space;
1491 MR_ITEM_X_OFFSET(msp->menu) = MST_BORDER_WIDTH(msp->menu);
1492 if (sidepic_is_left)
1493 {
1494 MR_ITEM_X_OFFSET(msp->menu) += sidepic_space;
1495 }
1496 if (!relief_begin_placed)
1497 {
1498 MR_HILIGHT_X_OFFSET(msp->menu) = MR_ITEM_X_OFFSET(msp->menu);
1499 }
1500 if (relief_end_placed)
1501 {
1502 MR_HILIGHT_WIDTH(msp->menu) =
1503 MR_HILIGHT_WIDTH(msp->menu) -
1504 MR_HILIGHT_X_OFFSET(msp->menu);
1505 }
1506 else
1507 {
1508 MR_HILIGHT_WIDTH(msp->menu) =
1509 MR_ITEM_WIDTH(msp->menu) +
1510 MR_ITEM_X_OFFSET(msp->menu) -
1511 MR_HILIGHT_X_OFFSET(msp->menu);
1512 }
1513
1514 /* Now calculate the offsets for the individual labels. */
1515 for (mi = MR_FIRST_ITEM(msp->menu); mi != NULL; mi = MI_NEXT_ITEM(mi))
1516 {
1517 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
1518 {
1519 if (MI_LABEL(mi)[i] == NULL)
1520 {
1521 continue;
1522 }
1523 if (!MI_IS_TITLE(mi) || !MI_IS_TITLE_CENTERED(mi))
1524 {
1525 switch (lcr_column[i])
1526 {
1527 case 'l':
1528 MI_LABEL_OFFSET(mi)[i] =
1529 label_offset[i];
1530 break;
1531 case 'c':
1532 MI_LABEL_OFFSET(mi)[i] =
1533 label_offset[i] +
1534 (msp->max.i.label_width[i] -
1535 MI_LABEL_OFFSET(mi)[i]) / 2;
1536 break;
1537 case 'r':
1538 MI_LABEL_OFFSET(mi)[i] =
1539 label_offset[i] +
1540 msp->max.i.label_width[i] -
1541 MI_LABEL_OFFSET(mi)[i];
1542 break;
1543 }
1544 }
1545 else
1546 {
1547 /* This is a centered title item (indicated by
1548 * negative width). */
1549 MI_LABEL_OFFSET(mi)[i] =
1550 menudim_middle_x_offset(
1551 &MR_DIM(msp->menu)) -
1552 MI_LABEL_OFFSET(mi)[i] / 2;
1553 }
1554 } /* for */
1555 } /* for */
1556
1557 return;
1558 }
1559
calc_more_item_height(MenuSizingParameters * msp)1560 static int calc_more_item_height(MenuSizingParameters *msp)
1561 {
1562 int height;
1563
1564 height =
1565 MST_PSTDFONT(msp->menu)->height +
1566 MST_ITEM_GAP_ABOVE(msp->menu) +
1567 MST_ITEM_GAP_BELOW(msp->menu) +
1568 MST_RELIEF_THICKNESS(msp->menu);
1569
1570 return height;
1571 }
1572
calc_normal_item_height(MenuSizingParameters * msp,MenuItem * mi)1573 static int calc_normal_item_height(MenuSizingParameters *msp, MenuItem *mi)
1574 {
1575 int height;
1576
1577 height =
1578 MST_ITEM_GAP_ABOVE(msp->menu) +
1579 MST_ITEM_GAP_BELOW(msp->menu) +
1580 MST_RELIEF_THICKNESS(msp->menu);
1581 /* Normal text entry or an entry with a sub menu triangle */
1582 if (
1583 (MI_HAS_TEXT(mi) && msp->used_item_labels) ||
1584 (MI_IS_POPUP(mi) &&
1585 msp->flags.is_popup_indicator_used))
1586 {
1587 height += MST_PSTDFONT(msp->menu)->height;
1588 }
1589
1590 return height;
1591 }
1592
1593 /*
1594 *
1595 * Calculate the positions of the columns in the menu.
1596 * Called by make_menu().
1597 *
1598 */
size_menu_vertically(MenuSizingParameters * msp)1599 static Bool size_menu_vertically(MenuSizingParameters *msp)
1600 {
1601 MenuItem *mi;
1602 int y;
1603 int cItems;
1604 int relief_thickness = MST_RELIEF_THICKNESS(msp->menu);
1605 int i;
1606 Bool has_continuation_menu = False;
1607
1608 MR_ITEM_TEXT_Y_OFFSET(msp->menu) =
1609 MST_PSTDFONT(msp->menu)->ascent + relief_thickness +
1610 MST_ITEM_GAP_ABOVE(msp->menu);
1611
1612 /* mi_prev trails one behind mi, since we need to move that
1613 into a newly-made menu if we run out of space */
1614 y = MST_BORDER_WIDTH(msp->menu) + MST_VERTICAL_MARGIN_TOP(msp->menu);
1615 for (
1616 cItems = 0, mi = MR_FIRST_ITEM(msp->menu); mi != NULL;
1617 mi = MI_NEXT_ITEM(mi), cItems++)
1618 {
1619 Bool last_item_has_relief =
1620 (MI_PREV_ITEM(mi)) ?
1621 MI_IS_SELECTABLE(MI_PREV_ITEM(mi)) : False;
1622 Bool has_mini_icon = False;
1623 int separator_height;
1624 int menu_height;
1625
1626 separator_height = (last_item_has_relief) ?
1627 MENU_SEPARATOR_HEIGHT + relief_thickness :
1628 MENU_SEPARATOR_TOTAL_HEIGHT;
1629 MI_Y_OFFSET(mi) = y;
1630 if (MI_IS_TITLE(mi))
1631 {
1632 MI_HEIGHT(mi) = MST_PTITLEFONT(msp->menu)->height +
1633 MST_TITLE_GAP_ABOVE(msp->menu) +
1634 MST_TITLE_GAP_BELOW(msp->menu);
1635 }
1636 else if (MI_IS_SEPARATOR(mi))
1637 {
1638 /* Separator */
1639 MI_HEIGHT(mi) = separator_height;
1640 }
1641 else if (MI_IS_TEAR_OFF_BAR(mi))
1642 {
1643 /* Tear off bar */
1644 MI_HEIGHT(mi) = relief_thickness +
1645 MENU_TEAR_OFF_BAR_HEIGHT;
1646 }
1647 else
1648 {
1649 MI_HEIGHT(mi) = calc_normal_item_height(msp, mi);
1650 }
1651 if (MI_IS_TITLE(mi))
1652 {
1653 /* add space for the underlines */
1654 switch (MST_TITLE_UNDERLINES(msp->menu))
1655 {
1656 case 0:
1657 if (last_item_has_relief)
1658 MI_HEIGHT(mi) += relief_thickness;
1659 break;
1660 case 1:
1661 if (mi != MR_FIRST_ITEM(msp->menu))
1662 {
1663 /* Space to draw the separator plus a
1664 * gap above */
1665 MI_HEIGHT(mi) += separator_height;
1666 }
1667 if (MI_NEXT_ITEM(mi) != NULL)
1668 {
1669 /* Space to draw the separator */
1670 MI_HEIGHT(mi) += MENU_SEPARATOR_HEIGHT;
1671 }
1672 break;
1673 default:
1674 /* Space to draw n underlines. */
1675 MI_HEIGHT(mi) +=
1676 MENU_UNDERLINE_HEIGHT *
1677 MST_TITLE_UNDERLINES(msp->menu);
1678 if (last_item_has_relief)
1679 MI_HEIGHT(mi) += relief_thickness;
1680 break;
1681 }
1682 }
1683 for (i = 0; i < msp->used_mini_icons; i++)
1684 {
1685 if (MI_MINI_ICON(mi)[i])
1686 {
1687 has_mini_icon = True;
1688 }
1689 if (MI_MINI_ICON(mi)[i] &&
1690 MI_HEIGHT(mi) <
1691 MI_MINI_ICON(mi)[i]->height + relief_thickness)
1692 {
1693 MI_HEIGHT(mi) = MI_MINI_ICON(mi)[i]->height +
1694 relief_thickness;
1695 }
1696 }
1697 if (MI_PICTURE(mi))
1698 {
1699 if ((MI_HAS_TEXT(mi) && msp->used_item_labels) ||
1700 has_mini_icon)
1701 {
1702 MI_HEIGHT(mi) += MI_PICTURE(mi)->height;
1703 }
1704 else
1705 {
1706 MI_HEIGHT(mi) = MI_PICTURE(mi)->height +
1707 relief_thickness;
1708 }
1709 }
1710 y += MI_HEIGHT(mi);
1711 /* this item would have to be the last item, or else
1712 * we need to add a "More..." entry pointing to a new menu */
1713 menu_height =
1714 y + MST_BORDER_WIDTH(msp->menu) +
1715 MST_VERTICAL_MARGIN_BOTTOM(msp->menu) +
1716 ((MI_IS_SELECTABLE(mi)) ? relief_thickness : 0);
1717 if (menu_height > MR_SCREEN_HEIGHT(msp->menu))
1718 {
1719 /* Item does not fit on screen anymore. */
1720 char *t;
1721 char *tempname;
1722 const char *gt_name;
1723 char *name;
1724 MenuRoot *menuContinuation;
1725 int more_item_height;
1726
1727 more_item_height = calc_more_item_height(msp);
1728 /* Remove items form the menu until it fits (plus a
1729 * 'More' entry). */
1730 while (
1731 MI_PREV_ITEM(mi) != NULL &&
1732 menu_height > MR_SCREEN_HEIGHT(msp->menu))
1733 {
1734 /* Remove current item. */
1735 y -= MI_HEIGHT(mi);
1736 mi = MI_PREV_ITEM(mi);
1737 cItems--;
1738 menu_height =
1739 y + MST_BORDER_WIDTH(msp->menu) +
1740 more_item_height + relief_thickness +
1741 MST_VERTICAL_MARGIN_BOTTOM(msp->menu);
1742 }
1743 if (
1744 MI_PREV_ITEM(mi) == NULL ||
1745 menu_height > MR_SCREEN_HEIGHT(msp->menu))
1746 {
1747 fvwm_msg(ERR, "size_menu_vertically",
1748 "Menu entry does not fit on screen");
1749 /* leave a coredump */
1750 abort();
1751 exit(1);
1752 }
1753
1754 t = EscapeString(MR_NAME(msp->menu), "\"", '\\');
1755 tempname = (char *)safemalloc(
1756 (10 + strlen(t)) * sizeof(char));
1757 strcpy(tempname, "Popup \"");
1758 strcat(tempname, t);
1759 strcat(tempname, "$\"");
1760 free(t);
1761 /* NewMenuRoot inserts at the head of the list of menus
1762 but, we need it at the end. (Give it just the name,
1763 * which is 6 chars past the action since
1764 * strlen("Popup ")==6 ) */
1765 t = (char *)safemalloc(strlen(MR_NAME(msp->menu)) + 2);
1766 strcpy(t, MR_NAME(msp->menu));
1767 strcat(t, "$");
1768 menuContinuation = NewMenuRoot(t);
1769 free(t);
1770 MR_CONTINUATION_MENU(msp->menu) = menuContinuation;
1771
1772 /* Now move this item and the remaining items into the
1773 * new menu */
1774 MR_FIRST_ITEM(menuContinuation) = MI_NEXT_ITEM(mi);
1775 MR_LAST_ITEM(menuContinuation) =
1776 MR_LAST_ITEM(msp->menu);
1777 MR_ITEMS(menuContinuation) =
1778 MR_ITEMS(msp->menu) - cItems;
1779 MI_PREV_ITEM(MI_NEXT_ITEM(mi)) = NULL;
1780
1781 /* mi_prev is now the last item in the parent menu */
1782 MR_LAST_ITEM(msp->menu) = mi;
1783 MR_ITEMS(msp->menu) = cItems;
1784 MI_NEXT_ITEM(mi) = NULL;
1785
1786 /* use the same style for the submenu */
1787 MR_STYLE(menuContinuation) = MR_STYLE(msp->menu);
1788 MR_IS_LEFT_TRIANGLE(menuContinuation) =
1789 MR_IS_LEFT_TRIANGLE(msp->menu);
1790 /* migo: propagate missing_submenu_func */
1791 if (MR_MISSING_SUBMENU_FUNC(msp->menu))
1792 {
1793 MR_MISSING_SUBMENU_FUNC(menuContinuation) =
1794 safestrdup(MR_MISSING_SUBMENU_FUNC(
1795 msp->menu));
1796 }
1797 /* don't propagate sidepic, sidecolor, popup and
1798 * popdown actions */
1799 /* And add the entry pointing to the new menu */
1800 gt_name = gettext("More&...");
1801 name = safestrdup(gt_name);
1802 AddToMenu(
1803 msp->menu, name, tempname,
1804 False /* no pixmap scan */, False, True);
1805 free(name);
1806 free(tempname);
1807 has_continuation_menu = True;
1808 }
1809 } /* for */
1810 /* The menu may be empty here! */
1811 if (MR_LAST_ITEM(msp->menu) != NULL &&
1812 MI_IS_SELECTABLE(MR_LAST_ITEM(msp->menu)))
1813 {
1814 y += relief_thickness;
1815 }
1816 MR_HEIGHT(msp->menu) =
1817 y + MST_BORDER_WIDTH(msp->menu) +
1818 MST_VERTICAL_MARGIN_BOTTOM(msp->menu);
1819
1820 return has_continuation_menu;
1821 }
1822
1823 /*
1824 *
1825 * Merge menu continuations back into the original menu.
1826 * Called by make_menu().
1827 *
1828 */
merge_continuation_menus(MenuRoot * mr)1829 static void merge_continuation_menus(MenuRoot *mr)
1830 {
1831 /* merge menu continuations into one menu again - needed when changing
1832 * the font size of a long menu. */
1833 while (MR_CONTINUATION_MENU(mr) != NULL)
1834 {
1835 MenuRoot *cont = MR_CONTINUATION_MENU(mr);
1836
1837 /* link first item of continuation to item before 'more...' */
1838 MI_NEXT_ITEM(MI_PREV_ITEM(MR_LAST_ITEM(mr))) =
1839 MR_FIRST_ITEM(cont);
1840 MI_PREV_ITEM(MR_FIRST_ITEM(cont)) =
1841 MI_PREV_ITEM(MR_LAST_ITEM(mr));
1842 menuitem_free(MR_LAST_ITEM(mr));
1843 MR_LAST_ITEM(mr) = MR_LAST_ITEM(cont);
1844 MR_CONTINUATION_MENU(mr) = MR_CONTINUATION_MENU(cont);
1845 /* fake an empty menu so that DestroyMenu does not destroy the
1846 * items. */
1847 MR_FIRST_ITEM(cont) = NULL;
1848 DestroyMenu(cont, False, False);
1849 }
1850
1851 return;
1852 }
1853
1854 /*
1855 *
1856 * Creates the window for the menu.
1857 *
1858 */
make_menu_window(MenuRoot * mr,Bool is_tear_off)1859 static void make_menu_window(MenuRoot *mr, Bool is_tear_off)
1860 {
1861 unsigned long valuemask;
1862 XSetWindowAttributes attributes;
1863 int w;
1864 int h;
1865 unsigned int evmask;
1866
1867 w = MR_WIDTH(mr);
1868 if (w == 0)
1869 {
1870 w = 1;
1871 }
1872 h = MR_HEIGHT(mr);
1873 if (h == 0)
1874 {
1875 h = 1;
1876 }
1877
1878 attributes.background_pixel = (MST_HAS_MENU_CSET(mr)) ?
1879 Colorset[MST_CSET_MENU(mr)].bg : MST_MENU_COLORS(mr).back;
1880 if (MR_WINDOW(mr) != None)
1881 {
1882 /* just resize the existing window */
1883 XResizeWindow(dpy, MR_WINDOW(mr), w, h);
1884 /* and change the background color */
1885 valuemask = CWBackPixel | CWCursor;
1886 XChangeWindowAttributes(
1887 dpy, MR_WINDOW(mr), valuemask, &attributes);
1888 }
1889 else
1890 {
1891 /* create a new window */
1892 valuemask = CWBackPixel | CWEventMask | CWCursor | CWColormap
1893 | CWBorderPixel | CWSaveUnder;
1894 attributes.border_pixel = 0;
1895 attributes.colormap = Pcmap;
1896 evmask = XEVMASK_MENUW;
1897 attributes.event_mask = 0;
1898 attributes.cursor = Scr.FvwmCursors[CRS_MENU];
1899 attributes.save_under = True;
1900
1901 /* Create a display used to create the window. Can't use the
1902 * normal display because 'xkill' would kill the window
1903 * manager if used on a tear off menu. The display can't be
1904 * deleted right now because that would either destroy the new
1905 * window or leave it as an orphan if fvwm dies or is
1906 * restarted. */
1907 if (is_tear_off)
1908 {
1909 MR_CREATE_DPY(mr) = XOpenDisplay(display_name);
1910 if (MR_CREATE_DPY(mr) == NULL)
1911 {
1912 /* Doh. Use the standard display instead. */
1913 MR_CREATE_DPY(mr) = dpy;
1914 }
1915 }
1916 else
1917 {
1918 MR_CREATE_DPY(mr) = dpy;
1919 }
1920 MR_WINDOW(mr) = XCreateWindow(
1921 MR_CREATE_DPY(mr), Scr.Root, 0, 0, w, h,
1922 0, Pdepth, InputOutput, Pvisual, valuemask,
1923 &attributes);
1924 if (MR_CREATE_DPY(mr) != dpy)
1925 {
1926 /* We *must* synchronize the display here. Otherwise
1927 * the request will never be processed. */
1928 XSync(MR_CREATE_DPY(mr), 1);
1929 }
1930 if (MR_WINDOW(mr) != None)
1931 {
1932 /* select events for the window from the standard
1933 * display */
1934 XSelectInput(dpy, MR_WINDOW(mr), evmask);
1935 }
1936 XSaveContext(dpy, MR_WINDOW(mr), MenuContext,(caddr_t)mr);
1937 }
1938
1939 return;
1940 }
1941
1942
1943 /*
1944 *
1945 * Generates the window for a menu
1946 *
1947 */
make_menu(MenuRoot * mr,Bool is_tear_off)1948 static void make_menu(MenuRoot *mr, Bool is_tear_off)
1949 {
1950 MenuSizingParameters msp;
1951 Bool has_continuation_menu = False;
1952
1953 if (MR_MAPPED_COPIES(mr) > 0)
1954 {
1955 return;
1956 }
1957 merge_continuation_menus(mr);
1958 do
1959 {
1960 memset(&msp, 0, sizeof(MenuSizingParameters));
1961 msp.menu = mr;
1962 calculate_item_sizes(&msp);
1963 /* Call size_menu_horizontally first because it calculated
1964 * some values used by size_menu_vertically. */
1965 size_menu_horizontally(&msp);
1966 has_continuation_menu = size_menu_vertically(&msp);
1967 /* repeat this step if the menu was split */
1968 } while (has_continuation_menu);
1969 MR_USED_MINI_ICONS(mr) = msp.used_mini_icons;
1970
1971 MR_XANIMATION(mr) = 0;
1972 memset(&(MR_DYNAMIC_FLAGS(mr)), 0, sizeof(MR_DYNAMIC_FLAGS(mr)));
1973
1974 /* create a new window for the menu */
1975 make_menu_window(mr, is_tear_off);
1976 MR_IS_UPDATED(mr) = 0;
1977
1978 return;
1979 }
1980
1981 /* Make sure the menu is properly rebuilt when the style or the menu has
1982 * changed. */
update_menu(MenuRoot * mr,MenuParameters * pmp)1983 static void update_menu(MenuRoot *mr, MenuParameters *pmp)
1984 {
1985 int sw;
1986 int sh;
1987 Bool has_screen_size_changed = False;
1988 fscreen_scr_arg fscr;
1989
1990 if (MST_IS_UPDATED(mr))
1991 {
1992 /* The menu style has changed. */
1993 MenuRoot *menu;
1994
1995 for (menu = Menus.all; menu; menu = MR_NEXT_MENU(menu))
1996 {
1997 if (MR_STYLE(menu) == MR_STYLE(mr))
1998 {
1999 /* Mark all other menus with the same style as
2000 * changed. */
2001 MR_IS_UPDATED(menu) = 1;
2002 }
2003 }
2004 MST_IS_UPDATED(mr) = 0;
2005 }
2006 fscr.xypos.x = pmp->screen_origin_x;
2007 fscr.xypos.y = pmp->screen_origin_y;
2008 FScreenGetScrRect(&fscr, FSCREEN_XYPOS, &JunkX, &JunkY, &sw, &sh);
2009 if (sw != MR_SCREEN_WIDTH(mr) || sh != MR_SCREEN_HEIGHT(mr))
2010 {
2011 has_screen_size_changed = True;
2012 MR_SCREEN_WIDTH(mr) = sw;
2013 MR_SCREEN_HEIGHT(mr) = sh;
2014 }
2015 if (MR_IS_UPDATED(mr) || has_screen_size_changed)
2016 {
2017 /* The menu or the screen dimensions have changed. We have to
2018 * re-make it. */
2019 make_menu(mr, False);
2020 }
2021
2022 return;
2023 }
2024
2025 /*
2026 *
2027 * Procedure:
2028 * copy_menu_root - creates a new instance of an existing menu
2029 *
2030 * Returned Value:
2031 * (MenuRoot *)
2032 *
2033 * Inputs:
2034 * mr - the MenuRoot structure of the existing menu
2035 *
2036 */
copy_menu_root(MenuRoot * mr)2037 static MenuRoot *copy_menu_root(MenuRoot *mr)
2038 {
2039 MenuRoot *tmp;
2040
2041 if (!mr || MR_COPIES(mr) >= MAX_MENU_COPIES)
2042 {
2043 return NULL;
2044 }
2045 tmp = (MenuRoot *)safemalloc(sizeof(MenuRoot));
2046 tmp->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic));
2047 memset(tmp->d, 0, sizeof(MenuRootDynamic));
2048 tmp->s = mr->s;
2049
2050 MR_COPIES(mr)++;
2051 MR_ORIGINAL_MENU(tmp) = MR_ORIGINAL_MENU(mr);
2052 MR_CONTINUATION_MENU(tmp) = MR_CONTINUATION_MENU(mr);
2053 MR_NEXT_MENU(tmp) = MR_NEXT_MENU(mr);
2054 MR_NEXT_MENU(mr) = tmp;
2055 MR_WINDOW(tmp) = None;
2056 memset(&(MR_DYNAMIC_FLAGS(tmp)), 0, sizeof(MR_DYNAMIC_FLAGS(tmp)));
2057
2058 return tmp;
2059 }
2060
2061 /*
2062 *
2063 * Procedure:
2064 * clone_menu - duplicates an existing menu in newly allocated
2065 memory. The new menu is independent of the original.
2066 *
2067 * Returned Value:
2068 * (MenuRoot *)
2069 *
2070 * Inputs:
2071 * mr - the MenuRoot structure of the existing menu
2072 *
2073 */
clone_menu_root_static(MenuRoot * dest_mr,MenuRoot * src_mr)2074 static void clone_menu_root_static(
2075 MenuRoot *dest_mr, MenuRoot *src_mr)
2076 {
2077 dest_mr->s = (MenuRootStatic *)safemalloc(sizeof(MenuRootStatic));
2078 /* copy everything */
2079 memcpy(dest_mr->s, src_mr->s, sizeof(MenuRootStatic));
2080 /* special treatment for a few parts */
2081 if (MR_NAME(src_mr) != NULL)
2082 {
2083 MR_NAME(dest_mr) = safestrdup(MR_NAME(src_mr));
2084 }
2085 MR_COPIES(dest_mr) = 1;
2086 MR_MAPPED_COPIES(dest_mr) = 0;
2087 MR_POPUP_ACTION(dest_mr) = NULL;
2088 MR_POPDOWN_ACTION(dest_mr) = NULL;
2089 if (MR_MISSING_SUBMENU_FUNC(src_mr))
2090 {
2091 MR_MISSING_SUBMENU_FUNC(dest_mr) =
2092 safestrdup(MR_MISSING_SUBMENU_FUNC(src_mr));
2093 }
2094 if (MR_HAS_SIDECOLOR(src_mr))
2095 {
2096 MR_SIDECOLOR(dest_mr) =
2097 fvwmlib_clone_color(MR_SIDECOLOR(src_mr));
2098 }
2099 MR_SIDEPIC(dest_mr) = PCloneFvwmPicture(MR_SIDEPIC(src_mr));
2100 clone_menu_item_list(dest_mr, src_mr);
2101
2102 return;
2103 }
2104
clone_menu(MenuRoot * mr)2105 static MenuRoot *clone_menu(MenuRoot *mr)
2106 {
2107 MenuRoot *new_mr;
2108
2109 new_mr = (MenuRoot *)safemalloc(sizeof(MenuRoot));
2110 new_mr->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic));
2111 memset(new_mr->d, 0, sizeof(MenuRootDynamic));
2112 clone_menu_root_static(new_mr, mr);
2113
2114 return new_mr;
2115 }
2116
2117 /* ---------------------------- position hints ----------------------------- */
2118
float_to_int_with_tolerance(float f)2119 static int float_to_int_with_tolerance(float f)
2120 {
2121 int low;
2122
2123 if (f < 0)
2124 {
2125 low = (int)(f - ROUNDING_ERROR_TOLERANCE);
2126 }
2127 else
2128 {
2129 low = (int)(f + ROUNDING_ERROR_TOLERANCE);
2130 }
2131 if ((int)f != low)
2132 {
2133 return low;
2134 }
2135 else
2136 {
2137 return (int)(f);
2138 }
2139 }
2140
get_xy_from_position_hints(struct MenuPosHints * ph,int width,int height,int context_width,Bool do_reverse_x,int * ret_x,int * ret_y)2141 static void get_xy_from_position_hints(
2142 struct MenuPosHints *ph, int width, int height, int context_width,
2143 Bool do_reverse_x, int *ret_x, int *ret_y)
2144 {
2145 float x_add;
2146 float y_add;
2147
2148 *ret_x = ph->x;
2149 *ret_y = ph->y;
2150 if (ph->is_menu_relative)
2151 {
2152 if (do_reverse_x)
2153 {
2154 *ret_x -= ph->x_offset;
2155 x_add = width * (-1.0 - ph->x_factor) +
2156 ph->menu_width * (1.0 - ph->context_x_factor);
2157 }
2158 else
2159 {
2160 *ret_x += ph->x_offset;
2161 x_add = width * ph->x_factor +
2162 ph->menu_width * ph->context_x_factor;
2163 }
2164 y_add = height * ph->y_factor;
2165 }
2166 else
2167 {
2168 x_add = width * ph->x_factor;
2169 y_add = height * ph->y_factor;
2170 }
2171 *ret_x += float_to_int_with_tolerance(x_add);
2172 *ret_y += float_to_int_with_tolerance(y_add);
2173
2174 return;
2175 }
2176
2177 /*
2178 * Used by get_menu_options
2179 *
2180 * The vars are named for the x-direction, but this is used for both x and y
2181 */
get_one_menu_position_argument(char * action,int x,int w,int * pFinalX,int * x_offset,float * width_factor,float * context_width_factor,Bool * is_menu_relative)2182 static char *get_one_menu_position_argument(
2183 char *action, int x, int w, int *pFinalX, int *x_offset,
2184 float *width_factor, float *context_width_factor,
2185 Bool *is_menu_relative)
2186 {
2187 char *token, *orgtoken, *naction;
2188 char c;
2189 int val;
2190 int chars;
2191 float fval;
2192 float factor = (float)w/100;
2193 float x_add = 0;
2194
2195 naction = GetNextToken(action, &token);
2196 if (token == NULL)
2197 {
2198 return action;
2199 }
2200 orgtoken = token;
2201 *pFinalX = x;
2202 *x_offset = 0;
2203 *width_factor = 0.0;
2204 *context_width_factor = 0.0;
2205 if (sscanf(token,"o%d%n", &val, &chars) >= 1)
2206 {
2207 fval = val;
2208 token += chars;
2209 x_add += fval*factor;
2210 *width_factor -= fval / 100.0;
2211 *context_width_factor += fval / 100.0;
2212 }
2213 else if (token[0] == 'c')
2214 {
2215 token++;
2216 x_add += ((float)w) / 2.0;
2217 *width_factor -= 0.5;
2218 *context_width_factor += 0.5;
2219 }
2220 while (*token != 0)
2221 {
2222 if (sscanf(token,"%d%n", &val, &chars) < 1)
2223 {
2224 naction = action;
2225 break;
2226 }
2227 fval = (float)val;
2228 token += chars;
2229 if (sscanf(token,"%c", &c) == 1)
2230 {
2231 switch (c)
2232 {
2233 case 'm':
2234 token++;
2235 *width_factor += fval / 100.0;
2236 *is_menu_relative = True;
2237 break;
2238 case 'p':
2239 token++;
2240 x_add += val;
2241 *x_offset += val;
2242 break;
2243 default:
2244 x_add += fval * factor;
2245 *context_width_factor += fval / 100.0;
2246 break;
2247 }
2248 }
2249 else
2250 {
2251 x_add += fval * factor;
2252 *context_width_factor += fval / 100.0;
2253 }
2254 }
2255 *pFinalX += float_to_int_with_tolerance(x_add);
2256 free(orgtoken);
2257
2258 return naction;
2259 }
2260
2261 /* Returns the menu options for the menu that a given menu item pops up */
get_popup_options(MenuParameters * pmp,MenuItem * mi,MenuOptions * pops)2262 static void get_popup_options(
2263 MenuParameters *pmp, MenuItem *mi, MenuOptions *pops)
2264 {
2265 if (!mi)
2266 {
2267 return;
2268 }
2269 pops->flags.has_poshints = 0;
2270 pops->pos_hints.has_screen_origin = True;
2271 pops->pos_hints.screen_origin_x = pmp->screen_origin_x;
2272 pops->pos_hints.screen_origin_y = pmp->screen_origin_y;
2273 /* just look past "Popup <name>" in the action */
2274 get_menu_options(
2275 SkipNTokens(MI_ACTION(mi), 2), MR_WINDOW(pmp->menu), NULL,
2276 NULL, pmp->menu, mi, pops);
2277
2278 return;
2279 }
2280
2281 /* ---------------------------- menu painting functions --------------------- */
2282
clear_expose_menu_area(Window win,XEvent * e)2283 static void clear_expose_menu_area(Window win, XEvent *e)
2284 {
2285 if (e == NULL)
2286 {
2287 XClearWindow(dpy, win);
2288 }
2289 else
2290 {
2291 XClearArea(
2292 dpy, win, e->xexpose.x, e->xexpose.y, e->xexpose.width,
2293 e->xexpose.height, False);
2294 }
2295
2296 return;
2297 }
2298
2299 /*
2300 *
2301 * Draws a picture on the left side of the menu
2302 * What about a SidePic Colorset ? (olicha 2002-08-21)
2303 *
2304 */
paint_side_pic(MenuRoot * mr,XEvent * pevent)2305 static void paint_side_pic(MenuRoot *mr, XEvent *pevent)
2306 {
2307 GC gc;
2308 FvwmPicture *sidePic;
2309 int ys;
2310 int yt;
2311 int h;
2312 int bw = MST_BORDER_WIDTH(mr);
2313
2314 if (MR_SIDEPIC(mr))
2315 {
2316 sidePic = MR_SIDEPIC(mr);
2317 }
2318 else if (MST_SIDEPIC(mr))
2319 {
2320 sidePic = MST_SIDEPIC(mr);
2321 }
2322 else
2323 {
2324 return;
2325 }
2326 if (Pdepth < 2)
2327 {
2328 /* ? */
2329 gc = SHADOW_GC(MST_MENU_INACTIVE_GCS(mr));
2330 }
2331 else
2332 {
2333 gc = FORE_GC(MST_MENU_INACTIVE_GCS(mr));
2334 }
2335 if (sidePic->height > MR_HEIGHT(mr) - 2 * bw)
2336 {
2337 h = MR_HEIGHT(mr) - 2 * bw;
2338 ys = sidePic->height - h;
2339 yt = bw;
2340 }
2341 else
2342 {
2343 h = sidePic->height;
2344 ys = 0;
2345 yt = MR_HEIGHT(mr) - bw - sidePic->height;
2346 }
2347 if (pevent != NULL && pevent->type == Expose)
2348 {
2349 if (
2350 pevent->xexpose.x + pevent->xexpose.width <
2351 MR_SIDEPIC_X_OFFSET(mr) ||
2352 pevent->xexpose.x >=
2353 MR_SIDEPIC_X_OFFSET(mr) + sidePic->width)
2354 {
2355 /* out of x-range for side bar */
2356 return;
2357 }
2358 if (
2359 pevent->xexpose.y + pevent->xexpose.height < bw ||
2360 pevent->xexpose.y >= bw + MR_HEIGHT(mr))
2361 {
2362 /* in the border */
2363 return;
2364 }
2365 if (
2366 !(MR_HAS_SIDECOLOR(mr) || MST_HAS_SIDE_COLOR(mr)) &&
2367 pevent->xexpose.y + pevent->xexpose.height < yt)
2368 {
2369 /* outside picture and no background */
2370 return;
2371 }
2372
2373 }
2374 if (MR_HAS_SIDECOLOR(mr))
2375 {
2376 Globalgcv.foreground = MR_SIDECOLOR(mr);
2377 }
2378 else if (MST_HAS_SIDE_COLOR(mr))
2379 {
2380 Globalgcv.foreground = MST_SIDE_COLOR(mr);
2381 }
2382 if (MR_HAS_SIDECOLOR(mr) || MST_HAS_SIDE_COLOR(mr))
2383 {
2384 Globalgcm = GCForeground;
2385 XChangeGC(dpy, Scr.ScratchGC1, Globalgcm, &Globalgcv);
2386 XFillRectangle(
2387 dpy, MR_WINDOW(mr), Scr.ScratchGC1,
2388 MR_SIDEPIC_X_OFFSET(mr), bw, sidePic->width,
2389 MR_HEIGHT(mr) - 2 * bw);
2390 }
2391 else if (sidePic->alpha != None)
2392 {
2393 XClearArea(
2394 dpy, MR_WINDOW(mr),
2395 MR_SIDEPIC_X_OFFSET(mr), yt, sidePic->width, h, False);
2396 }
2397 PGraphicsRenderPicture(
2398 dpy, MR_WINDOW(mr), sidePic, 0, MR_WINDOW(mr),
2399 gc, Scr.MonoGC, Scr.AlphaGC,
2400 0, ys, sidePic->width, h,
2401 MR_SIDEPIC_X_OFFSET(mr), yt, sidePic->width, h, False);
2402
2403 return;
2404 }
2405
paint_menu_gradient_background(MenuRoot * mr,XEvent * pevent)2406 static Bool paint_menu_gradient_background(
2407 MenuRoot *mr, XEvent *pevent)
2408 {
2409 MenuStyle *ms = MR_STYLE(mr);
2410 int bw = MST_BORDER_WIDTH(mr);
2411 XRectangle bounds;
2412 Pixmap pmap;
2413 GC pmapgc;
2414 XGCValues gcv;
2415 unsigned long gcm = GCLineWidth;
2416 int switcher = -1;
2417 Bool do_clear = False;
2418
2419 gcv.line_width = 1;
2420 bounds.x = bw;
2421 bounds.y = bw;
2422 bounds.width = MR_WIDTH(mr) - bw;
2423 bounds.height = MR_HEIGHT(mr) - bw;
2424 /* H, V, D and B gradients are optimized and have
2425 * their own code here. (if no dither) */
2426 if (!ST_FACE(ms).u.grad.do_dither)
2427 {
2428 switcher = ST_FACE(ms).gradient_type;
2429 }
2430 switch (switcher)
2431 {
2432 case H_GRADIENT:
2433 if (MR_IS_BACKGROUND_SET(mr) == False)
2434 {
2435 register int i;
2436 register int dw;
2437
2438 pmap = XCreatePixmap(
2439 dpy, MR_WINDOW(mr), MR_WIDTH(mr),
2440 DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS,
2441 Pdepth);
2442 pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv);
2443 dw = (float)
2444 (bounds.width / ST_FACE(ms).u.grad.npixels)
2445 + 1;
2446 for (i = 0; i < ST_FACE(ms).u.grad.npixels; i++)
2447 {
2448 int x;
2449
2450 x = i * bounds.width /
2451 ST_FACE(ms).u.grad.npixels;
2452 XSetForeground(
2453 dpy, pmapgc,
2454 ST_FACE(ms).u.grad.xcs[i].pixel);
2455 XFillRectangle(
2456 dpy, pmap, pmapgc, x, 0, dw,
2457 DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS);
2458 }
2459 XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap);
2460 XFreeGC(dpy,pmapgc);
2461 XFreePixmap(dpy,pmap);
2462 MR_IS_BACKGROUND_SET(mr) = True;
2463 }
2464 do_clear = True;
2465 break;
2466 case V_GRADIENT:
2467 if (MR_IS_BACKGROUND_SET(mr) == False)
2468 {
2469 register int i;
2470 register int dh;
2471 static int best_tile_width = 0;
2472 int junk;
2473
2474 if (best_tile_width == 0)
2475 {
2476 int tw = DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS;
2477
2478 if (!XQueryBestTile(
2479 dpy, Scr.screen, tw, tw,
2480 (unsigned int*)&best_tile_width,
2481 (unsigned int*)&junk))
2482 {
2483 /* call failed, use default and risk a
2484 * screwed up tile */
2485 best_tile_width = tw;
2486 }
2487 }
2488 pmap = XCreatePixmap(
2489 dpy, MR_WINDOW(mr), best_tile_width,
2490 MR_HEIGHT(mr), Pdepth);
2491 pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv);
2492 dh = (float) (bounds.height /
2493 ST_FACE(ms).u.grad.npixels) + 1;
2494 for (i = 0; i < ST_FACE(ms).u.grad.npixels; i++)
2495 {
2496 int y;
2497
2498 y = i * bounds.height /
2499 ST_FACE(ms).u.grad.npixels;
2500 XSetForeground(
2501 dpy, pmapgc,
2502 ST_FACE(ms).u.grad.xcs[i].pixel);
2503 XFillRectangle(
2504 dpy, pmap, pmapgc, 0, y,
2505 best_tile_width, dh);
2506 }
2507 XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap);
2508 XFreeGC(dpy,pmapgc);
2509 XFreePixmap(dpy,pmap);
2510 MR_IS_BACKGROUND_SET(mr) = True;
2511 }
2512 do_clear = True;
2513 break;
2514 case D_GRADIENT:
2515 case B_GRADIENT:
2516 {
2517 register int i = 0, numLines;
2518 int cindex = -1;
2519 XRectangle r;
2520
2521 if (pevent)
2522 {
2523 r.x = pevent->xexpose.x;
2524 r.y = pevent->xexpose.y;
2525 r.width = pevent->xexpose.width;
2526 r.height = pevent->xexpose.height;
2527 }
2528 else
2529 {
2530 r.x = bw;
2531 r.y = bw;
2532 r.width = MR_WIDTH(mr) - 2 * bw;
2533 r.height = MR_HEIGHT(mr) - 2 * bw;
2534 }
2535 XSetClipRectangles(
2536 dpy, Scr.TransMaskGC, 0, 0, &r, 1, Unsorted);
2537 numLines = MR_WIDTH(mr) + MR_HEIGHT(mr) - 2 * bw;
2538 for (i = 0; i < numLines; i++)
2539 {
2540 if ((int)(i * ST_FACE(ms).u.grad.npixels / numLines) >
2541 cindex)
2542 {
2543 /* pick the next colour (skip if necc.) */
2544 cindex = i * ST_FACE(ms).u.grad.npixels /
2545 numLines;
2546 XSetForeground(
2547 dpy, Scr.TransMaskGC,
2548 ST_FACE(ms).u.grad.xcs[cindex].pixel);
2549 }
2550 if (ST_FACE(ms).gradient_type == D_GRADIENT)
2551 {
2552 XDrawLine(dpy, MR_WINDOW(mr),
2553 Scr.TransMaskGC, 0, i, i, 0);
2554 }
2555 else /* B_GRADIENT */
2556 {
2557 XDrawLine(dpy, MR_WINDOW(mr), Scr.TransMaskGC,
2558 0, MR_HEIGHT(mr) - 1 - i, i,
2559 MR_HEIGHT(mr) - 1);
2560 }
2561 }
2562 }
2563 XSetClipMask(dpy, Scr.TransMaskGC, None);
2564 break;
2565 default:
2566 if (MR_IS_BACKGROUND_SET(mr) == False)
2567 {
2568 int g_width;
2569 int g_height;
2570
2571 /* let library take care of all other gradients */
2572 pmap = XCreatePixmap(
2573 dpy, MR_WINDOW(mr), MR_WIDTH(mr),
2574 MR_HEIGHT(mr), Pdepth);
2575 pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv);
2576
2577 /* find out the size the pixmap should be */
2578 CalculateGradientDimensions(
2579 dpy, MR_WINDOW(mr), ST_FACE(ms).u.grad.npixels,
2580 ST_FACE(ms).gradient_type,
2581 ST_FACE(ms).u.grad.do_dither, &g_width,
2582 &g_height);
2583 /* draw the gradient directly into the window */
2584 CreateGradientPixmap(
2585 dpy, MR_WINDOW(mr), pmapgc,
2586 ST_FACE(ms).gradient_type, g_width, g_height,
2587 ST_FACE(ms).u.grad.npixels,
2588 ST_FACE(ms).u.grad.xcs,
2589 ST_FACE(ms).u.grad.do_dither,
2590 &(MR_STORED_PIXELS(mr).d_pixels),
2591 &(MR_STORED_PIXELS(mr).d_npixels),
2592 pmap, bw, bw,
2593 MR_WIDTH(mr) - bw, MR_HEIGHT(mr) - bw, NULL);
2594 XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap);
2595 XFreeGC(dpy, pmapgc);
2596 XFreePixmap(dpy, pmap);
2597 MR_IS_BACKGROUND_SET(mr) = True;
2598 }
2599 do_clear = True;
2600 break;
2601 }
2602 return do_clear;
2603 }
2604
paint_menu_pixmap_background(MenuRoot * mr,XEvent * pevent)2605 static Bool paint_menu_pixmap_background(
2606 MenuRoot *mr, XEvent *pevent)
2607 {
2608 MenuStyle *ms = MR_STYLE(mr);
2609 int width, height, x, y;
2610 int bw = MST_BORDER_WIDTH(mr);
2611 FvwmPicture *p;
2612
2613 p = ST_FACE(ms).u.p;
2614 width = MR_WIDTH(mr) - 2 * bw;
2615 height = MR_HEIGHT(mr) - 2 * bw;
2616 y = (int)(height - p->height) / 2;
2617 x = (int)(width - p->width) / 2;
2618 if (x < bw)
2619 {
2620 x = bw;
2621 }
2622 if (y < bw)
2623 {
2624 y = bw;
2625 }
2626 if (width > p->width)
2627 {
2628 width = p->width;
2629 }
2630 if (height > p->height)
2631 {
2632 height = p->height;
2633 }
2634 if (width > MR_WIDTH(mr) - x - bw)
2635 {
2636 width = MR_WIDTH(mr) - x - bw;
2637 }
2638 if (height > MR_HEIGHT(mr) - y - bw)
2639 {
2640 height = MR_HEIGHT(mr) - y - bw;
2641 }
2642 XSetClipMask(dpy, Scr.TransMaskGC, p->mask);
2643 XSetClipOrigin(dpy, Scr.TransMaskGC, x, y);
2644 XCopyArea(
2645 dpy, p->picture, MR_WINDOW(mr), Scr.TransMaskGC,
2646 bw, bw, width, height, x, y);
2647
2648 return False;
2649 }
2650
2651 /*
2652 *
2653 * Procedure:
2654 * get_menu_paint_item_parameters - prepares the parameters to be
2655 * passed to menuitem_paint().
2656 *
2657 * mr - the menu instance that holds the menu item
2658 * mi - the menu item to redraw
2659 * fw - the FvwmWindow structure to check against allowed functions
2660 *
2661 */
get_menu_paint_item_parameters(MenuPaintItemParameters * mpip,MenuRoot * mr,MenuItem * mi,FvwmWindow * fw,XEvent * pevent,Bool do_redraw_menu_border)2662 static void get_menu_paint_item_parameters(
2663 MenuPaintItemParameters *mpip, MenuRoot *mr, MenuItem *mi,
2664 FvwmWindow *fw, XEvent *pevent, Bool do_redraw_menu_border)
2665 {
2666 mpip->ms = MR_STYLE(mr);
2667 mpip->w = MR_WINDOW(mr);
2668 mpip->selected_item = MR_SELECTED_ITEM(mr);
2669 mpip->dim = &MR_DIM(mr);
2670 mpip->fw = fw;
2671 mpip->used_mini_icons = MR_USED_MINI_ICONS(mr);
2672 mpip->cb_mr = mr;
2673 mpip->cb_reset_bg = paint_menu_gradient_background;
2674 mpip->flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
2675 mpip->flags.is_left_triangle = MR_IS_LEFT_TRIANGLE(mr);
2676 mpip->ev = pevent;
2677
2678 return;
2679 }
2680
2681 /*
2682 *
2683 * Procedure:
2684 * paint_menu - draws the entire menu
2685 *
2686 */
paint_menu(MenuRoot * mr,XEvent * pevent,FvwmWindow * fw)2687 static void paint_menu(
2688 MenuRoot *mr, XEvent *pevent, FvwmWindow *fw)
2689 {
2690 MenuItem *mi;
2691 MenuStyle *ms = MR_STYLE(mr);
2692 int bw = MST_BORDER_WIDTH(mr);
2693 int relief_thickness = ST_RELIEF_THICKNESS(MR_STYLE(mr));
2694
2695 if (fw && !check_if_fvwm_window_exists(fw))
2696 {
2697 fw = NULL;
2698 }
2699 if (MR_IS_PAINTED(mr) && pevent &&
2700 (pevent->xexpose.x >= MR_WIDTH(mr) - bw ||
2701 pevent->xexpose.x + pevent->xexpose.width <= bw ||
2702 pevent->xexpose.y >= MR_HEIGHT(mr) - bw ||
2703 pevent->xexpose.y + pevent->xexpose.height <= bw))
2704 {
2705 /* Only the border was obscured. Redraw it centrally instead of
2706 * redrawing several menu items. */
2707 RelieveRectangle(
2708 dpy, MR_WINDOW(mr), 0, 0, MR_WIDTH(mr) - 1,
2709 MR_HEIGHT(mr) - 1, (Pdepth < 2) ?
2710 SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)) :
2711 HILIGHT_GC(MST_MENU_INACTIVE_GCS(mr)),
2712 SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)), bw);
2713 {
2714 return;
2715 }
2716 }
2717 MR_IS_PAINTED(mr) = 1;
2718 /* paint the menu background */
2719 if (ms && ST_HAS_MENU_CSET(ms))
2720 {
2721 if (MR_IS_BACKGROUND_SET(mr) == False)
2722 {
2723 SetWindowBackground(
2724 dpy, MR_WINDOW(mr), MR_WIDTH(mr),
2725 MR_HEIGHT(mr), &Colorset[ST_CSET_MENU(ms)],
2726 Pdepth, FORE_GC(ST_MENU_INACTIVE_GCS(ms)),
2727 True);
2728 MR_IS_BACKGROUND_SET(mr) = True;
2729 }
2730 }
2731 else if (ms)
2732 {
2733 int type;
2734 Bool do_clear = False;
2735
2736 type = ST_FACE(ms).type;
2737 switch(type)
2738 {
2739 case SolidMenu:
2740 XSetWindowBackground(
2741 dpy, MR_WINDOW(mr), MST_FACE(mr).u.back);
2742 do_clear = True;
2743 break;
2744 case GradientMenu:
2745 do_clear = paint_menu_gradient_background(mr, pevent);
2746 break;
2747 case PixmapMenu:
2748 do_clear = paint_menu_pixmap_background(mr, pevent);
2749 break;
2750 case TiledPixmapMenu:
2751 XSetWindowBackgroundPixmap(
2752 dpy, MR_WINDOW(mr), ST_FACE(ms).u.p->picture);
2753 do_clear = True;
2754 break;
2755 } /* switch(type) */
2756 if (do_clear == True)
2757 {
2758 clear_expose_menu_area(MR_WINDOW(mr), pevent);
2759 }
2760 } /* if (ms) */
2761 /* draw the relief */
2762 RelieveRectangle(dpy, MR_WINDOW(mr), 0, 0, MR_WIDTH(mr) - 1,
2763 MR_HEIGHT(mr) - 1, (Pdepth < 2) ?
2764 SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)) :
2765 HILIGHT_GC(MST_MENU_INACTIVE_GCS(mr)),
2766 SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)), bw);
2767 /* paint the menu items */
2768 for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
2769 {
2770 int do_draw = 0;
2771
2772 /* be smart about handling the expose, redraw only the entries
2773 * that we need to */
2774 if (pevent == NULL)
2775 {
2776 do_draw = True;
2777 }
2778 else
2779 {
2780 register int b_offset;
2781
2782 b_offset = MI_Y_OFFSET(mi) + MI_HEIGHT(mi);
2783 if (MR_SELECTED_ITEM(mr) == mi)
2784 {
2785 b_offset += relief_thickness;
2786 }
2787 if (pevent->xexpose.y < b_offset &&
2788 (pevent->xexpose.y + pevent->xexpose.height) >
2789 MI_Y_OFFSET(mi))
2790 {
2791 do_draw = True;
2792 }
2793 }
2794 if (do_draw)
2795 {
2796 MenuPaintItemParameters mpip;
2797
2798 get_menu_paint_item_parameters(
2799 &mpip, mr, NULL, fw, pevent, True);
2800 mpip.flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
2801 menuitem_paint(mi, &mpip);
2802 }
2803 }
2804 paint_side_pic(mr, pevent);
2805 XFlush(dpy);
2806
2807 return;
2808 }
2809
2810 /* Set the selected-ness state of the menuitem passed in */
select_menu_item(MenuRoot * mr,MenuItem * mi,Bool select,FvwmWindow * fw)2811 static void select_menu_item(
2812 MenuRoot *mr, MenuItem *mi, Bool select, FvwmWindow *fw)
2813 {
2814 if (select == True && MR_SELECTED_ITEM(mr) != NULL &&
2815 MR_SELECTED_ITEM(mr) != mi)
2816 {
2817 select_menu_item(mr, MR_SELECTED_ITEM(mr), False, fw);
2818 }
2819 else if (select == False && MR_SELECTED_ITEM(mr) == NULL)
2820 {
2821 return;
2822 }
2823 else if (select == True && MR_SELECTED_ITEM(mr) == mi)
2824 {
2825 return;
2826 }
2827 if (!MI_IS_SELECTABLE(mi))
2828 {
2829 return;
2830 }
2831
2832 if (select == False)
2833 {
2834 MI_WAS_DESELECTED(mi) = True;
2835 }
2836
2837 if (!MST_HAS_MENU_CSET(mr))
2838 {
2839 switch (MST_FACE(mr).type)
2840 {
2841 case GradientMenu:
2842 if (select == True)
2843 {
2844 int iy;
2845 int ih;
2846 int mw;
2847 XEvent e;
2848
2849 if (!MR_IS_PAINTED(mr))
2850 {
2851 FCheckWeedTypedWindowEvents(
2852 dpy, MR_WINDOW(mr), Expose,
2853 NULL);
2854 paint_menu(mr, NULL, fw);
2855 }
2856 iy = MI_Y_OFFSET(mi);
2857 ih = MI_HEIGHT(mi) +
2858 (MI_IS_SELECTABLE(mi) ?
2859 MST_RELIEF_THICKNESS(mr) : 0);
2860 if (iy < 0)
2861 {
2862 ih += iy;
2863 iy = 0;
2864 }
2865 mw = MR_WIDTH(mr) - 2 * MST_BORDER_WIDTH(mr);
2866 if (iy + ih > MR_HEIGHT(mr))
2867 ih = MR_HEIGHT(mr) - iy;
2868 /* grab image */
2869 MR_STORED_ITEM(mr).stored =
2870 XCreatePixmap(
2871 dpy, Scr.NoFocusWin, mw, ih,
2872 Pdepth);
2873 XSetGraphicsExposures(
2874 dpy,
2875 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2876 True);
2877 XSync(dpy, 0);
2878 while (FCheckTypedEvent(dpy, NoExpose, &e))
2879 {
2880 /* nothing to do here */
2881 }
2882 XCopyArea(
2883 dpy, MR_WINDOW(mr),
2884 MR_STORED_ITEM(mr).stored,
2885 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2886 MST_BORDER_WIDTH(mr), iy, mw, ih, 0,
2887 0);
2888 XSync(dpy, 0);
2889 if (FCheckTypedEvent(dpy, NoExpose, &e))
2890 {
2891 MR_STORED_ITEM(mr).y = iy;
2892 MR_STORED_ITEM(mr).width = mw;
2893 MR_STORED_ITEM(mr).height = ih;
2894 }
2895 else
2896 {
2897 XFreePixmap(
2898 dpy,
2899 MR_STORED_ITEM(mr).stored);
2900 MR_STORED_ITEM(mr).stored = None;
2901 MR_STORED_ITEM(mr).width = 0;
2902 MR_STORED_ITEM(mr).height = 0;
2903 MR_STORED_ITEM(mr).y = 0;
2904 }
2905 XSetGraphicsExposures(
2906 dpy,
2907 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2908 False);
2909 }
2910 else if (select == False &&
2911 MR_STORED_ITEM(mr).width != 0)
2912 {
2913 /* ungrab image */
2914 XCopyArea(
2915 dpy, MR_STORED_ITEM(mr).stored,
2916 MR_WINDOW(mr),
2917 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
2918 0, 0, MR_STORED_ITEM(mr).width,
2919 MR_STORED_ITEM(mr).height,
2920 MST_BORDER_WIDTH(mr),
2921 MR_STORED_ITEM(mr).y);
2922
2923 if (MR_STORED_ITEM(mr).stored != None)
2924 {
2925 XFreePixmap(
2926 dpy, MR_STORED_ITEM(mr).stored);
2927 }
2928 MR_STORED_ITEM(mr).stored = None;
2929 MR_STORED_ITEM(mr).width = 0;
2930 MR_STORED_ITEM(mr).height = 0;
2931 MR_STORED_ITEM(mr).y = 0;
2932 }
2933 else if (select == False)
2934 {
2935 XEvent e;
2936 FvwmPicture *sidePic = NULL;
2937
2938 e.type = Expose;
2939 e.xexpose.x = MST_BORDER_WIDTH(mr);
2940 e.xexpose.y = MI_Y_OFFSET(mi);
2941 e.xexpose.width = MR_WIDTH(mr) - 2 *
2942 MST_BORDER_WIDTH(mr);
2943 e.xexpose.height = MI_HEIGHT(mi) +
2944 (MI_IS_SELECTABLE(mi) ?
2945 MST_RELIEF_THICKNESS(mr) : 0);
2946 if (MR_SIDEPIC(mr))
2947 {
2948 sidePic = MR_SIDEPIC(mr);
2949 }
2950 else if (MST_SIDEPIC(mr))
2951 {
2952 sidePic = MST_SIDEPIC(mr);
2953 }
2954 if (sidePic)
2955 {
2956 e.xexpose.width -= sidePic->width;
2957 if (MR_SIDEPIC_X_OFFSET(mr) ==
2958 MST_BORDER_WIDTH(mr))
2959 {
2960 e.xexpose.x += sidePic->width;
2961 }
2962 }
2963 MR_SELECTED_ITEM(mr) = (select) ? mi : NULL;
2964 paint_menu(mr, &e, fw);
2965 }
2966 break;
2967 default:
2968 if (MR_STORED_ITEM(mr).width != 0)
2969 {
2970 if (MR_STORED_ITEM(mr).stored != None)
2971 {
2972 XFreePixmap(
2973 dpy,
2974 MR_STORED_ITEM(mr).stored);
2975 }
2976 MR_STORED_ITEM(mr).stored = None;
2977 MR_STORED_ITEM(mr).width = 0;
2978 MR_STORED_ITEM(mr).height = 0;
2979 MR_STORED_ITEM(mr).y = 0;
2980 }
2981 break;
2982 } /* switch */
2983 } /* if */
2984
2985 if (fw && !check_if_fvwm_window_exists(fw))
2986 {
2987 fw = NULL;
2988 }
2989 MR_SELECTED_ITEM(mr) = (select) ? mi : NULL;
2990 if (MR_IS_PAINTED(mr))
2991 {
2992 MenuPaintItemParameters mpip;
2993
2994 get_menu_paint_item_parameters(&mpip, mr, mi, fw, NULL, False);
2995 menuitem_paint(mi, &mpip);
2996 }
2997
2998 return;
2999 }
3000
3001 /* ---------------------------- popping menu up or down -------------------- */
3002
get_left_popup_x_position(MenuRoot * mr,MenuRoot * submenu,int x)3003 static int get_left_popup_x_position(MenuRoot *mr, MenuRoot *submenu, int x)
3004 {
3005 if (MST_USE_LEFT_SUBMENUS(mr))
3006 {
3007 return (x - MST_POPUP_OFFSET_ADD(mr) - MR_WIDTH(submenu) +
3008 MR_WIDTH(mr) *
3009 (100 - MST_POPUP_OFFSET_PERCENT(mr)) / 100);
3010 }
3011 else
3012 {
3013 return (x - MR_WIDTH(submenu) + MST_BORDER_WIDTH(mr));
3014 }
3015 }
3016
get_right_popup_x_position(MenuRoot * mr,MenuRoot * submenu,int x)3017 static int get_right_popup_x_position(MenuRoot *mr, MenuRoot *submenu, int x)
3018 {
3019 if (MST_USE_LEFT_SUBMENUS(mr))
3020 {
3021 return (x + MR_WIDTH(mr) - MST_BORDER_WIDTH(mr));
3022 }
3023 else
3024 {
3025 return (x + MR_WIDTH(mr) * MST_POPUP_OFFSET_PERCENT(mr) / 100 +
3026 MST_POPUP_OFFSET_ADD(mr));
3027 }
3028 }
3029
get_prefered_popup_position(MenuRoot * mr,MenuRoot * submenu,int * px,int * py,Bool * pprefer_left_submenus)3030 static void get_prefered_popup_position(
3031 MenuRoot *mr, MenuRoot *submenu, int *px, int *py,
3032 Bool *pprefer_left_submenus)
3033 {
3034 int menu_x, menu_y;
3035 MenuItem *mi = NULL;
3036
3037 if (!menu_get_geometry(
3038 mr, &JunkRoot, &menu_x, &menu_y, &JunkWidth, &JunkHeight,
3039 &JunkBW, &JunkDepth))
3040 {
3041 *px = 0;
3042 *py = 0;
3043 *pprefer_left_submenus = False;
3044 fvwm_msg(ERR, "get_prefered_popup_position",
3045 "can't get geometry of menu %s", MR_NAME(mr));
3046 return;
3047 }
3048 /* set the direction flag */
3049 *pprefer_left_submenus =
3050 (MR_HAS_POPPED_UP_LEFT(mr) ||
3051 (MST_USE_LEFT_SUBMENUS(mr) &&
3052 !MR_HAS_POPPED_UP_RIGHT(mr)));
3053 /* get the x position */
3054 if (*pprefer_left_submenus)
3055 {
3056 *px = get_left_popup_x_position(mr, submenu, menu_x);
3057 }
3058 else
3059 {
3060 *px = get_right_popup_x_position(mr, submenu, menu_x);
3061 }
3062 /* get the y position */
3063 if (MR_SELECTED_ITEM(mr))
3064 {
3065 mi = MR_SELECTED_ITEM(mr);
3066 }
3067 if (mi)
3068 {
3069 *py = menu_y + MI_Y_OFFSET(mi) -
3070 MST_BORDER_WIDTH(mr) + MST_RELIEF_THICKNESS(mr);
3071 }
3072 else
3073 {
3074 *py = menu_y;
3075 }
3076 }
3077
do_menus_overlap(MenuRoot * mr,int x,int y,int width,int height,int h_tolerance,int v_tolerance,int s_tolerance,Bool allow_popup_offset_tolerance)3078 static int do_menus_overlap(
3079 MenuRoot *mr, int x, int y, int width, int height, int h_tolerance,
3080 int v_tolerance, int s_tolerance, Bool allow_popup_offset_tolerance)
3081 {
3082 int prior_x, prior_y, x_overlap;
3083 int prior_width, prior_height;
3084
3085 if (mr == NULL)
3086 {
3087 return 0;
3088 }
3089 if (!menu_get_geometry(
3090 mr, &JunkRoot,&prior_x,&prior_y, &prior_width,
3091 &prior_height, &JunkBW, &JunkDepth))
3092 {
3093 return 0;
3094 }
3095 x_overlap = 0;
3096 if (allow_popup_offset_tolerance)
3097 {
3098 if (MST_POPUP_OFFSET_ADD(mr) < 0)
3099 {
3100 s_tolerance = -MST_POPUP_OFFSET_ADD(mr);
3101 }
3102 if (MST_USE_LEFT_SUBMENUS(mr))
3103 {
3104 prior_x += (100 - MST_POPUP_OFFSET_PERCENT(mr)) / 100;
3105 }
3106 else
3107 {
3108 prior_width *=
3109 (float)(MST_POPUP_OFFSET_PERCENT(mr)) / 100.0;
3110 }
3111 }
3112 if (MST_USE_LEFT_SUBMENUS(mr))
3113 {
3114 int t = s_tolerance;
3115 s_tolerance = h_tolerance;
3116 h_tolerance = t;
3117 }
3118 if (y < prior_y + prior_height - v_tolerance &&
3119 prior_y < y + height - v_tolerance &&
3120 x < prior_x + prior_width - s_tolerance &&
3121 prior_x < x + width - h_tolerance)
3122 {
3123 x_overlap = x - prior_x;
3124 if (x <= prior_x)
3125 {
3126 x_overlap--;
3127 }
3128 }
3129
3130 return x_overlap;
3131 }
3132
3133 /*
3134 *
3135 * Procedure:
3136 * pop_menu_up - pop up a pull down menu
3137 *
3138 * Inputs:
3139 * x, y - location of upper left of menu
3140 * do_warp_to_item - warp pointer to the first item after title
3141 * pops - pointer to the menu options for new menu
3142 *
3143 */
pop_menu_up(MenuRoot ** pmenu,MenuParameters * pmp,MenuRoot * parent_menu,MenuItem * parent_item,const exec_context_t ** pexc,int x,int y,Bool prefer_left_submenu,Bool do_warp_to_item,MenuOptions * pops,Bool * ret_overlap,Window popdown_window)3144 static int pop_menu_up(
3145 MenuRoot **pmenu, MenuParameters *pmp, MenuRoot *parent_menu,
3146 MenuItem *parent_item, const exec_context_t **pexc, int x, int y,
3147 Bool prefer_left_submenu, Bool do_warp_to_item, MenuOptions *pops,
3148 Bool *ret_overlap, Window popdown_window)
3149 {
3150 Bool do_warp_to_title = False;
3151 int x_overlap = 0;
3152 int x_clipped_overlap;
3153 MenuRoot *mrMi;
3154 MenuRoot *mr;
3155 FvwmWindow *fw;
3156 int context;
3157 int bw = 0;
3158 int bwp = 0;
3159 int prev_x;
3160 int prev_y;
3161 int prev_width;
3162 int prev_height;
3163 unsigned int event_mask;
3164 int scr_x, scr_y;
3165 int scr_w, scr_h;
3166
3167 mr = *pmenu;
3168 if (!mr ||
3169 (MR_MAPPED_COPIES(mr) > 0 && MR_COPIES(mr) >= MAX_MENU_COPIES))
3170 {
3171 return False;
3172 }
3173
3174 /*
3175 * handle dynamic menu actions
3176 */
3177
3178 /* First of all, execute the popup action (if defined). */
3179 if (MR_POPUP_ACTION(mr))
3180 {
3181 char *menu_name;
3182 saved_pos_hints pos_hints;
3183 Bool is_busy_grabbed = False;
3184 int mapped_copies = MR_MAPPED_COPIES(mr);
3185
3186 /* save variables that we still need but that may be
3187 * overwritten */
3188 menu_name = safestrdup(MR_NAME(mr));
3189 pos_hints = last_saved_pos_hints;
3190 if (Scr.BusyCursor & BUSY_DYNAMICMENU)
3191 {
3192 is_busy_grabbed = GrabEm(CRS_WAIT, GRAB_BUSYMENU);
3193 }
3194 /* Execute the action */
3195 __menu_execute_function(pmp->pexc, MR_POPUP_ACTION(mr));
3196 if (is_busy_grabbed)
3197 {
3198 UngrabEm(GRAB_BUSYMENU);
3199 }
3200 /* restore the stuff we saved */
3201 last_saved_pos_hints = pos_hints;
3202 if (mapped_copies == 0)
3203 {
3204 /* Now let's see if the menu still exists. It may have
3205 * been destroyed and recreated, so we have to look for
3206 * a menu with the saved name. menus_find_menu() always
3207 * returns the original menu, not one of its copies, so
3208 * below logic would fail miserably if used with a menu
3209 * copy. On the other hand, menu copies can't be
3210 * deleted within a dynamic popup action, so just
3211 * ignore this case. */
3212 *pmenu = menus_find_menu(menu_name);
3213 if (menu_name)
3214 {
3215 free(menu_name);
3216 }
3217 mr = *pmenu;
3218 }
3219 }
3220 if (mr)
3221 {
3222 update_menu(mr, pmp);
3223 }
3224 if (mr == NULL || MR_FIRST_ITEM(mr) == NULL || MR_ITEMS(mr) == 0)
3225 {
3226 /* The menu deleted itself or all its items or it has been
3227 * empty from the start. */
3228 return False;
3229 }
3230 fw = (*pexc)->w.fw;
3231 if (fw && !check_if_fvwm_window_exists(fw))
3232 {
3233 fw = NULL;
3234 }
3235 context = (*pexc)->w.wcontext;
3236
3237 /*
3238 * Create a new menu instance (if necessary)
3239 */
3240
3241 if (MR_MAPPED_COPIES(mr) > 0)
3242 {
3243 /* create a new instance of the menu */
3244 *pmenu = copy_menu_root(*pmenu);
3245 if (!*pmenu)
3246 {
3247 return False;
3248 }
3249 make_menu_window(*pmenu, False);
3250 mr = *pmenu;
3251 }
3252
3253 /*
3254 * Evaluate position hints
3255 */
3256
3257 /* calculate position from position hints if available */
3258 if (pops->flags.has_poshints &&
3259 !last_saved_pos_hints.flags.do_ignore_pos_hints)
3260 {
3261 get_xy_from_position_hints(
3262 &(pops->pos_hints), MR_WIDTH(mr), MR_HEIGHT(mr),
3263 (parent_menu) ? MR_WIDTH(parent_menu) : 0,
3264 prefer_left_submenu, &x, &y);
3265 }
3266
3267 /*
3268 * Initialise new menu
3269 */
3270
3271 MR_PARENT_MENU(mr) = parent_menu;
3272 MR_PARENT_ITEM(mr) = parent_item;
3273 MR_IS_PAINTED(mr) = 0;
3274 MR_IS_LEFT(mr) = 0;
3275 MR_IS_RIGHT(mr) = 0;
3276 MR_IS_UP(mr) = 0;
3277 MR_IS_DOWN(mr) = 0;
3278 MR_XANIMATION(mr) = 0;
3279 InstallFvwmColormap();
3280
3281 /*
3282 * Handle popups from button clicks on buttons in the title bar,
3283 * or the title bar itself. Position hints override this.
3284 */
3285
3286 if (!pops->flags.has_poshints && fw && parent_menu == NULL &&
3287 pmp->flags.is_first_root_menu)
3288 {
3289 int cx;
3290 int cy;
3291 int gx;
3292 int gy;
3293 Bool has_context;
3294 rectangle button_g;
3295
3296 has_context = get_title_button_geometry(
3297 fw, &button_g, context);
3298 if (has_context)
3299 {
3300 fscreen_scr_arg fscr;
3301
3302 get_title_gravity_factors(fw, &gx, &gy);
3303 cx = button_g.x + button_g.width / 2;
3304 cy = button_g.y + button_g.height / 2;
3305 if (gx != 0)
3306 {
3307 x = button_g.x +
3308 (gx == 1) * button_g.width -
3309 (gx == -1) * MR_WIDTH(mr);
3310 }
3311 if (gy != 0)
3312 {
3313 y = button_g.y +
3314 (gy == 1) * button_g.height -
3315 (gy == -1) * MR_HEIGHT(mr);
3316 }
3317 if (gx == 0 && x < button_g.x)
3318 {
3319 x = button_g.x;
3320 }
3321 else if (gx == 0 && x + MR_WIDTH(mr) >=
3322 button_g.x + button_g.width)
3323 {
3324 x = button_g.x + button_g.width - MR_WIDTH(mr);
3325 }
3326 if (gy == 0 && y < button_g.y)
3327 {
3328 y = button_g.y;
3329 }
3330 else if (gy == 0 && y + MR_HEIGHT(mr) >=
3331 button_g.y + button_g.height)
3332 {
3333 y = button_g.y + button_g.height -
3334 MR_HEIGHT(mr);
3335 }
3336 pops->pos_hints.has_screen_origin = True;
3337 fscr.xypos.x = cx;
3338 fscr.xypos.y = cy;
3339 if (FScreenGetScrRect(
3340 &fscr, FSCREEN_XYPOS, &JunkX, &JunkY,
3341 &JunkWidth, &JunkHeight))
3342 {
3343 /* use current cx/cy */
3344 }
3345 else if (FQueryPointer(
3346 dpy, Scr.Root, &JunkRoot, &JunkChild,
3347 &cx, &cy, &JunkX, &JunkY, &JunkMask))
3348 {
3349 /* use pointer's position */
3350 }
3351 else
3352 {
3353 cx = -1;
3354 cy = -1;
3355 }
3356 pops->pos_hints.screen_origin_x = cx;
3357 pops->pos_hints.screen_origin_y = cy;
3358 }
3359 } /* if (pops->flags.has_poshints) */
3360
3361 /*
3362 * Clip to screen
3363 */
3364
3365 {
3366 fscreen_scr_arg fscr;
3367
3368 fscr.xypos.x = pops->pos_hints.screen_origin_x;
3369 fscr.xypos.y = pops->pos_hints.screen_origin_y;
3370 /* clip to screen */
3371 FScreenClipToScreen(
3372 &fscr, FSCREEN_XYPOS, &x, &y, MR_WIDTH(mr),
3373 MR_HEIGHT(mr));
3374 /* "this" screen is defined -- so get its coords for future
3375 * reference */
3376 FScreenGetScrRect(
3377 &fscr, FSCREEN_XYPOS, &scr_x, &scr_y, &scr_w, &scr_h);
3378 }
3379
3380 if (parent_menu)
3381 {
3382 bw = MST_BORDER_WIDTH(mr);
3383 bwp = MST_BORDER_WIDTH(parent_menu);
3384 x_overlap = do_menus_overlap(
3385 parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr), bwp,
3386 bwp, bwp, True);
3387 }
3388
3389 /*
3390 * Calculate position and animate menus
3391 */
3392
3393 if (parent_menu == NULL ||
3394 !menu_get_geometry(
3395 parent_menu, &JunkRoot, &prev_x, &prev_y, &prev_width,
3396 &prev_height, &JunkBW, &JunkDepth))
3397 {
3398 MR_HAS_POPPED_UP_LEFT(mr) = 0;
3399 MR_HAS_POPPED_UP_RIGHT(mr) = 0;
3400 }
3401 else
3402 {
3403 int left_x;
3404 int right_x;
3405 Bool use_left_submenus = MST_USE_LEFT_SUBMENUS(mr);
3406 MenuRepaintTransparentParameters mrtp;
3407 Bool transparent_bg = False;
3408
3409 /* check if menus overlap */
3410 x_clipped_overlap =
3411 do_menus_overlap(
3412 parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr),
3413 bwp, bwp, bwp, True);
3414 if (x_clipped_overlap &&
3415 (!pops->flags.has_poshints ||
3416 pops->pos_hints.is_relative == False ||
3417 x_overlap == 0))
3418 {
3419 /* menus do overlap, but do not reposition if overlap
3420 * was caused by relative positioning hints */
3421 left_x = get_left_popup_x_position(
3422 parent_menu, mr, prev_x);
3423 right_x = get_right_popup_x_position(
3424 parent_menu, mr, prev_x);
3425 if (use_left_submenus)
3426 {
3427 if (left_x + MR_WIDTH(mr) < prev_x + bwp)
3428 {
3429 left_x = prev_x + bwp - MR_WIDTH(mr);
3430 }
3431 }
3432 else
3433 {
3434 if (right_x > prev_x + prev_width - bwp)
3435 {
3436 right_x = prev_x + prev_width - bwp;
3437 }
3438 }
3439
3440 /*
3441 * Animate parent menu
3442 */
3443
3444 if (MST_IS_ANIMATED(mr))
3445 {
3446 /* animate previous out of the way */
3447 int a_left_x;
3448 int a_right_x;
3449 int end_x;
3450 Window w;
3451
3452 if (use_left_submenus)
3453 {
3454 if (prev_x - left_x < MR_WIDTH(mr))
3455 {
3456 a_right_x =
3457 x + (prev_x - left_x);
3458 }
3459 else
3460 {
3461 a_right_x =
3462 x + MR_WIDTH(mr) - bw;
3463 }
3464 a_left_x = x - MR_WIDTH(parent_menu);
3465 }
3466 else
3467 {
3468 if (right_x - prev_x < prev_width)
3469 {
3470 a_left_x =
3471 x + (prev_x - right_x);
3472 }
3473 else
3474 {
3475 a_left_x =
3476 x - prev_width + bw;
3477 }
3478 a_right_x = x + MR_WIDTH(mr);
3479 }
3480 if (prefer_left_submenu)
3481 {
3482 /* popup menu is left of old menu, try
3483 * to move prior menu right */
3484 if (a_right_x + prev_width <=
3485 scr_x + scr_w)
3486 {
3487 end_x = a_right_x;
3488 }
3489 else if (a_left_x >= scr_x)
3490 {
3491 end_x = a_left_x;
3492 }
3493 else
3494 {
3495 end_x = scr_x + scr_w -
3496 prev_width;
3497 }
3498 }
3499 else
3500 {
3501 /* popup menu is right of old menu, try
3502 * to move prior menu left */
3503 if (a_left_x >= scr_x)
3504 {
3505 end_x = a_left_x;
3506 }
3507 else if (a_right_x + prev_width <=
3508 scr_x + scr_w)
3509 {
3510 end_x = a_right_x;
3511 }
3512 else
3513 {
3514 end_x = scr_x;
3515 }
3516 }
3517 if (end_x == a_left_x || end_x == 0)
3518 {
3519 MR_HAS_POPPED_UP_LEFT(mr) = 0;
3520 MR_HAS_POPPED_UP_RIGHT(mr) = 1;
3521 }
3522 else
3523 {
3524 MR_HAS_POPPED_UP_LEFT(mr) = 1;
3525 MR_HAS_POPPED_UP_RIGHT(mr) = 0;
3526 }
3527 MR_XANIMATION(parent_menu) += end_x - prev_x;
3528 if (ST_HAS_MENU_CSET(MR_STYLE(parent_menu)) &&
3529 CSET_IS_TRANSPARENT(
3530 ST_CSET_MENU(
3531 MR_STYLE(parent_menu))))
3532 {
3533 transparent_bg = True;
3534 get_menu_repaint_transparent_parameters(
3535 &mrtp, parent_menu, fw);
3536 }
3537
3538 if (MR_IS_TEAR_OFF_MENU(parent_menu))
3539 {
3540 int cx;
3541 int cy;
3542
3543 w = FW_W_FRAME(
3544 pmp->tear_off_root_menu_window
3545 );
3546 if (XGetGeometry(
3547 dpy, w, &JunkRoot, &cx,
3548 &cy,
3549 (unsigned int*)&JunkWidth,
3550 (unsigned int*)&JunkHeight,
3551 (unsigned int*)&JunkBW,
3552 (unsigned int*)&JunkDepth))
3553 {
3554 end_x += (cx - prev_x );
3555 prev_x = cx;
3556 prev_y = cy;
3557 }
3558 }
3559 else
3560 {
3561 w = MR_WINDOW(parent_menu);
3562 }
3563 AnimatedMoveOfWindow(
3564 w, prev_x, prev_y, end_x, prev_y, True,
3565 -1, NULL,
3566 (transparent_bg)? &mrtp:NULL);
3567 } /* if (MST_IS_ANIMATED(mr)) */
3568
3569 /*
3570 * Try the other side of the parent menu
3571 */
3572
3573 else if (!pops->flags.is_fixed)
3574 {
3575 Bool may_use_left;
3576 Bool may_use_right;
3577 Bool do_use_left;
3578 Bool use_left_as_last_resort;
3579
3580 use_left_as_last_resort =
3581 (left_x > scr_x + scr_w - right_x -
3582 MR_WIDTH(mr));
3583 may_use_left = (left_x >= scr_x);
3584 may_use_right = (right_x + MR_WIDTH(mr) <=
3585 scr_x + scr_w);
3586 if (!may_use_left && !may_use_right)
3587 {
3588 /* If everything goes wrong, put the
3589 * submenu on the side with more free
3590 * space. */
3591 do_use_left = use_left_as_last_resort;
3592 }
3593 else if (may_use_left &&
3594 (prefer_left_submenu ||
3595 !may_use_right))
3596 {
3597 do_use_left = True;
3598 }
3599 else
3600 {
3601 do_use_left = False;
3602 }
3603 x = (do_use_left) ? left_x : right_x;
3604 MR_HAS_POPPED_UP_LEFT(mr) = do_use_left;
3605 MR_HAS_POPPED_UP_RIGHT(mr) = !do_use_left;
3606
3607 /* Force the menu onto the screen, but leave at
3608 * least PARENT_MENU_FORCE_VISIBLE_WIDTH pixels
3609 * of the parent menu visible */
3610 if (x + MR_WIDTH(mr) > scr_x + scr_w)
3611 {
3612 int d = x + MR_WIDTH(mr) -
3613 (scr_x + scr_w);
3614 int c;
3615
3616 if (prev_width >=
3617 PARENT_MENU_FORCE_VISIBLE_WIDTH)
3618 {
3619 c = PARENT_MENU_FORCE_VISIBLE_WIDTH +
3620 prev_x;
3621 }
3622 else
3623 {
3624 c = prev_x + prev_width;
3625 }
3626
3627 if (x - c >= d || x <= prev_x)
3628 {
3629 x -= d;
3630 }
3631 else if (x > c)
3632 {
3633 x = c;
3634 }
3635 }
3636 if (x < scr_x)
3637 {
3638 int c = prev_width -
3639 PARENT_MENU_FORCE_VISIBLE_WIDTH;
3640
3641 if (c < 0)
3642 {
3643 c = 0;
3644 }
3645 if (scr_x - x > c)
3646 {
3647 x += c;
3648 }
3649 else
3650 {
3651 x = scr_x;
3652 }
3653 }
3654 } /* else if (non-overlapping menu style) */
3655 } /* if (x_clipped_overlap && ...) */
3656 else
3657 {
3658 MR_HAS_POPPED_UP_LEFT(mr) = prefer_left_submenu;
3659 MR_HAS_POPPED_UP_RIGHT(mr) = !prefer_left_submenu;
3660 }
3661
3662 if (x < prev_x)
3663 {
3664 MR_IS_LEFT(mr) = 1;
3665 }
3666 if (x + MR_WIDTH(mr) > prev_x + prev_width)
3667 {
3668 MR_IS_RIGHT(mr) = 1;
3669 }
3670 if (y < prev_y)
3671 {
3672 MR_IS_UP(mr) = 1;
3673 }
3674 if (y + MR_HEIGHT(mr) > prev_y + prev_height)
3675 {
3676 MR_IS_DOWN(mr) = 1;
3677 }
3678 if (!MR_IS_LEFT(mr) && !MR_IS_RIGHT(mr))
3679 {
3680 MR_IS_LEFT(mr) = 1;
3681 MR_IS_RIGHT(mr) = 1;
3682 }
3683 } /* if (parent_menu) */
3684
3685 /*
3686 * Make sure we have the correct events selected
3687 */
3688
3689 if (pmp->tear_off_root_menu_window == NULL)
3690 {
3691 /* normal menus and sub menus */
3692 event_mask = XEVMASK_MENUW;
3693 }
3694 else if (parent_menu == NULL)
3695 {
3696 /* tear off menu needs more events */
3697 event_mask = XEVMASK_TEAR_OFF_MENUW;
3698 }
3699 else
3700 {
3701 /* sub menus of tear off menus need LeaveNotify */
3702 event_mask = XEVMASK_TEAR_OFF_SUBMENUW;
3703 }
3704
3705 /*
3706 * Pop up the menu
3707 */
3708
3709 XMoveWindow(dpy, MR_WINDOW(mr), x, y);
3710 XSelectInput(dpy, MR_WINDOW(mr), event_mask);
3711 XMapRaised(dpy, MR_WINDOW(mr));
3712 if (popdown_window)
3713 XUnmapWindow(dpy, popdown_window);
3714 XFlush(dpy);
3715 MR_MAPPED_COPIES(mr)++;
3716 MST_USAGE_COUNT(mr)++;
3717 if (ret_overlap)
3718 {
3719 *ret_overlap =
3720 do_menus_overlap(
3721 parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr),
3722 0, 0, 0, False) ? True : False;
3723 }
3724
3725 /*
3726 * Warp the pointer
3727 */
3728
3729 if (!do_warp_to_item && parent_menu != NULL)
3730 {
3731 MenuItem *mi;
3732
3733 mi = find_entry(pmp, NULL, &mrMi, None, -1, -1);
3734 if (mi && mrMi == mr && mi != MR_FIRST_ITEM(mrMi))
3735 {
3736 /* pointer is on an item of the popup */
3737 if (MST_DO_WARP_TO_TITLE(mr))
3738 {
3739 /* warp pointer if not on a root menu */
3740 do_warp_to_title = True;
3741 }
3742 }
3743 } /* if (!do_warp_to_item) */
3744
3745 if (pops->flags.do_not_warp)
3746 {
3747 do_warp_to_title = False;
3748 }
3749 else if (pops->flags.do_warp_title)
3750 {
3751 do_warp_to_title = True;
3752 }
3753 if (pops->flags.has_poshints &&
3754 !last_saved_pos_hints.flags.do_ignore_pos_hints &&
3755 pops->flags.do_warp_title)
3756 {
3757 do_warp_to_title = True;
3758 }
3759 if (do_warp_to_item)
3760 {
3761 /* also warp */
3762 MR_SELECTED_ITEM(mr) = NULL;
3763 warp_pointer_to_item(
3764 mr, MR_FIRST_ITEM(mr), True /* skip Title */);
3765 select_menu_item(mr, MR_SELECTED_ITEM(mr), True, fw);
3766 }
3767 else if (do_warp_to_title)
3768 {
3769 /* Warp pointer to middle of top line, since we don't
3770 * want the user to come up directly on an option */
3771 warp_pointer_to_title(mr);
3772 }
3773
3774 return True;
3775 }
3776
3777 /*
3778 *
3779 * Procedure:
3780 * pop_menu_down - unhighlight the current menu selection and
3781 * take down the menus
3782 *
3783 * mr - menu to pop down; this pointer is invalid after the function
3784 * returns. Don't use it anymore!
3785 * parent - the menu that has spawned mr (may be NULL). this is
3786 * used to see if mr was spawned by itself on some level.
3787 * this is a hack to allow specifying 'Popup foo' within
3788 * menu foo. You must use the MenuRoot that is currently
3789 * being processed here. DO NOT USE MR_PARENT_MENU(mr) here!
3790 *
3791 */
pop_menu_down(MenuRoot ** pmr,MenuParameters * pmp)3792 static void pop_menu_down(MenuRoot **pmr, MenuParameters *pmp)
3793 {
3794 MenuItem *mi;
3795
3796 assert(*pmr);
3797 memset(&(MR_DYNAMIC_FLAGS(*pmr)), 0, sizeof(MR_DYNAMIC_FLAGS(*pmr)));
3798 XUnmapWindow(dpy, MR_WINDOW(*pmr));
3799 MR_MAPPED_COPIES(*pmr)--;
3800 MST_USAGE_COUNT(*pmr)--;
3801 UninstallFvwmColormap();
3802 XFlush(dpy);
3803 if ((mi = MR_SELECTED_ITEM(*pmr)) != NULL)
3804 {
3805 select_menu_item(*pmr, mi, False, (*pmp->pexc)->w.fw);
3806 }
3807 if (MR_STORED_PIXELS(*pmr).d_pixels != NULL)
3808 {
3809 PictureFreeColors(
3810 dpy, Pcmap, MR_STORED_PIXELS(*pmr).d_pixels,
3811 MR_STORED_PIXELS(*pmr).d_npixels, 0, False);
3812 free(MR_STORED_PIXELS(*pmr).d_pixels);
3813 MR_STORED_PIXELS(*pmr).d_pixels = NULL;
3814 }
3815 if (MR_COPIES(*pmr) > 1)
3816 {
3817 /* delete this instance of the menu */
3818 DestroyMenu(*pmr, False, False);
3819 }
3820 else if (MR_POPDOWN_ACTION(*pmr))
3821 {
3822 /* Finally execute the popdown action (if defined). */
3823 saved_pos_hints pos_hints;
3824
3825 /* save variables that we still need but that may be
3826 * overwritten */
3827 pos_hints = last_saved_pos_hints;
3828 /* Execute the action */
3829 __menu_execute_function(pmp->pexc, MR_POPDOWN_ACTION(*pmr));
3830 /* restore the stuff we saved */
3831 last_saved_pos_hints = pos_hints;
3832 }
3833
3834 return;
3835 }
3836
3837 /*
3838 *
3839 * Procedure:
3840 * pop_menu_down_and_repaint_parent - Pops down a menu and repaints the
3841 * overlapped portions of the parent menu. This is done only if
3842 * *fSubmenuOverlaps is True. *fSubmenuOverlaps is set to False
3843 * afterwards.
3844 *
3845 */
pop_menu_down_and_repaint_parent(MenuRoot ** pmr,Bool * fSubmenuOverlaps,MenuParameters * pmp)3846 static void pop_menu_down_and_repaint_parent(
3847 MenuRoot **pmr, Bool *fSubmenuOverlaps, MenuParameters *pmp)
3848 {
3849 MenuRoot *parent = MR_PARENT_MENU(*pmr);
3850 XEvent event;
3851 int mr_x;
3852 int mr_y;
3853 int mr_width;
3854 int mr_height;
3855 int parent_x;
3856 int parent_y;
3857 int parent_width;
3858 int parent_height;
3859
3860 if (*fSubmenuOverlaps && parent)
3861 {
3862 /* popping down the menu may destroy the menu via the dynamic
3863 * popdown action! Thus we must not access *pmr afterwards. */
3864 /* Create a fake event to pass into paint_menu */
3865 event.type = Expose;
3866 if (!menu_get_geometry(
3867 *pmr, &JunkRoot, &mr_x, &mr_y, &mr_width,
3868 &mr_height, &JunkBW, &JunkDepth) ||
3869 !menu_get_geometry(
3870 parent, &JunkRoot, &parent_x, &parent_y,
3871 &parent_width, &parent_height, &JunkBW,
3872 &JunkDepth))
3873 {
3874 pop_menu_down(pmr, pmp);
3875 paint_menu(parent, NULL, (*pmp->pexc)->w.fw);
3876 }
3877 else
3878 {
3879 pop_menu_down(pmr, pmp);
3880 event.xexpose.x = mr_x - parent_x;
3881 event.xexpose.width = mr_width;
3882 if (event.xexpose.x < 0)
3883 {
3884 event.xexpose.width += event.xexpose.x;
3885 event.xexpose.x = 0;
3886 }
3887 if (event.xexpose.x + event.xexpose.width >
3888 parent_width)
3889 {
3890 event.xexpose.width =
3891 parent_width - event.xexpose.x;
3892 }
3893 event.xexpose.y = mr_y - parent_y;
3894 event.xexpose.height = mr_height;
3895 if (event.xexpose.y < 0)
3896 {
3897 event.xexpose.height += event.xexpose.y;
3898 event.xexpose.y = 0;
3899 }
3900 if (event.xexpose.y + event.xexpose.height >
3901 parent_height)
3902 {
3903 event.xexpose.height =
3904 parent_height - event.xexpose.y;
3905 }
3906 flush_accumulate_expose(MR_WINDOW(parent), &event);
3907 paint_menu(parent, &event, (*pmp->pexc)->w.fw);
3908 }
3909 }
3910 else
3911 {
3912 /* popping down the menu may destroy the menu via the dynamic
3913 * popdown action! Thus we must not access *pmr afterwards. */
3914 pop_menu_down(pmr, pmp);
3915 }
3916 *fSubmenuOverlaps = False;
3917
3918 return;
3919 }
3920
3921 /* ---------------------------- menu main loop ------------------------------ */
3922
__mloop_init(MenuParameters * pmp,MenuReturn * pmret,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi,MenuOptions * pops)3923 static void __mloop_init(
3924 MenuParameters *pmp, MenuReturn *pmret,
3925 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
3926 MenuOptions *pops)
3927 {
3928 memset(in, 0, sizeof(*in));
3929 in->mif.do_force_reposition = 1;
3930 memset(med, 0, sizeof(*med));
3931 msi->t0 = fev_get_evtime();
3932 pmret->rc = MENU_NOP;
3933 memset(pops, 0, sizeof(*pops));
3934 /* remember where the pointer was so we can tell if it has moved */
3935 if (FQueryPointer(
3936 dpy, Scr.Root, &JunkRoot, &JunkChild, &(msi->x_init),
3937 &(msi->y_init), &JunkX, &JunkY, &JunkMask) == False)
3938 {
3939 /* pointer is on a different screen */
3940 msi->x_init = 0;
3941 msi->y_init = 0;
3942 }
3943 /* get the event mask right */
3944 msi->event_mask = (pmp->tear_off_root_menu_window == NULL) ?
3945 XEVMASK_MENU : XEVMASK_TEAR_OFF_MENU;
3946
3947 return;
3948 }
3949
__mloop_get_event_timeout_loop(MenuParameters * pmp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi)3950 static void __mloop_get_event_timeout_loop(
3951 MenuParameters *pmp,
3952 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
3953 {
3954 XEvent e = *(*pmp->pexc)->x.elast;
3955
3956 while (!FPending(dpy) || !FCheckMaskEvent(dpy, msi->event_mask, &e))
3957 {
3958 Bool is_popup_timed_out =
3959 (MST_POPUP_DELAY(pmp->menu) > 0 &&
3960 med->popup_delay_10ms++ >=
3961 MST_POPUP_DELAY(pmp->menu) + 1);
3962 Bool is_popdown_timed_out =
3963 (MST_POPDOWN_DELAY(pmp->menu) > 0 && in->mrPopdown &&
3964 med->popdown_delay_10ms++ >=
3965 MST_POPDOWN_DELAY(pmp->menu) + 1);
3966 Bool do_fake_motion = False;
3967
3968 if (is_popup_timed_out)
3969 {
3970 med->popup_delay_10ms = MST_POPUP_DELAY(pmp->menu);
3971 }
3972 if (is_popdown_timed_out)
3973 {
3974 med->popdown_delay_10ms = MST_POPDOWN_DELAY(pmp->menu);
3975 }
3976 if (
3977 in->mif.do_force_popup ||
3978 (is_popup_timed_out &&
3979 (is_popdown_timed_out ||
3980 in->mif.is_item_entered_by_key_press ||
3981 !in->mrPopdown ||
3982 MST_DO_POPDOWN_IMMEDIATELY(pmp->menu))))
3983 {
3984 in->mif.do_popup_now = True;
3985 in->mif.do_force_popup = False;
3986 do_fake_motion = True;
3987 in->mif.is_popped_up_by_timeout = True;
3988 is_popdown_timed_out = True;
3989 }
3990 if ((in->mrPopdown || in->mrPopup) &&
3991 (is_popdown_timed_out ||
3992 MST_DO_POPDOWN_IMMEDIATELY(pmp->menu)))
3993 {
3994 MenuRoot *m = (in->mrPopdown) ?
3995 in->mrPopdown : in->mrPopup;
3996
3997 if (!m || med->mi != MR_PARENT_ITEM(m))
3998 {
3999 in->mif.do_popdown_now = True;
4000 do_fake_motion = True;
4001 if (MST_DO_POPUP_IMMEDIATELY(pmp->menu))
4002 {
4003 in->mif.do_popup_now = True;
4004 in->mif.is_popped_up_by_timeout = True;
4005 }
4006 else if (
4007 !MST_DO_POPUP_IMMEDIATELY(pmp->menu) &&
4008 in->mif.is_pointer_in_active_item_area)
4009 {
4010 in->mif.do_popup_now = True;
4011 in->mif.is_popped_up_by_timeout = True;
4012 }
4013 else if (
4014 !MST_DO_POPUP_IMMEDIATELY(pmp->menu) &&
4015 !MST_DO_POPDOWN_IMMEDIATELY(pmp->menu)
4016 && MST_POPUP_DELAY(pmp->menu) <=
4017 MST_POPDOWN_DELAY(pmp->menu) &&
4018 med->popup_delay_10ms ==
4019 med->popdown_delay_10ms)
4020 {
4021 in->mif.do_popup_now = True;
4022 in->mif.is_popped_up_by_timeout = True;
4023 }
4024 }
4025 }
4026 if (in->mif.do_popup_now && med->mi == in->miRemovedSubmenu &&
4027 !in->mif.is_key_press)
4028 {
4029 /* prevent popping up the menu again with
4030 * RemoveSubemenus */
4031 in->mif.do_popup_now = False;
4032 in->mif.do_force_popup = False;
4033 do_fake_motion = in->mif.do_popdown_now;
4034 in->mif.is_popped_up_by_timeout = False;
4035 }
4036
4037 if (do_fake_motion)
4038 {
4039 /* fake a motion event, and set in->mif.do_popup_now */
4040 e.type = MotionNotify;
4041 e.xmotion.time = fev_get_evtime();
4042 fev_fake_event(&e);
4043 in->mif.is_motion_faked = True;
4044 break;
4045 }
4046 usleep(10000 /* 10 ms*/);
4047 }
4048
4049 return;
4050 }
4051
__mloop_get_event(MenuParameters * pmp,MenuReturn * pmret,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi)4052 static mloop_ret_code_t __mloop_get_event(
4053 MenuParameters *pmp, MenuReturn *pmret,
4054 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
4055 {
4056 XEvent e = *(*pmp->pexc)->x.elast;
4057
4058 in->mif.do_popup_and_warp = False;
4059 in->mif.do_popup_now = False;
4060 in->mif.do_popdown_now = False;
4061 in->mif.do_propagate_event_into_submenu = False;
4062 in->mif.is_key_press = False;
4063 in->mif.is_button_release = 0;
4064 if (pmp->event_propagate_to_submenu)
4065 {
4066 /* handle an event that was passed in from the parent menu */
4067 fev_fake_event(pmp->event_propagate_to_submenu);
4068 pmp->event_propagate_to_submenu = NULL;
4069 }
4070 else if (in->mif.do_recycle_event)
4071 {
4072 in->mif.is_popped_up_by_timeout = False;
4073 in->mif.do_recycle_event = 0;
4074 if (pmp->menu != pmret->target_menu)
4075 {
4076 /* the event is for a previous menu, just close this
4077 * one */
4078 pmret->rc = MENU_PROPAGATE_EVENT;
4079 return MENU_MLOOP_RET_END;
4080 }
4081 if ((*pmp->pexc)->x.elast->type == KeyPress)
4082 {
4083 /* since the pointer has been warped since the key was
4084 * pressed, fake a different key press position */
4085 if (FQueryPointer(
4086 dpy, Scr.Root, &JunkRoot, &JunkChild,
4087 &e.xkey.x_root, &e.xkey.y_root,
4088 &JunkX, &JunkY, &e.xkey.state) == False)
4089 {
4090 /* pointer is on a different screen */
4091 e.xkey.x_root = 0;
4092 e.xkey.y_root = 0;
4093 }
4094 fev_fake_event(&e);
4095 med->mi = MR_SELECTED_ITEM(pmp->menu);
4096 }
4097 }
4098 else if (pmp->tear_off_root_menu_window != NULL &&
4099 FCheckTypedWindowEvent(
4100 dpy, FW_W_PARENT(pmp->tear_off_root_menu_window),
4101 ClientMessage, &e))
4102 {
4103 /* Got a ClientMessage for the tear out menu */
4104 }
4105 else
4106 {
4107 if (in->mif.do_force_reposition)
4108 {
4109 e.type = MotionNotify;
4110 e.xmotion.time = fev_get_evtime();
4111 in->mif.is_motion_faked = True;
4112 in->mif.do_force_reposition = False;
4113 in->mif.is_popped_up_by_timeout = False;
4114 fev_fake_event(&e);
4115 }
4116 else if (!FCheckMaskEvent(dpy, ExposureMask, &e))
4117 {
4118 Bool is_popdown_timer_active = False;
4119 Bool is_popup_timer_active = False;
4120
4121 if (MST_POPDOWN_DELAY(pmp->menu) > 0 &&
4122 in->mi_with_popup != NULL &&
4123 in->mi_with_popup != MR_SELECTED_ITEM(pmp->menu))
4124 {
4125 is_popdown_timer_active = True;
4126 }
4127 if (MST_POPUP_DELAY(pmp->menu) > 0 &&
4128 !in->mif.is_popped_up_by_timeout &&
4129 in->mi_wants_popup != NULL)
4130 {
4131 is_popup_timer_active = True;
4132 }
4133 /* handle exposure events first */
4134 if (in->mif.do_force_popup ||
4135 in->mif.is_pointer_in_active_item_area ||
4136 is_popdown_timer_active || is_popup_timer_active)
4137 {
4138 __mloop_get_event_timeout_loop(
4139 pmp, in, med, msi);
4140 }
4141 else
4142 {
4143 /* block until there is an event */
4144 FMaskEvent(dpy, msi->event_mask, &e);
4145 in->mif.is_popped_up_by_timeout = False;
4146 }
4147 }
4148 else
4149 {
4150 in->mif.is_popped_up_by_timeout = False;
4151 }
4152 }
4153
4154 in->mif.is_pointer_in_active_item_area = False;
4155 if (e.type == MotionNotify)
4156 {
4157 /* discard any extra motion events before a release */
4158 while (FCheckMaskEvent(
4159 dpy, ButtonMotionMask | ButtonReleaseMask,
4160 &e) && (e.type != ButtonRelease))
4161 {
4162 /* nothing */
4163 }
4164 }
4165
4166 return MENU_MLOOP_RET_NORMAL;
4167 }
4168
__mloop_handle_event(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi)4169 static mloop_ret_code_t __mloop_handle_event(
4170 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
4171 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
4172 {
4173 MenuRoot *tmrMi;
4174 Bool rc;
4175
4176 pmret->rc = MENU_NOP;
4177 switch ((*pmp->pexc)->x.elast->type)
4178 {
4179 case ButtonRelease:
4180 in->mif.is_button_release = 1;
4181 med->mi = find_entry(
4182 pmp, &med->x_offset, &med->mrMi,
4183 (*pmp->pexc)->x.elast->xbutton.subwindow,
4184 (*pmp->pexc)->x.elast->xbutton.x_root,
4185 (*pmp->pexc)->x.elast->xbutton.y_root);
4186 /* hold the menu up when the button is released
4187 * for the first time if released OFF of the menu */
4188 if (pmp->flags.is_sticky && !in->mif.is_motion_first)
4189 {
4190 in->mif.is_release_first = True;
4191 pmp->flags.is_sticky = False;
4192 return MENU_MLOOP_RET_LOOP;
4193 }
4194 if (med->mrMi != NULL)
4195 {
4196 int menu_x;
4197 int menu_y;
4198
4199 menu_shortcuts(
4200 med->mrMi, pmp, pmret, (*pmp->pexc)->x.elast,
4201 &med->mi, pdkp, &menu_x, &menu_y);
4202 if (pmret->rc == MENU_NEWITEM_MOVEMENU)
4203 {
4204 move_any_menu(med->mrMi, pmp, menu_x, menu_y);
4205 pmret->rc = MENU_NEWITEM;
4206 }
4207 else if (pmret->rc == MENU_NEWITEM_FIND)
4208 {
4209 med->mi = find_entry(
4210 pmp, NULL, NULL, None, -1, -1);
4211 pmret->rc = MENU_NEWITEM;
4212 }
4213 }
4214 else
4215 {
4216 pmret->rc = MENU_SELECTED;
4217 }
4218 switch (pmret->rc)
4219 {
4220 case MENU_NOP:
4221 return MENU_MLOOP_RET_LOOP;
4222 case MENU_POPDOWN:
4223 case MENU_ABORTED:
4224 case MENU_DOUBLE_CLICKED:
4225 case MENU_TEAR_OFF:
4226 case MENU_KILL_TEAR_OFF_MENU:
4227 case MENU_EXEC_CMD:
4228 return MENU_MLOOP_RET_END;
4229 case MENU_POPUP:
4230 /* Allow for MoveLeft/MoveRight action to work with
4231 * Mouse */
4232 in->mif.do_popup_and_warp = True;
4233 case MENU_NEWITEM:
4234 /* unpost the menu if posted */
4235 pmret->flags.is_menu_posted = 0;
4236 return MENU_MLOOP_RET_NORMAL;
4237 case MENU_SELECTED:
4238 in->mif.was_item_unposted = 0;
4239 if (pmret->flags.is_menu_posted && med->mrMi != NULL)
4240 {
4241 if (pmret->flags.do_unpost_submenu)
4242 {
4243 pmret->flags.do_unpost_submenu = 0;
4244 pmret->flags.is_menu_posted = 0;
4245 /* just ignore the event */
4246 return MENU_MLOOP_RET_LOOP;
4247 }
4248 else if (med->mi && MI_IS_POPUP(med->mi)
4249 && med->mrMi == pmp->menu)
4250 {
4251 /* post menu - done below */
4252 }
4253 else if (MR_PARENT_ITEM(pmp->menu) &&
4254 med->mi == MR_PARENT_ITEM(pmp->menu))
4255 {
4256 /* propagate back to parent menu
4257 * and unpost current menu */
4258 pmret->flags.do_unpost_submenu = 1;
4259 pmret->rc = MENU_PROPAGATE_EVENT;
4260 pmret->target_menu = med->mrMi;
4261 return MENU_MLOOP_RET_END;
4262 }
4263 else if (med->mrMi != pmp->menu &&
4264 med->mrMi != in->mrPopup)
4265 {
4266 /* unpost and propagate back to
4267 * ancestor */
4268 pmret->flags.is_menu_posted = 0;
4269 pmret->rc = MENU_PROPAGATE_EVENT;
4270 pmret->target_menu = med->mrMi;
4271 return MENU_MLOOP_RET_END;
4272 }
4273 else if (in->mrPopup && med->mrMi ==
4274 in->mrPopup)
4275 {
4276 /* unpost and propagate into
4277 * submenu */
4278 in->mif.was_item_unposted = 1;
4279 in->mif.do_propagate_event_into_submenu
4280 = True;
4281 break;
4282 }
4283 else
4284 {
4285 /* simply unpost the menu */
4286 pmret->flags.is_menu_posted = 0;
4287 }
4288 }
4289 if (is_double_click(
4290 msi->t0, med->mi, pmp, pmret, pdkp,
4291 in->mif.has_mouse_moved))
4292 {
4293 pmret->rc = MENU_DOUBLE_CLICKED;
4294 return MENU_MLOOP_RET_END;
4295 }
4296 if (med->mi == NULL)
4297 {
4298 pmret->rc = MENU_ABORTED;
4299 }
4300 else if (MI_IS_POPUP(med->mi))
4301 {
4302 switch (MST_DO_POPUP_AS(pmp->menu))
4303 {
4304 case MDP_POST_MENU:
4305 if (in->mif.was_item_unposted)
4306 {
4307 pmret->flags.is_menu_posted =
4308 0;
4309 pmret->rc = MENU_UNPOST;
4310 pmret->target_menu = NULL;
4311 in->mif.do_popup_now = False;
4312 }
4313 else
4314 {
4315 pmret->flags.is_menu_posted =
4316 1;
4317 pmret->rc = MENU_POST;
4318 pmret->target_menu = NULL;
4319 in->mif.do_popup_now = True;
4320 if ((in->mrPopup ||
4321 in->mrPopdown)
4322 && med->mi !=
4323 MR_SELECTED_ITEM(
4324 pmp->menu))
4325 {
4326 in->mif.do_popdown_now
4327 = True;
4328 }
4329 }
4330 return MENU_MLOOP_RET_NORMAL;
4331 case MDP_IGNORE:
4332 pmret->rc = MENU_NOP;
4333 return MENU_MLOOP_RET_NORMAL;
4334 case MDP_CLOSE:
4335 pmret->rc = MENU_ABORTED;
4336 break;
4337 case MDP_ROOT_MENU:
4338 default:
4339 break;
4340 }
4341 }
4342 break;
4343 default:
4344 break;
4345 }
4346 pdkp->timestamp = 0;
4347
4348 return MENU_MLOOP_RET_END;
4349
4350 case ButtonPress:
4351 /* if the first event is a button press allow the release to
4352 * select something */
4353 pmp->flags.is_sticky = False;
4354 return MENU_MLOOP_RET_LOOP;
4355
4356 case VisibilityNotify:
4357 return MENU_MLOOP_RET_LOOP;
4358
4359 case KeyRelease:
4360 if ((*pmp->pexc)->x.elast->xkey.keycode !=
4361 MST_SELECT_ON_RELEASE_KEY(pmp->menu))
4362 {
4363 return MENU_MLOOP_RET_LOOP;
4364 }
4365 /* fall through to KeyPress */
4366
4367 case KeyPress:
4368 /* Handle a key press events to allow mouseless operation */
4369 in->mif.is_key_press = True;
4370 med->x_offset = menudim_middle_x_offset(&MR_DIM(pmp->menu));
4371
4372 /* if there is a posted menu we may have to move back into a
4373 * previous menu or possibly ignore the mouse position */
4374 if (pmret->flags.is_menu_posted)
4375 {
4376 MenuRoot *l_mrMi;
4377 int l_x_offset;
4378 XEvent e;
4379
4380 pmret->flags.is_menu_posted = 0;
4381 (void)find_entry(
4382 pmp, &l_x_offset, &l_mrMi, None, -1, -1);
4383 if (l_mrMi != NULL)
4384 {
4385 if (pmp->menu != l_mrMi)
4386 {
4387 /* unpost the menu and propagate the
4388 * event to the correct menu */
4389 pmret->rc = MENU_PROPAGATE_EVENT;
4390 pmret->target_menu = l_mrMi;
4391 return MENU_MLOOP_RET_END;
4392 }
4393 }
4394 med->mi = MR_SELECTED_ITEM(pmp->menu);
4395 e = *(*pmp->pexc)->x.elast;
4396 e.xkey.x_root = med->x_offset;
4397 e.xkey.y_root = menuitem_middle_y_offset(
4398 med->mi, MR_STYLE(pmp->menu));
4399 fev_fake_event(&e);
4400 }
4401
4402 /* now handle the actual key press */
4403 {
4404 int menu_x;
4405 int menu_y;
4406
4407 menu_shortcuts(
4408 pmp->menu, pmp, pmret, (*pmp->pexc)->x.elast,
4409 &med->mi, pdkp, &menu_x, &menu_y);
4410 if (pmret->rc == MENU_NEWITEM_MOVEMENU)
4411 {
4412 move_any_menu(pmp->menu, pmp, menu_x, menu_y);
4413 pmret->rc = MENU_NEWITEM;
4414 }
4415 else if (pmret->rc == MENU_NEWITEM_FIND)
4416 {
4417 med->mi = find_entry(
4418 pmp, NULL, NULL, None, -1, -1);
4419 pmret->rc = MENU_NEWITEM;
4420 }
4421 }
4422 if (pmret->rc != MENU_NOP)
4423 {
4424 /* using a key 'unposts' the posted menu */
4425 pmret->flags.is_menu_posted = 0;
4426 }
4427 switch (pmret->rc)
4428 {
4429 case MENU_SELECTED:
4430 if (med->mi && MI_IS_POPUP(med->mi))
4431 {
4432 switch (MST_DO_POPUP_AS(pmp->menu))
4433 {
4434 case MDP_POST_MENU:
4435 pmret->rc = MENU_POPUP;
4436 break;
4437 case MDP_IGNORE:
4438 pmret->rc = MENU_NOP;
4439 return MENU_MLOOP_RET_NORMAL;
4440 case MDP_CLOSE:
4441 pmret->rc = MENU_ABORTED;
4442 return MENU_MLOOP_RET_END;
4443 case MDP_ROOT_MENU:
4444 default:
4445 return MENU_MLOOP_RET_END;
4446 }
4447 break;
4448 }
4449 return MENU_MLOOP_RET_END;
4450 case MENU_POPDOWN:
4451 case MENU_ABORTED:
4452 case MENU_DOUBLE_CLICKED:
4453 case MENU_TEAR_OFF:
4454 case MENU_KILL_TEAR_OFF_MENU:
4455 case MENU_EXEC_CMD:
4456 return MENU_MLOOP_RET_END;
4457 case MENU_NEWITEM:
4458 case MENU_POPUP:
4459 if (med->mrMi == NULL)
4460 {
4461 /* Set the MenuRoot of the current item in case
4462 * we have just warped to the menu from the
4463 * void or unposted a popup menu. */
4464 med->mrMi = pmp->menu;
4465 }
4466 /*tmrMi = med->mrMi;*/
4467 break;
4468 default:
4469 break;
4470 }
4471 /* now warp to the new menu item, if any */
4472 if (pmret->rc == MENU_NEWITEM && med->mi)
4473 {
4474 warp_pointer_to_item(med->mrMi, med->mi, False);
4475 }
4476 if (pmret->rc == MENU_POPUP && med->mi && MI_IS_POPUP(med->mi))
4477 {
4478 in->mif.do_popup_and_warp = True;
4479 break;
4480 }
4481 break;
4482
4483 case MotionNotify:
4484 {
4485 int p_rx = -1;
4486 int p_ry = -1;
4487 Window p_child = None;
4488
4489 if (in->mif.has_mouse_moved == False)
4490 {
4491 if (FQueryPointer(
4492 dpy, Scr.Root, &JunkRoot, &p_child, &p_rx,
4493 &p_ry, &JunkX, &JunkY, &JunkMask) == False)
4494 {
4495 /* pointer is on a different screen */
4496 p_rx = 0;
4497 p_ry = 0;
4498 }
4499 if (p_rx - msi->x_init > Scr.MoveThreshold ||
4500 msi->x_init - p_rx > Scr.MoveThreshold ||
4501 p_ry - msi->y_init > Scr.MoveThreshold ||
4502 msi->y_init - p_ry > Scr.MoveThreshold)
4503 {
4504 /* remember that this isn't just a click any
4505 * more since the pointer moved */
4506 in->mif.has_mouse_moved = True;
4507 }
4508 }
4509 med->mi = find_entry(
4510 pmp, &med->x_offset, &med->mrMi, p_child, p_rx, p_ry);
4511 if (pmret->flags.is_menu_posted &&
4512 med->mrMi != pmret->target_menu)
4513 {
4514 /* ignore mouse movement outside a posted menu */
4515 med->mrMi = NULL;
4516 med->mi = NULL;
4517 }
4518 if (!in->mif.is_release_first && !in->mif.is_motion_faked &&
4519 in->mif.has_mouse_moved)
4520 {
4521 in->mif.is_motion_first = True;
4522 }
4523 in->mif.is_motion_faked = False;
4524 break;
4525 }
4526
4527 case Expose:
4528 /* grab our expose events, let the rest go through */
4529 XFlush(dpy);
4530 rc = menu_expose((*pmp->pexc)->x.elast, (*pmp->pexc)->w.fw);
4531 /* we want to dispatch this too so that icons and maybe tear
4532 * off get redrawn after being obscured by menus. */
4533 if (rc == False)
4534 {
4535 dispatch_event((*pmp->pexc)->x.elast);
4536 }
4537 return MENU_MLOOP_RET_LOOP;
4538
4539 case ClientMessage:
4540 if ((*pmp->pexc)->x.elast->xclient.format == 32 &&
4541 (*pmp->pexc)->x.elast->xclient.data.l[0] ==
4542 _XA_WM_DELETE_WINDOW &&
4543 pmp->tear_off_root_menu_window != NULL &&
4544 (*pmp->pexc)->x.elast->xclient.window == FW_W_PARENT(
4545 pmp->tear_off_root_menu_window))
4546 {
4547 /* handle deletion of tear out menus */
4548 pmret->rc = MENU_KILL_TEAR_OFF_MENU;
4549 return MENU_MLOOP_RET_END;
4550 }
4551 break;
4552
4553 case EnterNotify:
4554 /* ignore EnterNotify events */
4555 break;
4556
4557 case LeaveNotify:
4558 if (pmp->tear_off_root_menu_window != NULL &&
4559 find_entry(pmp, NULL, &tmrMi, None, -1, -1) == NULL &&
4560 tmrMi == NULL)
4561 {
4562 /* handle deletion of tear out menus */
4563 pmret->rc = MENU_ABORTED;
4564 return MENU_MLOOP_RET_END;
4565 }
4566 /* ignore it */
4567 return MENU_MLOOP_RET_LOOP;
4568
4569 case UnmapNotify:
4570 /* should never happen, but does not hurt */
4571 if (pmp->tear_off_root_menu_window != NULL &&
4572 (*pmp->pexc)->x.elast->xunmap.window ==
4573 FW_W(pmp->tear_off_root_menu_window))
4574 {
4575 /* handle deletion of tear out menus */
4576 pmret->rc = MENU_KILL_TEAR_OFF_MENU;
4577 /* extra safety: pass event back to main event loop to
4578 * make sure the window is destroyed */
4579 FPutBackEvent(dpy, (*pmp->pexc)->x.elast);
4580 return MENU_MLOOP_RET_END;
4581 }
4582 break;
4583
4584 default:
4585 /* We must not dispatch events here. There is no guarantee
4586 * that dispatch_event doesn't destroy a window stored in the
4587 * menu structures. Anyway, no events should ever get here
4588 * except to tear off menus and these must be handled
4589 * individually. */
4590 #if 0
4591 dispatch_event((*pmp->pexc)->x.elast);
4592 #endif
4593 break;
4594 }
4595
4596 return MENU_MLOOP_RET_NORMAL;
4597 }
4598
__mloop_select_item(MenuParameters * pmp,mloop_evh_input_t * in,mloop_evh_data_t * med,Bool does_submenu_overlap,Bool * pdoes_popdown_submenu_overlap)4599 static void __mloop_select_item(
4600 MenuParameters *pmp, mloop_evh_input_t *in, mloop_evh_data_t *med,
4601 Bool does_submenu_overlap, Bool *pdoes_popdown_submenu_overlap)
4602 {
4603 in->mif.is_item_entered_by_key_press = in->mif.is_key_press;
4604 med->popup_delay_10ms = 0;
4605 /* we're on the same menu, but a different item, so we need to unselect
4606 * the old item */
4607 if (MR_SELECTED_ITEM(pmp->menu))
4608 {
4609 /* something else was already selected on this menu. We have
4610 * to pop down the menu before unselecting the item in case we
4611 * are using gradient menus. The recalled image would paint
4612 * over the submenu. */
4613 if (in->mrPopup && in->mrPopup != in->mrPopdown)
4614 {
4615 in->mrPopdown = in->mrPopup;
4616 med->popdown_delay_10ms = 0;
4617 *pdoes_popdown_submenu_overlap = does_submenu_overlap;
4618 in->mrPopup = NULL;
4619 }
4620 select_menu_item(
4621 pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
4622 (*pmp->pexc)->w.fw);
4623 }
4624 /* highlight the new item; sets MR_SELECTED_ITEM(pmp->menu) too */
4625 select_menu_item(pmp->menu, med->mi, True, (*pmp->pexc)->w.fw);
4626
4627 return;
4628 }
4629
__mloop_wants_popup(MenuParameters * pmp,mloop_evh_input_t * in,mloop_evh_data_t * med,MenuRoot * mrMiPopup)4630 static void __mloop_wants_popup(
4631 MenuParameters *pmp, mloop_evh_input_t *in, mloop_evh_data_t *med,
4632 MenuRoot *mrMiPopup)
4633 {
4634 Bool do_it_now = False;
4635
4636 in->mi_wants_popup = med->mi;
4637 if (in->mif.do_popup_now)
4638 {
4639 do_it_now = True;
4640 }
4641 else if (MST_DO_POPUP_IMMEDIATELY(pmp->menu) &&
4642 med->mi != in->miRemovedSubmenu)
4643 {
4644 if (in->mif.is_key_press ||
4645 MST_DO_POPDOWN_IMMEDIATELY(pmp->menu) || !in->mrPopdown)
4646 {
4647 do_it_now = True;
4648 }
4649 }
4650 else if (pointer_in_active_item_area(med->x_offset, med->mrMi))
4651 {
4652 if (in->mif.is_key_press || med->mi == in->miRemovedSubmenu ||
4653 MST_DO_POPDOWN_IMMEDIATELY(pmp->menu) || !in->mrPopdown)
4654 {
4655 do_it_now = True;
4656 }
4657 else
4658 {
4659 in->mif.is_pointer_in_active_item_area = 1;
4660 }
4661 }
4662 if (do_it_now)
4663 {
4664 in->miRemovedSubmenu = NULL;
4665 /* must create a new menu or popup */
4666 if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
4667 {
4668 if (in->mif.do_popup_now)
4669 {
4670 in->mif.do_popup = True;
4671 }
4672 else
4673 {
4674 /* pop up in next pass through loop */
4675 in->mif.do_force_popup = True;
4676 }
4677 }
4678 else if (in->mif.do_popup_and_warp)
4679 {
4680 warp_pointer_to_item(
4681 in->mrPopup, MR_FIRST_ITEM(in->mrPopup), True);
4682 }
4683 }
4684
4685 return;
4686 }
4687
__mloop_make_popup(MenuParameters * pmp,MenuReturn * pmret,mloop_evh_input_t * in,mloop_evh_data_t * med,MenuOptions * pops,Bool * pdoes_submenu_overlap)4688 static mloop_ret_code_t __mloop_make_popup(
4689 MenuParameters *pmp, MenuReturn *pmret,
4690 mloop_evh_input_t *in, mloop_evh_data_t *med,
4691 MenuOptions *pops, Bool *pdoes_submenu_overlap)
4692 {
4693 /* create a popup menu */
4694 if (!is_submenu_mapped(pmp->menu, med->mi))
4695 {
4696 /* We want to pop prepop menus so it doesn't *have* to be
4697 unpopped; do_menu pops down any menus it pops up, but we
4698 want to be able to popdown w/o actually removing the menu */
4699 int x;
4700 int y;
4701 Bool prefer_left_submenus;
4702
4703 /* Make sure we are using the latest style and menu layout. */
4704 update_menu(in->mrPopup, pmp);
4705
4706 get_prefered_popup_position(
4707 pmp->menu, in->mrPopup, &x, &y, &prefer_left_submenus);
4708 /* Note that we don't care if popping up the menu works. If it
4709 * doesn't we'll catch it below. */
4710 pop_menu_up(
4711 &in->mrPopup, pmp, pmp->menu, med->mi, pmp->pexc, x, y,
4712 prefer_left_submenus, in->mif.do_popup_and_warp, pops,
4713 pdoes_submenu_overlap,
4714 (in->mrPopdown) ? MR_WINDOW(in->mrPopdown) : None);
4715 in->mi_with_popup = med->mi;
4716 in->mi_wants_popup = NULL;
4717 if (in->mrPopup == NULL)
4718 {
4719 /* the menu deleted itself when execution the dynamic
4720 * popup * action */
4721 pmret->rc = MENU_ERROR;
4722 return MENU_MLOOP_RET_END;
4723 }
4724 MR_SUBMENU_ITEM(pmp->menu) = med->mi;
4725 }
4726
4727 return MENU_MLOOP_RET_NORMAL;
4728 }
4729
__mloop_get_mi_actions(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi,MenuRoot * mrMiPopup,Bool * pdoes_submenu_overlap,Bool * pdoes_popdown_submenu_overlap)4730 static mloop_ret_code_t __mloop_get_mi_actions(
4731 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
4732 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
4733 MenuRoot *mrMiPopup, Bool *pdoes_submenu_overlap,
4734 Bool *pdoes_popdown_submenu_overlap)
4735 {
4736 in->mif.do_popdown = False;
4737 in->mif.do_popup = False;
4738 in->mif.do_menu = False;
4739 in->mif.is_submenu_mapped = False;
4740 if (!in->mrPopup && in->mrPopdown &&
4741 med->mi == MR_PARENT_ITEM(in->mrPopdown))
4742 {
4743 /* We're again on the item that we left before.
4744 * Deschedule popping it down. */
4745 in->mrPopup = in->mrPopdown;
4746 in->mrPopdown = NULL;
4747 }
4748 if (med->mrMi == in->mrPopup)
4749 {
4750 /* must make current popup menu a real menu */
4751 in->mif.do_menu = True;
4752 in->mif.is_submenu_mapped = True;
4753 }
4754 else if (pmret->rc == MENU_POST)
4755 {
4756 /* must create a real menu and warp into it */
4757 if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
4758 {
4759 in->mif.do_popup = True;
4760 }
4761 in->mif.do_menu = True;
4762 in->mif.is_submenu_mapped = True;
4763 }
4764 else if (in->mif.do_popup_and_warp)
4765 {
4766 /* must create a real menu and warp into it */
4767 if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
4768 {
4769 in->mif.do_popup = True;
4770 }
4771 else
4772 {
4773 XRaiseWindow(dpy, MR_WINDOW(in->mrPopup));
4774 warp_pointer_to_item(
4775 in->mrPopup, MR_FIRST_ITEM(in->mrPopup), True);
4776 in->mif.do_menu = True;
4777 in->mif.is_submenu_mapped = True;
4778 }
4779 }
4780 else if (med->mi && MI_IS_POPUP(med->mi))
4781 {
4782 if ((*pmp->pexc)->x.elast->type == ButtonPress &&
4783 is_double_click(
4784 msi->t0, med->mi, pmp, pmret, pdkp,
4785 in->mif.has_mouse_moved))
4786 {
4787 pmret->rc = MENU_DOUBLE_CLICKED;
4788 return MENU_MLOOP_RET_END;
4789 }
4790 else if (!in->mrPopup || in->mrPopup != mrMiPopup ||
4791 in->mif.do_popup_and_warp)
4792 {
4793 __mloop_wants_popup(pmp, in, med, mrMiPopup);
4794 }
4795 }
4796 if (in->mif.do_popdown_now)
4797 {
4798 in->mif.do_popdown = True;
4799 in->mif.do_popdown_now = False;
4800 }
4801 else if (in->mif.do_popup)
4802 {
4803 if (in->mrPopup && in->mrPopup != mrMiPopup)
4804 {
4805 in->mif.do_popdown = True;
4806 in->mrPopdown = in->mrPopup;
4807 med->popdown_delay_10ms = 0;
4808 *pdoes_popdown_submenu_overlap =
4809 *pdoes_submenu_overlap;
4810 }
4811 else if (in->mrPopdown && in->mrPopdown != mrMiPopup)
4812 {
4813 in->mif.do_popdown = True;
4814 }
4815 else if (in->mrPopdown && in->mrPopdown == mrMiPopup)
4816 {
4817 in->mrPopup = in->mrPopdown;
4818 *pdoes_submenu_overlap =
4819 *pdoes_popdown_submenu_overlap;
4820 in->mrPopdown = NULL;
4821 in->mif.do_popup = False;
4822 in->mif.do_popdown = False;
4823 }
4824 }
4825
4826 return MENU_MLOOP_RET_NORMAL;
4827 }
4828
__mloop_do_popdown(MenuParameters * pmp,mloop_evh_input_t * in,Bool * pdoes_popdown_submenu_overlap)4829 static void __mloop_do_popdown(
4830 MenuParameters *pmp, mloop_evh_input_t *in,
4831 Bool *pdoes_popdown_submenu_overlap)
4832 {
4833 if (in->mrPopdown)
4834 {
4835 pop_menu_down_and_repaint_parent(
4836 &in->mrPopdown, pdoes_popdown_submenu_overlap, pmp);
4837 in->mi_with_popup = NULL;
4838 MR_SUBMENU_ITEM(pmp->menu) = NULL;
4839 if (in->mrPopup == in->mrPopdown)
4840 {
4841 in->mrPopup = NULL;
4842 }
4843 in->mrPopdown = NULL;
4844 }
4845 in->mif.do_popdown = False;
4846
4847 return;
4848 }
4849
__mloop_do_popup(MenuParameters * pmp,MenuReturn * pmret,mloop_evh_input_t * in,mloop_evh_data_t * med,MenuOptions * pops,MenuRoot * mrMiPopup,Bool * pdoes_submenu_overlap,Bool * pdoes_popdown_submenu_overlap)4850 static mloop_ret_code_t __mloop_do_popup(
4851 MenuParameters *pmp, MenuReturn *pmret,
4852 mloop_evh_input_t *in, mloop_evh_data_t *med,
4853 MenuOptions *pops, MenuRoot *mrMiPopup, Bool *pdoes_submenu_overlap,
4854 Bool *pdoes_popdown_submenu_overlap)
4855 {
4856 if (!MR_IS_PAINTED(pmp->menu))
4857 {
4858 /* draw the parent menu if it is not already drawn */
4859 FCheckWeedTypedWindowEvents(
4860 dpy, MR_WINDOW(pmp->menu), Expose, NULL);
4861 paint_menu(pmp->menu, NULL, (*pmp->pexc)->w.fw);
4862 }
4863 /* get pos hints for item's action */
4864 get_popup_options(pmp, med->mi, pops);
4865 if (med->mrMi == pmp->menu && mrMiPopup == NULL &&
4866 MI_IS_POPUP(med->mi) && MR_MISSING_SUBMENU_FUNC(pmp->menu))
4867 {
4868 /* We're on a submenu item, but the submenu does not exist.
4869 * The user defined missing_submenu_action may create it. */
4870 Bool is_complex_function;
4871 Bool is_busy_grabbed = False;
4872 char *menu_name;
4873 char *action;
4874 char *missing_action = MR_MISSING_SUBMENU_FUNC(pmp->menu);
4875
4876 menu_name = PeekToken(
4877 SkipNTokens(MI_ACTION(med->mi), 1), NULL);
4878 if (!menu_name)
4879 {
4880 menu_name = "";
4881 }
4882 is_complex_function =
4883 functions_is_complex_function(missing_action);
4884 if (is_complex_function)
4885 {
4886 char *action_ptr;
4887 action =
4888 safemalloc(
4889 strlen("Function") + 3 +
4890 strlen(missing_action) * 2 + 3 +
4891 strlen(menu_name) * 2 + 1);
4892 strcpy(action, "Function ");
4893 action_ptr = action + strlen(action);
4894 action_ptr = QuoteString(action_ptr, missing_action);
4895 *action_ptr++ = ' ';
4896 action_ptr = QuoteString(action_ptr, menu_name);
4897 }
4898 else
4899 {
4900 action = MR_MISSING_SUBMENU_FUNC(pmp->menu);
4901 }
4902 if (Scr.BusyCursor & BUSY_DYNAMICMENU)
4903 {
4904 is_busy_grabbed = GrabEm(CRS_WAIT, GRAB_BUSYMENU);
4905 }
4906 /* Execute the action */
4907 __menu_execute_function(pmp->pexc, action);
4908 if (is_complex_function)
4909 {
4910 free(action);
4911 }
4912 if (is_busy_grabbed)
4913 {
4914 UngrabEm(GRAB_BUSYMENU);
4915 }
4916 /* Let's see if the menu exists now. */
4917 mrMiPopup = mr_popup_for_mi(pmp->menu, med->mi);
4918 } /* run MISSING_SUBMENU_FUNCTION */
4919 in->mrPopup = mrMiPopup;
4920 if (!in->mrPopup)
4921 {
4922 in->mif.do_menu = False;
4923 pmret->flags.is_menu_posted = 0;
4924 }
4925 else
4926 {
4927 if (__mloop_make_popup(
4928 pmp, pmret, in, med, pops, pdoes_submenu_overlap)
4929 == MENU_MLOOP_RET_END)
4930 {
4931 return MENU_MLOOP_RET_END;
4932 }
4933 } /* else (in->mrPopup) */
4934 if (in->mif.do_popdown)
4935 {
4936 if (in->mrPopdown)
4937 {
4938 if (in->mrPopdown != in->mrPopup)
4939 {
4940 if (in->mi_with_popup ==
4941 MR_PARENT_ITEM(in->mrPopdown))
4942 {
4943 in->mi_with_popup = NULL;
4944 }
4945 pop_menu_down_and_repaint_parent(
4946 &in->mrPopdown,
4947 pdoes_popdown_submenu_overlap, pmp);
4948 }
4949 in->mrPopdown = NULL;
4950 }
4951 in->mif.do_popdown = False;
4952 }
4953 if (in->mrPopup)
4954 {
4955 if (MR_PARENT_MENU(in->mrPopup) == pmp->menu)
4956 {
4957 med->mi = find_entry(
4958 pmp, NULL, &med->mrMi, None, -1, -1);
4959 if (med->mi && med->mrMi == in->mrPopup)
4960 {
4961 in->mif.do_menu = True;
4962 in->mif.is_submenu_mapped = True;
4963 }
4964 }
4965 else
4966 {
4967 /* This menu must be already mapped somewhere else, so
4968 * ignore it completely. This can only happen if we
4969 * have reached the maximum allowed number of menu
4970 * copies. */
4971 in->mif.do_menu = False;
4972 in->mif.do_popdown = False;
4973 in->mrPopup = NULL;
4974 }
4975 }
4976
4977 return MENU_MLOOP_RET_NORMAL;
4978 }
4979
__mloop_do_menu(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,MenuOptions * pops,Bool * pdoes_submenu_overlap)4980 static mloop_ret_code_t __mloop_do_menu(
4981 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
4982 mloop_evh_input_t *in, mloop_evh_data_t *med, MenuOptions *pops,
4983 Bool *pdoes_submenu_overlap)
4984 {
4985 MenuParameters mp;
4986 XEvent e;
4987
4988 memset(&mp, 0, sizeof(mp));
4989 mp.menu = in->mrPopup;
4990 mp.pexc = pmp->pexc;
4991 mp.parent_menu = pmp->menu;
4992 mp.parent_item = med->mi;
4993 mp.tear_off_root_menu_window = pmp->tear_off_root_menu_window;
4994 MR_IS_TEAR_OFF_MENU(in->mrPopup) = 0;
4995 mp.flags.has_default_action = False;
4996 mp.flags.is_already_mapped = in->mif.is_submenu_mapped;
4997 mp.flags.is_sticky = False;
4998 mp.flags.is_submenu = True;
4999 mp.flags.is_triggered_by_keypress = !!in->mif.do_popup_and_warp;
5000 mp.pops = pops;
5001 mp.ret_paction = pmp->ret_paction;
5002 mp.screen_origin_x = pmp->screen_origin_x;
5003 mp.screen_origin_y = pmp->screen_origin_y;
5004 if (in->mif.do_propagate_event_into_submenu)
5005 {
5006 e = *(*pmp->pexc)->x.elast;
5007 mp.event_propagate_to_submenu = &e;
5008 }
5009 else
5010 {
5011 mp.event_propagate_to_submenu = NULL;
5012 }
5013
5014 /* recursively do the new menu we've moved into */
5015 do_menu(&mp, pmret);
5016
5017 in->mif.do_propagate_event_into_submenu = False;
5018 if (pmret->rc == MENU_PROPAGATE_EVENT)
5019 {
5020 in->mif.do_recycle_event = 1;
5021 return MENU_MLOOP_RET_LOOP;
5022 }
5023 if (IS_MENU_RETURN(pmret->rc))
5024 {
5025 pdkp->timestamp = 0;
5026 return MENU_MLOOP_RET_END;
5027 }
5028 if (MST_DO_UNMAP_SUBMENU_ON_POPDOWN(pmp->menu) &&
5029 pmret->flags.is_key_press)
5030 {
5031 in->miRemovedSubmenu = MR_PARENT_ITEM(in->mrPopup);
5032 pop_menu_down_and_repaint_parent(
5033 &in->mrPopup, pdoes_submenu_overlap, pmp);
5034 in->mi_with_popup = NULL;
5035 MR_SUBMENU_ITEM(pmp->menu) = NULL;
5036 if (in->mrPopup == in->mrPopdown)
5037 {
5038 in->mrPopdown = NULL;
5039 }
5040 in->mrPopup = NULL;
5041 }
5042 if (pmret->rc == MENU_POPDOWN)
5043 {
5044 med->popup_delay_10ms = 0;
5045 in->mif.do_force_reposition = True;
5046 }
5047
5048 return MENU_MLOOP_RET_NORMAL;
5049 }
5050
__mloop_handle_action_with_mi(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi,MenuOptions * pops,Bool * pdoes_submenu_overlap,Bool * pdoes_popdown_submenu_overlap)5051 static mloop_ret_code_t __mloop_handle_action_with_mi(
5052 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
5053 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
5054 MenuOptions *pops, Bool *pdoes_submenu_overlap,
5055 Bool *pdoes_popdown_submenu_overlap)
5056 {
5057 MenuItem *tmi;
5058 MenuRoot *tmrMi;
5059 MenuRoot *mrMiPopup = NULL;
5060
5061 pmret->flags.do_unpost_submenu = 0;
5062 /* we're on a menu item */
5063 in->mif.is_off_menu_allowed = False;
5064 if (med->mrMi == pmp->menu && med->mi != in->miRemovedSubmenu)
5065 {
5066 in->miRemovedSubmenu = NULL;
5067 }
5068 if (med->mrMi != pmp->menu && med->mrMi != in->mrPopup &&
5069 med->mrMi != in->mrPopdown)
5070 {
5071 /* we're on an item from a prior menu */
5072 if (med->mrMi != MR_PARENT_MENU(pmp->menu))
5073 {
5074 /* the event is for a previous menu, just close
5075 * this one */
5076 pmret->rc = MENU_PROPAGATE_EVENT;
5077 pmret->target_menu = med->mrMi;
5078 }
5079 else
5080 {
5081 pmret->rc = MENU_POPDOWN;
5082 }
5083 pdkp->timestamp = 0;
5084 return MENU_MLOOP_RET_END;
5085 }
5086 if (med->mi != MR_SELECTED_ITEM(pmp->menu) && med->mrMi == pmp->menu)
5087 {
5088 /* new item of the same menu */
5089 __mloop_select_item(
5090 pmp, in, med, *pdoes_submenu_overlap,
5091 pdoes_popdown_submenu_overlap);
5092 }
5093 else if (med->mi != MR_SELECTED_ITEM(pmp->menu) && med->mrMi &&
5094 med->mrMi == in->mrPopdown)
5095 {
5096 /* we're on the popup menu of a different menu item of this
5097 * menu */
5098 med->mi = MR_PARENT_ITEM(in->mrPopdown);
5099 in->mrPopup = in->mrPopdown;
5100 in->mrPopdown = NULL;
5101 *pdoes_submenu_overlap = *pdoes_popdown_submenu_overlap;
5102 select_menu_item(pmp->menu, med->mi, True, (*pmp->pexc)->w.fw);
5103 }
5104 mrMiPopup = mr_popup_for_mi(pmp->menu, med->mi);
5105 /* check what has to be done with the item */
5106 if (__mloop_get_mi_actions(
5107 pmp, pmret, pdkp, in, med, msi, mrMiPopup,
5108 pdoes_submenu_overlap, pdoes_popdown_submenu_overlap) ==
5109 MENU_MLOOP_RET_END)
5110 {
5111 return MENU_MLOOP_RET_END;
5112 }
5113 /* do what needs to be done */
5114 if (in->mif.do_popdown && !in->mif.do_popup)
5115 {
5116 /* popdown previous popup */
5117 __mloop_do_popdown(pmp, in, pdoes_popdown_submenu_overlap);
5118 }
5119 if (in->mif.do_popup)
5120 {
5121 if (__mloop_do_popup(
5122 pmp, pmret, in, med, pops, mrMiPopup,
5123 pdoes_submenu_overlap,
5124 pdoes_popdown_submenu_overlap) ==
5125 MENU_MLOOP_RET_END)
5126 {
5127 return MENU_MLOOP_RET_END;
5128 }
5129 }
5130 /* remember the 'posted' menu */
5131 if (pmret->flags.is_menu_posted && in->mrPopup != NULL &&
5132 pmret->target_menu == NULL)
5133 {
5134 pmret->target_menu = in->mrPopup;
5135 }
5136 else if (pmret->flags.is_menu_posted && in->mrPopup == NULL)
5137 {
5138 pmret->flags.is_menu_posted = 0;
5139 }
5140 if (in->mif.do_menu)
5141 {
5142 mloop_ret_code_t rc;
5143
5144 rc = __mloop_do_menu(
5145 pmp, pmret, pdkp, in, med, pops,
5146 pdoes_submenu_overlap);
5147 if (rc != MENU_MLOOP_RET_NORMAL)
5148 {
5149 return rc;
5150 }
5151 }
5152 /* Now check whether we can animate the current popup menu back to the
5153 * original place to unobscure the current menu; this happens only
5154 * when using animation */
5155 if (in->mrPopup && MR_XANIMATION(in->mrPopup) &&
5156 (tmi = find_entry(pmp, NULL, &tmrMi, None, -1, -1)) &&
5157 (tmi == MR_SELECTED_ITEM(pmp->menu) || tmrMi != pmp->menu))
5158 {
5159 animated_move_back(in->mrPopup, False, (*pmp->pexc)->w.fw);
5160 }
5161 /* now check whether we should animate the current real menu
5162 * over to the right to unobscure the prior menu; only a very
5163 * limited case where this might be helpful and not too disruptive */
5164 /* but this cause terrible back-and-forth under certain circonstance,
5165 * I think we should disable this ... 2002-09-17 olicha */
5166 if (in->mrPopup == NULL && pmp->parent_menu != NULL &&
5167 MR_XANIMATION(pmp->menu) != 0 &&
5168 pointer_in_passive_item_area(med->x_offset, med->mrMi))
5169 {
5170 /* we have to see if we need menu to be moved */
5171 animated_move_back(pmp->menu, True, (*pmp->pexc)->w.fw);
5172 if (in->mrPopdown)
5173 {
5174 if (in->mrPopdown != in->mrPopup)
5175 {
5176 pop_menu_down_and_repaint_parent(
5177 &in->mrPopdown,
5178 pdoes_popdown_submenu_overlap, pmp);
5179 in->mi_with_popup = NULL;
5180 }
5181 in->mrPopdown = NULL;
5182 }
5183 in->mif.do_popdown = False;
5184 }
5185
5186 return MENU_MLOOP_RET_NORMAL;
5187 }
5188
__mloop_handle_action_without_mi(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi,MenuOptions * pops,Bool * pdoes_submenu_overlap,Bool * pdoes_popdown_submenu_overlap)5189 static mloop_ret_code_t __mloop_handle_action_without_mi(
5190 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
5191 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
5192 MenuOptions *pops, Bool *pdoes_submenu_overlap,
5193 Bool *pdoes_popdown_submenu_overlap)
5194 {
5195 pmret->flags.do_unpost_submenu = 0;
5196 /* moved off menu, deselect selected item... */
5197 if (!MR_SELECTED_ITEM(pmp->menu) ||
5198 in->mif.is_off_menu_allowed == True || pmret->flags.is_menu_posted)
5199 {
5200 /* nothing to do */
5201 return MENU_MLOOP_RET_NORMAL;
5202 }
5203 if (in->mrPopup)
5204 {
5205 int x, y, mx, my;
5206 int mw, mh;
5207
5208 if (FQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
5209 &x, &y, &JunkX, &JunkY, &JunkMask) == False)
5210 {
5211 /* pointer is on a different screen */
5212 x = 0;
5213 y = 0;
5214 }
5215 if (menu_get_geometry(
5216 pmp->menu, &JunkRoot, &mx, &my, &mw, &mh, &JunkBW,
5217 &JunkDepth) &&
5218 ((!MR_IS_LEFT(in->mrPopup) && x < mx) ||
5219 (!MR_IS_RIGHT(in->mrPopup) && x > mx + mw) ||
5220 (!MR_IS_UP(in->mrPopup) && y < my) ||
5221 (!MR_IS_DOWN(in->mrPopup) && y > my + mh)))
5222 {
5223 select_menu_item(
5224 pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
5225 (*pmp->pexc)->w.fw);
5226 pop_menu_down_and_repaint_parent(
5227 &in->mrPopup, pdoes_submenu_overlap, pmp);
5228 in->mi_with_popup = NULL;
5229 MR_SUBMENU_ITEM(pmp->menu) = NULL;
5230 if (in->mrPopup == in->mrPopdown)
5231 {
5232 in->mrPopdown = NULL;
5233 }
5234 in->mrPopup = NULL;
5235 }
5236 else if (x < mx || x >= mx + mw || y < my || y >= my + mh)
5237 {
5238 /* pointer is outside the menu but do not pop down */
5239 in->mif.is_off_menu_allowed = True;
5240 }
5241 else
5242 {
5243 /* Pointer is still in the menu. Postpone the decision
5244 * if we have to pop down. */
5245 }
5246 } /* if (in->mrPopup) */
5247 else
5248 {
5249 select_menu_item(
5250 pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
5251 (*pmp->pexc)->w.fw);
5252 }
5253
5254 return MENU_MLOOP_RET_NORMAL;
5255 }
5256
__mloop_exit_warp_back(MenuParameters * pmp)5257 static void __mloop_exit_warp_back(MenuParameters *pmp)
5258 {
5259 MenuRoot *tmrMi;
5260
5261 if (pmp->parent_menu && MR_SELECTED_ITEM(pmp->parent_menu))
5262 {
5263 warp_pointer_to_item(
5264 pmp->parent_menu, MR_SELECTED_ITEM(pmp->parent_menu),
5265 False);
5266 (void)find_entry(pmp, NULL, &tmrMi, None, -1, -1);
5267 if (pmp->parent_menu != tmrMi && MR_XANIMATION(pmp->menu) == 0)
5268 {
5269 /* Warping didn't take us to the correct menu, i.e. the
5270 * spot we want to warp to is obscured. So raise our
5271 * window first. */
5272 XRaiseWindow(dpy, MR_WINDOW(pmp->parent_menu));
5273 }
5274 }
5275
5276 return;
5277 }
5278
__mloop_exit_select_in_place(MenuParameters * pmp,mloop_evh_data_t * med,MenuOptions * pops)5279 static void __mloop_exit_select_in_place(
5280 MenuParameters *pmp, mloop_evh_data_t *med, MenuOptions *pops)
5281 {
5282 MenuRoot *submenu;
5283 XWindowAttributes win_attribs;
5284
5285 last_saved_pos_hints.flags.is_last_menu_pos_hints_valid = True;
5286 last_saved_pos_hints.pos_hints.x_offset = 0;
5287 last_saved_pos_hints.pos_hints.x_factor = 0;
5288 last_saved_pos_hints.pos_hints.context_x_factor = 0;
5289 last_saved_pos_hints.pos_hints.y_factor = 0;
5290 last_saved_pos_hints.pos_hints.is_relative = False;
5291 last_saved_pos_hints.pos_hints.is_menu_relative = False;
5292 submenu = mr_popup_for_mi(pmp->menu, med->mi);
5293 if (submenu && MR_WINDOW(submenu) != None &&
5294 XGetWindowAttributes(dpy, MR_WINDOW(submenu), &win_attribs) &&
5295 win_attribs.map_state == IsViewable &&
5296 menu_get_geometry(
5297 submenu, &JunkRoot, &last_saved_pos_hints.pos_hints.x,
5298 &last_saved_pos_hints.pos_hints.y, &JunkWidth, &JunkHeight,
5299 &JunkBW, &JunkDepth))
5300 {
5301 /* The submenu is mapped, just take its position and put it in
5302 * the position hints. */
5303 }
5304 else if (pops->flags.has_poshints)
5305 {
5306 last_saved_pos_hints.pos_hints = pops->pos_hints;
5307 }
5308 else
5309 {
5310 Bool dummy;
5311
5312 get_prefered_popup_position(
5313 pmp->menu, submenu, &last_saved_pos_hints. pos_hints.x,
5314 &last_saved_pos_hints. pos_hints.y, &dummy);
5315 }
5316 if (pops->flags.do_warp_on_select)
5317 {
5318 last_saved_pos_hints.flags.do_warp_title = 1;
5319 }
5320
5321 return;
5322 }
5323
__mloop_exit_selected(MenuParameters * pmp,MenuReturn * pmret,mloop_evh_data_t * med,MenuOptions * pops)5324 static void __mloop_exit_selected(
5325 MenuParameters *pmp, MenuReturn *pmret, mloop_evh_data_t *med,
5326 MenuOptions *pops)
5327 {
5328 /* save action to execute so that the menu may be destroyed now */
5329 if (pmp->ret_paction)
5330 {
5331 if (pmret->rc == MENU_EXEC_CMD)
5332 {
5333 *pmp->ret_paction = safestrdup(*pmp->ret_paction);
5334 }
5335 else
5336 {
5337 if (*pmp->ret_paction)
5338 {
5339 free(*pmp->ret_paction);
5340 }
5341 *pmp->ret_paction = (med->mi) ?
5342 safestrdup(MI_ACTION(med->mi)) : NULL;
5343 }
5344 }
5345 if (
5346 pmp->ret_paction && *pmp->ret_paction &&
5347 med->mi && MI_IS_POPUP(med->mi))
5348 {
5349 get_popup_options(pmp, med->mi, pops);
5350 if (pops->flags.do_select_in_place)
5351 {
5352 __mloop_exit_select_in_place(pmp, med, pops);
5353 }
5354 else
5355 {
5356 last_saved_pos_hints.flags.do_ignore_pos_hints = True;
5357 } /* pops->flags.do_select_in_place */
5358 last_saved_pos_hints.pos_hints.screen_origin_x =
5359 pmp->screen_origin_x;
5360 last_saved_pos_hints.pos_hints.screen_origin_y =
5361 pmp->screen_origin_y;
5362 }
5363
5364 return;
5365 }
5366
__mloop_exit(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp,mloop_evh_input_t * in,mloop_evh_data_t * med,mloop_static_info_t * msi,MenuOptions * pops)5367 static void __mloop_exit(
5368 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
5369 mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
5370 MenuOptions *pops)
5371 {
5372 Bool no = False;
5373 Bool do_deselect = False;
5374
5375 if (in->mrPopdown)
5376 {
5377 pop_menu_down_and_repaint_parent(&in->mrPopdown, &no, pmp);
5378 MR_SUBMENU_ITEM(pmp->menu) = NULL;
5379 }
5380 if (
5381 pmret->rc == MENU_SELECTED && is_double_click(
5382 msi->t0, med->mi, pmp, pmret, pdkp,
5383 in->mif.has_mouse_moved))
5384 {
5385 pmret->rc = MENU_DOUBLE_CLICKED;
5386 }
5387 if (
5388 pmret->rc == MENU_SELECTED && med->mi &&
5389 MI_FUNC_TYPE(med->mi) == F_TEARMENUOFF)
5390 {
5391 pmret->rc = (MR_IS_TEAR_OFF_MENU(pmp->menu)) ?
5392 MENU_KILL_TEAR_OFF_MENU : MENU_TEAR_OFF;
5393 }
5394 switch (pmret->rc)
5395 {
5396 case MENU_POPDOWN:
5397 case MENU_PROPAGATE_EVENT:
5398 case MENU_DOUBLE_CLICKED:
5399 do_deselect = True;
5400 /* Allow popdown to warp back pointer to main menu with mouse
5401 button control. (MoveLeft/MoveRight on a mouse binding) */
5402 if (((pmret->rc == MENU_POPDOWN && in->mif.is_button_release)
5403 || in->mif.is_key_press) &&
5404 pmret->rc != MENU_DOUBLE_CLICKED)
5405 {
5406 if (!pmp->flags.is_submenu)
5407 {
5408 /* abort a root menu rather than pop it down */
5409 pmret->rc = MENU_ABORTED;
5410 }
5411 __mloop_exit_warp_back(pmp);
5412 }
5413 break;
5414 case MENU_ABORTED:
5415 case MENU_TEAR_OFF:
5416 case MENU_KILL_TEAR_OFF_MENU:
5417 do_deselect = True;
5418 break;
5419 case MENU_SELECTED:
5420 case MENU_EXEC_CMD:
5421 __mloop_exit_selected(pmp, pmret, med, pops);
5422 pmret->rc = MENU_DONE;
5423 break;
5424 default:
5425 break;
5426 }
5427 if (do_deselect && MR_SELECTED_ITEM(pmp->menu))
5428 {
5429 select_menu_item(
5430 pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
5431 (*pmp->pexc)->w.fw);
5432 }
5433 if (pmret->rc == MENU_SUBMENU_TORN_OFF)
5434 {
5435 in->mrPopup = NULL;
5436 MR_SUBMENU_ITEM(pmp->menu) = NULL;
5437 }
5438 if (in->mrPopup)
5439 {
5440 pop_menu_down_and_repaint_parent(&in->mrPopup, &no, pmp);
5441 MR_SUBMENU_ITEM(pmp->menu) = NULL;
5442 }
5443 pmret->flags.is_key_press = in->mif.is_key_press;
5444
5445 return;
5446 }
5447
__menu_loop(MenuParameters * pmp,MenuReturn * pmret,double_keypress * pdkp)5448 static void __menu_loop(
5449 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp)
5450 {
5451 mloop_evh_input_t mei;
5452 mloop_ret_code_t mloop_ret;
5453 mloop_evh_data_t med;
5454 mloop_static_info_t msi;
5455 MenuOptions mops;
5456 Bool is_finished;
5457 Bool does_submenu_overlap = False;
5458 Bool does_popdown_submenu_overlap = False;
5459
5460 __mloop_init(pmp, pmret, &mei, &med, &msi, &mops);
5461 for (is_finished = False; !is_finished; )
5462 {
5463 mloop_ret = __mloop_get_event(pmp, pmret, &mei, &med, &msi);
5464 switch (mloop_ret)
5465 {
5466 case MENU_MLOOP_RET_END:
5467 is_finished = True;
5468 case MENU_MLOOP_RET_LOOP:
5469 continue;
5470 default:
5471 break;
5472 }
5473 mloop_ret = __mloop_handle_event(
5474 pmp, pmret, pdkp, &mei, &med, &msi);
5475 switch (mloop_ret)
5476 {
5477 case MENU_MLOOP_RET_END:
5478 is_finished = True;
5479 continue;
5480 case MENU_MLOOP_RET_LOOP:
5481 continue;
5482 default:
5483 break;
5484 }
5485 /* Now handle new menu items, whether it is from a keypress or
5486 * a pointer motion event. */
5487 if (med.mi != NULL)
5488 {
5489 mloop_ret = __mloop_handle_action_with_mi(
5490 pmp, pmret, pdkp, &mei, &med, &msi, &mops,
5491 &does_submenu_overlap,
5492 &does_popdown_submenu_overlap);
5493 }
5494 else
5495 {
5496 mloop_ret = __mloop_handle_action_without_mi(
5497 pmp, pmret, pdkp, &mei, &med, &msi, &mops,
5498 &does_submenu_overlap,
5499 &does_popdown_submenu_overlap);
5500 }
5501 if (mloop_ret == MENU_MLOOP_RET_END)
5502 {
5503 is_finished = True;
5504 }
5505 XFlush(dpy);
5506 }
5507 __mloop_exit(pmp, pmret, pdkp, &mei, &med, &msi, &mops);
5508
5509 return;
5510 }
5511
5512 /*
5513 * Functions dealing with tear off menus
5514 */
5515
menu_strip_tear_off_title(MenuRoot * mr)5516 static char *menu_strip_tear_off_title(MenuRoot *mr)
5517 {
5518 MenuItem *mi;
5519 int i;
5520 int len;
5521 char *name;
5522
5523 for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
5524 {
5525 if (MI_IS_TITLE(mi))
5526 {
5527 break;
5528 }
5529 else if (!MI_IS_SEPARATOR(mi) && !MI_IS_TEAR_OFF_BAR(mi))
5530 {
5531 /* a normal item, no title found */
5532 return NULL;
5533 }
5534 /* skip separators and tear off bars */
5535 }
5536 if (mi == NULL || !MI_HAS_TEXT(mi) || MI_NEXT_ITEM(mi) == NULL)
5537 {
5538 return NULL;
5539 }
5540 /* extract the window title from the labels */
5541 for (i = 0, len = 0; i < MAX_MENU_ITEM_LABELS; i++)
5542 {
5543 if (MI_LABEL(mi)[i] != 0)
5544 {
5545 len += strlen(MI_LABEL(mi)[i]) + 1;
5546 }
5547 }
5548 if (len == 0)
5549 {
5550 return NULL;
5551 }
5552 name = safemalloc(len + 1);
5553 *name = 0;
5554 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
5555 {
5556 if (MI_LABEL(mi)[i] != 0)
5557 {
5558 strcat(name, MI_LABEL(mi)[i]);
5559 strcat(name, " ");
5560 }
5561 }
5562 /* strip the last space */
5563 name[len - 1] = 0;
5564 /* unlink and destroy the item */
5565 unlink_item_from_menu(mr, mi);
5566 menuitem_free(mi);
5567
5568 return name;
5569 }
5570
_pred_menu_window_weed_events(Display * display,XEvent * event,XPointer arg)5571 static int _pred_menu_window_weed_events(
5572 Display *display, XEvent *event, XPointer arg)
5573 {
5574 switch (event->type)
5575 {
5576 case CirculateNotify:
5577 case ConfigureNotify:
5578 case CreateNotify:
5579 case DestroyNotify:
5580 case GravityNotify:
5581 case MapNotify:
5582 case ReparentNotify:
5583 case UnmapNotify:
5584 /* events in SubstructureNotifyMask */
5585 return 1;
5586 default:
5587 return 0;
5588 }
5589 }
5590
menu_tear_off(MenuRoot * mr_to_copy)5591 static void menu_tear_off(MenuRoot *mr_to_copy)
5592 {
5593 MenuRoot *mr;
5594 MenuStyle *ms;
5595 XEvent ev;
5596 XSizeHints menusizehints;
5597 XClassHint menuclasshints;
5598 XTextProperty menunametext;
5599 XWMHints menuwmhints;
5600 char *list[] ={ NULL, NULL };
5601 char *t;
5602 char *name = NULL;
5603 Atom protocols[1];
5604 int x = 0;
5605 int y = 0;
5606 unsigned int add_mask = 0;
5607 initial_window_options_t win_opts;
5608 evh_args_t ea;
5609 exec_context_changes_t ecc;
5610 char *buffer;
5611 char *action;
5612 cond_rc_t *cond_rc = NULL;
5613 const exec_context_t *exc = NULL;
5614
5615 /* keep the menu open */
5616 if (MR_WINDOW(mr_to_copy) != None)
5617 {
5618 XSync(dpy, 0);
5619 FWeedIfWindowEvents(
5620 dpy, MR_WINDOW(mr_to_copy),
5621 _pred_menu_window_weed_events, NULL);
5622 }
5623 mr = clone_menu(mr_to_copy);
5624 /* also dump the menu style */
5625 buffer = (char *)safemalloc(23);
5626 sprintf(buffer,"%lu",(unsigned long)mr);
5627 action = buffer;
5628 ms = menustyle_parse_style(F_PASS_ARGS);
5629 if (!ms)
5630 {
5631 /* this must never happen */
5632 fvwm_msg(
5633 ERR, "menu_tear_off",
5634 "impossible to create %s menu style", buffer);
5635 free(buffer);
5636 DestroyMenu(mr, False, False);
5637 return;
5638 }
5639 free(buffer);
5640 menustyle_copy(MR_STYLE(mr_to_copy),ms);
5641 MR_STYLE(mr) = ms;
5642 MST_USAGE_COUNT(mr) = 0;
5643 name = menu_strip_tear_off_title(mr);
5644 /* create the menu window and size the menu */
5645 make_menu(mr, True);
5646 /* set position */
5647 if (menu_get_geometry(
5648 mr_to_copy, &JunkRoot, &x, &y, &JunkWidth, &JunkHeight,
5649 &JunkBW, &JunkDepth))
5650 {
5651 add_mask = PPosition;
5652 XMoveWindow(dpy, MR_WINDOW(mr), x, y);
5653 }
5654 else
5655 {
5656 add_mask = 0;
5657 }
5658 /* focus policy */
5659 menuwmhints.flags = InputHint;
5660 menuwmhints.input = False;
5661 /* size hints */
5662 menusizehints.flags = PBaseSize | PMinSize | PMaxSize | add_mask;
5663 menusizehints.base_width = 0;
5664 menusizehints.base_height = 0;
5665 menusizehints.min_width = MR_WIDTH(mr);
5666 menusizehints.min_height = MR_HEIGHT(mr);
5667 menusizehints.max_width = MR_WIDTH(mr);
5668 menusizehints.max_height = MR_HEIGHT(mr);
5669 /* class, resource and names */
5670 menuclasshints.res_name =
5671 (name != NULL) ? name : safestrdup(MR_NAME(mr));
5672 menuclasshints.res_class = safestrdup("fvwm_menu");
5673 for (t = menuclasshints.res_name; t != NULL && *t != 0; t++)
5674 {
5675 /* replace tabs in the title with spaces */
5676 if (*t == '\t')
5677 {
5678 *t = ' ';
5679 }
5680 }
5681 list[0] = menuclasshints.res_name;
5682 menunametext.value = NULL;
5683 XStringListToTextProperty(list, 1, &menunametext);
5684 /* set all properties and hints */
5685 XSetWMProperties(
5686 dpy, MR_WINDOW(mr), &menunametext, &menunametext, NULL, 0,
5687 &menusizehints, NULL, &menuclasshints);
5688 XSetWMHints(dpy, MR_WINDOW(mr), &menuwmhints);
5689 protocols[0] = _XA_WM_DELETE_WINDOW;
5690 XSetWMProtocols(dpy, MR_WINDOW(mr), &(protocols[0]), 1);
5691
5692 /* free memory */
5693 if (menunametext.value != NULL)
5694 {
5695 XFree(menunametext.value);
5696 }
5697 free(menuclasshints.res_class);
5698 free(menuclasshints.res_name);
5699
5700 /* manage the window */
5701 memset(&win_opts, 0, sizeof(win_opts));
5702 win_opts.flags.is_menu = True;
5703 ev.type = MapRequest;
5704 ev.xmaprequest.send_event = True;
5705 ev.xmaprequest.display = dpy;
5706 ev.xmaprequest.parent = Scr.Root;
5707 ev.xmaprequest.window = MR_WINDOW(mr);
5708 fev_fake_event(&ev);
5709 ecc.type = EXCT_NULL;
5710 ecc.x.etrigger = &ev;
5711 ecc.w.w = MR_WINDOW(mr);
5712 ecc.w.wcontext = C_ROOT;
5713 ea.exc = exc_create_context(
5714 &ecc, ECC_TYPE | ECC_ETRIGGER | ECC_W | ECC_WCONTEXT);
5715 HandleMapRequestKeepRaised(&ea, None, NULL, &win_opts);
5716 exc_destroy_context(ea.exc);
5717
5718 return;
5719 }
5720
do_menu_close_tear_off_menu(MenuRoot * mr,MenuParameters mp)5721 static void do_menu_close_tear_off_menu(MenuRoot *mr, MenuParameters mp)
5722 {
5723 pop_menu_down(&mr, &mp);
5724 menustyle_free(MR_STYLE(mr));
5725 DestroyMenu(mr, False, False);
5726 }
5727
5728 /* ---------------------------- interface functions ------------------------- */
5729
menus_init(void)5730 void menus_init(void)
5731 {
5732 memset((&Menus), 0, sizeof(MenuInfo));
5733
5734 return;
5735 }
5736
5737 /* menus_find_menu expects a token as the input. Make sure you have used
5738 * GetNextToken before passing a menu name to remove quotes (if necessary) */
menus_find_menu(char * name)5739 MenuRoot *menus_find_menu(char *name)
5740 {
5741 MenuRoot *mr;
5742
5743 if (name == NULL)
5744 {
5745 return NULL;
5746 }
5747 for (mr = Menus.all; mr != NULL; mr = MR_NEXT_MENU(mr))
5748 {
5749 if (!MR_IS_DESTROYED(mr) && mr == MR_ORIGINAL_MENU(mr) &&
5750 MR_NAME(mr) != NULL && StrEquals(name, MR_NAME(mr)))
5751 {
5752 break;
5753 }
5754 }
5755
5756 return mr;
5757 }
5758
menus_remove_style_from_menus(MenuStyle * ms)5759 void menus_remove_style_from_menus(MenuStyle *ms)
5760 {
5761 MenuRoot *mr;
5762
5763 for (mr = Menus.all; mr; mr = MR_NEXT_MENU(mr))
5764 {
5765 if (MR_STYLE(mr) == ms)
5766 {
5767 MR_STYLE(mr) = menustyle_get_default_style();
5768 MR_IS_UPDATED(mr) = 1;
5769 }
5770 }
5771
5772 return;
5773 }
5774
5775 /*
5776 * Functions dealing with tear off menus
5777 */
5778
menu_enter_tear_off_menu(const exec_context_t * exc)5779 void menu_enter_tear_off_menu(const exec_context_t *exc)
5780 {
5781 MenuRoot *mr;
5782 char *ret_action = NULL;
5783 MenuOptions mops;
5784 MenuParameters mp;
5785 MenuReturn mret;
5786 const exec_context_t *exc2;
5787 exec_context_changes_t ecc;
5788
5789 if (XFindContext(
5790 dpy, FW_W(exc->w.fw), MenuContext,
5791 (caddr_t *)&mr) == XCNOENT)
5792 {
5793 return;
5794 }
5795 ecc.w.fw = NULL;
5796 ecc.w.w = None;
5797 ecc.w.wcontext = C_ROOT;
5798 exc2 = exc_clone_context(exc, &ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
5799 memset(&mops, 0, sizeof(mops));
5800 memset(&mret, 0, sizeof(MenuReturn));
5801 memset(&mp, 0, sizeof(mp));
5802 mp.menu = mr;
5803 mp.pexc = &exc2;
5804 mp.tear_off_root_menu_window = exc->w.fw;
5805 MR_IS_TEAR_OFF_MENU(mr) = 1;
5806 mp.flags.has_default_action = 0;
5807 mp.flags.is_already_mapped = True;
5808 mp.flags.is_sticky = False;
5809 mp.flags.is_submenu = False;
5810 mp.pops = &mops;
5811 mp.ret_paction = &ret_action;
5812 do_menu(&mp, &mret);
5813 exc_destroy_context(exc2);
5814
5815 return;
5816 }
5817
menu_close_tear_off_menu(FvwmWindow * fw)5818 void menu_close_tear_off_menu(FvwmWindow *fw)
5819 {
5820 MenuRoot *mr;
5821 MenuParameters mp;
5822 const exec_context_t *exc;
5823 exec_context_changes_t ecc;
5824
5825 if (XFindContext(
5826 dpy, FW_W(fw), MenuContext, (caddr_t *)&mr) == XCNOENT)
5827 {
5828 return;
5829 }
5830 memset(&mp, 0, sizeof(mp));
5831 mp.menu = mr;
5832 ecc.w.fw = NULL;
5833 ecc.w.w = None;
5834 ecc.w.wcontext = C_ROOT;
5835 exc = exc_create_context(&ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
5836 mp.pexc = &exc;
5837 do_menu_close_tear_off_menu(mr, mp);
5838 exc_destroy_context(exc);
5839
5840 return;
5841 }
5842
menu_redraw_transparent_tear_off_menu(FvwmWindow * fw,Bool pr_only)5843 Bool menu_redraw_transparent_tear_off_menu(FvwmWindow *fw, Bool pr_only)
5844 {
5845 MenuRoot *mr;
5846 MenuStyle *ms = NULL;
5847 int cs;
5848
5849 if (!(IS_TEAR_OFF_MENU(fw) &&
5850 XFindContext(dpy, FW_W(fw), MenuContext,(caddr_t *)&mr) !=
5851 XCNOENT &&
5852 (ms = MR_STYLE(mr)) &&
5853 ST_HAS_MENU_CSET(ms)))
5854 {
5855 return False;
5856 }
5857
5858 cs = ST_CSET_MENU(ms);
5859
5860 if (!CSET_IS_TRANSPARENT(cs) ||
5861 (pr_only && !CSET_IS_TRANSPARENT_PR(cs)))
5862 {
5863 return False;
5864 }
5865
5866 return UpdateBackgroundTransparency(
5867 dpy, MR_WINDOW(mr), MR_WIDTH(mr),
5868 MR_HEIGHT(mr),
5869 &Colorset[ST_CSET_MENU(ms)],
5870 Pdepth,
5871 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
5872 True);
5873 }
5874
5875 /*
5876 *
5877 * Initiates a menu pop-up
5878 *
5879 * fStick = True = sticky menu, stays up on initial button release.
5880 * fStick = False = transient menu, drops on initial release.
5881 *
5882 * eventp = 0: menu opened by mouse, do not warp
5883 * eventp > 1: root menu opened by keypress with 'Menu', warp pointer and
5884 * allow 'double-keypress'.
5885 * eventp = 1: menu opened by keypress, warp but forbid 'double-keypress'
5886 * this should always be used except in the call in 'staysup_func'
5887 * in builtin.c
5888 *
5889 * Returns one of MENU_NOP, MENU_ERROR, MENU_ABORTED, MENU_DONE
5890 * do_menu() may destroy the *pmp->pexec member and replace it with a new
5891 * copy. Be sure not to rely on the original structure being kept intact
5892 * when calling do_menu().
5893 */
do_menu(MenuParameters * pmp,MenuReturn * pmret)5894 void do_menu(MenuParameters *pmp, MenuReturn *pmret)
5895 {
5896 int x;
5897 int y;
5898 Bool fWasAlreadyPopped = False;
5899 Bool key_press;
5900 Bool is_pointer_grabbed = False;
5901 Bool is_pointer_ungrabbed = False;
5902 Bool do_menu_interaction;
5903 Bool do_check_pop_down;
5904 Bool do_warp;
5905 Time t0 = fev_get_evtime();
5906 XEvent tmpevent;
5907 double_keypress dkp;
5908 /* don't save these ones, we want them to work even within recursive
5909 * menus popped up by dynamic actions. */
5910 static int indirect_depth = 0;
5911 static int x_start;
5912 static int y_start;
5913 int scr_x, scr_y;
5914 int scr_w, scr_h;
5915
5916 pmret->rc = MENU_NOP;
5917 if (pmp->flags.is_sticky && !pmp->flags.is_submenu)
5918 {
5919 FCheckTypedEvent(dpy, ButtonPressMask, &tmpevent);
5920 }
5921 if (pmp->menu == NULL)
5922 {
5923 pmret->rc = MENU_ERROR;
5924 return;
5925 }
5926 key_press = pmp->flags.is_triggered_by_keypress;
5927
5928 /* Try to pick a root-relative optimal x,y to
5929 * put the mouse right on the title w/o warping */
5930 if (FQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
5931 &x, &y, &JunkX, &JunkY, &JunkMask) == False)
5932 {
5933 /* pointer is on a different screen */
5934 x = 0;
5935 y = 0;
5936 }
5937 /* Save these - we want to warp back here if this is a top level
5938 * menu brought up by a keystroke */
5939 if (!pmp->flags.is_submenu)
5940 {
5941 pmp->flags.is_invoked_by_key_press = key_press;
5942 pmp->flags.is_first_root_menu = !indirect_depth;
5943 }
5944 else
5945 {
5946 pmp->flags.is_first_root_menu = 0;
5947 }
5948 if (!pmp->flags.is_submenu && indirect_depth == 0)
5949 {
5950 if (key_press)
5951 {
5952 x_start = x;
5953 y_start = y;
5954 }
5955 else
5956 {
5957 x_start = -1;
5958 y_start = -1;
5959 }
5960 pmp->screen_origin_x = pmp->pops->pos_hints.screen_origin_x;
5961 pmp->screen_origin_y = pmp->pops->pos_hints.screen_origin_y;
5962 }
5963 if (pmp->pops->flags.do_warp_title)
5964 {
5965 do_warp = True;
5966 }
5967 else if (pmp->pops->flags.do_not_warp)
5968 {
5969 do_warp = False;
5970 }
5971 else if (key_press)
5972 {
5973 do_warp = True;
5974 }
5975 else
5976 {
5977 do_warp = False;
5978 }
5979 /* Figure out where we should popup, if possible */
5980 if (!pmp->flags.is_already_mapped)
5981 {
5982 Bool prefer_left_submenus = False;
5983 /* Make sure we are using the latest style and menu layout. */
5984 update_menu(pmp->menu, pmp);
5985
5986 if (pmp->flags.is_submenu)
5987 {
5988 /* this is a submenu popup */
5989 get_prefered_popup_position(
5990 pmp->parent_menu, pmp->menu, &x, &y,
5991 &prefer_left_submenus);
5992 }
5993 else
5994 {
5995 fscreen_scr_arg fscr;
5996
5997 /* we're a top level menu */
5998 if (!GrabEm(CRS_MENU, GRAB_MENU))
5999 {
6000 /* GrabEm specifies the cursor to use */
6001 XBell(dpy, 0);
6002 pmret->rc = MENU_ABORTED;
6003 return;
6004 }
6005 is_pointer_grabbed = True;
6006 /* Make the menu appear under the pointer rather than
6007 * warping */
6008 fscr.xypos.x = x;
6009 fscr.xypos.y = y;
6010 FScreenGetScrRect(
6011 &fscr, FSCREEN_XYPOS, &scr_x, &scr_y, &scr_w,
6012 &scr_h);
6013 x -= menudim_middle_x_offset(&MR_DIM(pmp->menu));
6014 y -= menuitem_middle_y_offset(
6015 MR_FIRST_ITEM(pmp->menu), MR_STYLE(pmp->menu));
6016 if (x < scr_x)
6017 {
6018 x = scr_x;
6019 }
6020 if (y < scr_y)
6021 {
6022 y = scr_y;
6023 }
6024 }
6025
6026 /* pop_menu_up may move the x,y to make it fit on screen more
6027 * nicely. It might also move parent_menu out of the way. */
6028 if (!pop_menu_up(
6029 &(pmp->menu), pmp, pmp->parent_menu, NULL,
6030 pmp->pexc, x, y, prefer_left_submenus, do_warp,
6031 pmp->pops, NULL, None))
6032 {
6033 XBell(dpy, 0);
6034 UngrabEm(GRAB_MENU);
6035 pmret->rc = MENU_ERROR;
6036 return;
6037 }
6038 }
6039 else
6040 {
6041 fWasAlreadyPopped = True;
6042 if (pmp->tear_off_root_menu_window != NULL)
6043 {
6044 if (!GrabEm(CRS_MENU, GRAB_MENU))
6045 {
6046 /* GrabEm specifies the cursor to use */
6047 XBell(dpy, 0);
6048 pmret->rc = MENU_ABORTED;
6049 return;
6050 }
6051 is_pointer_grabbed = True;
6052 }
6053 if (key_press)
6054 {
6055 warp_pointer_to_item(
6056 pmp->menu, MR_FIRST_ITEM(pmp->menu),
6057 True /* skip Title */);
6058 }
6059 }
6060
6061 /* Remember the key that popped up the root menu. */
6062 if (pmp->flags.is_submenu)
6063 {
6064 dkp.timestamp = 0;
6065 }
6066 else
6067 {
6068 if (pmp->flags.is_triggered_by_keypress)
6069 {
6070 /* we have a real key event */
6071 dkp.keystate = (*pmp->pexc)->x.etrigger->xkey.state;
6072 dkp.keycode = (*pmp->pexc)->x.etrigger->xkey.keycode;
6073 }
6074 dkp.timestamp =
6075 (key_press && pmp->flags.has_default_action) ? t0 : 0;
6076 }
6077 if (!pmp->flags.is_submenu && indirect_depth == 0)
6078 {
6079 /* we need to grab the keyboard so we are sure no key presses
6080 * are lost */
6081 MyXGrabKeyboard(dpy);
6082 }
6083 /* This may loop for tear off menus */
6084 for (do_menu_interaction = True; do_menu_interaction == True; )
6085 {
6086 if (is_pointer_ungrabbed && !GrabEm(CRS_MENU, GRAB_MENU))
6087 {
6088 /* re-grab the pointer in this cycle */
6089 XBell(dpy, 0);
6090 pmret->rc = MENU_ABORTED;
6091 break;
6092 }
6093 do_menu_interaction = False;
6094 if (pmp->pops->flags.do_tear_off_immediately == 1)
6095 {
6096 pmret->rc = MENU_TEAR_OFF;
6097 }
6098 else
6099 {
6100 if (!pmp->flags.is_submenu)
6101 {
6102 XSelectInput(
6103 dpy, Scr.NoFocusWin, XEVMASK_MENUNFW);
6104 XFlush(dpy);
6105 }
6106 __menu_loop(pmp, pmret, &dkp);
6107 if (!pmp->flags.is_submenu)
6108 {
6109 XSelectInput(
6110 dpy, Scr.NoFocusWin, XEVMASK_NOFOCUSW);
6111 XFlush(dpy);
6112 }
6113 }
6114 do_check_pop_down = False;
6115 switch (pmret->rc)
6116 {
6117 case MENU_TEAR_OFF:
6118 menu_tear_off(pmp->menu);
6119 pop_menu_down(&pmp->menu, pmp);
6120 break;
6121 case MENU_KILL_TEAR_OFF_MENU:
6122 if (MR_IS_TEAR_OFF_MENU(pmp->menu))
6123 {
6124 /* kill the menu */
6125 do_menu_close_tear_off_menu(pmp->menu, *pmp);
6126 pmret->rc = MENU_ABORTED;
6127 }
6128 else
6129 {
6130 /* pass return code up to the torn off menu */
6131 }
6132 break;
6133 case MENU_DOUBLE_CLICKED:
6134 case MENU_DONE:
6135 if (MR_IS_TEAR_OFF_MENU(pmp->menu))
6136 {
6137 do_menu_interaction = True;
6138 }
6139 else
6140 {
6141 do_check_pop_down = True;
6142 }
6143 break;
6144 case MENU_SUBMENU_TORN_OFF:
6145 pmret->rc = MENU_ABORTED;
6146 do_check_pop_down = True;
6147 break;
6148 default:
6149 do_check_pop_down = True;
6150 break;
6151 }
6152 if (do_check_pop_down == True)
6153 {
6154 /* popping down may destroy the menu via the dynamic
6155 * popdown action! */
6156 if (!MR_IS_TEAR_OFF_MENU(pmp->menu) &&
6157 fWasAlreadyPopped == False)
6158 {
6159 pop_menu_down(&pmp->menu, pmp);
6160 }
6161 }
6162 XFlush(dpy);
6163
6164 if (!pmp->flags.is_submenu && x_start >= 0 && y_start >= 0 &&
6165 pmret->flags.is_key_press && pmret->rc != MENU_TEAR_OFF)
6166 {
6167 /* warp pointer back to where invoked if this was
6168 * brought up with a keypress and we're returning from
6169 * a top level menu, and a button release event didn't
6170 * end it */
6171 FWarpPointer(
6172 dpy, 0, Scr.Root, 0, 0, Scr.MyDisplayWidth,
6173 Scr.MyDisplayHeight, x_start, y_start);
6174 if ((*pmp->pexc)->x.elast->type == KeyPress)
6175 {
6176 XEvent e = *(*pmp->pexc)->x.elast;
6177
6178 e.xkey.x_root = x_start;
6179 e.xkey.y_root = y_start;
6180 fev_fake_event(&e);
6181 }
6182 }
6183 if (pmret->rc == MENU_TEAR_OFF)
6184 {
6185 pmret->rc = MENU_SUBMENU_TORN_OFF;
6186 }
6187
6188 dkp.timestamp = 0;
6189 if (is_pointer_grabbed)
6190 {
6191 UngrabEm(GRAB_MENU);
6192 WaitForButtonsUp(True);
6193 is_pointer_ungrabbed = True;
6194 }
6195 if (!pmp->flags.is_submenu)
6196 {
6197 if (pmret->rc == MENU_DONE)
6198 {
6199 if (pmp->ret_paction && *pmp->ret_paction)
6200 {
6201 indirect_depth++;
6202 /* Execute the action */
6203 __menu_execute_function(
6204 pmp->pexc, *pmp->ret_paction);
6205 indirect_depth--;
6206 free(*pmp->ret_paction);
6207 *pmp->ret_paction = NULL;
6208 }
6209 last_saved_pos_hints.flags.
6210 do_ignore_pos_hints = False;
6211 last_saved_pos_hints.flags.
6212 is_last_menu_pos_hints_valid = False;
6213 }
6214 if (indirect_depth == 0)
6215 {
6216 last_saved_pos_hints.flags.
6217 do_ignore_pos_hints = False;
6218 last_saved_pos_hints.flags.
6219 is_last_menu_pos_hints_valid = False;
6220 }
6221 }
6222 }
6223 if (!pmp->flags.is_submenu && indirect_depth == 0)
6224 {
6225 /* release the keyboard when the last menu closes */
6226 MyXUngrabKeyboard(dpy);
6227 }
6228
6229 return;
6230 }
6231
menu_expose(XEvent * event,FvwmWindow * fw)6232 Bool menu_expose(XEvent *event, FvwmWindow *fw)
6233 {
6234 MenuRoot *mr = NULL;
6235
6236 if ((XFindContext(
6237 dpy, event->xany.window, MenuContext, (caddr_t *)&mr) !=
6238 XCNOENT))
6239 {
6240 flush_accumulate_expose(event->xany.window, event);
6241 paint_menu(mr, event, fw);
6242
6243 return True;
6244 }
6245 else
6246 {
6247 return False;
6248 }
6249 }
6250
6251 /*
6252 *
6253 * Procedure:
6254 * update_transparent_menu_bg - set the background of the menu to
6255 * match a forseen move of a menu. If the background is updated
6256 * for the target position before the move is done, and repainted
6257 * after the move, the move will look more seamless.
6258 *
6259 * This method should be folleowd by a call to repaint_transparent_menu
6260 * with the same step_x, stey_y, end_x and any_y, with is_bg_set True
6261 */
update_transparent_menu_bg(MenuRepaintTransparentParameters * prtm,int current_x,int current_y,int step_x,int step_y,int end_x,int end_y)6262 void update_transparent_menu_bg(
6263 MenuRepaintTransparentParameters *prtm,
6264 int current_x, int current_y, int step_x, int step_y,
6265 int end_x, int end_y)
6266 {
6267 MenuRoot *mr;
6268 MenuStyle *ms;
6269 Bool last = False;
6270
6271 mr = prtm->mr;
6272 ms = MR_STYLE(mr);
6273 if (step_x == end_x && step_y == end_y)
6274 {
6275 last = True;
6276 }
6277 if (!last && CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
6278 {
6279 /* too slow ... */
6280 return;
6281 }
6282 SetWindowBackgroundWithOffset(
6283 dpy, MR_WINDOW(mr), step_x - current_x, step_y - current_y,
6284 MR_WIDTH(mr), MR_HEIGHT(mr),
6285 &Colorset[ST_CSET_MENU(ms)], Pdepth,
6286 FORE_GC(MST_MENU_INACTIVE_GCS(mr)), False);
6287 }
6288
6289
6290 /*
6291 *
6292 * Procedure:
6293 * repaint_transparent_menu - repaint the menu background if it is
6294 * tranparent during an animated move. Called in move_resize.c
6295 * (AnimatedMoveAnyWindow). Performance improvement Welcome!
6296 * ideas: - write the foreground into a pixmap and a mask the first time
6297 * this function is called. Then use these pixmaps to draw
6298 * the items
6299 * - Use a Buffer if !IS_TRANSPARENT_PR_PURE and if we do not have one
6300 * already
6301 */
repaint_transparent_menu(MenuRepaintTransparentParameters * prtm,Bool first,int x,int y,int end_x,int end_y,Bool is_bg_set)6302 void repaint_transparent_menu(
6303 MenuRepaintTransparentParameters *prtm,
6304 Bool first, int x, int y, int end_x, int end_y, Bool is_bg_set)
6305 {
6306 MenuItem *mi;
6307 MenuRoot *mr;
6308 MenuStyle *ms;
6309 int h = 0;
6310 int s_h = 0;
6311 int e_h = 0;
6312 Bool last = False;
6313
6314 mr = prtm->mr;
6315 ms = MR_STYLE(mr);
6316 if (x == end_x && y == end_y)
6317 {
6318 last = True;
6319 }
6320 if (!last && CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
6321 {
6322 /* too slow ... */
6323 return;
6324 }
6325 if (!is_bg_set)
6326 {
6327 SetWindowBackground(
6328 dpy, MR_WINDOW(mr), MR_WIDTH(mr), MR_HEIGHT(mr),
6329 &Colorset[ST_CSET_MENU(ms)], Pdepth,
6330 FORE_GC(MST_MENU_INACTIVE_GCS(mr)), False);
6331 }
6332 /* redraw the background of non active item */
6333 for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
6334 {
6335 if (mi == MR_SELECTED_ITEM(mr) && MST_DO_HILIGHT_BACK(mr))
6336 {
6337 int left;
6338
6339 left = MR_HILIGHT_X_OFFSET(mr) - MR_ITEM_X_OFFSET(mr);
6340 if (left > 0)
6341 {
6342 XClearArea(
6343 dpy, MR_WINDOW(mr),
6344 MR_ITEM_X_OFFSET(mr), MI_Y_OFFSET(mi),
6345 left, MI_HEIGHT(mi) +
6346 MST_RELIEF_THICKNESS(mr), 0);
6347 }
6348 h = MI_HEIGHT(mi);
6349 continue;
6350 }
6351 if (h == 0)
6352 {
6353 s_h += MI_HEIGHT(mi);
6354 }
6355 else
6356 {
6357 e_h += MI_HEIGHT(mi);
6358 }
6359 }
6360 XClearArea(
6361 dpy, MR_WINDOW(mr), MR_ITEM_X_OFFSET(mr), MST_BORDER_WIDTH(mr),
6362 MR_ITEM_WIDTH(mr), s_h, 0);
6363 if (e_h != 0)
6364 {
6365 XClearArea(
6366 dpy, MR_WINDOW(mr), MR_ITEM_X_OFFSET(mr),
6367 s_h + h + MST_RELIEF_THICKNESS(mr) +
6368 MST_BORDER_WIDTH(mr), MR_ITEM_WIDTH(mr), e_h, 0);
6369 }
6370
6371 /* now redraw the items */
6372 for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
6373 {
6374 MenuPaintItemParameters mpip;
6375
6376 if (mi == MR_SELECTED_ITEM(mr) && MST_DO_HILIGHT_BACK(mr) &&
6377 !CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
6378 {
6379 continue;
6380 }
6381 get_menu_paint_item_parameters(
6382 &mpip, mr, NULL, prtm->fw, NULL, True);
6383 mpip.flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
6384 menuitem_paint(mi, &mpip);
6385 }
6386
6387 /* if we have a side pic and no side colors we shound repaint the side
6388 * pic */
6389 if ((MR_SIDEPIC(mr) || MST_SIDEPIC(mr)) &&
6390 !MR_HAS_SIDECOLOR(mr) && !MST_HAS_SIDE_COLOR(mr))
6391 {
6392 FvwmPicture *sidePic;
6393 if (MR_SIDEPIC(mr))
6394 {
6395 sidePic = MR_SIDEPIC(mr);
6396 }
6397 else if (MST_SIDEPIC(mr))
6398 {
6399 sidePic = MST_SIDEPIC(mr);
6400 }
6401 else
6402 {
6403 return;
6404 }
6405 XClearArea(
6406 dpy, MR_WINDOW(mr), MR_SIDEPIC_X_OFFSET(mr),
6407 MST_BORDER_WIDTH(mr), sidePic->width,
6408 MR_HEIGHT(mr) - 2 * MST_BORDER_WIDTH(mr), 0);
6409 paint_side_pic(mr, NULL);
6410 }
6411 }
6412
DestroyMenu(MenuRoot * mr,Bool do_recreate,Bool is_command_request)6413 Bool DestroyMenu(MenuRoot *mr, Bool do_recreate, Bool is_command_request)
6414 {
6415 MenuItem *mi,*tmp2;
6416 MenuRoot *tmp, *prev;
6417 Bool in_list = True;
6418
6419 if (mr == NULL)
6420 {
6421 return False;
6422 }
6423
6424 /* seek menu in master list */
6425 tmp = Menus.all;
6426 prev = NULL;
6427 while (tmp && tmp != mr)
6428 {
6429 prev = tmp;
6430 tmp = MR_NEXT_MENU(tmp);
6431 }
6432 if (tmp != mr)
6433 {
6434 /* no such menu */
6435 in_list = False;
6436 }
6437
6438 if (MR_MAPPED_COPIES(mr) > 0 &&
6439 (is_command_request || MR_COPIES(mr) == 1))
6440 {
6441 /* can't destroy a menu while in use */
6442 fvwm_msg(ERR, "DestroyMenu", "Menu %s is in use", MR_NAME(mr));
6443 return False;
6444 }
6445
6446 if (in_list && MR_COPIES(mr) > 1 && MR_ORIGINAL_MENU(mr) == mr)
6447 {
6448 MenuRoot *m;
6449 MenuRoot *new_orig;
6450
6451 /* find a new 'original' menu */
6452 for (m = Menus.all, new_orig = NULL; m; m = MR_NEXT_MENU(m))
6453 {
6454 if (m != mr && MR_ORIGINAL_MENU(m) == mr)
6455 {
6456 if (new_orig == NULL)
6457 {
6458 new_orig = m;
6459 }
6460 MR_ORIGINAL_MENU(m) = new_orig;
6461 }
6462 }
6463 MR_ORIGINAL_MENU(mr) = new_orig;
6464 /* now dump old original menu */
6465 }
6466
6467 MR_COPIES(mr)--;
6468 if (MR_STORED_ITEM(mr).stored)
6469 {
6470 XFreePixmap(dpy, MR_STORED_ITEM(mr).stored);
6471 }
6472 if (MR_COPIES(mr) > 0)
6473 {
6474 do_recreate = False;
6475 }
6476 else
6477 {
6478 /* free all items */
6479 mi = MR_FIRST_ITEM(mr);
6480 while (mi != NULL)
6481 {
6482 tmp2 = MI_NEXT_ITEM(mi);
6483 menuitem_free(mi);
6484 mi = tmp2;
6485 }
6486 if (do_recreate)
6487 {
6488 /* just dump the menu items but keep the menu itself */
6489 MR_COPIES(mr)++;
6490 MR_FIRST_ITEM(mr) = NULL;
6491 MR_LAST_ITEM(mr) = NULL;
6492 MR_SELECTED_ITEM(mr) = NULL;
6493 MR_CONTINUATION_MENU(mr) = NULL;
6494 MR_PARENT_MENU(mr) = NULL;
6495 MR_ITEMS(mr) = 0;
6496 memset(&(MR_STORED_ITEM(mr)), 0 ,
6497 sizeof(MR_STORED_ITEM(mr)));
6498 MR_IS_UPDATED(mr) = 1;
6499 return True;
6500 }
6501 }
6502
6503 /* unlink menu from list */
6504 if (in_list)
6505 {
6506 if (prev == NULL)
6507 {
6508 Menus.all = MR_NEXT_MENU(mr);
6509 }
6510 else
6511 {
6512 MR_NEXT_MENU(prev) = MR_NEXT_MENU(mr);
6513 }
6514 }
6515 /* destroy the window and the display */
6516 if (MR_WINDOW(mr) != None)
6517 {
6518 XDeleteContext(dpy, MR_WINDOW(mr), MenuContext);
6519 XFlush(dpy);
6520 XDestroyWindow(MR_CREATE_DPY(mr), MR_WINDOW(mr));
6521 MR_WINDOW(mr) = None;
6522 XFlush(MR_CREATE_DPY(mr));
6523 }
6524 if (MR_CREATE_DPY(mr) != NULL && MR_CREATE_DPY(mr) != dpy)
6525 {
6526 XCloseDisplay(MR_CREATE_DPY(mr));
6527 MR_CREATE_DPY(mr) = NULL;
6528 }
6529
6530 if (MR_COPIES(mr) == 0)
6531 {
6532 if (MR_POPUP_ACTION(mr))
6533 {
6534 free(MR_POPUP_ACTION(mr));
6535 }
6536 if (MR_POPDOWN_ACTION(mr))
6537 {
6538 free(MR_POPDOWN_ACTION(mr));
6539 }
6540 if (MR_MISSING_SUBMENU_FUNC(mr))
6541 {
6542 free(MR_MISSING_SUBMENU_FUNC(mr));
6543 }
6544 free(MR_NAME(mr));
6545 if (MR_SIDEPIC(mr))
6546 {
6547 PDestroyFvwmPicture(dpy, MR_SIDEPIC(mr));
6548 }
6549 memset(mr->s, 0, sizeof(*(mr->s)));
6550 free(mr->s);
6551 }
6552 memset(mr->d, 0, sizeof(*(mr->d)));
6553 free(mr->d);
6554 memset(mr, 0, sizeof(*mr));
6555 free(mr);
6556
6557 return True;
6558 }
6559
6560 /* FollowMenuContinuations
6561 * Given an menu root, return the menu root to add to by
6562 * following continuation links until there are no more
6563 */
FollowMenuContinuations(MenuRoot * mr,MenuRoot ** pmrPrior)6564 MenuRoot *FollowMenuContinuations(MenuRoot *mr, MenuRoot **pmrPrior )
6565 {
6566 *pmrPrior = NULL;
6567 while ((mr != NULL) &&
6568 (MR_CONTINUATION_MENU(mr) != NULL))
6569 {
6570 *pmrPrior = mr;
6571 mr = MR_CONTINUATION_MENU(mr);
6572 }
6573
6574 return mr;
6575 }
6576
6577 /*
6578 *
6579 * Procedure:
6580 * AddToMenu - add an item to a root menu
6581 *
6582 * Returned Value:
6583 * (MenuItem *)
6584 *
6585 * Inputs:
6586 * menu - pointer to the root menu to add the item
6587 * item - the text to appear in the menu
6588 * action - the string to possibly execute
6589 *
6590 * ckh - need to add boolean to say whether or not to expand for pixmaps,
6591 * so built in window list can handle windows w/ * and % in title.
6592 *
6593 */
AddToMenu(MenuRoot * mr,char * item,char * action,Bool fPixmapsOk,Bool fNoPlus,Bool is_continuation_item)6594 void AddToMenu(
6595 MenuRoot *mr, char *item, char *action, Bool fPixmapsOk, Bool fNoPlus,
6596 Bool is_continuation_item)
6597 {
6598 MenuItem *tmp;
6599 char *start;
6600 char *end;
6601 char *token = NULL;
6602 char *option = NULL;
6603 int i;
6604 int is_empty;
6605 Bool do_replace_title;
6606
6607 if (MR_MAPPED_COPIES(mr) > 0)
6608 {
6609 /* whoa, we can't handle *everything* */
6610 return;
6611 }
6612 if ((item == NULL || *item == 0) && (action == NULL || *action == 0) &&
6613 fNoPlus)
6614 {
6615 return;
6616 }
6617 /* empty items screw up our menu when painted, so we replace them with
6618 * a separator */
6619 if (item == NULL)
6620 item = "";
6621
6622 /*
6623 * Handle dynamic actions
6624 */
6625
6626 if (StrEquals(item, "DynamicPopupAction"))
6627 {
6628 if (MR_POPUP_ACTION(mr))
6629 {
6630 free(MR_POPUP_ACTION(mr));
6631 }
6632 if (!action || *action == 0)
6633 {
6634 MR_POPUP_ACTION(mr) = NULL;
6635 }
6636 else
6637 {
6638 MR_POPUP_ACTION(mr) = stripcpy(action);
6639 }
6640 return;
6641 }
6642 else if (StrEquals(item, "DynamicPopdownAction"))
6643 {
6644 if (MR_POPDOWN_ACTION(mr))
6645 {
6646 free(MR_POPDOWN_ACTION(mr));
6647 }
6648 if (!action || *action == 0)
6649 {
6650 MR_POPDOWN_ACTION(mr) = NULL;
6651 }
6652 else
6653 {
6654 MR_POPDOWN_ACTION(mr) = stripcpy(action);
6655 }
6656 return;
6657 }
6658 else if (StrEquals(item, "MissingSubmenuFunction"))
6659 {
6660 if (MR_MISSING_SUBMENU_FUNC(mr))
6661 {
6662 free(MR_MISSING_SUBMENU_FUNC(mr));
6663 }
6664 if (!action || *action == 0)
6665 {
6666 MR_MISSING_SUBMENU_FUNC(mr) = NULL;
6667 }
6668 else
6669 {
6670 MR_MISSING_SUBMENU_FUNC(mr) = stripcpy(action);
6671 }
6672 return;
6673 }
6674
6675 /*
6676 * Parse the action
6677 */
6678
6679 if (action == NULL || *action == 0)
6680 {
6681 action = "Nop";
6682 }
6683 GetNextToken(GetNextToken(action, &token), &option);
6684 tmp = menuitem_create();
6685 if (MR_FIRST_ITEM(mr) != NULL && StrEquals(token, "title") &&
6686 option != NULL && StrEquals(option, "top"))
6687 {
6688 do_replace_title = True;
6689 }
6690 else
6691 {
6692 do_replace_title = False;
6693 }
6694 append_item_to_menu(mr, tmp, do_replace_title);
6695 if (token)
6696 {
6697 free(token);
6698 }
6699 if (option)
6700 {
6701 free(option);
6702 }
6703
6704 MI_ACTION(tmp) = stripcpy(action);
6705
6706 /*
6707 * Parse the labels
6708 */
6709
6710 start = item;
6711 end = item;
6712 for (i = 0; i < MAX_MENU_ITEM_LABELS; i++, start = end)
6713 {
6714 /* Read label up to next tab. */
6715 if (*end)
6716 {
6717 if (i < MAX_MENU_ITEM_LABELS - 1)
6718 {
6719 while (*end && *end != '\t')
6720 {
6721 /* seek next tab or end of string */
6722 end++;
6723 }
6724 }
6725 else
6726 {
6727 /* remove all tabs in last label */
6728 while (*end)
6729 {
6730 if (*end == '\t')
6731 {
6732 *end = ' ';
6733 }
6734 end++;
6735 }
6736 }
6737 /* Copy the label. */
6738 MI_LABEL(tmp)[i] = safemalloc(end - start + 1);
6739 strncpy(MI_LABEL(tmp)[i], start, end - start);
6740 (MI_LABEL(tmp)[i])[end - start] = 0;
6741 if (*end == '\t')
6742 {
6743 /* skip the tab */
6744 end++;
6745 }
6746 }
6747 else
6748 {
6749 MI_LABEL(tmp)[i] = NULL;
6750 }
6751
6752 /* Parse the label. */
6753 if (MI_LABEL(tmp)[i] != NULL)
6754 {
6755 if (fPixmapsOk)
6756 {
6757 string_def_t item_pixmaps[] = {
6758 {'*', __scan_for_pixmap},
6759 {'%', __scan_for_pixmap},
6760 {'\0', NULL}};
6761 string_context_t ctx;
6762
6763 SCTX_SET_MI(ctx,tmp);
6764 scanForStrings(
6765 MI_LABEL(tmp)[i], item_pixmaps, &ctx);
6766 }
6767 if (!MI_HAS_HOTKEY(tmp))
6768 {
6769 /* pete@tecc.co.uk */
6770 scanForHotkeys(tmp, i);
6771
6772 if (!MI_HAS_HOTKEY(tmp) &&
6773 *MI_LABEL(tmp)[i] != 0)
6774 {
6775 MI_HOTKEY_COFFSET(tmp) = 0;
6776 MI_HOTKEY_COLUMN(tmp) = i;
6777 MI_HAS_HOTKEY(tmp) = 1;
6778 MI_IS_HOTKEY_AUTOMATIC(tmp) = 1;
6779 }
6780 }
6781 if (*(MI_LABEL(tmp)[i]))
6782 {
6783 MI_HAS_TEXT(tmp) = True;
6784 }
6785 else
6786 {
6787 free(MI_LABEL(tmp)[i]);
6788 MI_LABEL(tmp)[i] = NULL;
6789 }
6790 }
6791 MI_LABEL_STRLEN(tmp)[i] =
6792 (MI_LABEL(tmp)[i]) ? strlen(MI_LABEL(tmp)[i]) : 0;
6793 } /* for */
6794
6795 /*
6796 * Set the type flags
6797 */
6798
6799 if (is_continuation_item)
6800 {
6801 MI_IS_CONTINUATION(tmp) = True;
6802 }
6803 find_func_t(MI_ACTION(tmp), &(MI_FUNC_TYPE(tmp)), NULL);
6804 switch (MI_FUNC_TYPE(tmp))
6805 {
6806 case F_POPUP:
6807 MI_IS_POPUP(tmp) = True;
6808 case F_WINDOWLIST:
6809 case F_STAYSUP:
6810 MI_IS_MENU(tmp) = True;
6811 break;
6812 case F_TITLE:
6813 MI_IS_TITLE(tmp) = True;
6814 break;
6815 default:
6816 break;
6817 }
6818 is_empty = (!MI_HAS_TEXT(tmp) && !MI_HAS_PICTURE(tmp));
6819 if (is_empty)
6820 {
6821 if (MI_FUNC_TYPE(tmp) == F_TEARMENUOFF)
6822 {
6823 MI_IS_TEAR_OFF_BAR(tmp) = 1;
6824 MI_IS_SEPARATOR(tmp) = 0;
6825 }
6826 else
6827 {
6828 MI_IS_TEAR_OFF_BAR(tmp) = 0;
6829 MI_IS_SEPARATOR(tmp) = 1;
6830 }
6831 }
6832 if (MI_IS_SEPARATOR(tmp))
6833 {
6834 /* An empty title is handled like a separator. */
6835 MI_IS_TITLE(tmp) = False;
6836 }
6837 MI_IS_SELECTABLE(tmp) =
6838 ((MI_HAS_TEXT(tmp) || MI_HAS_PICTURE(tmp) ||
6839 MI_IS_TEAR_OFF_BAR(tmp)) && !MI_IS_TITLE(tmp));
6840
6841 /*
6842 * misc stuff
6843 */
6844
6845 MR_ITEMS(mr)++;
6846 MR_IS_UPDATED(mr) = 1;
6847
6848 return;
6849 }
6850
6851 /*
6852 *
6853 * Procedure:
6854 * NewMenuRoot - create a new menu root
6855 *
6856 * Returned Value:
6857 * (MenuRoot *)
6858 *
6859 * Inputs:
6860 * name - the name of the menu root
6861 *
6862 */
NewMenuRoot(char * name)6863 MenuRoot *NewMenuRoot(char *name)
6864 {
6865 MenuRoot *mr;
6866 string_def_t root_strings[] = {
6867 {'@', __scan_for_pixmap},
6868 {'^', __scan_for_color},
6869 {'\0', NULL}};
6870 string_context_t ctx;
6871
6872 mr = (MenuRoot *)safemalloc(sizeof(MenuRoot));
6873 mr->s = (MenuRootStatic *)safemalloc(sizeof(MenuRootStatic));
6874 mr->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic));
6875
6876 memset(mr->s, 0, sizeof(MenuRootStatic));
6877 memset(mr->d, 0, sizeof(MenuRootDynamic));
6878 MR_NEXT_MENU(mr) = Menus.all;
6879 MR_NAME(mr) = safestrdup(name);
6880 MR_WINDOW(mr) = None;
6881 SCTX_SET_MR(ctx,mr);
6882 MR_HAS_SIDECOLOR(mr) = False;
6883 scanForStrings(
6884 MR_NAME(mr), root_strings, &ctx);
6885 MR_STYLE(mr) = menustyle_get_default_style();
6886 MR_ORIGINAL_MENU(mr) = mr;
6887 MR_COPIES(mr) = 1;
6888 MR_IS_UPDATED(mr) = 1;
6889
6890 Menus.all = mr;
6891 return mr;
6892 }
6893
SetMenuCursor(Cursor cursor)6894 void SetMenuCursor(Cursor cursor)
6895 {
6896 MenuRoot *mr;
6897
6898 mr = Menus.all;
6899 for (mr = Menus.all; mr; mr = MR_NEXT_MENU(mr))
6900 {
6901 if (MR_WINDOW(mr))
6902 {
6903 XDefineCursor(dpy, MR_WINDOW(mr), cursor);
6904 }
6905 }
6906
6907 return;
6908 }
6909
UpdateAllMenuStyles(void)6910 void UpdateAllMenuStyles(void)
6911 {
6912 MenuStyle *ms;
6913
6914 for (ms = menustyle_get_default_style(); ms; ms = ST_NEXT_STYLE(ms))
6915 {
6916 menustyle_update(ms);
6917 }
6918
6919 return;
6920 }
6921
UpdateMenuColorset(int cset)6922 void UpdateMenuColorset(int cset)
6923 {
6924 MenuStyle *ms;
6925 FvwmWindow *t;
6926
6927 for (ms = menustyle_get_default_style(); ms; ms = ST_NEXT_STYLE(ms))
6928 {
6929 if ((ST_HAS_MENU_CSET(ms) && ST_CSET_MENU(ms) == cset) ||
6930 (ST_HAS_ACTIVE_CSET(ms) && ST_CSET_ACTIVE(ms) == cset) ||
6931 (ST_HAS_GREYED_CSET(ms) && ST_CSET_GREYED(ms) == cset) ||
6932 (ST_HAS_TITLE_CSET(ms) && ST_CSET_TITLE(ms) == cset))
6933 {
6934 menustyle_update(ms);
6935 }
6936 }
6937
6938 for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
6939 {
6940 MenuRoot *mr = NULL;
6941 MenuStyle *ms = NULL;
6942
6943 if (IS_TEAR_OFF_MENU(t) &&
6944 XFindContext(dpy, FW_W(t), MenuContext, (caddr_t *)&mr) !=
6945 XCNOENT &&
6946 (ms = MR_STYLE(mr)))
6947 {
6948 if (ST_HAS_MENU_CSET(ms) &&
6949 ST_CSET_MENU(ms) == cset)
6950 {
6951 SetWindowBackground(
6952 dpy, MR_WINDOW(mr), MR_WIDTH(mr),
6953 MR_HEIGHT(mr),
6954 &Colorset[ST_CSET_MENU(ms)],
6955 Pdepth,
6956 FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
6957 True);
6958 }
6959 else if ((ST_HAS_ACTIVE_CSET(ms) &&
6960 ST_CSET_ACTIVE(ms) == cset) ||
6961 (ST_HAS_GREYED_CSET(ms) &&
6962 ST_CSET_GREYED(ms) == cset))
6963 {
6964 paint_menu(mr, NULL, NULL);
6965 }
6966 }
6967 }
6968
6969 return;
6970 }
6971
6972 /* This is called by the window list function */
change_mr_menu_style(MenuRoot * mr,char * stylename)6973 void change_mr_menu_style(MenuRoot *mr, char *stylename)
6974 {
6975 MenuStyle *ms = NULL;
6976
6977 ms = menustyle_find(stylename);
6978 if (ms == NULL)
6979 {
6980 return;
6981 }
6982 if (MR_MAPPED_COPIES(mr) != 0)
6983 {
6984 fvwm_msg(ERR,"ChangeMenuStyle", "menu %s is in use",
6985 MR_NAME(mr));
6986 }
6987 else
6988 {
6989 MR_STYLE(mr) = ms;
6990 MR_IS_UPDATED(mr) = 1;
6991 }
6992
6993 return;
6994 }
6995
add_another_menu_item(char * action)6996 void add_another_menu_item(char *action)
6997 {
6998 MenuRoot *mr;
6999 MenuRoot *mrPrior;
7000 char *rest,*item;
7001
7002 mr = FollowMenuContinuations(Scr.last_added_item.item, &mrPrior);
7003 if (mr == NULL)
7004 {
7005 return;
7006 }
7007 if (MR_MAPPED_COPIES(mr) != 0)
7008 {
7009 fvwm_msg(ERR,"add_another_menu_item", "menu is in use");
7010 return;
7011 }
7012 rest = GetNextToken(action,&item);
7013 AddToMenu(mr, item, rest, True /* pixmap scan */, False, False);
7014 if (item)
7015 {
7016 free(item);
7017 }
7018
7019 return;
7020 }
7021
7022 /*
7023 * get_menu_options is used for Menu, Popup and WindowList
7024 * It parses strings matching
7025 *
7026 * [ [context-rectangle] x y ] [special-options] [other arguments]
7027 *
7028 * and returns a pointer to the first part of the input string that doesn't
7029 * match this syntax.
7030 *
7031 * See documentation for a detailed description.
7032 */
get_menu_options(char * action,Window w,FvwmWindow * fw,XEvent * e,MenuRoot * mr,MenuItem * mi,MenuOptions * pops)7033 char *get_menu_options(
7034 char *action, Window w, FvwmWindow *fw, XEvent *e, MenuRoot *mr,
7035 MenuItem *mi, MenuOptions *pops)
7036 {
7037 char *tok = NULL;
7038 char *naction = action;
7039 char *taction;
7040 int x;
7041 int y;
7042 int button;
7043 int width;
7044 int height;
7045 int dummy_int;
7046 float dummy_float;
7047 Bool dummy_flag;
7048 Window context_window = None;
7049 Bool fHasContext, fUseItemOffset, fRectangleContext, fXineramaRoot;
7050 Bool fValidPosHints =
7051 last_saved_pos_hints.flags.is_last_menu_pos_hints_valid;
7052 Bool is_action_empty = False;
7053 Bool once_more = True;
7054 Bool is_icon_context;
7055 rectangle icon_g;
7056
7057 /* If this is set we may want to reverse the position hints, so don't
7058 * sum up the totals right now. This is useful for the SubmenusLeft
7059 * style. */
7060
7061 fXineramaRoot = False;
7062 last_saved_pos_hints.flags.is_last_menu_pos_hints_valid = False;
7063 if (pops == NULL)
7064 {
7065 fvwm_msg(ERR,
7066 "get_menu_options","no MenuOptions pointer passed");
7067 return action;
7068 }
7069
7070 taction = action;
7071 memset(&(pops->flags), 0, sizeof(pops->flags));
7072 pops->flags.has_poshints = 0;
7073 if (!action || *action == 0)
7074 {
7075 is_action_empty = True;
7076 }
7077 while (action != NULL && *action != 0 && once_more)
7078 {
7079 /* ^ just to be able to jump to end of loop without 'goto' */
7080 pops->pos_hints.is_relative = False;
7081 pops->pos_hints.menu_width = 0;
7082 pops->pos_hints.is_menu_relative = False;
7083 /* parse context argument (if present) */
7084 naction = GetNextToken(taction, &tok);
7085 if (!tok)
7086 {
7087 /* no context string */
7088 fHasContext = False;
7089 is_action_empty = True;
7090 break;
7091 }
7092 /* set to False for absolute hints! */
7093 pops->pos_hints.is_relative = True;
7094 fUseItemOffset = False;
7095 fHasContext = True;
7096 fRectangleContext = False;
7097 is_icon_context = False;
7098 if (StrEquals(tok, "context"))
7099 {
7100 if (mi && mr)
7101 {
7102 context_window = MR_WINDOW(mr);
7103 }
7104 else if (fw)
7105 {
7106 if (IS_ICONIFIED(fw))
7107 {
7108 is_icon_context = True;
7109 get_icon_geometry(fw, &icon_g);
7110 context_window = None;
7111 }
7112 else
7113 {
7114 context_window =
7115 FW_W_FRAME(fw);
7116 }
7117 }
7118 else
7119 {
7120 context_window = w;
7121 }
7122 }
7123 else if (StrEquals(tok,"menu"))
7124 {
7125 if (mr)
7126 {
7127 context_window = MR_WINDOW(mr);
7128 pops->pos_hints.is_menu_relative = True;
7129 pops->pos_hints.menu_width = MR_WIDTH(mr);
7130 }
7131 }
7132 else if (StrEquals(tok,"item"))
7133 {
7134 if (mi && mr)
7135 {
7136 context_window = MR_WINDOW(mr);
7137 fUseItemOffset = True;
7138 pops->pos_hints.is_menu_relative = True;
7139 pops->pos_hints.menu_width = MR_WIDTH(mr);
7140 }
7141 }
7142 else if (StrEquals(tok,"icon"))
7143 {
7144 if (fw && IS_ICONIFIED(fw))
7145 {
7146 is_icon_context = True;
7147 get_icon_geometry(fw, &icon_g);
7148 context_window = None;
7149 }
7150 }
7151 else if (StrEquals(tok,"window"))
7152 {
7153 if (fw && !IS_ICONIFIED(fw))
7154 {
7155 context_window = FW_W_FRAME(fw);
7156 }
7157 }
7158 else if (StrEquals(tok,"interior"))
7159 {
7160 if (fw && !IS_ICONIFIED(fw))
7161 {
7162 context_window = FW_W(fw);
7163 }
7164 }
7165 else if (StrEquals(tok,"title"))
7166 {
7167 if (fw)
7168 {
7169 if (IS_ICONIFIED(fw))
7170 {
7171 context_window =
7172 FW_W_ICON_TITLE(fw);
7173 }
7174 else
7175 {
7176 context_window = FW_W_TITLE(fw);
7177 }
7178 }
7179 }
7180 else if (strncasecmp(tok,"button",6) == 0)
7181 {
7182 if (sscanf(&(tok[6]),"%d",&button) != 1 ||
7183 tok[6] == '+' || tok[6] == '-' || button < 0 ||
7184 button > 9)
7185 {
7186 fHasContext = False;
7187 }
7188 else if (fw && !IS_ICONIFIED(fw))
7189 {
7190 button = BUTTON_INDEX(button);
7191 context_window = FW_W_BUTTON(fw, button);
7192 }
7193 }
7194 else if (StrEquals(tok,"root"))
7195 {
7196 context_window = Scr.Root;
7197 pops->pos_hints.is_relative = False;
7198 }
7199 else if (StrEquals(tok,"xineramaroot"))
7200 {
7201 context_window = Scr.Root;
7202 pops->pos_hints.is_relative = False;
7203 fXineramaRoot = True;
7204 }
7205 else if (StrEquals(tok,"mouse"))
7206 {
7207 context_window = None;
7208 }
7209 else if (StrEquals(tok,"rectangle"))
7210 {
7211 int flags;
7212 int screen;
7213 int sx;
7214 int sy;
7215 int sw;
7216 int sh;
7217
7218 /* parse the rectangle */
7219 free(tok);
7220 naction = GetNextToken(naction, &tok);
7221 if (tok == NULL)
7222 {
7223 fvwm_msg(ERR, "get_menu_options",
7224 "missing rectangle geometry");
7225 if (!pops->pos_hints.has_screen_origin)
7226 {
7227 /* xinerama: emergency fallback */
7228 pops->pos_hints.has_screen_origin = 1;
7229 pops->pos_hints.screen_origin_x = 0;
7230 pops->pos_hints.screen_origin_y = 0;
7231 }
7232 return action;
7233 }
7234 flags = FScreenParseGeometryWithScreen(
7235 tok, &x, &y,
7236 (unsigned int*)&width,
7237 (unsigned int*)&height, &screen);
7238 if ((flags & (XValue | YValue)) != (XValue | YValue))
7239 {
7240 free(tok);
7241 fvwm_msg(ERR, "get_menu_options",
7242 "invalid rectangle geometry");
7243 if (!pops->pos_hints.has_screen_origin)
7244 {
7245 /* xinerama: emergency fallback */
7246 pops->pos_hints.has_screen_origin = 1;
7247 pops->pos_hints.screen_origin_x = 0;
7248 pops->pos_hints.screen_origin_y = 0;
7249 }
7250 return action;
7251 }
7252 pops->pos_hints.has_screen_origin = 1;
7253 FScreenGetScrRect(NULL, screen, &sx, &sy, &sw, &sh);
7254 pops->pos_hints.screen_origin_x = sx;
7255 pops->pos_hints.screen_origin_y = sy;
7256 if (!(flags & WidthValue))
7257 {
7258 width = 1;
7259 }
7260 if (!(flags & HeightValue))
7261 {
7262 height = 1;
7263 }
7264 x += sx;
7265 y += sy;
7266 if (flags & XNegative)
7267 {
7268 /* x is negative */
7269 x = sx + sw + x;
7270 }
7271 if (flags & YNegative)
7272 {
7273 /* y is negative */
7274 y = sy + sh + y;
7275 }
7276 pops->pos_hints.is_relative = False;
7277 fRectangleContext = True;
7278 }
7279 else if (StrEquals(tok,"this"))
7280 {
7281 MenuRoot *dummy_mr;
7282
7283 context_window = w;
7284 if (XFindContext(
7285 dpy, w, MenuContext,
7286 (caddr_t *)&dummy_mr) != XCNOENT)
7287 {
7288 if (mr)
7289 {
7290 /* the parent menu */
7291 pops->pos_hints.is_menu_relative =
7292 True;
7293 pops->pos_hints.menu_width =
7294 MR_WIDTH(mr);
7295 }
7296 }
7297 }
7298 else
7299 {
7300 /* no context string */
7301 fHasContext = False;
7302 }
7303
7304 if (tok)
7305 {
7306 free(tok);
7307 }
7308 if (fHasContext)
7309 {
7310 taction = naction;
7311 }
7312 else
7313 {
7314 naction = action;
7315 }
7316
7317 if (fRectangleContext)
7318 {
7319 if (!pops->pos_hints.has_screen_origin)
7320 {
7321 /* xinerama: use global screen as reference */
7322 pops->pos_hints.has_screen_origin = 1;
7323 pops->pos_hints.screen_origin_x = -1;
7324 pops->pos_hints.screen_origin_y = -1;
7325 }
7326 /* nothing else to do */
7327 }
7328 else if (!is_icon_context &&
7329 (!fHasContext || !context_window ||
7330 !XGetGeometry(
7331 dpy, context_window, &JunkRoot, &JunkX,
7332 &JunkY, (unsigned int*)&width,
7333 (unsigned int*)&height,
7334 (unsigned int*)&JunkBW,
7335 (unsigned int*)&JunkDepth) ||
7336 !XTranslateCoordinates(
7337 dpy, context_window, Scr.Root, 0, 0, &x, &y,
7338 &JunkChild)))
7339 {
7340 /* no window or could not get geometry */
7341 if (FQueryPointer(
7342 dpy,Scr.Root, &JunkRoot, &JunkChild, &x,
7343 &y, &JunkX, &JunkY, &JunkMask) == False)
7344 {
7345 /* pointer is on a different screen - that's
7346 * okay here */
7347 x = 0;
7348 y = 0;
7349 }
7350 width = height = 1;
7351 if (!pops->pos_hints.has_screen_origin)
7352 {
7353 /* xinerama: use screen with pinter as
7354 * reference */
7355 pops->pos_hints.has_screen_origin = 1;
7356 pops->pos_hints.screen_origin_x = x;
7357 pops->pos_hints.screen_origin_y = y;
7358 }
7359 }
7360 else
7361 {
7362 if (is_icon_context)
7363 {
7364 x = icon_g.x;
7365 y = icon_g.y;
7366 width = icon_g.width;
7367 height = icon_g.height;
7368 }
7369 /* we have a context window */
7370 if (fUseItemOffset)
7371 {
7372 y += MI_Y_OFFSET(mi);
7373 height = MI_HEIGHT(mi);
7374 }
7375 if (!pops->pos_hints.has_screen_origin)
7376 {
7377 pops->pos_hints.has_screen_origin = 1;
7378 if (fXineramaRoot)
7379 {
7380 /* use whole screen */
7381 pops->pos_hints.screen_origin_x = -1;
7382 pops->pos_hints.screen_origin_y = -1;
7383 }
7384 else if (context_window == Scr.Root)
7385 {
7386 /* xinerama: use screen that contains
7387 * the window center as reference */
7388 if (!fev_get_evpos_or_query(
7389 dpy, context_window, e,
7390 &pops->pos_hints.
7391 screen_origin_x,
7392 &pops->pos_hints.
7393 screen_origin_y))
7394 {
7395 pops->pos_hints.
7396 screen_origin_x = 0;
7397 pops->pos_hints.
7398 screen_origin_y = 0;
7399 }
7400 else
7401 {
7402 fscreen_scr_arg fscr;
7403
7404 fscr.xypos.x = pops->pos_hints.
7405 screen_origin_x;
7406 fscr.xypos.y = pops->pos_hints.
7407 screen_origin_y;
7408 FScreenGetScrRect(
7409 &fscr, FSCREEN_XYPOS,
7410 &x, &y, &width,
7411 &height);
7412 }
7413 }
7414 else
7415 {
7416 /* xinerama: use screen that contains
7417 * the window center as reference */
7418 pops->pos_hints.screen_origin_x =
7419 JunkX + width / 2;
7420 pops->pos_hints.screen_origin_y =
7421 JunkY + height / 2;
7422 }
7423 }
7424 }
7425
7426 /* parse position arguments */
7427 taction = get_one_menu_position_argument(
7428 naction, x, width, &(pops->pos_hints.x),
7429 &(pops->pos_hints.x_offset),
7430 &(pops->pos_hints.x_factor),
7431 &(pops->pos_hints.context_x_factor),
7432 &pops->pos_hints.is_menu_relative);
7433 if (pops->pos_hints.is_menu_relative)
7434 {
7435 pops->pos_hints.x = x;
7436 if (pops->pos_hints.menu_width == 0 && mr)
7437 {
7438 pops->pos_hints.menu_width = MR_WIDTH(mr);
7439 }
7440 }
7441 naction = get_one_menu_position_argument(
7442 taction, y, height, &(pops->pos_hints.y), &dummy_int,
7443 &(pops->pos_hints.y_factor), &dummy_float,
7444 &dummy_flag);
7445 if (naction == taction)
7446 {
7447 /* argument is missing or invalid */
7448 if (fHasContext)
7449 {
7450 fvwm_msg(ERR, "get_menu_options",
7451 "invalid position arguments");
7452 }
7453 naction = action;
7454 taction = action;
7455 break;
7456 }
7457 taction = naction;
7458 pops->flags.has_poshints = 1;
7459 if (fValidPosHints == True &&
7460 pops->pos_hints.is_relative == True)
7461 {
7462 pops->pos_hints = last_saved_pos_hints.pos_hints;
7463 }
7464 /* we want to do this only once */
7465 once_more = False;
7466 } /* while */
7467 if (is_action_empty)
7468 {
7469 if (!pops->pos_hints.has_screen_origin)
7470 {
7471 pops->pos_hints.has_screen_origin = 1;
7472 if (!fev_get_evpos_or_query(
7473 dpy, Scr.Root, e,
7474 &pops->pos_hints.screen_origin_x,
7475 &pops->pos_hints.screen_origin_y))
7476 {
7477 pops->pos_hints.screen_origin_x = 0;
7478 pops->pos_hints.screen_origin_y = 0;
7479 }
7480 }
7481 }
7482
7483 if (!pops->flags.has_poshints && fValidPosHints)
7484 {
7485 pops->flags.has_poshints = 1;
7486 pops->pos_hints = last_saved_pos_hints.pos_hints;
7487 pops->pos_hints.is_relative = False;
7488 }
7489
7490 action = naction;
7491 /* to keep Purify silent */
7492 pops->flags.do_select_in_place = 0;
7493 /* parse additional options */
7494 while (naction && *naction)
7495 {
7496 naction = GetNextToken(action, &tok);
7497 if (!tok)
7498 {
7499 break;
7500 }
7501 if (StrEquals(tok, "WarpTitle"))
7502 {
7503 pops->flags.do_warp_title = 1;
7504 pops->flags.do_not_warp = 0;
7505 }
7506 else if (StrEquals(tok, "NoWarp"))
7507 {
7508 pops->flags.do_warp_title = 0;
7509 pops->flags.do_not_warp = 1;
7510 }
7511 else if (StrEquals(tok, "Fixed"))
7512 {
7513 pops->flags.is_fixed = 1;
7514 }
7515 else if (StrEquals(tok, "SelectInPlace"))
7516 {
7517 pops->flags.do_select_in_place = 1;
7518 }
7519 else if (StrEquals(tok, "SelectWarp"))
7520 {
7521 pops->flags.do_warp_on_select = 1;
7522 }
7523 else if (StrEquals(tok, "TearOffImmediately"))
7524 {
7525 pops->flags.do_tear_off_immediately = 1;
7526 }
7527 else
7528 {
7529 free (tok);
7530 break;
7531 }
7532 action = naction;
7533 free (tok);
7534 }
7535 if (!pops->flags.do_select_in_place)
7536 {
7537 pops->flags.do_warp_on_select = 0;
7538 }
7539
7540 return action;
7541 }
7542
7543 /* ---------------------------- new menu loop code ------------------------- */
7544
7545 #if 0
7546 /*!!!*/
7547 typedef enum
7548 {
7549 MTR_XEVENT = 0x1,
7550 MTR_FAKE_ENTER_ITEM = 0x2,
7551 MTR_PROPAGATE_XEVENT_UP = 0x4,
7552 MTR_PROPAGATE_XEVENT_DOWN = 0x8,
7553 MTR_POPUP_TIMEOUT = 0x10,
7554 MTR_POPDOWN_TIMEOUT = 0x20
7555 } mloop_trigger_type_t;
7556
7557 typedef_struct
7558 {
7559 mloop_trigger_type_t type;
7560 XEvent trigger_ev;
7561 int ticks_passed;
7562 } mloop_trigger_t;
7563
7564 typedef_struct
7565 {
7566 int popup_10ms;
7567 int popdown_10ms;
7568 } mloop_timeouts_t;
7569
7570 typedef struct
7571 {
7572 MenuRoot *current_menu;
7573 XEvent *in_ev;
7574 struct
7575 {
7576 unsigned do_fake_enter_item : 1;
7577 unsigned do_propagete_event_up : 1;
7578 unsigned do_propagete_event_down : 1;
7579 } flags;
7580 } mloop_get_trigger_input_t;
7581
7582 /*!!!static*/
7583 mloop_trigger_type_t __mloop_get_trigger(
7584 mloop_trigger_t *ret_trigger, mloop_timeouts_t *io_timeouts,
7585 const mloop_get_trigger_input_t * const in,
7586 {
7587 if (in_out->in_flags->do_propagate_event_down)
7588 {
7589 return MTR_PROPAGATE_XEVENT_DOWN;
7590 }
7591 else if (in_out->in_flags->do_propagate_event_up)
7592 {
7593 if (a != b)
7594 {
7595 return MTR_PROPAGATE_XEVENT_UP;
7596 }
7597 else
7598 {
7599 }
7600 /*!!!return propagate up*/
7601 /*!!!*/
7602 }
7603 /*!!!read event or wait for timeout*/
7604 while (0/*!!!not finished*/)
7605 {
7606 /*!!!rc = 0*/
7607 if (0/*!!!wait for tiomeout*/)
7608 {
7609 /*!!!check for event*/
7610 }
7611 else
7612 {
7613 /*!!!block for event*/
7614 }
7615 if (0/*got event*/)
7616 {
7617 /*!!!rc = MTR_XEVENT*/
7618 }
7619 if (0/*!!!popup timed out;break*/)
7620 {
7621 /*!!!rc = MTR_POPUP;break*/
7622 }
7623 if (0/*!!!popdown timed out;break*/)
7624 {
7625 /*!!!rc = MTR_POPDOWN;break*/
7626 }
7627 /*!!!sleep*/
7628 }
7629 if (0/*!!!rc == MTR_XEVENT && evtype == MotionNotify*/)
7630 {
7631 /*!!!eat up further MotionNotify events*/
7632 }
7633
7634 return 0/*!!!rc*/;
7635 }
7636
7637 /*!!!static*/
7638 void __menu_loop_new(
7639 MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp)
7640 {
7641 mloop_evh_input_t mei;
7642 #if 0
7643 mloop_ret_code_t mloop_ret;
7644 #endif
7645 mloop_evh_data_t med;
7646 mloop_static_info_t msi;
7647 MenuOptions mops;
7648 Bool is_finished;
7649
7650 /*!!!init menu loop*/
7651 __mloop_init(pmp, pmret, &mei, &med, &msi, &mops);
7652 for (is_finished = False; !is_finished; )
7653 {
7654 mloop_trigger_type_t mtr;
7655
7656 mtr = __mloop_get_trigger(
7657 pmp, pmret, &mei, &med, &msi);
7658 switch (mtr)
7659 {
7660 case MTR_XEVENT:
7661 /*!!!handle event*/
7662 break;
7663 case MTR_FAKE_ENTER_ITEM:
7664 /*!!!fake enter item*/
7665 break;
7666 case MTR_PROPAGATE_XEVENT_UP:
7667 /*!!!handle propagation*/
7668 break;
7669 case MTR_PROPAGATE_XEVENT_DOWN:
7670 /*!!!handle propagation*/
7671 break;
7672 case MTR_POPUP_TIMEOUT:
7673 /*!!!handle popup*/
7674 break;
7675 case MTR_POPDOWN_TIMEOUT:
7676 /*!!!handle popdown*/
7677 break;
7678 }
7679
7680
7681
7682
7683
7684 #if 0
7685 mloop_ret = __mloop_handle_event(
7686 pmp, pmret, pdkp, &mei, &med, &msi);
7687 switch (mloop_ret)
7688 {
7689 case MENU_MLOOP_RET_LOOP:
7690 continue;
7691 case MENU_MLOOP_RET_END:
7692 is_finished = True;
7693 break;
7694 default:
7695 break;
7696 }
7697 /* Now handle new menu items, whether it is from a
7698 * keypress or a pointer motion event. */
7699 if (med.mi != NULL)
7700 {
7701 mloop_ret = __mloop_handle_action_with_mi(
7702 pmp, pmret, pdkp, &mei, &med, &msi,
7703 &mops);
7704 }
7705 else
7706 {
7707 mloop_ret = __mloop_handle_action_without_mi(
7708 pmp, pmret, pdkp, &mei, &med, &msi,
7709 &mops);
7710 }
7711 if (mloop_ret == MENU_MLOOP_RET_END)
7712 {
7713 is_finished = True;
7714 }
7715 XFlush(dpy);
7716 #endif
7717 }
7718 __mloop_exit(pmp, pmret, pdkp, &mei, &med, &msi, &mops);
7719
7720 return;
7721 }
7722 #endif
7723