*** empty log message ***
[emacs.git] / lwlib / xlwmenu.c
blobb33c35ea5ad3207dc090c4ca73d2b3f4711285fd
1 /* Implements a lightweight menubar widget.
2 Copyright (C) 1992 Lucid, Inc.
3 Copyright (C) 2002 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 "../src/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 {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
139 offset(menu.font),XtRString, "XtDefaultFont"},
140 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
141 offset(menu.foreground), XtRString, "XtDefaultForeground"},
142 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
143 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
144 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
145 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
146 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
147 offset(menu.margin), XtRImmediate, (XtPointer)1},
148 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
149 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
150 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
151 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
152 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
153 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
155 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
156 sizeof (Dimension), offset (menu.shadow_thickness),
157 XtRImmediate, (XtPointer)1},
158 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
159 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
160 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
161 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
162 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
163 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
164 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
165 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
167 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
168 offset(menu.open), XtRCallback, (XtPointer)NULL},
169 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
170 offset(menu.select), XtRCallback, (XtPointer)NULL},
171 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
172 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
173 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
174 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
175 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
176 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
177 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
178 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
180 #undef offset
182 static Boolean XlwMenuSetValues();
183 static void XlwMenuRealize();
184 static void XlwMenuRedisplay();
185 static void XlwMenuResize();
186 static void XlwMenuInitialize();
187 static void XlwMenuRedisplay();
188 static void XlwMenuDestroy();
189 static void XlwMenuClassInitialize();
190 static void Start();
191 static void Drag();
192 static void Down();
193 static void Up();
194 static void Left();
195 static void Right();
196 static void Select();
197 static void Key();
198 static void Nothing();
199 static int separator_height ();
201 static XtActionsRec
202 xlwMenuActionsList [] =
204 {"start", Start},
205 {"drag", Drag},
206 {"down", Down},
207 {"up", Up},
208 {"left", Left},
209 {"right", Right},
210 {"select", Select},
211 {"key", Key},
212 {"nothing", Nothing},
215 #define SuperClass ((CoreWidgetClass)&coreClassRec)
217 XlwMenuClassRec xlwMenuClassRec =
219 { /* CoreClass fields initialization */
220 (WidgetClass) SuperClass, /* superclass */
221 "XlwMenu", /* class_name */
222 sizeof(XlwMenuRec), /* size */
223 XlwMenuClassInitialize, /* class_initialize */
224 NULL, /* class_part_initialize */
225 FALSE, /* class_inited */
226 XlwMenuInitialize, /* initialize */
227 NULL, /* initialize_hook */
228 XlwMenuRealize, /* realize */
229 xlwMenuActionsList, /* actions */
230 XtNumber(xlwMenuActionsList), /* num_actions */
231 xlwMenuResources, /* resources */
232 XtNumber(xlwMenuResources), /* resource_count */
233 NULLQUARK, /* xrm_class */
234 TRUE, /* compress_motion */
235 TRUE, /* compress_exposure */
236 TRUE, /* compress_enterleave */
237 FALSE, /* visible_interest */
238 XlwMenuDestroy, /* destroy */
239 XlwMenuResize, /* resize */
240 XlwMenuRedisplay, /* expose */
241 XlwMenuSetValues, /* set_values */
242 NULL, /* set_values_hook */
243 XtInheritSetValuesAlmost, /* set_values_almost */
244 NULL, /* get_values_hook */
245 NULL, /* accept_focus */
246 XtVersion, /* version */
247 NULL, /* callback_private */
248 xlwMenuTranslations, /* tm_table */
249 XtInheritQueryGeometry, /* query_geometry */
250 XtInheritDisplayAccelerator, /* display_accelerator */
251 NULL /* extension */
252 }, /* XlwMenuClass fields initialization */
254 0 /* dummy */
258 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
260 int submenu_destroyed;
262 /* For debug, if installation-directory is non-nil this is not an installed
263 Emacs. In that case we do not grab the keyboard to make it easier to
264 debug. */
265 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
267 static int next_release_must_exit;
269 \f/* Utilities */
271 /* Ungrab pointer and keyboard */
272 static void
273 ungrab_all (w, ungrabtime)
274 Widget w;
275 Time ungrabtime;
277 XtUngrabPointer (w, ungrabtime);
278 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
281 /* Like abort, but remove grabs from widget W before. */
283 static void
284 abort_gracefully (w)
285 Widget w;
287 if (XtIsShell (XtParent (w)))
288 XtRemoveGrab (w);
289 ungrab_all (w, CurrentTime);
290 abort ();
293 static void
294 push_new_stack (mw, val)
295 XlwMenuWidget mw;
296 widget_value* val;
298 if (!mw->menu.new_stack)
300 mw->menu.new_stack_length = 10;
301 mw->menu.new_stack =
302 (widget_value**)XtCalloc (mw->menu.new_stack_length,
303 sizeof (widget_value*));
305 else if (mw->menu.new_depth == mw->menu.new_stack_length)
307 mw->menu.new_stack_length *= 2;
308 mw->menu.new_stack =
309 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
310 mw->menu.new_stack_length * sizeof (widget_value*));
312 mw->menu.new_stack [mw->menu.new_depth++] = val;
315 static void
316 pop_new_stack_if_no_contents (mw)
317 XlwMenuWidget mw;
319 if (mw->menu.new_depth)
321 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
322 mw->menu.new_depth -= 1;
326 static void
327 make_old_stack_space (mw, n)
328 XlwMenuWidget mw;
329 int n;
331 if (!mw->menu.old_stack)
333 mw->menu.old_stack_length = 10;
334 mw->menu.old_stack =
335 (widget_value**)XtCalloc (mw->menu.old_stack_length,
336 sizeof (widget_value*));
338 else if (mw->menu.old_stack_length < n)
340 mw->menu.old_stack_length *= 2;
341 mw->menu.old_stack =
342 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
343 mw->menu.old_stack_length * sizeof (widget_value*));
347 \f/* Size code */
349 string_width (mw, s)
350 XlwMenuWidget mw;
351 char *s;
353 XCharStruct xcs;
354 int drop;
356 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
357 return xcs.width;
360 static int
361 arrow_width (mw)
362 XlwMenuWidget mw;
364 return (mw->menu.font->ascent * 3/4) | 1;
367 /* Return the width of toggle buttons of widget MW. */
369 static int
370 toggle_button_width (mw)
371 XlwMenuWidget mw;
373 return ((mw->menu.font->ascent + mw->menu.font->descent) * 2 / 3) | 1;
377 /* Return the width of radio buttons of widget MW. */
379 static int
380 radio_button_width (mw)
381 XlwMenuWidget mw;
383 return toggle_button_width (mw) * 1.41;
387 static XtResource
388 nameResource[] =
390 {"labelString", "LabelString", XtRString, sizeof(String),
391 0, XtRImmediate, 0},
394 static char*
395 resource_widget_value (mw, val)
396 XlwMenuWidget mw;
397 widget_value *val;
399 if (!val->toolkit_data)
401 char* resourced_name = NULL;
402 char* complete_name;
403 XtGetSubresources ((Widget) mw,
404 (XtPointer) &resourced_name,
405 val->name, val->name,
406 nameResource, 1, NULL, 0);
407 if (!resourced_name)
408 resourced_name = val->name;
409 if (!val->value)
411 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
412 strcpy (complete_name, resourced_name);
414 else
416 int complete_length =
417 strlen (resourced_name) + strlen (val->value) + 2;
418 complete_name = XtMalloc (complete_length);
419 *complete_name = 0;
420 strcat (complete_name, resourced_name);
421 strcat (complete_name, " ");
422 strcat (complete_name, val->value);
425 val->toolkit_data = complete_name;
426 val->free_toolkit_data = True;
428 return (char*)val->toolkit_data;
431 /* Returns the sizes of an item */
432 static void
433 size_menu_item (mw, val, horizontal_p, label_width, rest_width, button_width,
434 height)
435 XlwMenuWidget mw;
436 widget_value* val;
437 int horizontal_p;
438 int* label_width;
439 int* rest_width;
440 int* button_width;
441 int* height;
443 enum menu_separator separator;
445 if (lw_separator_p (val->name, &separator, 0))
447 *height = separator_height (separator);
448 *label_width = 1;
449 *rest_width = 0;
450 *button_width = 0;
452 else
454 *height =
455 mw->menu.font->ascent + mw->menu.font->descent
456 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
458 *label_width =
459 string_width (mw, resource_widget_value (mw, val))
460 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
462 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
463 if (!horizontal_p)
465 if (val->contents)
466 /* Add width of the arrow displayed for submenus. */
467 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
468 else if (val->key)
469 /* Add width of key equivalent string. */
470 *rest_width += (string_width (mw, val->key)
471 + mw->menu.arrow_spacing);
473 if (val->button_type == BUTTON_TYPE_TOGGLE)
474 *button_width = (toggle_button_width (mw)
475 + mw->menu.horizontal_spacing);
476 else if (val->button_type == BUTTON_TYPE_RADIO)
477 *button_width = (radio_button_width (mw)
478 + mw->menu.horizontal_spacing);
483 static void
484 size_menu (mw, level)
485 XlwMenuWidget mw;
486 int level;
488 unsigned int label_width = 0;
489 int rest_width = 0;
490 int button_width = 0;
491 int max_rest_width = 0;
492 int max_button_width = 0;
493 unsigned int height = 0;
494 int horizontal_p = mw->menu.horizontal && (level == 0);
495 widget_value* val;
496 window_state* ws;
498 if (level >= mw->menu.old_depth)
499 abort_gracefully ((Widget) mw);
501 ws = &mw->menu.windows [level];
502 ws->width = 0;
503 ws->height = 0;
504 ws->label_width = 0;
505 ws->button_width = 0;
507 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
509 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
510 &button_width, &height);
511 if (horizontal_p)
513 ws->width += label_width + rest_width;
514 if (height > ws->height)
515 ws->height = height;
517 else
519 if (label_width > ws->label_width)
520 ws->label_width = label_width;
521 if (rest_width > max_rest_width)
522 max_rest_width = rest_width;
523 if (button_width > max_button_width)
524 max_button_width = button_width;
525 ws->height += height;
529 if (horizontal_p)
530 ws->label_width = ws->button_width = 0;
531 else
533 ws->width = ws->label_width + max_rest_width + max_button_width;
534 ws->button_width = max_button_width;
537 ws->width += 2 * mw->menu.shadow_thickness;
538 ws->height += 2 * mw->menu.shadow_thickness;
540 if (horizontal_p)
542 ws->width += 2 * mw->menu.margin;
543 ws->height += 2 * mw->menu.margin;
548 \f/* Display code */
550 static void
551 draw_arrow (mw, window, gc, x, y, width, down_p)
552 XlwMenuWidget mw;
553 Window window;
554 GC gc;
555 int x;
556 int y;
557 int width;
558 int down_p;
560 Display *dpy = XtDisplay (mw);
561 GC top_gc = mw->menu.shadow_top_gc;
562 GC bottom_gc = mw->menu.shadow_bottom_gc;
563 int thickness = mw->menu.shadow_thickness;
564 int height = width;
565 XPoint pt[10];
566 /* alpha = atan (0.5)
567 factor = (1 + sin (alpha)) / cos (alpha) */
568 double factor = 1.62;
569 int thickness2 = thickness * factor;
571 y += (mw->menu.font->ascent + mw->menu.font->descent - height) / 2;
573 if (down_p)
575 GC temp;
576 temp = top_gc;
577 top_gc = bottom_gc;
578 bottom_gc = temp;
581 pt[0].x = x;
582 pt[0].y = y + height;
583 pt[1].x = x + thickness;
584 pt[1].y = y + height - thickness2;
585 pt[2].x = x + thickness2;
586 pt[2].y = y + thickness2;
587 pt[3].x = x;
588 pt[3].y = y;
589 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
591 pt[0].x = x;
592 pt[0].y = y;
593 pt[1].x = x + thickness;
594 pt[1].y = y + thickness2;
595 pt[2].x = x + width - thickness2;
596 pt[2].y = y + height / 2;
597 pt[3].x = x + width;
598 pt[3].y = y + height / 2;
599 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
601 pt[0].x = x;
602 pt[0].y = y + height;
603 pt[1].x = x + thickness;
604 pt[1].y = y + height - thickness2;
605 pt[2].x = x + width - thickness2;
606 pt[2].y = y + height / 2;
607 pt[3].x = x + width;
608 pt[3].y = y + height / 2;
609 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
614 static void
615 draw_shadow_rectangle (mw, window, x, y, width, height, erase_p, down_p)
616 XlwMenuWidget mw;
617 Window window;
618 int x;
619 int y;
620 int width;
621 int height;
622 int erase_p;
623 int down_p;
625 Display *dpy = XtDisplay (mw);
626 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
627 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
628 int thickness = mw->menu.shadow_thickness;
629 XPoint points [4];
631 if (!erase_p && down_p)
633 GC temp;
634 temp = top_gc;
635 top_gc = bottom_gc;
636 bottom_gc = temp;
639 points [0].x = x;
640 points [0].y = y;
641 points [1].x = x + width;
642 points [1].y = y;
643 points [2].x = x + width - thickness;
644 points [2].y = y + thickness;
645 points [3].x = x;
646 points [3].y = y + thickness;
647 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
648 points [0].x = x;
649 points [0].y = y + thickness;
650 points [1].x = x;
651 points [1].y = y + height;
652 points [2].x = x + thickness;
653 points [2].y = y + height - thickness;
654 points [3].x = x + thickness;
655 points [3].y = y + thickness;
656 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
657 points [0].x = x + width;
658 points [0].y = y;
659 points [1].x = x + width - thickness;
660 points [1].y = y + thickness;
661 points [2].x = x + width - thickness;
662 points [2].y = y + height - thickness;
663 points [3].x = x + width;
664 points [3].y = y + height - thickness;
665 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
666 points [0].x = x;
667 points [0].y = y + height;
668 points [1].x = x + width;
669 points [1].y = y + height;
670 points [2].x = x + width;
671 points [2].y = y + height - thickness;
672 points [3].x = x + thickness;
673 points [3].y = y + height - thickness;
674 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
678 static void
679 draw_shadow_rhombus (mw, window, x, y, width, height, erase_p, down_p)
680 XlwMenuWidget mw;
681 Window window;
682 int x;
683 int y;
684 int width;
685 int height;
686 int erase_p;
687 int down_p;
689 Display *dpy = XtDisplay (mw);
690 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
691 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
692 int thickness = mw->menu.shadow_thickness;
693 XPoint points [4];
695 if (!erase_p && down_p)
697 GC temp;
698 temp = top_gc;
699 top_gc = bottom_gc;
700 bottom_gc = temp;
703 points [0].x = x;
704 points [0].y = y + height / 2;
705 points [1].x = x + thickness;
706 points [1].y = y + height / 2;
707 points [2].x = x + width / 2;
708 points [2].y = y + thickness;
709 points [3].x = x + width / 2;
710 points [3].y = y;
711 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
712 points [0].x = x + width / 2;
713 points [0].y = y;
714 points [1].x = x + width / 2;
715 points [1].y = y + thickness;
716 points [2].x = x + width - thickness;
717 points [2].y = y + height / 2;
718 points [3].x = x + width;
719 points [3].y = y + height / 2;
720 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
721 points [0].x = x;
722 points [0].y = y + height / 2;
723 points [1].x = x + thickness;
724 points [1].y = y + height / 2;
725 points [2].x = x + width / 2;
726 points [2].y = y + height - thickness;
727 points [3].x = x + width / 2;
728 points [3].y = y + height;
729 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
730 points [0].x = x + width / 2;
731 points [0].y = y + height;
732 points [1].x = x + width / 2;
733 points [1].y = y + height - thickness;
734 points [2].x = x + width - thickness;
735 points [2].y = y + height / 2;
736 points [3].x = x + width;
737 points [3].y = y + height / 2;
738 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
742 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
743 top-left corner of the menu item. SELECTED_P non-zero means the
744 toggle button is selected. */
746 static void
747 draw_toggle (mw, window, x, y, selected_p)
748 XlwMenuWidget mw;
749 Window window;
750 int x, y, selected_p;
752 int width, height;
754 width = toggle_button_width (mw);
755 height = width;
756 x += mw->menu.horizontal_spacing;
757 y += (mw->menu.font->ascent - height) / 2;
758 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
762 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
763 top-left corner of the menu item. SELECTED_P non-zero means the
764 toggle button is selected. */
766 static void
767 draw_radio (mw, window, x, y, selected_p)
768 XlwMenuWidget mw;
769 Window window;
770 int x, y, selected_p;
772 int width, height;
774 width = radio_button_width (mw);
775 height = width;
776 x += mw->menu.horizontal_spacing;
777 y += (mw->menu.font->ascent - height) / 2;
778 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
782 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
783 top-left corner of the menu item. WIDTH is the width of the
784 separator to draw. TYPE is the separator type. */
786 static void
787 draw_separator (mw, window, x, y, width, type)
788 XlwMenuWidget mw;
789 Window window;
790 int x, y, width;
791 enum menu_separator type;
793 Display *dpy = XtDisplay (mw);
794 XGCValues xgcv;
796 switch (type)
798 case SEPARATOR_NO_LINE:
799 break;
801 case SEPARATOR_SINGLE_LINE:
802 XDrawLine (dpy, window, mw->menu.foreground_gc,
803 x, y, x + width, y);
804 break;
806 case SEPARATOR_DOUBLE_LINE:
807 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
808 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
809 break;
811 case SEPARATOR_SINGLE_DASHED_LINE:
812 xgcv.line_style = LineOnOffDash;
813 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
814 XDrawLine (dpy, window, mw->menu.foreground_gc,
815 x, y, x + width, y);
816 xgcv.line_style = LineSolid;
817 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
818 break;
820 case SEPARATOR_DOUBLE_DASHED_LINE:
821 draw_separator (mw, window, x, y, width,
822 SEPARATOR_SINGLE_DASHED_LINE);
823 draw_separator (mw, window, x, y + 2, width,
824 SEPARATOR_SINGLE_DASHED_LINE);
825 break;
827 case SEPARATOR_SHADOW_ETCHED_IN:
828 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
829 x, y, x + width, y);
830 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
831 x, y + 1, x + width, y + 1);
832 break;
834 case SEPARATOR_SHADOW_ETCHED_OUT:
835 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
836 x, y, x + width, y);
837 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
838 x, y + 1, x + width, y + 1);
839 break;
841 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
842 xgcv.line_style = LineOnOffDash;
843 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
844 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
845 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
846 xgcv.line_style = LineSolid;
847 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
848 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
849 break;
851 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
852 xgcv.line_style = LineOnOffDash;
853 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
854 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
855 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
856 xgcv.line_style = LineSolid;
857 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
858 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
859 break;
861 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
862 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
863 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
864 break;
866 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
867 draw_separator (mw, window, x, y, width,
868 SEPARATOR_SHADOW_ETCHED_OUT);
869 draw_separator (mw, window, x, y + 3, width,
870 SEPARATOR_SHADOW_ETCHED_OUT);
871 break;
873 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
874 xgcv.line_style = LineOnOffDash;
875 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
876 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
877 draw_separator (mw, window, x, y, width,
878 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
879 xgcv.line_style = LineSolid;
880 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
881 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
882 break;
884 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
885 xgcv.line_style = LineOnOffDash;
886 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
887 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
888 draw_separator (mw, window, x, y, width,
889 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
890 xgcv.line_style = LineSolid;
891 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
892 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
893 break;
895 default:
896 abort ();
901 /* Return the pixel height of menu separator SEPARATOR. */
903 static int
904 separator_height (separator)
905 enum menu_separator separator;
907 switch (separator)
909 case SEPARATOR_NO_LINE:
910 return 2;
912 case SEPARATOR_SINGLE_LINE:
913 case SEPARATOR_SINGLE_DASHED_LINE:
914 return 1;
916 case SEPARATOR_DOUBLE_LINE:
917 case SEPARATOR_DOUBLE_DASHED_LINE:
918 return 3;
920 case SEPARATOR_SHADOW_ETCHED_IN:
921 case SEPARATOR_SHADOW_ETCHED_OUT:
922 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
923 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
924 return 2;
926 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
927 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
928 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
929 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
930 return 5;
932 default:
933 abort ();
938 /* Display the menu item and increment where.x and where.y to show how large
939 the menu item was. */
941 static void
942 display_menu_item (mw, val, ws, where, highlighted_p, horizontal_p,
943 just_compute_p)
944 XlwMenuWidget mw;
945 widget_value* val;
946 window_state* ws;
947 XPoint* where;
948 Boolean highlighted_p;
949 Boolean horizontal_p;
950 Boolean just_compute_p;
952 GC deco_gc;
953 GC text_gc;
954 int font_ascent = mw->menu.font->ascent;
955 int font_descent = mw->menu.font->descent;
956 int shadow = mw->menu.shadow_thickness;
957 int margin = mw->menu.margin;
958 int h_spacing = mw->menu.horizontal_spacing;
959 int v_spacing = mw->menu.vertical_spacing;
960 int label_width;
961 int rest_width;
962 int button_width;
963 int height;
964 int width;
965 enum menu_separator separator;
966 int separator_p = lw_separator_p (val->name, &separator, 0);
968 /* compute the sizes of the item */
969 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
970 &button_width, &height);
972 if (horizontal_p)
973 width = label_width + rest_width;
974 else
976 label_width = ws->label_width;
977 width = ws->width - 2 * shadow;
980 /* Only highlight an enabled item that has a callback. */
981 if (highlighted_p)
982 if (!val->enabled || !(val->call_data || val->contents))
983 highlighted_p = 0;
985 /* do the drawing. */
986 if (!just_compute_p)
988 /* Add the shadow border of the containing menu */
989 int x = where->x + shadow;
990 int y = where->y + shadow;
992 if (horizontal_p)
994 x += margin;
995 y += margin;
998 /* pick the foreground and background GC. */
999 if (val->enabled)
1000 text_gc = mw->menu.foreground_gc;
1001 else
1002 text_gc = mw->menu.disabled_gc;
1003 deco_gc = mw->menu.foreground_gc;
1005 if (separator_p)
1007 draw_separator (mw, ws->window, x, y, width, separator);
1009 else
1011 int x_offset = x + h_spacing + shadow;
1012 char* display_string = resource_widget_value (mw, val);
1013 draw_shadow_rectangle (mw, ws->window, x, y, width, height, True,
1014 False);
1016 /* Deal with centering a menu title. */
1017 if (!horizontal_p && !val->contents && !val->call_data)
1019 int l = string_width (mw, display_string);
1021 if (width > l)
1022 x_offset = (width - l) >> 1;
1024 else if (!horizontal_p && ws->button_width)
1025 x_offset += ws->button_width;
1028 XDrawString (XtDisplay (mw), ws->window, text_gc, x_offset,
1029 y + v_spacing + shadow + font_ascent,
1030 display_string, strlen (display_string));
1032 if (!horizontal_p)
1034 if (val->button_type == BUTTON_TYPE_TOGGLE)
1035 draw_toggle (mw, ws->window, x, y + v_spacing + shadow,
1036 val->selected);
1037 else if (val->button_type == BUTTON_TYPE_RADIO)
1038 draw_radio (mw, ws->window, x, y + v_spacing + shadow,
1039 val->selected);
1041 if (val->contents)
1043 int a_w = arrow_width (mw);
1044 draw_arrow (mw, ws->window, deco_gc,
1045 x + width - a_w
1046 - mw->menu.horizontal_spacing
1047 - mw->menu.shadow_thickness,
1048 y + v_spacing + shadow, a_w,
1049 highlighted_p);
1051 else if (val->key)
1053 XDrawString (XtDisplay (mw), ws->window, text_gc,
1054 x + label_width + mw->menu.arrow_spacing,
1055 y + v_spacing + shadow + font_ascent,
1056 val->key, strlen (val->key));
1059 else
1061 XDrawRectangle (XtDisplay (mw), ws->window,
1062 mw->menu.background_gc,
1063 x + shadow, y + shadow,
1064 label_width + h_spacing - 1,
1065 font_ascent + font_descent + 2 * v_spacing - 1);
1066 draw_shadow_rectangle (mw, ws->window, x, y, width, height,
1067 True, False);
1070 if (highlighted_p)
1071 draw_shadow_rectangle (mw, ws->window, x, y, width, height, False,
1072 False);
1076 where->x += width;
1077 where->y += height;
1080 static void
1081 display_menu (mw, level, just_compute_p, highlighted_pos, hit, hit_return,
1082 this, that)
1083 XlwMenuWidget mw;
1084 int level;
1085 Boolean just_compute_p;
1086 XPoint* highlighted_pos;
1087 XPoint* hit;
1088 widget_value** hit_return;
1089 widget_value* this;
1090 widget_value* that;
1092 widget_value* val;
1093 widget_value* following_item;
1094 window_state* ws;
1095 XPoint where;
1096 int horizontal_p = mw->menu.horizontal && (level == 0);
1097 int highlighted_p;
1098 int just_compute_this_one_p;
1099 /* This is set nonzero if the element containing HIGHLIGHTED_POS
1100 is disabled, so that we do not return any subsequent element either. */
1101 int no_return = 0;
1102 enum menu_separator separator;
1104 if (level >= mw->menu.old_depth)
1105 abort_gracefully ((Widget) mw);
1107 if (level < mw->menu.old_depth - 1)
1108 following_item = mw->menu.old_stack [level + 1];
1109 else
1110 following_item = NULL;
1112 if (hit)
1113 *hit_return = NULL;
1115 where.x = 0;
1116 where.y = 0;
1118 ws = &mw->menu.windows [level];
1119 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1121 highlighted_p = val == following_item;
1122 if (highlighted_p && highlighted_pos)
1124 if (horizontal_p)
1125 highlighted_pos->x = where.x;
1126 else
1127 highlighted_pos->y = where.y;
1130 just_compute_this_one_p =
1131 just_compute_p || ((this || that) && val != this && val != that);
1133 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1134 just_compute_this_one_p);
1136 if (highlighted_p && highlighted_pos)
1138 if (horizontal_p)
1139 highlighted_pos->y = where.y;
1140 else
1141 highlighted_pos->x = where.x;
1144 if (hit
1145 && !*hit_return
1146 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1147 && !lw_separator_p (val->name, &separator, 0)
1148 && !no_return)
1150 if (val->enabled)
1151 *hit_return = val;
1152 else
1153 no_return = 1;
1156 if (horizontal_p)
1157 where.y = 0;
1158 else
1159 where.x = 0;
1162 if (!just_compute_p)
1163 draw_shadow_rectangle (mw, ws->window, 0, 0, ws->width, ws->height,
1164 False, False);
1167 \f/* Motion code */
1168 static void
1169 set_new_state (mw, val, level)
1170 XlwMenuWidget mw;
1171 widget_value* val;
1172 int level;
1174 int i;
1176 mw->menu.new_depth = 0;
1177 for (i = 0; i < level; i++)
1178 push_new_stack (mw, mw->menu.old_stack [i]);
1179 push_new_stack (mw, val);
1182 static void
1183 make_windows_if_needed (mw, n)
1184 XlwMenuWidget mw;
1185 int n;
1187 int i;
1188 int start_at;
1189 XSetWindowAttributes xswa;
1190 int mask;
1191 Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1192 window_state* windows;
1194 if (mw->menu.windows_length >= n)
1195 return;
1197 xswa.save_under = True;
1198 xswa.override_redirect = True;
1199 xswa.background_pixel = mw->core.background_pixel;
1200 xswa.border_pixel = mw->core.border_pixel;
1201 xswa.event_mask =
1202 ExposureMask | PointerMotionMask | PointerMotionHintMask
1203 | ButtonReleaseMask | ButtonPressMask;
1204 xswa.cursor = mw->menu.cursor_shape;
1205 mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
1206 | CWEventMask | CWCursor;
1208 if (!mw->menu.windows)
1210 mw->menu.windows =
1211 (window_state*)XtMalloc (n * sizeof (window_state));
1212 start_at = 0;
1214 else
1216 mw->menu.windows =
1217 (window_state*)XtRealloc ((char*)mw->menu.windows,
1218 n * sizeof (window_state));
1219 start_at = mw->menu.windows_length;
1221 mw->menu.windows_length = n;
1223 windows = mw->menu.windows;
1225 for (i = start_at; i < n; i++)
1227 windows [i].x = 0;
1228 windows [i].y = 0;
1229 windows [i].width = 1;
1230 windows [i].height = 1;
1231 windows [i].window =
1232 XCreateWindow (XtDisplay (mw), root, 0, 0, 1, 1,
1233 0, 0, CopyFromParent, CopyFromParent, mask, &xswa);
1237 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1240 xlwmenu_window_p (w, window)
1241 Widget w;
1242 Window window;
1244 XlwMenuWidget mw = (XlwMenuWidget) w;
1245 int i;
1247 for (i = 0; i < mw->menu.windows_length; ++i)
1248 if (window == mw->menu.windows[i].window)
1249 break;
1251 return i < mw->menu.windows_length;
1254 /* Make the window fit in the screen */
1255 static void
1256 fit_to_screen (mw, ws, previous_ws, horizontal_p)
1257 XlwMenuWidget mw;
1258 window_state* ws;
1259 window_state* previous_ws;
1260 Boolean horizontal_p;
1262 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1263 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1264 /* 1 if we are unable to avoid an overlap between
1265 this menu and the parent menu in the X dimension. */
1266 int horizontal_overlap = 0;
1268 if (ws->x < 0)
1269 ws->x = 0;
1270 else if (ws->x + ws->width > screen_width)
1272 if (!horizontal_p)
1273 /* The addition of shadow-thickness for a sub-menu's position is
1274 to reflect a similar adjustment when the menu is displayed to
1275 the right of the invoking menu-item; it makes the sub-menu
1276 look more `attached' to the menu-item. */
1277 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1278 else
1279 ws->x = screen_width - ws->width;
1280 if (ws->x < 0)
1282 ws->x = 0;
1283 horizontal_overlap = 1;
1286 /* If we overlap in X, try to avoid overlap in Y. */
1287 if (horizontal_overlap
1288 && ws->y < previous_ws->y + previous_ws->height
1289 && previous_ws->y < ws->y + ws->height)
1291 /* Put this menu right below or right above PREVIOUS_WS
1292 if there's room. */
1293 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1294 ws->y = previous_ws->y + previous_ws->height;
1295 else if (previous_ws->y - ws->height > 0)
1296 ws->y = previous_ws->y - ws->height;
1299 if (ws->y < 0)
1300 ws->y = 0;
1301 else if (ws->y + ws->height > screen_height)
1303 if (horizontal_p)
1304 ws->y = previous_ws->y - ws->height;
1305 else
1306 ws->y = screen_height - ws->height;
1307 if (ws->y < 0)
1308 ws->y = 0;
1312 /* Updates old_stack from new_stack and redisplays. */
1313 static void
1314 remap_menubar (mw)
1315 XlwMenuWidget mw;
1317 int i;
1318 int last_same;
1319 XPoint selection_position;
1320 int old_depth = mw->menu.old_depth;
1321 int new_depth = mw->menu.new_depth;
1322 widget_value** old_stack;
1323 widget_value** new_stack;
1324 window_state* windows;
1325 widget_value* old_selection;
1326 widget_value* new_selection;
1328 /* Check that enough windows and old_stack are ready. */
1329 make_windows_if_needed (mw, new_depth);
1330 make_old_stack_space (mw, new_depth);
1331 windows = mw->menu.windows;
1332 old_stack = mw->menu.old_stack;
1333 new_stack = mw->menu.new_stack;
1335 /* compute the last identical different entry */
1336 for (i = 1; i < old_depth && i < new_depth; i++)
1337 if (old_stack [i] != new_stack [i])
1338 break;
1339 last_same = i - 1;
1341 /* Memorize the previously selected item to be able to refresh it */
1342 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1343 if (old_selection && !old_selection->enabled)
1344 old_selection = NULL;
1345 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1346 if (new_selection && !new_selection->enabled)
1347 new_selection = NULL;
1349 /* Call callback when the hightlighted item changes. */
1350 if (old_selection || new_selection)
1351 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1352 (XtPointer) new_selection);
1354 /* updates old_state from new_state. It has to be done now because
1355 display_menu (called below) uses the old_stack to know what to display. */
1356 for (i = last_same + 1; i < new_depth; i++)
1357 old_stack [i] = new_stack [i];
1358 mw->menu.old_depth = new_depth;
1360 /* refresh the last selection */
1361 selection_position.x = 0;
1362 selection_position.y = 0;
1363 display_menu (mw, last_same, new_selection == old_selection,
1364 &selection_position, NULL, NULL, old_selection, new_selection);
1366 /* Now place the new menus. */
1367 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1369 window_state *previous_ws = &windows[i - 1];
1370 window_state *ws = &windows[i];
1372 ws->x = (previous_ws->x + selection_position.x
1373 + mw->menu.shadow_thickness);
1374 if (mw->menu.horizontal && i == 1)
1375 ws->x += mw->menu.margin;
1377 #if 0
1378 if (!mw->menu.horizontal || i > 1)
1379 ws->x += mw->menu.shadow_thickness;
1380 #endif
1382 ws->y = (previous_ws->y + selection_position.y
1383 + mw->menu.shadow_thickness);
1384 if (mw->menu.horizontal && i == 1)
1385 ws->y += mw->menu.margin;
1387 size_menu (mw, i);
1389 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1391 XClearWindow (XtDisplay (mw), ws->window);
1392 XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y,
1393 ws->width, ws->height);
1394 XMapRaised (XtDisplay (mw), ws->window);
1395 display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL);
1398 /* unmap the menus that popped down */
1399 for (i = new_depth - 1; i < old_depth; i++)
1400 if (i >= new_depth || !new_stack[i]->contents)
1401 XUnmapWindow (XtDisplay (mw), windows[i].window);
1404 static Boolean
1405 motion_event_is_in_menu (mw, ev, level, relative_pos)
1406 XlwMenuWidget mw;
1407 XMotionEvent* ev;
1408 int level;
1409 XPoint* relative_pos;
1411 window_state* ws = &mw->menu.windows [level];
1412 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1413 int x = ws->x + shadow;
1414 int y = ws->y + shadow;
1415 relative_pos->x = ev->x_root - x;
1416 relative_pos->y = ev->y_root - y;
1417 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1418 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1421 static Boolean
1422 map_event_to_widget_value (mw, ev, val, level)
1423 XlwMenuWidget mw;
1424 XMotionEvent* ev;
1425 widget_value** val;
1426 int* level;
1428 int i;
1429 XPoint relative_pos;
1430 window_state* ws;
1432 *val = NULL;
1434 /* Find the window */
1435 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1437 ws = &mw->menu.windows [i];
1438 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1440 display_menu (mw, i, True, NULL, &relative_pos, val, NULL, NULL);
1442 if (*val)
1444 *level = i + 1;
1445 return True;
1449 return False;
1452 \f/* Procedures */
1453 static void
1454 make_drawing_gcs (mw)
1455 XlwMenuWidget mw;
1457 XGCValues xgcv;
1458 float scale;
1460 xgcv.font = mw->menu.font->fid;
1461 xgcv.foreground = mw->menu.foreground;
1462 xgcv.background = mw->core.background_pixel;
1463 mw->menu.foreground_gc = XtGetGC ((Widget)mw,
1464 GCFont | GCForeground | GCBackground,
1465 &xgcv);
1467 xgcv.font = mw->menu.font->fid;
1468 xgcv.foreground = mw->menu.button_foreground;
1469 xgcv.background = mw->core.background_pixel;
1470 mw->menu.button_gc = XtGetGC ((Widget)mw,
1471 GCFont | GCForeground | GCBackground,
1472 &xgcv);
1474 xgcv.font = mw->menu.font->fid;
1475 xgcv.background = mw->core.background_pixel;
1477 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1479 /* Allocate color for disabled menu-items. */
1480 mw->menu.disabled_foreground = mw->menu.foreground;
1481 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1482 scale = 2.3;
1483 else
1484 scale = 0.55;
1486 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1487 mw->core.colormap,
1488 &mw->menu.disabled_foreground,
1489 scale,
1490 0x8000);
1492 if (mw->menu.foreground == mw->menu.disabled_foreground
1493 || mw->core.background_pixel == mw->menu.disabled_foreground)
1495 /* Too few colors, use stipple. */
1496 xgcv.foreground = mw->menu.foreground;
1497 xgcv.fill_style = FillStippled;
1498 xgcv.stipple = mw->menu.gray_pixmap;
1499 mw->menu.disabled_gc = XtGetGC ((Widget)mw,
1500 (GCFont | GCForeground | GCBackground
1501 | GCFillStyle | GCStipple), &xgcv);
1503 else
1505 /* Many colors available, use disabled pixel. */
1506 xgcv.foreground = mw->menu.disabled_foreground;
1507 mw->menu.disabled_gc = XtGetGC ((Widget)mw,
1508 (GCFont | GCForeground | GCBackground), &xgcv);
1511 xgcv.font = mw->menu.font->fid;
1512 xgcv.foreground = mw->menu.button_foreground;
1513 xgcv.background = mw->core.background_pixel;
1514 xgcv.fill_style = FillStippled;
1515 xgcv.stipple = mw->menu.gray_pixmap;
1516 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw,
1517 (GCFont | GCForeground | GCBackground
1518 | GCFillStyle | GCStipple), &xgcv);
1520 xgcv.font = mw->menu.font->fid;
1521 xgcv.foreground = mw->core.background_pixel;
1522 xgcv.background = mw->menu.foreground;
1523 mw->menu.background_gc = XtGetGC ((Widget)mw,
1524 GCFont | GCForeground | GCBackground,
1525 &xgcv);
1528 static void
1529 release_drawing_gcs (mw)
1530 XlwMenuWidget mw;
1532 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1533 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1534 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1535 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1536 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1537 /* let's get some segvs if we try to use these... */
1538 mw->menu.foreground_gc = (GC) -1;
1539 mw->menu.button_gc = (GC) -1;
1540 mw->menu.disabled_gc = (GC) -1;
1541 mw->menu.inactive_button_gc = (GC) -1;
1542 mw->menu.background_gc = (GC) -1;
1545 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1546 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1548 static void
1549 make_shadow_gcs (mw)
1550 XlwMenuWidget mw;
1552 XGCValues xgcv;
1553 unsigned long pm = 0;
1554 Display *dpy = XtDisplay ((Widget) mw);
1555 Screen *screen = XtScreen ((Widget) mw);
1556 Colormap cmap = mw->core.colormap;
1557 XColor topc, botc;
1558 int top_frobbed = 0, bottom_frobbed = 0;
1560 mw->menu.free_top_shadow_color_p = 0;
1561 mw->menu.free_bottom_shadow_color_p = 0;
1563 if (mw->menu.top_shadow_color == -1)
1564 mw->menu.top_shadow_color = mw->core.background_pixel;
1565 else
1566 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1568 if (mw->menu.bottom_shadow_color == -1)
1569 mw->menu.bottom_shadow_color = mw->menu.foreground;
1570 else
1571 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1573 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1574 mw->menu.top_shadow_color == mw->menu.foreground)
1576 topc.pixel = mw->core.background_pixel;
1577 #ifdef emacs
1578 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1579 &topc.pixel,
1580 1.2, 0x8000))
1581 #else
1582 XQueryColor (dpy, cmap, &topc);
1583 /* don't overflow/wrap! */
1584 topc.red = MINL (65535, topc.red * 1.2);
1585 topc.green = MINL (65535, topc.green * 1.2);
1586 topc.blue = MINL (65535, topc.blue * 1.2);
1587 if (XAllocColor (dpy, cmap, &topc))
1588 #endif
1590 mw->menu.top_shadow_color = topc.pixel;
1591 mw->menu.free_top_shadow_color_p = 1;
1592 top_frobbed = 1;
1595 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1596 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1598 botc.pixel = mw->core.background_pixel;
1599 #ifdef emacs
1600 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1601 &botc.pixel,
1602 0.6, 0x4000))
1603 #else
1604 XQueryColor (dpy, cmap, &botc);
1605 botc.red *= 0.6;
1606 botc.green *= 0.6;
1607 botc.blue *= 0.6;
1608 if (XAllocColor (dpy, cmap, &botc))
1609 #endif
1611 mw->menu.bottom_shadow_color = botc.pixel;
1612 mw->menu.free_bottom_shadow_color_p = 1;
1613 bottom_frobbed = 1;
1617 if (top_frobbed && bottom_frobbed)
1619 if (topc.pixel == botc.pixel)
1621 if (botc.pixel == mw->menu.foreground)
1623 if (mw->menu.free_top_shadow_color_p)
1625 x_free_dpy_colors (dpy, screen, cmap,
1626 &mw->menu.top_shadow_color, 1);
1627 mw->menu.free_top_shadow_color_p = 0;
1629 mw->menu.top_shadow_color = mw->core.background_pixel;
1631 else
1633 if (mw->menu.free_bottom_shadow_color_p)
1635 x_free_dpy_colors (dpy, screen, cmap,
1636 &mw->menu.bottom_shadow_color, 1);
1637 mw->menu.free_bottom_shadow_color_p = 0;
1639 mw->menu.bottom_shadow_color = mw->menu.foreground;
1644 if (!mw->menu.top_shadow_pixmap &&
1645 mw->menu.top_shadow_color == mw->core.background_pixel)
1647 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1648 if (mw->menu.free_top_shadow_color_p)
1650 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1651 mw->menu.free_top_shadow_color_p = 0;
1653 mw->menu.top_shadow_color = mw->menu.foreground;
1655 if (!mw->menu.bottom_shadow_pixmap &&
1656 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1658 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1659 if (mw->menu.free_bottom_shadow_color_p)
1661 x_free_dpy_colors (dpy, screen, cmap,
1662 &mw->menu.bottom_shadow_color, 1);
1663 mw->menu.free_bottom_shadow_color_p = 0;
1665 mw->menu.bottom_shadow_color = mw->menu.foreground;
1668 xgcv.fill_style = FillStippled;
1669 xgcv.foreground = mw->menu.top_shadow_color;
1670 xgcv.stipple = mw->menu.top_shadow_pixmap;
1671 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1672 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1674 xgcv.foreground = mw->menu.bottom_shadow_color;
1675 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1676 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1677 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1681 static void
1682 release_shadow_gcs (mw)
1683 XlwMenuWidget mw;
1685 Display *dpy = XtDisplay ((Widget) mw);
1686 Screen *screen = XtScreen ((Widget) mw);
1687 Colormap cmap = mw->core.colormap;
1688 Pixel px[2];
1689 int i = 0;
1691 if (mw->menu.free_top_shadow_color_p)
1692 px[i++] = mw->menu.top_shadow_color;
1693 if (mw->menu.free_bottom_shadow_color_p)
1694 px[i++] = mw->menu.bottom_shadow_color;
1695 if (i > 0)
1696 x_free_dpy_colors (dpy, screen, cmap, px, i);
1698 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1699 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1702 static void
1703 XlwMenuInitialize (request, mw, args, num_args)
1704 Widget request;
1705 XlwMenuWidget mw;
1706 ArgList args;
1707 Cardinal *num_args;
1709 /* Get the GCs and the widget size */
1710 XSetWindowAttributes xswa;
1711 int mask;
1713 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1714 Display* display = XtDisplay (mw);
1716 #if 0
1717 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1719 /* _XtCreate is freeing the object that was passed to us,
1720 so make a copy that we will actually keep. */
1721 lwlib_bcopy (mw->menu.contents, tem, sizeof (widget_value));
1722 mw->menu.contents = tem;
1723 #endif
1725 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1726 mw->menu.cursor = mw->menu.cursor_shape;
1728 mw->menu.gray_pixmap
1729 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1730 gray_bitmap_width, gray_bitmap_height,
1731 (unsigned long)1, (unsigned long)0, 1);
1733 /* I don't understand why this ends up 0 sometimes,
1734 but it does. This kludge works around it.
1735 Can anyone find a real fix? -- rms. */
1736 if (mw->menu.font == 0)
1737 mw->menu.font = xlwmenu_default_font;
1739 make_drawing_gcs (mw);
1740 make_shadow_gcs (mw);
1742 xswa.background_pixel = mw->core.background_pixel;
1743 xswa.border_pixel = mw->core.border_pixel;
1744 mask = CWBackPixel | CWBorderPixel;
1746 mw->menu.popped_up = False;
1748 mw->menu.old_depth = 1;
1749 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1750 mw->menu.old_stack_length = 1;
1751 mw->menu.old_stack [0] = mw->menu.contents;
1753 mw->menu.new_depth = 0;
1754 mw->menu.new_stack = 0;
1755 mw->menu.new_stack_length = 0;
1756 push_new_stack (mw, mw->menu.contents);
1758 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1759 mw->menu.windows_length = 1;
1760 mw->menu.windows [0].x = 0;
1761 mw->menu.windows [0].y = 0;
1762 mw->menu.windows [0].width = 0;
1763 mw->menu.windows [0].height = 0;
1764 size_menu (mw, 0);
1766 mw->core.width = mw->menu.windows [0].width;
1767 mw->core.height = mw->menu.windows [0].height;
1770 static void
1771 XlwMenuClassInitialize ()
1775 static void
1776 XlwMenuRealize (w, valueMask, attributes)
1777 Widget w;
1778 Mask *valueMask;
1779 XSetWindowAttributes *attributes;
1781 XlwMenuWidget mw = (XlwMenuWidget)w;
1782 XSetWindowAttributes xswa;
1783 int mask;
1785 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1786 (w, valueMask, attributes);
1788 xswa.save_under = True;
1789 xswa.cursor = mw->menu.cursor_shape;
1790 mask = CWSaveUnder | CWCursor;
1791 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1793 mw->menu.windows [0].window = XtWindow (w);
1794 mw->menu.windows [0].x = w->core.x;
1795 mw->menu.windows [0].y = w->core.y;
1796 mw->menu.windows [0].width = w->core.width;
1797 mw->menu.windows [0].height = w->core.height;
1800 /* Only the toplevel menubar/popup is a widget so it's the only one that
1801 receives expose events through Xt. So we repaint all the other panes
1802 when receiving an Expose event. */
1803 static void
1804 XlwMenuRedisplay (w, ev, region)
1805 Widget w;
1806 XEvent* ev;
1807 Region region;
1809 XlwMenuWidget mw = (XlwMenuWidget)w;
1810 int i;
1812 /* If we have a depth beyond 1, it's because a submenu was displayed.
1813 If the submenu has been destroyed, set the depth back to 1. */
1814 if (submenu_destroyed)
1816 mw->menu.old_depth = 1;
1817 submenu_destroyed = 0;
1820 for (i = 0; i < mw->menu.old_depth; i++)
1821 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1825 /* Part of a hack to make the menu redisplay when a tooltip frame
1826 over a menu item is unmapped. */
1828 void
1829 xlwmenu_redisplay (w)
1830 Widget w;
1832 XlwMenuRedisplay (w, NULL, None);
1835 static void
1836 XlwMenuDestroy (w)
1837 Widget w;
1839 int i;
1840 XlwMenuWidget mw = (XlwMenuWidget) w;
1842 if (pointer_grabbed)
1843 ungrab_all ((Widget)w, CurrentTime);
1844 pointer_grabbed = 0;
1846 submenu_destroyed = 1;
1848 release_drawing_gcs (mw);
1849 release_shadow_gcs (mw);
1851 /* this doesn't come from the resource db but is created explicitly
1852 so we must free it ourselves. */
1853 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1854 mw->menu.gray_pixmap = (Pixmap) -1;
1856 #if 0
1857 /* Do free mw->menu.contents because nowadays we copy it
1858 during initialization. */
1859 XtFree (mw->menu.contents);
1860 #endif
1862 /* Don't free mw->menu.contents because that comes from our creator.
1863 The `*_stack' elements are just pointers into `contents' so leave
1864 that alone too. But free the stacks themselves. */
1865 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1866 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1868 /* Remember, you can't free anything that came from the resource
1869 database. This includes:
1870 mw->menu.cursor
1871 mw->menu.top_shadow_pixmap
1872 mw->menu.bottom_shadow_pixmap
1873 mw->menu.font
1874 Also the color cells of top_shadow_color, bottom_shadow_color,
1875 foreground, and button_foreground will never be freed until this
1876 client exits. Nice, eh?
1879 /* start from 1 because the one in slot 0 is w->core.window */
1880 for (i = 1; i < mw->menu.windows_length; i++)
1881 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1882 if (mw->menu.windows)
1883 XtFree ((char *) mw->menu.windows);
1886 static Boolean
1887 XlwMenuSetValues (current, request, new)
1888 Widget current;
1889 Widget request;
1890 Widget new;
1892 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1893 XlwMenuWidget newmw = (XlwMenuWidget)new;
1894 Boolean redisplay = False;
1895 int i;
1897 if (newmw->menu.contents
1898 && newmw->menu.contents->contents
1899 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1900 redisplay = True;
1901 /* Do redisplay if the contents are entirely eliminated. */
1902 if (newmw->menu.contents
1903 && newmw->menu.contents->contents == 0
1904 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1905 redisplay = True;
1907 if (newmw->core.background_pixel != oldmw->core.background_pixel
1908 || newmw->menu.foreground != oldmw->menu.foreground
1909 || newmw->menu.font != oldmw->menu.font)
1911 release_drawing_gcs (newmw);
1912 make_drawing_gcs (newmw);
1914 release_shadow_gcs (newmw);
1915 /* Cause the shadow colors to be recalculated. */
1916 newmw->menu.top_shadow_color = -1;
1917 newmw->menu.bottom_shadow_color = -1;
1918 make_shadow_gcs (newmw);
1920 redisplay = True;
1922 if (XtIsRealized (current))
1923 /* If the menu is currently displayed, change the display. */
1924 for (i = 0; i < oldmw->menu.windows_length; i++)
1926 XSetWindowBackground (XtDisplay (oldmw),
1927 oldmw->menu.windows [i].window,
1928 newmw->core.background_pixel);
1929 /* clear windows and generate expose events */
1930 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
1931 0, 0, 0, 0, True);
1935 return redisplay;
1938 static void
1939 XlwMenuResize (w)
1940 Widget w;
1942 XlwMenuWidget mw = (XlwMenuWidget)w;
1944 if (mw->menu.popped_up)
1946 /* Don't allow the popup menu to resize itself. */
1947 mw->core.width = mw->menu.windows [0].width;
1948 mw->core.height = mw->menu.windows [0].height;
1949 mw->core.parent->core.width = mw->core.width ;
1950 mw->core.parent->core.height = mw->core.height ;
1952 else
1954 mw->menu.windows [0].width = mw->core.width;
1955 mw->menu.windows [0].height = mw->core.height;
1959 \f/* Action procedures */
1960 static void
1961 handle_single_motion_event (mw, ev)
1962 XlwMenuWidget mw;
1963 XMotionEvent* ev;
1965 widget_value* val;
1966 int level;
1968 if (!map_event_to_widget_value (mw, ev, &val, &level))
1969 pop_new_stack_if_no_contents (mw);
1970 else
1971 set_new_state (mw, val, level);
1972 remap_menubar (mw);
1974 /* Sync with the display. Makes it feel better on X terms. */
1975 XSync (XtDisplay (mw), False);
1978 static void
1979 handle_motion_event (mw, ev)
1980 XlwMenuWidget mw;
1981 XMotionEvent* ev;
1983 int x = ev->x_root;
1984 int y = ev->y_root;
1985 int state = ev->state;
1987 handle_single_motion_event (mw, ev);
1989 /* allow motion events to be generated again */
1990 if (ev->is_hint
1991 && XQueryPointer (XtDisplay (mw), ev->window,
1992 &ev->root, &ev->subwindow,
1993 &ev->x_root, &ev->y_root,
1994 &ev->x, &ev->y,
1995 &ev->state)
1996 && ev->state == state
1997 && (ev->x_root != x || ev->y_root != y))
1998 handle_single_motion_event (mw, ev);
2001 static void
2002 Start (w, ev, params, num_params)
2003 Widget w;
2004 XEvent *ev;
2005 String *params;
2006 Cardinal *num_params;
2008 XlwMenuWidget mw = (XlwMenuWidget)w;
2010 if (!mw->menu.popped_up)
2012 menu_post_event = *ev;
2013 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2015 else
2017 /* If we push a button while the menu is posted semipermanently,
2018 releasing the button should always pop the menu down. */
2019 next_release_must_exit = 1;
2021 /* notes the absolute position of the menubar window */
2022 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2023 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2025 /* handles the down like a move, slots are compatible */
2026 handle_motion_event (mw, &ev->xmotion);
2030 static void
2031 Drag (w, ev, params, num_params)
2032 Widget w;
2033 XEvent *ev;
2034 String *params;
2035 Cardinal *num_params;
2037 XlwMenuWidget mw = (XlwMenuWidget)w;
2038 if (mw->menu.popped_up)
2039 handle_motion_event (mw, &ev->xmotion);
2042 /* Do nothing.
2043 This is how we handle presses and releases of modifier keys. */
2044 static void
2045 Nothing (w, ev, params, num_params)
2046 Widget w;
2047 XEvent *ev;
2048 String *params;
2049 Cardinal *num_params;
2053 widget_value *
2054 find_first_selectable (mw, item)
2055 XlwMenuWidget mw;
2056 widget_value *item;
2058 widget_value *current = item;
2059 enum menu_separator separator;
2061 while (lw_separator_p (current->name, &separator, 0) || !current->enabled)
2062 if (current->next)
2063 current=current->next;
2064 else
2065 return NULL;
2067 return current;
2070 widget_value *
2071 find_next_selectable (mw, item)
2072 XlwMenuWidget mw;
2073 widget_value *item;
2075 widget_value *current = item;
2076 enum menu_separator separator;
2078 while (current->next && (current=current->next) &&
2079 (lw_separator_p (current->name, &separator, 0) || !current->enabled))
2082 if (current == item)
2084 if (mw->menu.old_depth < 2)
2085 return current;
2086 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2088 while (lw_separator_p (current->name, &separator, 0) || !current->enabled)
2090 if (current->next)
2091 current=current->next;
2093 if (current == item)
2094 break;
2099 return current;
2102 widget_value *
2103 find_prev_selectable (mw, item)
2104 XlwMenuWidget mw;
2105 widget_value *item;
2107 widget_value *current = item;
2108 widget_value *prev = item;
2110 while ((current=find_next_selectable (mw, current)) != item)
2112 if (prev == current)
2113 break;
2114 prev=current;
2117 return prev;
2120 static void
2121 Down (w, ev, params, num_params)
2122 Widget w;
2123 XEvent *ev;
2124 String *params;
2125 Cardinal *num_params;
2127 XlwMenuWidget mw = (XlwMenuWidget) w;
2128 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2130 /* Inside top-level menu-bar? */
2131 if (mw->menu.old_depth == 2)
2132 /* When <down> in the menu-bar is pressed, display the corresponding
2133 sub-menu and select the first selectable menu item there. */
2134 set_new_state (mw, find_first_selectable (mw, selected_item->contents), mw->menu.old_depth);
2135 else
2136 /* Highlight next possible (enabled and not separator) menu item. */
2137 set_new_state (mw, find_next_selectable (mw, selected_item), mw->menu.old_depth - 1);
2139 remap_menubar (mw);
2142 static void
2143 Up (w, ev, params, num_params)
2144 Widget w;
2145 XEvent *ev;
2146 String *params;
2147 Cardinal *num_params;
2149 XlwMenuWidget mw = (XlwMenuWidget) w;
2150 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2152 /* Inside top-level menu-bar? */
2153 if (mw->menu.old_depth == 2)
2155 /* FIXME: this is tricky. <up> in the menu-bar should select the
2156 last selectable item in the list. So we select the first
2157 selectable one and find the previous selectable item. Is there
2158 a better way? */
2159 set_new_state (mw, find_first_selectable (mw, selected_item->contents), mw->menu.old_depth);
2160 remap_menubar (mw);
2161 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2162 set_new_state (mw, find_prev_selectable (mw, selected_item), mw->menu.old_depth - 1);
2164 else
2165 /* Highlight previous (enabled and not separator) menu item. */
2166 set_new_state (mw, find_prev_selectable (mw, selected_item), mw->menu.old_depth - 1);
2168 remap_menubar (mw);
2171 static void
2172 Left (w, ev, params, num_params)
2173 Widget w;
2174 XEvent *ev;
2175 String *params;
2176 Cardinal *num_params;
2178 XlwMenuWidget mw = (XlwMenuWidget) w;
2179 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2181 /* Inside top-level menu-bar? */
2182 if (mw->menu.old_depth == 2)
2183 /* When <left> in the menu-bar is pressed, display the previous item on
2184 the menu-bar. If the current item is the first one, highlight the
2185 last item in the menubar (probably Help). */
2186 set_new_state (mw, find_prev_selectable (mw, selected_item), mw->menu.old_depth - 1);
2187 else
2189 pop_new_stack_if_no_contents (mw);
2190 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2], mw->menu.old_depth - 2);
2193 remap_menubar (mw);
2196 static void
2197 Right (w, ev, params, num_params)
2198 Widget w;
2199 XEvent *ev;
2200 String *params;
2201 Cardinal *num_params;
2203 XlwMenuWidget mw = (XlwMenuWidget) w;
2204 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2206 /* Inside top-level menu-bar? */
2207 if (mw->menu.old_depth == 2)
2208 /* When <right> in the menu-bar is pressed, display the next item on
2209 the menu-bar. If the current item is the last one, highlight the
2210 first item (probably File). */
2211 set_new_state (mw, find_next_selectable (mw, selected_item), mw->menu.old_depth - 1);
2212 else if (selected_item->contents) /* Is this menu item expandable? */
2214 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2215 remap_menubar (mw);
2216 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2217 if (!selected_item->enabled && find_first_selectable (mw, selected_item))
2218 set_new_state (mw, find_first_selectable (mw, selected_item), mw->menu.old_depth - 1);
2220 else
2222 pop_new_stack_if_no_contents (mw);
2223 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2], mw->menu.old_depth - 2);
2226 remap_menubar (mw);
2229 /* Handle key press and release events while menu is popped up.
2230 Our action is to get rid of the menu. */
2231 static void
2232 Key (w, ev, params, num_params)
2233 Widget w;
2234 XEvent *ev;
2235 String *params;
2236 Cardinal *num_params;
2238 XlwMenuWidget mw = (XlwMenuWidget)w;
2240 /* Pop down everything. */
2241 mw->menu.new_depth = 1;
2242 remap_menubar (mw);
2244 if (mw->menu.popped_up)
2246 mw->menu.popped_up = False;
2247 ungrab_all ((Widget)mw, ev->xmotion.time);
2248 if (XtIsShell (XtParent ((Widget) mw)))
2249 XtPopdown (XtParent ((Widget) mw));
2250 else
2252 XtRemoveGrab ((Widget) mw);
2253 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2257 /* callback */
2258 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2261 static void
2262 Select (w, ev, params, num_params)
2263 Widget w;
2264 XEvent *ev;
2265 String *params;
2266 Cardinal *num_params;
2268 XlwMenuWidget mw = (XlwMenuWidget)w;
2269 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2271 /* If user releases the button quickly, without selecting anything,
2272 after the initial down-click that brought the menu up,
2273 do nothing. */
2274 if ((selected_item == 0
2275 || ((widget_value *) selected_item)->call_data == 0)
2276 && !next_release_must_exit
2277 && (ev->xbutton.time - menu_post_event.xbutton.time
2278 < XtGetMultiClickTime (XtDisplay (w))))
2279 return;
2281 /* pop down everything. */
2282 mw->menu.new_depth = 1;
2283 remap_menubar (mw);
2285 if (mw->menu.popped_up)
2287 mw->menu.popped_up = False;
2288 ungrab_all ((Widget)mw, ev->xmotion.time);
2289 if (XtIsShell (XtParent ((Widget) mw)))
2290 XtPopdown (XtParent ((Widget) mw));
2291 else
2293 XtRemoveGrab ((Widget) mw);
2294 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2298 /* callback */
2299 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2303 \f/* Special code to pop-up a menu */
2304 void
2305 pop_up_menu (mw, event)
2306 XlwMenuWidget mw;
2307 XButtonPressedEvent* event;
2309 int x = event->x_root;
2310 int y = event->y_root;
2311 int w;
2312 int h;
2313 int borderwidth = mw->menu.shadow_thickness;
2314 Screen* screen = XtScreen (mw);
2315 Display *display = XtDisplay (mw);
2316 int count;
2318 next_release_must_exit = 0;
2320 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2322 if (XtIsShell (XtParent ((Widget)mw)))
2323 size_menu (mw, 0);
2325 w = mw->menu.windows [0].width;
2326 h = mw->menu.windows [0].height;
2328 x -= borderwidth;
2329 y -= borderwidth;
2330 if (x < borderwidth)
2331 x = borderwidth;
2332 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2333 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2334 if (y < borderwidth)
2335 y = borderwidth;
2336 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2337 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2339 mw->menu.popped_up = True;
2340 if (XtIsShell (XtParent ((Widget)mw)))
2342 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2343 XtParent ((Widget)mw)->core.border_width);
2344 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2345 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2346 mw->menu.windows [0].x = x + borderwidth;
2347 mw->menu.windows [0].y = y + borderwidth;
2349 else
2351 XEvent *ev = (XEvent *) event;
2353 XtAddGrab ((Widget) mw, True, True);
2355 /* notes the absolute position of the menubar window */
2356 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2357 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2360 #ifdef emacs
2361 count = x_catch_errors (display);
2362 #endif
2363 if (XtGrabPointer ((Widget)mw, False,
2364 (PointerMotionMask
2365 | PointerMotionHintMask
2366 | ButtonReleaseMask
2367 | ButtonPressMask),
2368 GrabModeAsync, GrabModeAsync, None,
2369 mw->menu.cursor_shape,
2370 event->time) == Success)
2372 if (! GRAB_KEYBOARD
2373 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2374 GrabModeAsync, event->time) == Success)
2376 XtSetKeyboardFocus((Widget)mw, None);
2377 pointer_grabbed = 1;
2379 else
2380 XtUngrabPointer ((Widget)mw, event->time);
2383 #ifdef emacs
2384 if (x_had_errors_p (display))
2386 pointer_grabbed = 0;
2387 XtUngrabPointer ((Widget)mw, event->time);
2389 x_uncatch_errors (display, count);
2390 #endif
2392 handle_motion_event (mw, (XMotionEvent*)event);