Require generic again.
[emacs.git] / lwlib / xlwmenu.c
blobb128e6cefb1682f0565a092ab94ba7ba8ee8b0df
1 /* Implements a lightweight menubar widget.
2 Copyright (C) 1992 Lucid, Inc.
3 Copyright (C) 2002, 2005 Free Software Foundation, Inc.
5 This file is part of the Lucid Widget Library.
7 The Lucid Widget Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
12 The Lucid Widget Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Emacs; see the file COPYING. If not, write to the
19 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA. */
22 /* Created by devin@lucid.com */
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include "lisp.h"
30 #include <stdio.h>
32 #include <sys/types.h>
33 #if (defined __sun) && !(defined SUNOS41)
34 #define SUNOS41
35 #include <X11/Xos.h>
36 #undef SUNOS41
37 #else
38 #include <X11/Xos.h>
39 #endif
40 #include <X11/IntrinsicP.h>
41 #include <X11/ObjectP.h>
42 #include <X11/StringDefs.h>
43 #include <X11/cursorfont.h>
44 #include "xlwmenuP.h"
46 #ifdef emacs
48 /* Defined in xfns.c. When config.h defines `static' as empty, we get
49 redefinition errors when gray_bitmap is included more than once, so
50 we're referring to the one include in xfns.c here. */
52 extern int gray_bitmap_width;
53 extern int gray_bitmap_height;
54 extern char *gray_bitmap_bits;
56 /* Defined in xterm.c. */
57 extern int x_alloc_nearest_color_for_widget __P ((Widget, Colormap, XColor*));
58 extern int x_alloc_lighter_color_for_widget __P ((Widget, Display*, Colormap,
59 unsigned long *,
60 double, int));
61 extern int x_catch_errors __P ((Display*));
62 extern int x_uncatch_errors __P ((Display*, int));
63 extern int x_had_errors_p __P ((Display*));
64 extern int x_clear_errors __P ((Display*));
65 extern unsigned long x_copy_dpy_color __P ((Display *, Colormap,
66 unsigned long));
68 /* Defined in xfaces.c. */
69 extern void x_free_dpy_colors __P ((Display *, Screen *, Colormap,
70 unsigned long *pixels, int npixels));
71 #else /* not emacs */
73 #include <X11/bitmaps/gray>
74 #define gray_bitmap_width gray_width
75 #define gray_bitmap_height gray_height
76 #define gray_bitmap_bits gray_bits
78 #endif /* not emacs */
80 static int pointer_grabbed;
81 static XEvent menu_post_event;
83 XFontStruct *xlwmenu_default_font;
85 static char
86 xlwMenuTranslations [] =
87 "<BtnDown>: start()\n\
88 <Motion>: drag()\n\
89 <BtnUp>: select()\n\
90 <Key>Shift_L: nothing()\n\
91 <Key>Shift_R: nothing()\n\
92 <Key>Meta_L: nothing()\n\
93 <Key>Meta_R: nothing()\n\
94 <Key>Control_L: nothing()\n\
95 <Key>Control_R: nothing()\n\
96 <Key>Hyper_L: nothing()\n\
97 <Key>Hyper_R: nothing()\n\
98 <Key>Super_L: nothing()\n\
99 <Key>Super_R: nothing()\n\
100 <Key>Alt_L: nothing()\n\
101 <Key>Alt_R: nothing()\n\
102 <Key>Caps_Lock: nothing()\n\
103 <Key>Shift_Lock: nothing()\n\
104 <KeyUp>Shift_L: nothing()\n\
105 <KeyUp>Shift_R: nothing()\n\
106 <KeyUp>Meta_L: nothing()\n\
107 <KeyUp>Meta_R: nothing()\n\
108 <KeyUp>Control_L: nothing()\n\
109 <KeyUp>Control_R: nothing()\n\
110 <KeyUp>Hyper_L: nothing()\n\
111 <KeyUp>Hyper_R: nothing()\n\
112 <KeyUp>Super_L: nothing()\n\
113 <KeyUp>Super_R: nothing()\n\
114 <KeyUp>Alt_L: nothing()\n\
115 <KeyUp>Alt_R: nothing()\n\
116 <KeyUp>Caps_Lock: nothing()\n\
117 <KeyUp>Shift_Lock:nothing()\n\
118 <Key>Return: select()\n\
119 <Key>Down: down()\n\
120 <Key>Up: up()\n\
121 <Key>Left: left()\n\
122 <Key>Right: right()\n\
123 <Key>: key()\n\
124 <KeyUp>: key()\n\
127 /* FIXME: Space should toggle toggleable menu item but not remove the menu
128 so you can toggle the next one without entering the menu again. */
130 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
132 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
134 #define offset(field) XtOffset(XlwMenuWidget, field)
135 static XtResource
136 xlwMenuResources[] =
138 #ifdef HAVE_X_I18N
139 {XtNfont, XtCFont, XtRFontSet, sizeof(XFontSet),
140 offset(menu.font), XtRString, "XtDefaultFontSet"},
141 #else
142 {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
143 offset(menu.font), XtRString, "XtDefaultFont"},
144 #endif
145 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
146 offset(menu.foreground), XtRString, "XtDefaultForeground"},
147 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
148 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
149 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
150 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
151 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
152 offset(menu.margin), XtRImmediate, (XtPointer)1},
153 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
154 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
155 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
156 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
157 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
158 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
160 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
161 sizeof (Dimension), offset (menu.shadow_thickness),
162 XtRImmediate, (XtPointer)1},
163 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
164 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
165 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
166 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
167 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
168 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
169 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
170 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
172 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
173 offset(menu.open), XtRCallback, (XtPointer)NULL},
174 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
175 offset(menu.select), XtRCallback, (XtPointer)NULL},
176 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
177 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
178 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
179 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
180 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
181 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
182 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
183 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
185 #undef offset
187 static Boolean XlwMenuSetValues();
188 static void XlwMenuRealize();
189 static void XlwMenuRedisplay();
190 static void XlwMenuResize();
191 static void XlwMenuInitialize();
192 static void XlwMenuRedisplay();
193 static void XlwMenuDestroy();
194 static void XlwMenuClassInitialize();
195 static void Start();
196 static void Drag();
197 static void Down();
198 static void Up();
199 static void Left();
200 static void Right();
201 static void Select();
202 static void Key();
203 static void Nothing();
204 static int separator_height __P ((enum menu_separator));
205 static void pop_up_menu __P ((XlwMenuWidget, XButtonPressedEvent *));
208 static XtActionsRec
209 xlwMenuActionsList [] =
211 {"start", Start},
212 {"drag", Drag},
213 {"down", Down},
214 {"up", Up},
215 {"left", Left},
216 {"right", Right},
217 {"select", Select},
218 {"key", Key},
219 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
220 {"nothing", Nothing},
223 #define SuperClass ((CoreWidgetClass)&coreClassRec)
225 XlwMenuClassRec xlwMenuClassRec =
227 { /* CoreClass fields initialization */
228 (WidgetClass) SuperClass, /* superclass */
229 "XlwMenu", /* class_name */
230 sizeof(XlwMenuRec), /* size */
231 XlwMenuClassInitialize, /* class_initialize */
232 NULL, /* class_part_initialize */
233 FALSE, /* class_inited */
234 XlwMenuInitialize, /* initialize */
235 NULL, /* initialize_hook */
236 XlwMenuRealize, /* realize */
237 xlwMenuActionsList, /* actions */
238 XtNumber(xlwMenuActionsList), /* num_actions */
239 xlwMenuResources, /* resources */
240 XtNumber(xlwMenuResources), /* resource_count */
241 NULLQUARK, /* xrm_class */
242 TRUE, /* compress_motion */
243 XtExposeCompressMaximal, /* compress_exposure */
244 TRUE, /* compress_enterleave */
245 FALSE, /* visible_interest */
246 XlwMenuDestroy, /* destroy */
247 XlwMenuResize, /* resize */
248 XlwMenuRedisplay, /* expose */
249 XlwMenuSetValues, /* set_values */
250 NULL, /* set_values_hook */
251 XtInheritSetValuesAlmost, /* set_values_almost */
252 NULL, /* get_values_hook */
253 NULL, /* accept_focus */
254 XtVersion, /* version */
255 NULL, /* callback_private */
256 xlwMenuTranslations, /* tm_table */
257 XtInheritQueryGeometry, /* query_geometry */
258 XtInheritDisplayAccelerator, /* display_accelerator */
259 NULL /* extension */
260 }, /* XlwMenuClass fields initialization */
262 0 /* dummy */
266 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
268 int submenu_destroyed;
270 /* For debug, if installation-directory is non-nil this is not an installed
271 Emacs. In that case we do not grab the keyboard to make it easier to
272 debug. */
273 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
275 static int next_release_must_exit;
277 \f/* Utilities */
279 /* Ungrab pointer and keyboard */
280 static void
281 ungrab_all (w, ungrabtime)
282 Widget w;
283 Time ungrabtime;
285 XtUngrabPointer (w, ungrabtime);
286 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
289 /* Like abort, but remove grabs from widget W before. */
291 static void
292 abort_gracefully (w)
293 Widget w;
295 if (XtIsShell (XtParent (w)))
296 XtRemoveGrab (w);
297 ungrab_all (w, CurrentTime);
298 abort ();
301 static void
302 push_new_stack (mw, val)
303 XlwMenuWidget mw;
304 widget_value* val;
306 if (!mw->menu.new_stack)
308 mw->menu.new_stack_length = 10;
309 mw->menu.new_stack =
310 (widget_value**)XtCalloc (mw->menu.new_stack_length,
311 sizeof (widget_value*));
313 else if (mw->menu.new_depth == mw->menu.new_stack_length)
315 mw->menu.new_stack_length *= 2;
316 mw->menu.new_stack =
317 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
318 mw->menu.new_stack_length * sizeof (widget_value*));
320 mw->menu.new_stack [mw->menu.new_depth++] = val;
323 static void
324 pop_new_stack_if_no_contents (mw)
325 XlwMenuWidget mw;
327 if (mw->menu.new_depth > 1)
329 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
330 mw->menu.new_depth -= 1;
334 static void
335 make_old_stack_space (mw, n)
336 XlwMenuWidget mw;
337 int n;
339 if (!mw->menu.old_stack)
341 mw->menu.old_stack_length = 10;
342 mw->menu.old_stack =
343 (widget_value**)XtCalloc (mw->menu.old_stack_length,
344 sizeof (widget_value*));
346 else if (mw->menu.old_stack_length < n)
348 mw->menu.old_stack_length *= 2;
349 mw->menu.old_stack =
350 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
351 mw->menu.old_stack_length * sizeof (widget_value*));
355 \f/* Size code */
357 string_width (mw, s)
358 XlwMenuWidget mw;
359 char *s;
361 #ifdef HAVE_X_I18N
362 XRectangle ink, logical;
363 XmbTextExtents (mw->menu.font, s, strlen (s), &ink, &logical);
364 return logical.width;
365 #else
366 XCharStruct xcs;
367 int drop;
369 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
370 return xcs.width;
371 #endif
374 #ifdef HAVE_X_I18N
375 #define MENU_FONT_HEIGHT(mw) \
376 ((mw)->menu.font_extents->max_logical_extent.height)
377 #define MENU_FONT_ASCENT(mw) \
378 (- (mw)->menu.font_extents->max_logical_extent.y)
379 #else
380 #define MENU_FONT_HEIGHT(mw) \
381 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
382 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
383 #endif
385 static int
386 arrow_width (mw)
387 XlwMenuWidget mw;
389 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
392 /* Return the width of toggle buttons of widget MW. */
394 static int
395 toggle_button_width (mw)
396 XlwMenuWidget mw;
398 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
402 /* Return the width of radio buttons of widget MW. */
404 static int
405 radio_button_width (mw)
406 XlwMenuWidget mw;
408 return toggle_button_width (mw) * 1.41;
412 static XtResource
413 nameResource[] =
415 {"labelString", "LabelString", XtRString, sizeof(String),
416 0, XtRImmediate, 0},
419 static char*
420 resource_widget_value (mw, val)
421 XlwMenuWidget mw;
422 widget_value *val;
424 if (!val->toolkit_data)
426 char* resourced_name = NULL;
427 char* complete_name;
428 XtGetSubresources ((Widget) mw,
429 (XtPointer) &resourced_name,
430 val->name, val->name,
431 nameResource, 1, NULL, 0);
432 if (!resourced_name)
433 resourced_name = val->name;
434 if (!val->value)
436 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
437 strcpy (complete_name, resourced_name);
439 else
441 int complete_length =
442 strlen (resourced_name) + strlen (val->value) + 2;
443 complete_name = XtMalloc (complete_length);
444 *complete_name = 0;
445 strcat (complete_name, resourced_name);
446 strcat (complete_name, " ");
447 strcat (complete_name, val->value);
450 val->toolkit_data = complete_name;
451 val->free_toolkit_data = True;
453 return (char*)val->toolkit_data;
456 /* Returns the sizes of an item */
457 static void
458 size_menu_item (mw, val, horizontal_p, label_width, rest_width, button_width,
459 height)
460 XlwMenuWidget mw;
461 widget_value* val;
462 int horizontal_p;
463 int* label_width;
464 int* rest_width;
465 int* button_width;
466 int* height;
468 enum menu_separator separator;
470 if (lw_separator_p (val->name, &separator, 0))
472 *height = separator_height (separator);
473 *label_width = 1;
474 *rest_width = 0;
475 *button_width = 0;
477 else
479 *height = MENU_FONT_HEIGHT (mw)
480 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
482 *label_width =
483 string_width (mw, resource_widget_value (mw, val))
484 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
486 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
487 if (!horizontal_p)
489 if (val->contents)
490 /* Add width of the arrow displayed for submenus. */
491 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
492 else if (val->key)
493 /* Add width of key equivalent string. */
494 *rest_width += (string_width (mw, val->key)
495 + mw->menu.arrow_spacing);
497 if (val->button_type == BUTTON_TYPE_TOGGLE)
498 *button_width = (toggle_button_width (mw)
499 + mw->menu.horizontal_spacing);
500 else if (val->button_type == BUTTON_TYPE_RADIO)
501 *button_width = (radio_button_width (mw)
502 + mw->menu.horizontal_spacing);
507 static void
508 size_menu (mw, level)
509 XlwMenuWidget mw;
510 int level;
512 unsigned int label_width = 0;
513 int rest_width = 0;
514 int button_width = 0;
515 int max_rest_width = 0;
516 int max_button_width = 0;
517 unsigned int height = 0;
518 int horizontal_p = mw->menu.horizontal && (level == 0);
519 widget_value* val;
520 window_state* ws;
522 if (level >= mw->menu.old_depth)
523 abort_gracefully ((Widget) mw);
525 ws = &mw->menu.windows [level];
526 ws->width = 0;
527 ws->height = 0;
528 ws->label_width = 0;
529 ws->button_width = 0;
531 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
533 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
534 &button_width, &height);
535 if (horizontal_p)
537 ws->width += label_width + rest_width;
538 if (height > ws->height)
539 ws->height = height;
541 else
543 if (label_width > ws->label_width)
544 ws->label_width = label_width;
545 if (rest_width > max_rest_width)
546 max_rest_width = rest_width;
547 if (button_width > max_button_width)
548 max_button_width = button_width;
549 ws->height += height;
553 if (horizontal_p)
554 ws->label_width = ws->button_width = 0;
555 else
557 ws->width = ws->label_width + max_rest_width + max_button_width;
558 ws->button_width = max_button_width;
561 ws->width += 2 * mw->menu.shadow_thickness;
562 ws->height += 2 * mw->menu.shadow_thickness;
564 if (horizontal_p)
566 ws->width += 2 * mw->menu.margin;
567 ws->height += 2 * mw->menu.margin;
572 \f/* Display code */
574 static void
575 draw_arrow (mw, window, gc, x, y, width, down_p)
576 XlwMenuWidget mw;
577 Window window;
578 GC gc;
579 int x;
580 int y;
581 int width;
582 int down_p;
584 Display *dpy = XtDisplay (mw);
585 GC top_gc = mw->menu.shadow_top_gc;
586 GC bottom_gc = mw->menu.shadow_bottom_gc;
587 int thickness = mw->menu.shadow_thickness;
588 int height = width;
589 XPoint pt[10];
590 /* alpha = atan (0.5)
591 factor = (1 + sin (alpha)) / cos (alpha) */
592 double factor = 1.62;
593 int thickness2 = thickness * factor;
595 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
597 if (down_p)
599 GC temp;
600 temp = top_gc;
601 top_gc = bottom_gc;
602 bottom_gc = temp;
605 pt[0].x = x;
606 pt[0].y = y + height;
607 pt[1].x = x + thickness;
608 pt[1].y = y + height - thickness2;
609 pt[2].x = x + thickness2;
610 pt[2].y = y + thickness2;
611 pt[3].x = x;
612 pt[3].y = y;
613 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
615 pt[0].x = x;
616 pt[0].y = y;
617 pt[1].x = x + thickness;
618 pt[1].y = y + thickness2;
619 pt[2].x = x + width - thickness2;
620 pt[2].y = y + height / 2;
621 pt[3].x = x + width;
622 pt[3].y = y + height / 2;
623 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
625 pt[0].x = x;
626 pt[0].y = y + height;
627 pt[1].x = x + thickness;
628 pt[1].y = y + height - thickness2;
629 pt[2].x = x + width - thickness2;
630 pt[2].y = y + height / 2;
631 pt[3].x = x + width;
632 pt[3].y = y + height / 2;
633 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
638 static void
639 draw_shadow_rectangle (mw, window, x, y, width, height, erase_p, down_p)
640 XlwMenuWidget mw;
641 Window window;
642 int x;
643 int y;
644 int width;
645 int height;
646 int erase_p;
647 int down_p;
649 Display *dpy = XtDisplay (mw);
650 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
651 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
652 int thickness = mw->menu.shadow_thickness;
653 XPoint points [4];
655 if (!erase_p && down_p)
657 GC temp;
658 temp = top_gc;
659 top_gc = bottom_gc;
660 bottom_gc = temp;
663 points [0].x = x;
664 points [0].y = y;
665 points [1].x = x + width;
666 points [1].y = y;
667 points [2].x = x + width - thickness;
668 points [2].y = y + thickness;
669 points [3].x = x;
670 points [3].y = y + thickness;
671 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
672 points [0].x = x;
673 points [0].y = y + thickness;
674 points [1].x = x;
675 points [1].y = y + height;
676 points [2].x = x + thickness;
677 points [2].y = y + height - thickness;
678 points [3].x = x + thickness;
679 points [3].y = y + thickness;
680 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
681 points [0].x = x + width;
682 points [0].y = y;
683 points [1].x = x + width - thickness;
684 points [1].y = y + thickness;
685 points [2].x = x + width - thickness;
686 points [2].y = y + height - thickness;
687 points [3].x = x + width;
688 points [3].y = y + height - thickness;
689 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
690 points [0].x = x;
691 points [0].y = y + height;
692 points [1].x = x + width;
693 points [1].y = y + height;
694 points [2].x = x + width;
695 points [2].y = y + height - thickness;
696 points [3].x = x + thickness;
697 points [3].y = y + height - thickness;
698 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
702 static void
703 draw_shadow_rhombus (mw, window, x, y, width, height, erase_p, down_p)
704 XlwMenuWidget mw;
705 Window window;
706 int x;
707 int y;
708 int width;
709 int height;
710 int erase_p;
711 int down_p;
713 Display *dpy = XtDisplay (mw);
714 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
715 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
716 int thickness = mw->menu.shadow_thickness;
717 XPoint points [4];
719 if (!erase_p && down_p)
721 GC temp;
722 temp = top_gc;
723 top_gc = bottom_gc;
724 bottom_gc = temp;
727 points [0].x = x;
728 points [0].y = y + height / 2;
729 points [1].x = x + thickness;
730 points [1].y = y + height / 2;
731 points [2].x = x + width / 2;
732 points [2].y = y + thickness;
733 points [3].x = x + width / 2;
734 points [3].y = y;
735 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
736 points [0].x = x + width / 2;
737 points [0].y = y;
738 points [1].x = x + width / 2;
739 points [1].y = y + thickness;
740 points [2].x = x + width - thickness;
741 points [2].y = y + height / 2;
742 points [3].x = x + width;
743 points [3].y = y + height / 2;
744 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
745 points [0].x = x;
746 points [0].y = y + height / 2;
747 points [1].x = x + thickness;
748 points [1].y = y + height / 2;
749 points [2].x = x + width / 2;
750 points [2].y = y + height - thickness;
751 points [3].x = x + width / 2;
752 points [3].y = y + height;
753 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
754 points [0].x = x + width / 2;
755 points [0].y = y + height;
756 points [1].x = x + width / 2;
757 points [1].y = y + height - thickness;
758 points [2].x = x + width - thickness;
759 points [2].y = y + height / 2;
760 points [3].x = x + width;
761 points [3].y = y + height / 2;
762 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
766 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
767 top-left corner of the menu item. SELECTED_P non-zero means the
768 toggle button is selected. */
770 static void
771 draw_toggle (mw, window, x, y, selected_p)
772 XlwMenuWidget mw;
773 Window window;
774 int x, y, selected_p;
776 int width, height;
778 width = toggle_button_width (mw);
779 height = width;
780 x += mw->menu.horizontal_spacing;
781 y += (MENU_FONT_ASCENT (mw) - height) / 2;
782 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
786 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
787 top-left corner of the menu item. SELECTED_P non-zero means the
788 toggle button is selected. */
790 static void
791 draw_radio (mw, window, x, y, selected_p)
792 XlwMenuWidget mw;
793 Window window;
794 int x, y, selected_p;
796 int width, height;
798 width = radio_button_width (mw);
799 height = width;
800 x += mw->menu.horizontal_spacing;
801 y += (MENU_FONT_ASCENT (mw) - height) / 2;
802 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
806 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
807 top-left corner of the menu item. WIDTH is the width of the
808 separator to draw. TYPE is the separator type. */
810 static void
811 draw_separator (mw, window, x, y, width, type)
812 XlwMenuWidget mw;
813 Window window;
814 int x, y, width;
815 enum menu_separator type;
817 Display *dpy = XtDisplay (mw);
818 XGCValues xgcv;
820 switch (type)
822 case SEPARATOR_NO_LINE:
823 break;
825 case SEPARATOR_SINGLE_LINE:
826 XDrawLine (dpy, window, mw->menu.foreground_gc,
827 x, y, x + width, y);
828 break;
830 case SEPARATOR_DOUBLE_LINE:
831 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
832 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
833 break;
835 case SEPARATOR_SINGLE_DASHED_LINE:
836 xgcv.line_style = LineOnOffDash;
837 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
838 XDrawLine (dpy, window, mw->menu.foreground_gc,
839 x, y, x + width, y);
840 xgcv.line_style = LineSolid;
841 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
842 break;
844 case SEPARATOR_DOUBLE_DASHED_LINE:
845 draw_separator (mw, window, x, y, width,
846 SEPARATOR_SINGLE_DASHED_LINE);
847 draw_separator (mw, window, x, y + 2, width,
848 SEPARATOR_SINGLE_DASHED_LINE);
849 break;
851 case SEPARATOR_SHADOW_ETCHED_IN:
852 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
853 x, y, x + width, y);
854 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
855 x, y + 1, x + width, y + 1);
856 break;
858 case SEPARATOR_SHADOW_ETCHED_OUT:
859 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
860 x, y, x + width, y);
861 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
862 x, y + 1, x + width, y + 1);
863 break;
865 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
866 xgcv.line_style = LineOnOffDash;
867 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
868 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
869 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
870 xgcv.line_style = LineSolid;
871 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
872 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
873 break;
875 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
876 xgcv.line_style = LineOnOffDash;
877 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
878 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
879 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
880 xgcv.line_style = LineSolid;
881 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
882 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
883 break;
885 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
886 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
887 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
888 break;
890 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
891 draw_separator (mw, window, x, y, width,
892 SEPARATOR_SHADOW_ETCHED_OUT);
893 draw_separator (mw, window, x, y + 3, width,
894 SEPARATOR_SHADOW_ETCHED_OUT);
895 break;
897 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
898 xgcv.line_style = LineOnOffDash;
899 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
900 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
901 draw_separator (mw, window, x, y, width,
902 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
903 xgcv.line_style = LineSolid;
904 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
905 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
906 break;
908 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
909 xgcv.line_style = LineOnOffDash;
910 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
911 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
912 draw_separator (mw, window, x, y, width,
913 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
914 xgcv.line_style = LineSolid;
915 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
916 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
917 break;
919 default:
920 abort ();
925 /* Return the pixel height of menu separator SEPARATOR. */
927 static int
928 separator_height (separator)
929 enum menu_separator separator;
931 switch (separator)
933 case SEPARATOR_NO_LINE:
934 return 2;
936 case SEPARATOR_SINGLE_LINE:
937 case SEPARATOR_SINGLE_DASHED_LINE:
938 return 1;
940 case SEPARATOR_DOUBLE_LINE:
941 case SEPARATOR_DOUBLE_DASHED_LINE:
942 return 3;
944 case SEPARATOR_SHADOW_ETCHED_IN:
945 case SEPARATOR_SHADOW_ETCHED_OUT:
946 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
947 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
948 return 2;
950 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
951 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
952 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
953 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
954 return 5;
956 default:
957 abort ();
962 /* Display the menu item and increment where.x and where.y to show how large
963 the menu item was. */
965 static void
966 display_menu_item (mw, val, ws, where, highlighted_p, horizontal_p,
967 just_compute_p)
968 XlwMenuWidget mw;
969 widget_value* val;
970 window_state* ws;
971 XPoint* where;
972 Boolean highlighted_p;
973 Boolean horizontal_p;
974 Boolean just_compute_p;
976 GC deco_gc;
977 GC text_gc;
978 int font_height = MENU_FONT_HEIGHT (mw);
979 int font_ascent = MENU_FONT_ASCENT (mw);
980 int shadow = mw->menu.shadow_thickness;
981 int margin = mw->menu.margin;
982 int h_spacing = mw->menu.horizontal_spacing;
983 int v_spacing = mw->menu.vertical_spacing;
984 int label_width;
985 int rest_width;
986 int button_width;
987 int height;
988 int width;
989 enum menu_separator separator;
990 int separator_p = lw_separator_p (val->name, &separator, 0);
992 /* compute the sizes of the item */
993 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
994 &button_width, &height);
996 if (horizontal_p)
997 width = label_width + rest_width;
998 else
1000 label_width = ws->label_width;
1001 width = ws->width - 2 * shadow;
1004 /* Only highlight an enabled item that has a callback. */
1005 if (highlighted_p)
1006 if (!val->enabled || !(val->call_data || val->contents))
1007 highlighted_p = 0;
1009 /* do the drawing. */
1010 if (!just_compute_p)
1012 /* Add the shadow border of the containing menu */
1013 int x = where->x + shadow;
1014 int y = where->y + shadow;
1016 if (horizontal_p)
1018 x += margin;
1019 y += margin;
1022 /* pick the foreground and background GC. */
1023 if (val->enabled)
1024 text_gc = mw->menu.foreground_gc;
1025 else
1026 text_gc = mw->menu.disabled_gc;
1027 deco_gc = mw->menu.foreground_gc;
1029 if (separator_p)
1031 draw_separator (mw, ws->window, x, y, width, separator);
1033 else
1035 int x_offset = x + h_spacing + shadow;
1036 char* display_string = resource_widget_value (mw, val);
1037 draw_shadow_rectangle (mw, ws->window, x, y, width, height, True,
1038 False);
1040 /* Deal with centering a menu title. */
1041 if (!horizontal_p && !val->contents && !val->call_data)
1043 int l = string_width (mw, display_string);
1045 if (width > l)
1046 x_offset = (width - l) >> 1;
1048 else if (!horizontal_p && ws->button_width)
1049 x_offset += ws->button_width;
1052 #ifdef HAVE_X_I18N
1053 XmbDrawString (XtDisplay (mw), ws->window, mw->menu.font,
1054 #else
1055 XDrawString (XtDisplay (mw), ws->window,
1056 #endif
1057 text_gc, x_offset,
1058 y + v_spacing + shadow + font_ascent,
1059 display_string, strlen (display_string));
1061 if (!horizontal_p)
1063 if (val->button_type == BUTTON_TYPE_TOGGLE)
1064 draw_toggle (mw, ws->window, x, y + v_spacing + shadow,
1065 val->selected);
1066 else if (val->button_type == BUTTON_TYPE_RADIO)
1067 draw_radio (mw, ws->window, x, y + v_spacing + shadow,
1068 val->selected);
1070 if (val->contents)
1072 int a_w = arrow_width (mw);
1073 draw_arrow (mw, ws->window, deco_gc,
1074 x + width - a_w
1075 - mw->menu.horizontal_spacing
1076 - mw->menu.shadow_thickness,
1077 y + v_spacing + shadow, a_w,
1078 highlighted_p);
1080 else if (val->key)
1082 #ifdef HAVE_X_I18N
1083 XmbDrawString (XtDisplay (mw), ws->window, mw->menu.font,
1084 #else
1085 XDrawString (XtDisplay (mw), ws->window,
1086 #endif
1087 text_gc,
1088 x + label_width + mw->menu.arrow_spacing,
1089 y + v_spacing + shadow + font_ascent,
1090 val->key, strlen (val->key));
1093 else
1095 XDrawRectangle (XtDisplay (mw), ws->window,
1096 mw->menu.background_gc,
1097 x + shadow, y + shadow,
1098 label_width + h_spacing - 1,
1099 font_height + 2 * v_spacing - 1);
1100 draw_shadow_rectangle (mw, ws->window, x, y, width, height,
1101 True, False);
1104 if (highlighted_p)
1105 draw_shadow_rectangle (mw, ws->window, x, y, width, height, False,
1106 False);
1110 where->x += width;
1111 where->y += height;
1114 static void
1115 display_menu (mw, level, just_compute_p, highlighted_pos, hit, hit_return,
1116 this, that)
1117 XlwMenuWidget mw;
1118 int level;
1119 Boolean just_compute_p;
1120 XPoint* highlighted_pos;
1121 XPoint* hit;
1122 widget_value** hit_return;
1123 widget_value* this;
1124 widget_value* that;
1126 widget_value* val;
1127 widget_value* following_item;
1128 window_state* ws;
1129 XPoint where;
1130 int horizontal_p = mw->menu.horizontal && (level == 0);
1131 int highlighted_p;
1132 int just_compute_this_one_p;
1133 /* This is set nonzero if the element containing HIGHLIGHTED_POS
1134 is disabled, so that we do not return any subsequent element either. */
1135 int no_return = 0;
1136 enum menu_separator separator;
1138 if (level >= mw->menu.old_depth)
1139 abort_gracefully ((Widget) mw);
1141 if (level < mw->menu.old_depth - 1)
1142 following_item = mw->menu.old_stack [level + 1];
1143 else
1144 following_item = NULL;
1146 if (hit)
1147 *hit_return = NULL;
1149 where.x = 0;
1150 where.y = 0;
1152 ws = &mw->menu.windows [level];
1153 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1155 highlighted_p = val == following_item;
1156 if (highlighted_p && highlighted_pos)
1158 if (horizontal_p)
1159 highlighted_pos->x = where.x;
1160 else
1161 highlighted_pos->y = where.y;
1164 just_compute_this_one_p =
1165 just_compute_p || ((this || that) && val != this && val != that);
1167 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1168 just_compute_this_one_p);
1170 if (highlighted_p && highlighted_pos)
1172 if (horizontal_p)
1173 highlighted_pos->y = where.y;
1174 else
1175 highlighted_pos->x = where.x;
1178 if (hit
1179 && !*hit_return
1180 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1181 && !lw_separator_p (val->name, &separator, 0)
1182 && !no_return)
1184 if (val->enabled)
1185 *hit_return = val;
1186 else
1187 no_return = 1;
1190 if (horizontal_p)
1191 where.y = 0;
1192 else
1193 where.x = 0;
1196 if (!just_compute_p)
1197 draw_shadow_rectangle (mw, ws->window, 0, 0, ws->width, ws->height,
1198 False, False);
1201 \f/* Motion code */
1202 static void
1203 set_new_state (mw, val, level)
1204 XlwMenuWidget mw;
1205 widget_value* val;
1206 int level;
1208 int i;
1210 mw->menu.new_depth = 0;
1211 for (i = 0; i < level; i++)
1212 push_new_stack (mw, mw->menu.old_stack [i]);
1213 push_new_stack (mw, val);
1216 static void
1217 make_windows_if_needed (mw, n)
1218 XlwMenuWidget mw;
1219 int n;
1221 int i;
1222 int start_at;
1223 XSetWindowAttributes xswa;
1224 int mask;
1225 Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1226 window_state* windows;
1228 if (mw->menu.windows_length >= n)
1229 return;
1231 xswa.save_under = True;
1232 xswa.override_redirect = True;
1233 xswa.background_pixel = mw->core.background_pixel;
1234 xswa.border_pixel = mw->core.border_pixel;
1235 xswa.event_mask =
1236 ExposureMask | PointerMotionMask | PointerMotionHintMask
1237 | ButtonReleaseMask | ButtonPressMask;
1238 xswa.cursor = mw->menu.cursor_shape;
1239 mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
1240 | CWEventMask | CWCursor;
1242 if (!mw->menu.windows)
1244 mw->menu.windows =
1245 (window_state*)XtMalloc (n * sizeof (window_state));
1246 start_at = 0;
1248 else
1250 mw->menu.windows =
1251 (window_state*)XtRealloc ((char*)mw->menu.windows,
1252 n * sizeof (window_state));
1253 start_at = mw->menu.windows_length;
1255 mw->menu.windows_length = n;
1257 windows = mw->menu.windows;
1259 for (i = start_at; i < n; i++)
1261 windows [i].x = 0;
1262 windows [i].y = 0;
1263 windows [i].width = 1;
1264 windows [i].height = 1;
1265 windows [i].window =
1266 XCreateWindow (XtDisplay (mw), root, 0, 0, 1, 1,
1267 0, 0, CopyFromParent, CopyFromParent, mask, &xswa);
1271 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1274 xlwmenu_window_p (w, window)
1275 Widget w;
1276 Window window;
1278 XlwMenuWidget mw = (XlwMenuWidget) w;
1279 int i;
1281 for (i = 0; i < mw->menu.windows_length; ++i)
1282 if (window == mw->menu.windows[i].window)
1283 break;
1285 return i < mw->menu.windows_length;
1288 /* Make the window fit in the screen */
1289 static void
1290 fit_to_screen (mw, ws, previous_ws, horizontal_p)
1291 XlwMenuWidget mw;
1292 window_state* ws;
1293 window_state* previous_ws;
1294 Boolean horizontal_p;
1296 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1297 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1298 /* 1 if we are unable to avoid an overlap between
1299 this menu and the parent menu in the X dimension. */
1300 int horizontal_overlap = 0;
1302 if (ws->x < 0)
1303 ws->x = 0;
1304 else if (ws->x + ws->width > screen_width)
1306 if (!horizontal_p)
1307 /* The addition of shadow-thickness for a sub-menu's position is
1308 to reflect a similar adjustment when the menu is displayed to
1309 the right of the invoking menu-item; it makes the sub-menu
1310 look more `attached' to the menu-item. */
1311 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1312 else
1313 ws->x = screen_width - ws->width;
1314 if (ws->x < 0)
1316 ws->x = 0;
1317 horizontal_overlap = 1;
1320 /* If we overlap in X, try to avoid overlap in Y. */
1321 if (horizontal_overlap
1322 && ws->y < previous_ws->y + previous_ws->height
1323 && previous_ws->y < ws->y + ws->height)
1325 /* Put this menu right below or right above PREVIOUS_WS
1326 if there's room. */
1327 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1328 ws->y = previous_ws->y + previous_ws->height;
1329 else if (previous_ws->y - ws->height > 0)
1330 ws->y = previous_ws->y - ws->height;
1333 if (ws->y < 0)
1334 ws->y = 0;
1335 else if (ws->y + ws->height > screen_height)
1337 if (horizontal_p)
1338 ws->y = previous_ws->y - ws->height;
1339 else
1340 ws->y = screen_height - ws->height;
1341 if (ws->y < 0)
1342 ws->y = 0;
1346 /* Updates old_stack from new_stack and redisplays. */
1347 static void
1348 remap_menubar (mw)
1349 XlwMenuWidget mw;
1351 int i;
1352 int last_same;
1353 XPoint selection_position;
1354 int old_depth = mw->menu.old_depth;
1355 int new_depth = mw->menu.new_depth;
1356 widget_value** old_stack;
1357 widget_value** new_stack;
1358 window_state* windows;
1359 widget_value* old_selection;
1360 widget_value* new_selection;
1362 /* Check that enough windows and old_stack are ready. */
1363 make_windows_if_needed (mw, new_depth);
1364 make_old_stack_space (mw, new_depth);
1365 windows = mw->menu.windows;
1366 old_stack = mw->menu.old_stack;
1367 new_stack = mw->menu.new_stack;
1369 /* compute the last identical different entry */
1370 for (i = 1; i < old_depth && i < new_depth; i++)
1371 if (old_stack [i] != new_stack [i])
1372 break;
1373 last_same = i - 1;
1375 /* Memorize the previously selected item to be able to refresh it */
1376 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1377 if (old_selection && !old_selection->enabled)
1378 old_selection = NULL;
1379 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1380 if (new_selection && !new_selection->enabled)
1381 new_selection = NULL;
1383 /* Call callback when the hightlighted item changes. */
1384 if (old_selection || new_selection)
1385 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1386 (XtPointer) new_selection);
1388 /* updates old_state from new_state. It has to be done now because
1389 display_menu (called below) uses the old_stack to know what to display. */
1390 for (i = last_same + 1; i < new_depth; i++)
1391 old_stack [i] = new_stack [i];
1392 mw->menu.old_depth = new_depth;
1394 /* refresh the last selection */
1395 selection_position.x = 0;
1396 selection_position.y = 0;
1397 display_menu (mw, last_same, new_selection == old_selection,
1398 &selection_position, NULL, NULL, old_selection, new_selection);
1400 /* Now place the new menus. */
1401 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1403 window_state *previous_ws = &windows[i - 1];
1404 window_state *ws = &windows[i];
1406 ws->x = (previous_ws->x + selection_position.x
1407 + mw->menu.shadow_thickness);
1408 if (mw->menu.horizontal && i == 1)
1409 ws->x += mw->menu.margin;
1411 #if 0
1412 if (!mw->menu.horizontal || i > 1)
1413 ws->x += mw->menu.shadow_thickness;
1414 #endif
1416 ws->y = (previous_ws->y + selection_position.y
1417 + mw->menu.shadow_thickness);
1418 if (mw->menu.horizontal && i == 1)
1419 ws->y += mw->menu.margin;
1421 size_menu (mw, i);
1423 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1425 XClearWindow (XtDisplay (mw), ws->window);
1426 XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y,
1427 ws->width, ws->height);
1428 XMapRaised (XtDisplay (mw), ws->window);
1429 display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL);
1432 /* unmap the menus that popped down */
1433 for (i = new_depth - 1; i < old_depth; i++)
1434 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1435 XUnmapWindow (XtDisplay (mw), windows[i].window);
1438 static Boolean
1439 motion_event_is_in_menu (mw, ev, level, relative_pos)
1440 XlwMenuWidget mw;
1441 XMotionEvent* ev;
1442 int level;
1443 XPoint* relative_pos;
1445 window_state* ws = &mw->menu.windows [level];
1446 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1447 int x = ws->x + shadow;
1448 int y = ws->y + shadow;
1449 relative_pos->x = ev->x_root - x;
1450 relative_pos->y = ev->y_root - y;
1451 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1452 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1455 static Boolean
1456 map_event_to_widget_value (mw, ev, val, level)
1457 XlwMenuWidget mw;
1458 XMotionEvent* ev;
1459 widget_value** val;
1460 int* level;
1462 int i;
1463 XPoint relative_pos;
1464 window_state* ws;
1466 *val = NULL;
1468 /* Find the window */
1469 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1471 ws = &mw->menu.windows [i];
1472 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1474 display_menu (mw, i, True, NULL, &relative_pos, val, NULL, NULL);
1476 if (*val)
1478 *level = i + 1;
1479 return True;
1483 return False;
1486 \f/* Procedures */
1487 static void
1488 make_drawing_gcs (mw)
1489 XlwMenuWidget mw;
1491 XGCValues xgcv;
1492 float scale;
1494 #ifndef HAVE_X_I18N
1495 xgcv.font = mw->menu.font->fid;
1496 #endif
1497 xgcv.foreground = mw->menu.foreground;
1498 xgcv.background = mw->core.background_pixel;
1499 mw->menu.foreground_gc = XtGetGC ((Widget)mw,
1500 #ifndef HAVE_X_I18N
1501 GCFont |
1502 #endif
1503 GCForeground | GCBackground,
1504 &xgcv);
1506 #ifndef HAVE_X_I18N
1507 xgcv.font = mw->menu.font->fid;
1508 #endif
1509 xgcv.foreground = mw->menu.button_foreground;
1510 xgcv.background = mw->core.background_pixel;
1511 mw->menu.button_gc = XtGetGC ((Widget)mw,
1512 #ifndef HAVE_X_I18N
1513 GCFont |
1514 #endif
1515 GCForeground | GCBackground,
1516 &xgcv);
1518 #ifndef HAVE_X_I18N
1519 xgcv.font = mw->menu.font->fid;
1520 #endif
1521 xgcv.background = mw->core.background_pixel;
1523 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1525 /* Allocate color for disabled menu-items. */
1526 mw->menu.disabled_foreground = mw->menu.foreground;
1527 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1528 scale = 2.3;
1529 else
1530 scale = 0.55;
1532 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1533 mw->core.colormap,
1534 &mw->menu.disabled_foreground,
1535 scale,
1536 0x8000);
1538 if (mw->menu.foreground == mw->menu.disabled_foreground
1539 || mw->core.background_pixel == mw->menu.disabled_foreground)
1541 /* Too few colors, use stipple. */
1542 xgcv.foreground = mw->menu.foreground;
1543 xgcv.fill_style = FillStippled;
1544 xgcv.stipple = mw->menu.gray_pixmap;
1545 mw->menu.disabled_gc = XtGetGC ((Widget)mw,
1546 #ifndef HAVE_X_I18N
1547 GCFont |
1548 #endif
1549 GCForeground | GCBackground
1550 | GCFillStyle | GCStipple, &xgcv);
1552 else
1554 /* Many colors available, use disabled pixel. */
1555 xgcv.foreground = mw->menu.disabled_foreground;
1556 mw->menu.disabled_gc = XtGetGC ((Widget)mw,
1557 #ifndef HAVE_X_I18N
1558 GCFont |
1559 #endif
1560 GCForeground | GCBackground, &xgcv);
1563 #ifndef HAVE_X_I18N
1564 xgcv.font = mw->menu.font->fid;
1565 #endif
1566 xgcv.foreground = mw->menu.button_foreground;
1567 xgcv.background = mw->core.background_pixel;
1568 xgcv.fill_style = FillStippled;
1569 xgcv.stipple = mw->menu.gray_pixmap;
1570 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw,
1571 #ifndef HAVE_X_I18N
1572 GCFont |
1573 #endif
1574 GCForeground | GCBackground
1575 | GCFillStyle | GCStipple, &xgcv);
1577 #ifndef HAVE_X_I18N
1578 xgcv.font = mw->menu.font->fid;
1579 #endif
1580 xgcv.foreground = mw->core.background_pixel;
1581 xgcv.background = mw->menu.foreground;
1582 mw->menu.background_gc = XtGetGC ((Widget)mw,
1583 #ifndef HAVE_X_I18N
1584 GCFont |
1585 #endif
1586 GCForeground | GCBackground,
1587 &xgcv);
1590 static void
1591 release_drawing_gcs (mw)
1592 XlwMenuWidget mw;
1594 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1595 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1596 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1597 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1598 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1599 /* let's get some segvs if we try to use these... */
1600 mw->menu.foreground_gc = (GC) -1;
1601 mw->menu.button_gc = (GC) -1;
1602 mw->menu.disabled_gc = (GC) -1;
1603 mw->menu.inactive_button_gc = (GC) -1;
1604 mw->menu.background_gc = (GC) -1;
1607 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1608 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1610 static void
1611 make_shadow_gcs (mw)
1612 XlwMenuWidget mw;
1614 XGCValues xgcv;
1615 unsigned long pm = 0;
1616 Display *dpy = XtDisplay ((Widget) mw);
1617 Screen *screen = XtScreen ((Widget) mw);
1618 Colormap cmap = mw->core.colormap;
1619 XColor topc, botc;
1620 int top_frobbed = 0, bottom_frobbed = 0;
1622 mw->menu.free_top_shadow_color_p = 0;
1623 mw->menu.free_bottom_shadow_color_p = 0;
1625 if (mw->menu.top_shadow_color == -1)
1626 mw->menu.top_shadow_color = mw->core.background_pixel;
1627 else
1628 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1630 if (mw->menu.bottom_shadow_color == -1)
1631 mw->menu.bottom_shadow_color = mw->menu.foreground;
1632 else
1633 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1635 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1636 mw->menu.top_shadow_color == mw->menu.foreground)
1638 topc.pixel = mw->core.background_pixel;
1639 #ifdef emacs
1640 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1641 &topc.pixel,
1642 1.2, 0x8000))
1643 #else
1644 XQueryColor (dpy, cmap, &topc);
1645 /* don't overflow/wrap! */
1646 topc.red = MINL (65535, topc.red * 1.2);
1647 topc.green = MINL (65535, topc.green * 1.2);
1648 topc.blue = MINL (65535, topc.blue * 1.2);
1649 if (XAllocColor (dpy, cmap, &topc))
1650 #endif
1652 mw->menu.top_shadow_color = topc.pixel;
1653 mw->menu.free_top_shadow_color_p = 1;
1654 top_frobbed = 1;
1657 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1658 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1660 botc.pixel = mw->core.background_pixel;
1661 #ifdef emacs
1662 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1663 &botc.pixel,
1664 0.6, 0x4000))
1665 #else
1666 XQueryColor (dpy, cmap, &botc);
1667 botc.red *= 0.6;
1668 botc.green *= 0.6;
1669 botc.blue *= 0.6;
1670 if (XAllocColor (dpy, cmap, &botc))
1671 #endif
1673 mw->menu.bottom_shadow_color = botc.pixel;
1674 mw->menu.free_bottom_shadow_color_p = 1;
1675 bottom_frobbed = 1;
1679 if (top_frobbed && bottom_frobbed)
1681 if (topc.pixel == botc.pixel)
1683 if (botc.pixel == mw->menu.foreground)
1685 if (mw->menu.free_top_shadow_color_p)
1687 x_free_dpy_colors (dpy, screen, cmap,
1688 &mw->menu.top_shadow_color, 1);
1689 mw->menu.free_top_shadow_color_p = 0;
1691 mw->menu.top_shadow_color = mw->core.background_pixel;
1693 else
1695 if (mw->menu.free_bottom_shadow_color_p)
1697 x_free_dpy_colors (dpy, screen, cmap,
1698 &mw->menu.bottom_shadow_color, 1);
1699 mw->menu.free_bottom_shadow_color_p = 0;
1701 mw->menu.bottom_shadow_color = mw->menu.foreground;
1706 if (!mw->menu.top_shadow_pixmap &&
1707 mw->menu.top_shadow_color == mw->core.background_pixel)
1709 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1710 if (mw->menu.free_top_shadow_color_p)
1712 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1713 mw->menu.free_top_shadow_color_p = 0;
1715 mw->menu.top_shadow_color = mw->menu.foreground;
1717 if (!mw->menu.bottom_shadow_pixmap &&
1718 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1720 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1721 if (mw->menu.free_bottom_shadow_color_p)
1723 x_free_dpy_colors (dpy, screen, cmap,
1724 &mw->menu.bottom_shadow_color, 1);
1725 mw->menu.free_bottom_shadow_color_p = 0;
1727 mw->menu.bottom_shadow_color = mw->menu.foreground;
1730 xgcv.fill_style = FillStippled;
1731 xgcv.foreground = mw->menu.top_shadow_color;
1732 xgcv.stipple = mw->menu.top_shadow_pixmap;
1733 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1734 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1736 xgcv.foreground = mw->menu.bottom_shadow_color;
1737 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1738 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1739 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1743 static void
1744 release_shadow_gcs (mw)
1745 XlwMenuWidget mw;
1747 Display *dpy = XtDisplay ((Widget) mw);
1748 Screen *screen = XtScreen ((Widget) mw);
1749 Colormap cmap = mw->core.colormap;
1750 Pixel px[2];
1751 int i = 0;
1753 if (mw->menu.free_top_shadow_color_p)
1754 px[i++] = mw->menu.top_shadow_color;
1755 if (mw->menu.free_bottom_shadow_color_p)
1756 px[i++] = mw->menu.bottom_shadow_color;
1757 if (i > 0)
1758 x_free_dpy_colors (dpy, screen, cmap, px, i);
1760 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1761 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1764 static void
1765 XlwMenuInitialize (request, mw, args, num_args)
1766 Widget request;
1767 XlwMenuWidget mw;
1768 ArgList args;
1769 Cardinal *num_args;
1771 /* Get the GCs and the widget size */
1773 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1774 Display* display = XtDisplay (mw);
1776 #if 0
1777 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1779 /* _XtCreate is freeing the object that was passed to us,
1780 so make a copy that we will actually keep. */
1781 lwlib_bcopy (mw->menu.contents, tem, sizeof (widget_value));
1782 mw->menu.contents = tem;
1783 #endif
1785 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1786 mw->menu.cursor = mw->menu.cursor_shape;
1788 mw->menu.gray_pixmap
1789 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1790 gray_bitmap_width, gray_bitmap_height,
1791 (unsigned long)1, (unsigned long)0, 1);
1793 #ifndef HAVE_X_I18N
1794 /* I don't understand why this ends up 0 sometimes,
1795 but it does. This kludge works around it.
1796 Can anyone find a real fix? -- rms. */
1797 if (mw->menu.font == 0)
1798 mw->menu.font = xlwmenu_default_font;
1799 #else
1800 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.font);
1801 #endif
1803 make_drawing_gcs (mw);
1804 make_shadow_gcs (mw);
1806 mw->menu.popped_up = False;
1808 mw->menu.old_depth = 1;
1809 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1810 mw->menu.old_stack_length = 1;
1811 mw->menu.old_stack [0] = mw->menu.contents;
1813 mw->menu.new_depth = 0;
1814 mw->menu.new_stack = 0;
1815 mw->menu.new_stack_length = 0;
1816 push_new_stack (mw, mw->menu.contents);
1818 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1819 mw->menu.windows_length = 1;
1820 mw->menu.windows [0].x = 0;
1821 mw->menu.windows [0].y = 0;
1822 mw->menu.windows [0].width = 0;
1823 mw->menu.windows [0].height = 0;
1824 size_menu (mw, 0);
1826 mw->core.width = mw->menu.windows [0].width;
1827 mw->core.height = mw->menu.windows [0].height;
1830 static void
1831 XlwMenuClassInitialize ()
1835 static void
1836 XlwMenuRealize (w, valueMask, attributes)
1837 Widget w;
1838 Mask *valueMask;
1839 XSetWindowAttributes *attributes;
1841 XlwMenuWidget mw = (XlwMenuWidget)w;
1842 XSetWindowAttributes xswa;
1843 int mask;
1845 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1846 (w, valueMask, attributes);
1848 xswa.save_under = True;
1849 xswa.cursor = mw->menu.cursor_shape;
1850 mask = CWSaveUnder | CWCursor;
1851 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1853 mw->menu.windows [0].window = XtWindow (w);
1854 mw->menu.windows [0].x = w->core.x;
1855 mw->menu.windows [0].y = w->core.y;
1856 mw->menu.windows [0].width = w->core.width;
1857 mw->menu.windows [0].height = w->core.height;
1860 /* Only the toplevel menubar/popup is a widget so it's the only one that
1861 receives expose events through Xt. So we repaint all the other panes
1862 when receiving an Expose event. */
1863 static void
1864 XlwMenuRedisplay (w, ev, region)
1865 Widget w;
1866 XEvent* ev;
1867 Region region;
1869 XlwMenuWidget mw = (XlwMenuWidget)w;
1870 int i;
1872 /* If we have a depth beyond 1, it's because a submenu was displayed.
1873 If the submenu has been destroyed, set the depth back to 1. */
1874 if (submenu_destroyed)
1876 mw->menu.old_depth = 1;
1877 submenu_destroyed = 0;
1880 for (i = 0; i < mw->menu.old_depth; i++)
1881 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1885 /* Part of a hack to make the menu redisplay when a tooltip frame
1886 over a menu item is unmapped. */
1888 void
1889 xlwmenu_redisplay (w)
1890 Widget w;
1892 XlwMenuRedisplay (w, NULL, None);
1895 static void
1896 XlwMenuDestroy (w)
1897 Widget w;
1899 int i;
1900 XlwMenuWidget mw = (XlwMenuWidget) w;
1902 if (pointer_grabbed)
1903 ungrab_all ((Widget)w, CurrentTime);
1904 pointer_grabbed = 0;
1906 submenu_destroyed = 1;
1908 release_drawing_gcs (mw);
1909 release_shadow_gcs (mw);
1911 /* this doesn't come from the resource db but is created explicitly
1912 so we must free it ourselves. */
1913 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1914 mw->menu.gray_pixmap = (Pixmap) -1;
1916 #if 0
1917 /* Do free mw->menu.contents because nowadays we copy it
1918 during initialization. */
1919 XtFree (mw->menu.contents);
1920 #endif
1922 /* Don't free mw->menu.contents because that comes from our creator.
1923 The `*_stack' elements are just pointers into `contents' so leave
1924 that alone too. But free the stacks themselves. */
1925 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1926 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1928 /* Remember, you can't free anything that came from the resource
1929 database. This includes:
1930 mw->menu.cursor
1931 mw->menu.top_shadow_pixmap
1932 mw->menu.bottom_shadow_pixmap
1933 mw->menu.font
1934 Also the color cells of top_shadow_color, bottom_shadow_color,
1935 foreground, and button_foreground will never be freed until this
1936 client exits. Nice, eh?
1939 /* start from 1 because the one in slot 0 is w->core.window */
1940 for (i = 1; i < mw->menu.windows_length; i++)
1941 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1942 if (mw->menu.windows)
1943 XtFree ((char *) mw->menu.windows);
1946 static Boolean
1947 XlwMenuSetValues (current, request, new)
1948 Widget current;
1949 Widget request;
1950 Widget new;
1952 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1953 XlwMenuWidget newmw = (XlwMenuWidget)new;
1954 Boolean redisplay = False;
1955 int i;
1957 if (newmw->menu.contents
1958 && newmw->menu.contents->contents
1959 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1960 redisplay = True;
1961 /* Do redisplay if the contents are entirely eliminated. */
1962 if (newmw->menu.contents
1963 && newmw->menu.contents->contents == 0
1964 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1965 redisplay = True;
1967 if (newmw->core.background_pixel != oldmw->core.background_pixel
1968 || newmw->menu.foreground != oldmw->menu.foreground
1969 #ifndef HAVE_X_I18N
1970 || newmw->menu.font != oldmw->menu.font
1971 #endif
1974 release_drawing_gcs (newmw);
1975 make_drawing_gcs (newmw);
1977 release_shadow_gcs (newmw);
1978 /* Cause the shadow colors to be recalculated. */
1979 newmw->menu.top_shadow_color = -1;
1980 newmw->menu.bottom_shadow_color = -1;
1981 make_shadow_gcs (newmw);
1983 redisplay = True;
1985 if (XtIsRealized (current))
1986 /* If the menu is currently displayed, change the display. */
1987 for (i = 0; i < oldmw->menu.windows_length; i++)
1989 XSetWindowBackground (XtDisplay (oldmw),
1990 oldmw->menu.windows [i].window,
1991 newmw->core.background_pixel);
1992 /* clear windows and generate expose events */
1993 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
1994 0, 0, 0, 0, True);
1998 #ifdef HAVE_X_I18N
1999 if (newmw->menu.font != oldmw->menu.font)
2001 redisplay = True;
2002 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.font);
2004 #endif
2006 return redisplay;
2009 static void
2010 XlwMenuResize (w)
2011 Widget w;
2013 XlwMenuWidget mw = (XlwMenuWidget)w;
2015 if (mw->menu.popped_up)
2017 /* Don't allow the popup menu to resize itself. */
2018 mw->core.width = mw->menu.windows [0].width;
2019 mw->core.height = mw->menu.windows [0].height;
2020 mw->core.parent->core.width = mw->core.width ;
2021 mw->core.parent->core.height = mw->core.height ;
2023 else
2025 mw->menu.windows [0].width = mw->core.width;
2026 mw->menu.windows [0].height = mw->core.height;
2030 \f/* Action procedures */
2031 static void
2032 handle_single_motion_event (mw, ev)
2033 XlwMenuWidget mw;
2034 XMotionEvent* ev;
2036 widget_value* val;
2037 int level;
2039 if (!map_event_to_widget_value (mw, ev, &val, &level))
2040 pop_new_stack_if_no_contents (mw);
2041 else
2042 set_new_state (mw, val, level);
2043 remap_menubar (mw);
2045 /* Sync with the display. Makes it feel better on X terms. */
2046 XSync (XtDisplay (mw), False);
2049 static void
2050 handle_motion_event (mw, ev)
2051 XlwMenuWidget mw;
2052 XMotionEvent* ev;
2054 int x = ev->x_root;
2055 int y = ev->y_root;
2056 int state = ev->state;
2058 handle_single_motion_event (mw, ev);
2060 /* allow motion events to be generated again */
2061 if (ev->is_hint
2062 && XQueryPointer (XtDisplay (mw), ev->window,
2063 &ev->root, &ev->subwindow,
2064 &ev->x_root, &ev->y_root,
2065 &ev->x, &ev->y,
2066 &ev->state)
2067 && ev->state == state
2068 && (ev->x_root != x || ev->y_root != y))
2069 handle_single_motion_event (mw, ev);
2072 static void
2073 Start (w, ev, params, num_params)
2074 Widget w;
2075 XEvent *ev;
2076 String *params;
2077 Cardinal *num_params;
2079 XlwMenuWidget mw = (XlwMenuWidget)w;
2081 if (!mw->menu.popped_up)
2083 menu_post_event = *ev;
2084 /* If event is set to CurrentTime, get the last known time stamp.
2085 This is for calculating if (popup) menus should stay up after
2086 a fast click. */
2087 if (menu_post_event.xbutton.time == CurrentTime)
2088 menu_post_event.xbutton.time
2089 = XtLastTimestampProcessed (XtDisplay (w));
2091 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2093 else
2095 /* If we push a button while the menu is posted semipermanently,
2096 releasing the button should always pop the menu down. */
2097 next_release_must_exit = 1;
2099 /* notes the absolute position of the menubar window */
2100 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2101 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2103 /* handles the down like a move, slots are compatible */
2104 handle_motion_event (mw, &ev->xmotion);
2108 static void
2109 Drag (w, ev, params, num_params)
2110 Widget w;
2111 XEvent *ev;
2112 String *params;
2113 Cardinal *num_params;
2115 XlwMenuWidget mw = (XlwMenuWidget)w;
2116 if (mw->menu.popped_up)
2117 handle_motion_event (mw, &ev->xmotion);
2120 /* Do nothing.
2121 This is how we handle presses and releases of modifier keys. */
2122 static void
2123 Nothing (w, ev, params, num_params)
2124 Widget w;
2125 XEvent *ev;
2126 String *params;
2127 Cardinal *num_params;
2131 static widget_value *
2132 find_first_selectable (mw, item, skip_titles)
2133 XlwMenuWidget mw;
2134 widget_value *item;
2135 int skip_titles;
2137 widget_value *current = item;
2138 enum menu_separator separator;
2140 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2141 || (skip_titles && !current->call_data && !current->contents))
2142 if (current->next)
2143 current=current->next;
2144 else
2145 return NULL;
2147 return current;
2150 static widget_value *
2151 find_next_selectable (mw, item, skip_titles)
2152 XlwMenuWidget mw;
2153 widget_value *item;
2155 widget_value *current = item;
2156 enum menu_separator separator;
2158 while (current->next && (current=current->next) &&
2159 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2160 || (skip_titles && !current->call_data && !current->contents)))
2163 if (current == item)
2165 if (mw->menu.old_depth < 2)
2166 return current;
2167 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2169 while (lw_separator_p (current->name, &separator, 0)
2170 || !current->enabled
2171 || (skip_titles && !current->call_data
2172 && !current->contents))
2174 if (current->next)
2175 current=current->next;
2177 if (current == item)
2178 break;
2183 return current;
2186 static widget_value *
2187 find_prev_selectable (mw, item, skip_titles)
2188 XlwMenuWidget mw;
2189 widget_value *item;
2191 widget_value *current = item;
2192 widget_value *prev = item;
2194 while ((current=find_next_selectable (mw, current, skip_titles))
2195 != item)
2197 if (prev == current)
2198 break;
2199 prev=current;
2202 return prev;
2205 static void
2206 Down (w, ev, params, num_params)
2207 Widget w;
2208 XEvent *ev;
2209 String *params;
2210 Cardinal *num_params;
2212 XlwMenuWidget mw = (XlwMenuWidget) w;
2213 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2214 int popup_menu_p = mw->menu.top_depth == 1;
2216 /* Inside top-level menu-bar? */
2217 if (mw->menu.old_depth == mw->menu.top_depth)
2218 /* When <down> in the menu-bar is pressed, display the corresponding
2219 sub-menu and select the first selectable menu item there.
2220 If this is a popup menu, skip title item of the popup. */
2221 set_new_state (mw,
2222 find_first_selectable (mw,
2223 selected_item->contents,
2224 popup_menu_p),
2225 mw->menu.old_depth);
2226 else
2227 /* Highlight next possible (enabled and not separator) menu item. */
2228 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2229 mw->menu.old_depth - 1);
2231 remap_menubar (mw);
2234 static void
2235 Up (w, ev, params, num_params)
2236 Widget w;
2237 XEvent *ev;
2238 String *params;
2239 Cardinal *num_params;
2241 XlwMenuWidget mw = (XlwMenuWidget) w;
2242 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2243 int popup_menu_p = mw->menu.top_depth == 1;
2245 /* Inside top-level menu-bar? */
2246 if (mw->menu.old_depth == mw->menu.top_depth)
2248 /* FIXME: this is tricky. <up> in the menu-bar should select the
2249 last selectable item in the list. So we select the first
2250 selectable one and find the previous selectable item. Is there
2251 a better way? */
2252 /* If this is a popup menu, skip title item of the popup. */
2253 set_new_state (mw,
2254 find_first_selectable (mw,
2255 selected_item->contents,
2256 popup_menu_p),
2257 mw->menu.old_depth);
2258 remap_menubar (mw);
2259 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2260 set_new_state (mw,
2261 find_prev_selectable (mw,
2262 selected_item,
2263 popup_menu_p),
2264 mw->menu.old_depth - 1);
2266 else
2267 /* Highlight previous (enabled and not separator) menu item. */
2268 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2269 mw->menu.old_depth - 1);
2271 remap_menubar (mw);
2274 void
2275 Left (w, ev, params, num_params)
2276 Widget w;
2277 XEvent *ev;
2278 String *params;
2279 Cardinal *num_params;
2281 XlwMenuWidget mw = (XlwMenuWidget) w;
2282 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2284 /* Inside top-level menu-bar? */
2285 if (mw->menu.old_depth == mw->menu.top_depth)
2286 /* When <left> in the menu-bar is pressed, display the previous item on
2287 the menu-bar. If the current item is the first one, highlight the
2288 last item in the menubar (probably Help). */
2289 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2290 mw->menu.old_depth - 1);
2291 else if (mw->menu.old_depth == 1
2292 && selected_item->contents) /* Is this menu item expandable? */
2294 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2295 remap_menubar (mw);
2296 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2297 if (!selected_item->enabled && find_first_selectable (mw,
2298 selected_item,
2300 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2301 mw->menu.old_depth - 1);
2304 else
2306 pop_new_stack_if_no_contents (mw);
2307 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2308 mw->menu.old_depth - 2);
2311 remap_menubar (mw);
2314 void
2315 Right (w, ev, params, num_params)
2316 Widget w;
2317 XEvent *ev;
2318 String *params;
2319 Cardinal *num_params;
2321 XlwMenuWidget mw = (XlwMenuWidget) w;
2322 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2324 /* Inside top-level menu-bar? */
2325 if (mw->menu.old_depth == mw->menu.top_depth)
2326 /* When <right> in the menu-bar is pressed, display the next item on
2327 the menu-bar. If the current item is the last one, highlight the
2328 first item (probably File). */
2329 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2330 mw->menu.old_depth - 1);
2331 else if (selected_item->contents) /* Is this menu item expandable? */
2333 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2334 remap_menubar (mw);
2335 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2336 if (!selected_item->enabled && find_first_selectable (mw,
2337 selected_item,
2339 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2340 mw->menu.old_depth - 1);
2342 else
2344 pop_new_stack_if_no_contents (mw);
2345 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2346 mw->menu.old_depth - 2);
2349 remap_menubar (mw);
2352 /* Handle key press and release events while menu is popped up.
2353 Our action is to get rid of the menu. */
2354 static void
2355 Key (w, ev, params, num_params)
2356 Widget w;
2357 XEvent *ev;
2358 String *params;
2359 Cardinal *num_params;
2361 XlwMenuWidget mw = (XlwMenuWidget)w;
2363 /* Pop down everything. */
2364 mw->menu.new_depth = 1;
2365 remap_menubar (mw);
2367 if (mw->menu.popped_up)
2369 mw->menu.popped_up = False;
2370 ungrab_all ((Widget)mw, ev->xmotion.time);
2371 if (XtIsShell (XtParent ((Widget) mw)))
2372 XtPopdown (XtParent ((Widget) mw));
2373 else
2375 XtRemoveGrab ((Widget) mw);
2376 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2380 /* callback */
2381 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2384 static void
2385 Select (w, ev, params, num_params)
2386 Widget w;
2387 XEvent *ev;
2388 String *params;
2389 Cardinal *num_params;
2391 XlwMenuWidget mw = (XlwMenuWidget)w;
2392 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2394 /* If user releases the button quickly, without selecting anything,
2395 after the initial down-click that brought the menu up,
2396 do nothing. */
2397 if ((selected_item == 0
2398 || ((widget_value *) selected_item)->call_data == 0)
2399 && !next_release_must_exit
2400 && (ev->xbutton.time - menu_post_event.xbutton.time
2401 < XtGetMultiClickTime (XtDisplay (w))))
2402 return;
2404 /* pop down everything. */
2405 mw->menu.new_depth = 1;
2406 remap_menubar (mw);
2408 if (mw->menu.popped_up)
2410 mw->menu.popped_up = False;
2411 ungrab_all ((Widget)mw, ev->xmotion.time);
2412 if (XtIsShell (XtParent ((Widget) mw)))
2413 XtPopdown (XtParent ((Widget) mw));
2414 else
2416 XtRemoveGrab ((Widget) mw);
2417 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2421 /* callback */
2422 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2426 \f/* Special code to pop-up a menu */
2427 static void
2428 pop_up_menu (mw, event)
2429 XlwMenuWidget mw;
2430 XButtonPressedEvent* event;
2432 int x = event->x_root;
2433 int y = event->y_root;
2434 int w;
2435 int h;
2436 int borderwidth = mw->menu.shadow_thickness;
2437 Screen* screen = XtScreen (mw);
2438 Display *display = XtDisplay (mw);
2439 int count;
2441 next_release_must_exit = 0;
2443 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2445 if (XtIsShell (XtParent ((Widget)mw)))
2446 size_menu (mw, 0);
2448 w = mw->menu.windows [0].width;
2449 h = mw->menu.windows [0].height;
2451 x -= borderwidth;
2452 y -= borderwidth;
2453 if (x < borderwidth)
2454 x = borderwidth;
2455 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2456 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2457 if (y < borderwidth)
2458 y = borderwidth;
2459 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2460 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2462 mw->menu.popped_up = True;
2463 if (XtIsShell (XtParent ((Widget)mw)))
2465 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2466 XtParent ((Widget)mw)->core.border_width);
2467 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2468 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2469 mw->menu.windows [0].x = x + borderwidth;
2470 mw->menu.windows [0].y = y + borderwidth;
2471 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2473 else
2475 XEvent *ev = (XEvent *) event;
2477 XtAddGrab ((Widget) mw, True, True);
2479 /* notes the absolute position of the menubar window */
2480 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2481 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2482 mw->menu.top_depth = 2;
2485 #ifdef emacs
2486 count = x_catch_errors (display);
2487 #endif
2488 if (XtGrabPointer ((Widget)mw, False,
2489 (PointerMotionMask
2490 | PointerMotionHintMask
2491 | ButtonReleaseMask
2492 | ButtonPressMask),
2493 GrabModeAsync, GrabModeAsync, None,
2494 mw->menu.cursor_shape,
2495 event->time) == Success)
2497 if (! GRAB_KEYBOARD
2498 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2499 GrabModeAsync, event->time) == Success)
2501 XtSetKeyboardFocus((Widget)mw, None);
2502 pointer_grabbed = 1;
2504 else
2505 XtUngrabPointer ((Widget)mw, event->time);
2508 #ifdef emacs
2509 if (x_had_errors_p (display))
2511 pointer_grabbed = 0;
2512 XtUngrabPointer ((Widget)mw, event->time);
2514 x_uncatch_errors (display, count);
2515 #endif
2517 handle_motion_event (mw, (XMotionEvent*)event);
2520 /* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2521 (do not change this comment) */