Merged from miles@gnu.org--gnu-2005 (patch 683-684)
[emacs.git] / lwlib / xlwmenu.c
blob8f5e73820865f5808caa958a1b1e26bdb98208d3
1 /* Implements a lightweight menubar widget.
2 Copyright (C) 1992 Lucid, Inc.
3 Copyright (C) 2002, 2003, 2004, 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., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, 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 void x_uncatch_errors P_ ((Display *, int));
63 extern int x_had_errors_p __P ((Display*));
64 extern void 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 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
140 offset(menu.fontSet), XtRFontSet, NULL},
141 #endif
142 {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
143 offset(menu.font), XtRString, "XtDefaultFont"},
144 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
145 offset(menu.foreground), XtRString, "XtDefaultForeground"},
146 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
147 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
148 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
149 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
150 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
151 offset(menu.margin), XtRImmediate, (XtPointer)1},
152 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
153 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
154 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
155 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
156 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
157 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
159 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
160 sizeof (Dimension), offset (menu.shadow_thickness),
161 XtRImmediate, (XtPointer)1},
162 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
163 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
164 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
165 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
166 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
167 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
168 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
169 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
171 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
172 offset(menu.open), XtRCallback, (XtPointer)NULL},
173 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
174 offset(menu.select), XtRCallback, (XtPointer)NULL},
175 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
176 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
177 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
178 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
179 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
180 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
181 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
182 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
184 #undef offset
186 static Boolean XlwMenuSetValues();
187 static void XlwMenuRealize();
188 static void XlwMenuRedisplay();
189 static void XlwMenuResize();
190 static void XlwMenuInitialize();
191 static void XlwMenuRedisplay();
192 static void XlwMenuDestroy();
193 static void XlwMenuClassInitialize();
194 static void Start();
195 static void Drag();
196 static void Down();
197 static void Up();
198 static void Left();
199 static void Right();
200 static void Select();
201 static void Key();
202 static void Nothing();
203 static int separator_height __P ((enum menu_separator));
204 static void pop_up_menu __P ((XlwMenuWidget, XButtonPressedEvent *));
207 static XtActionsRec
208 xlwMenuActionsList [] =
210 {"start", Start},
211 {"drag", Drag},
212 {"down", Down},
213 {"up", Up},
214 {"left", Left},
215 {"right", Right},
216 {"select", Select},
217 {"key", Key},
218 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
219 {"nothing", Nothing},
222 #define SuperClass ((CoreWidgetClass)&coreClassRec)
224 XlwMenuClassRec xlwMenuClassRec =
226 { /* CoreClass fields initialization */
227 (WidgetClass) SuperClass, /* superclass */
228 "XlwMenu", /* class_name */
229 sizeof(XlwMenuRec), /* size */
230 XlwMenuClassInitialize, /* class_initialize */
231 NULL, /* class_part_initialize */
232 FALSE, /* class_inited */
233 XlwMenuInitialize, /* initialize */
234 NULL, /* initialize_hook */
235 XlwMenuRealize, /* realize */
236 xlwMenuActionsList, /* actions */
237 XtNumber(xlwMenuActionsList), /* num_actions */
238 xlwMenuResources, /* resources */
239 XtNumber(xlwMenuResources), /* resource_count */
240 NULLQUARK, /* xrm_class */
241 TRUE, /* compress_motion */
242 XtExposeCompressMaximal, /* compress_exposure */
243 TRUE, /* compress_enterleave */
244 FALSE, /* visible_interest */
245 XlwMenuDestroy, /* destroy */
246 XlwMenuResize, /* resize */
247 XlwMenuRedisplay, /* expose */
248 XlwMenuSetValues, /* set_values */
249 NULL, /* set_values_hook */
250 XtInheritSetValuesAlmost, /* set_values_almost */
251 NULL, /* get_values_hook */
252 NULL, /* accept_focus */
253 XtVersion, /* version */
254 NULL, /* callback_private */
255 xlwMenuTranslations, /* tm_table */
256 XtInheritQueryGeometry, /* query_geometry */
257 XtInheritDisplayAccelerator, /* display_accelerator */
258 NULL /* extension */
259 }, /* XlwMenuClass fields initialization */
261 0 /* dummy */
265 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
267 int submenu_destroyed;
269 /* For debug, if installation-directory is non-nil this is not an installed
270 Emacs. In that case we do not grab the keyboard to make it easier to
271 debug. */
272 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
274 static int next_release_must_exit;
276 \f/* Utilities */
278 /* Ungrab pointer and keyboard */
279 static void
280 ungrab_all (w, ungrabtime)
281 Widget w;
282 Time ungrabtime;
284 XtUngrabPointer (w, ungrabtime);
285 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
288 /* Like abort, but remove grabs from widget W before. */
290 static void
291 abort_gracefully (w)
292 Widget w;
294 if (XtIsShell (XtParent (w)))
295 XtRemoveGrab (w);
296 ungrab_all (w, CurrentTime);
297 abort ();
300 static void
301 push_new_stack (mw, val)
302 XlwMenuWidget mw;
303 widget_value* val;
305 if (!mw->menu.new_stack)
307 mw->menu.new_stack_length = 10;
308 mw->menu.new_stack =
309 (widget_value**)XtCalloc (mw->menu.new_stack_length,
310 sizeof (widget_value*));
312 else if (mw->menu.new_depth == mw->menu.new_stack_length)
314 mw->menu.new_stack_length *= 2;
315 mw->menu.new_stack =
316 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
317 mw->menu.new_stack_length * sizeof (widget_value*));
319 mw->menu.new_stack [mw->menu.new_depth++] = val;
322 static void
323 pop_new_stack_if_no_contents (mw)
324 XlwMenuWidget mw;
326 if (mw->menu.new_depth > 1)
328 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
329 mw->menu.new_depth -= 1;
333 static void
334 make_old_stack_space (mw, n)
335 XlwMenuWidget mw;
336 int n;
338 if (!mw->menu.old_stack)
340 mw->menu.old_stack_length = 10;
341 mw->menu.old_stack =
342 (widget_value**)XtCalloc (mw->menu.old_stack_length,
343 sizeof (widget_value*));
345 else if (mw->menu.old_stack_length < n)
347 mw->menu.old_stack_length *= 2;
348 mw->menu.old_stack =
349 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
350 mw->menu.old_stack_length * sizeof (widget_value*));
354 \f/* Size code */
356 string_width (mw, s)
357 XlwMenuWidget mw;
358 char *s;
360 XCharStruct xcs;
361 int drop;
362 #ifdef HAVE_X_I18N
363 XRectangle ink, logical;
364 if (mw->menu.fontSet)
366 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
367 return logical.width;
369 #endif
371 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
372 return xcs.width;
376 #ifdef HAVE_X_I18N
377 #define MENU_FONT_HEIGHT(mw) \
378 ((mw)->menu.fontSet != NULL \
379 ? (mw)->menu.font_extents->max_logical_extent.height \
380 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
381 #define MENU_FONT_ASCENT(mw) \
382 ((mw)->menu.fontSet != NULL \
383 ? - (mw)->menu.font_extents->max_logical_extent.y \
384 : (mw)->menu.font->ascent)
385 #else
386 #define MENU_FONT_HEIGHT(mw) \
387 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
388 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
389 #endif
391 static int
392 arrow_width (mw)
393 XlwMenuWidget mw;
395 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
398 /* Return the width of toggle buttons of widget MW. */
400 static int
401 toggle_button_width (mw)
402 XlwMenuWidget mw;
404 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
408 /* Return the width of radio buttons of widget MW. */
410 static int
411 radio_button_width (mw)
412 XlwMenuWidget mw;
414 return toggle_button_width (mw) * 1.41;
418 static XtResource
419 nameResource[] =
421 {"labelString", "LabelString", XtRString, sizeof(String),
422 0, XtRImmediate, 0},
425 static char*
426 resource_widget_value (mw, val)
427 XlwMenuWidget mw;
428 widget_value *val;
430 if (!val->toolkit_data)
432 char* resourced_name = NULL;
433 char* complete_name;
434 XtGetSubresources ((Widget) mw,
435 (XtPointer) &resourced_name,
436 val->name, val->name,
437 nameResource, 1, NULL, 0);
438 if (!resourced_name)
439 resourced_name = val->name;
440 if (!val->value)
442 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
443 strcpy (complete_name, resourced_name);
445 else
447 int complete_length =
448 strlen (resourced_name) + strlen (val->value) + 2;
449 complete_name = XtMalloc (complete_length);
450 *complete_name = 0;
451 strcat (complete_name, resourced_name);
452 strcat (complete_name, " ");
453 strcat (complete_name, val->value);
456 val->toolkit_data = complete_name;
457 val->free_toolkit_data = True;
459 return (char*)val->toolkit_data;
462 /* Returns the sizes of an item */
463 static void
464 size_menu_item (mw, val, horizontal_p, label_width, rest_width, button_width,
465 height)
466 XlwMenuWidget mw;
467 widget_value* val;
468 int horizontal_p;
469 int* label_width;
470 int* rest_width;
471 int* button_width;
472 int* height;
474 enum menu_separator separator;
476 if (lw_separator_p (val->name, &separator, 0))
478 *height = separator_height (separator);
479 *label_width = 1;
480 *rest_width = 0;
481 *button_width = 0;
483 else
485 *height = MENU_FONT_HEIGHT (mw)
486 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
488 *label_width =
489 string_width (mw, resource_widget_value (mw, val))
490 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
492 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
493 if (!horizontal_p)
495 if (val->contents)
496 /* Add width of the arrow displayed for submenus. */
497 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
498 else if (val->key)
499 /* Add width of key equivalent string. */
500 *rest_width += (string_width (mw, val->key)
501 + mw->menu.arrow_spacing);
503 if (val->button_type == BUTTON_TYPE_TOGGLE)
504 *button_width = (toggle_button_width (mw)
505 + mw->menu.horizontal_spacing);
506 else if (val->button_type == BUTTON_TYPE_RADIO)
507 *button_width = (radio_button_width (mw)
508 + mw->menu.horizontal_spacing);
513 static void
514 size_menu (mw, level)
515 XlwMenuWidget mw;
516 int level;
518 unsigned int label_width = 0;
519 int rest_width = 0;
520 int button_width = 0;
521 int max_rest_width = 0;
522 int max_button_width = 0;
523 unsigned int height = 0;
524 int horizontal_p = mw->menu.horizontal && (level == 0);
525 widget_value* val;
526 window_state* ws;
528 if (level >= mw->menu.old_depth)
529 abort_gracefully ((Widget) mw);
531 ws = &mw->menu.windows [level];
532 ws->width = 0;
533 ws->height = 0;
534 ws->label_width = 0;
535 ws->button_width = 0;
537 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
539 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
540 &button_width, &height);
541 if (horizontal_p)
543 ws->width += label_width + rest_width;
544 if (height > ws->height)
545 ws->height = height;
547 else
549 if (label_width > ws->label_width)
550 ws->label_width = label_width;
551 if (rest_width > max_rest_width)
552 max_rest_width = rest_width;
553 if (button_width > max_button_width)
554 max_button_width = button_width;
555 ws->height += height;
559 if (horizontal_p)
560 ws->label_width = ws->button_width = 0;
561 else
563 ws->width = ws->label_width + max_rest_width + max_button_width;
564 ws->button_width = max_button_width;
567 ws->width += 2 * mw->menu.shadow_thickness;
568 ws->height += 2 * mw->menu.shadow_thickness;
570 if (horizontal_p)
572 ws->width += 2 * mw->menu.margin;
573 ws->height += 2 * mw->menu.margin;
578 \f/* Display code */
580 static void
581 draw_arrow (mw, window, gc, x, y, width, down_p)
582 XlwMenuWidget mw;
583 Window window;
584 GC gc;
585 int x;
586 int y;
587 int width;
588 int down_p;
590 Display *dpy = XtDisplay (mw);
591 GC top_gc = mw->menu.shadow_top_gc;
592 GC bottom_gc = mw->menu.shadow_bottom_gc;
593 int thickness = mw->menu.shadow_thickness;
594 int height = width;
595 XPoint pt[10];
596 /* alpha = atan (0.5)
597 factor = (1 + sin (alpha)) / cos (alpha) */
598 double factor = 1.62;
599 int thickness2 = thickness * factor;
601 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
603 if (down_p)
605 GC temp;
606 temp = top_gc;
607 top_gc = bottom_gc;
608 bottom_gc = temp;
611 pt[0].x = x;
612 pt[0].y = y + height;
613 pt[1].x = x + thickness;
614 pt[1].y = y + height - thickness2;
615 pt[2].x = x + thickness2;
616 pt[2].y = y + thickness2;
617 pt[3].x = x;
618 pt[3].y = y;
619 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
621 pt[0].x = x;
622 pt[0].y = y;
623 pt[1].x = x + thickness;
624 pt[1].y = y + thickness2;
625 pt[2].x = x + width - thickness2;
626 pt[2].y = y + height / 2;
627 pt[3].x = x + width;
628 pt[3].y = y + height / 2;
629 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
631 pt[0].x = x;
632 pt[0].y = y + height;
633 pt[1].x = x + thickness;
634 pt[1].y = y + height - thickness2;
635 pt[2].x = x + width - thickness2;
636 pt[2].y = y + height / 2;
637 pt[3].x = x + width;
638 pt[3].y = y + height / 2;
639 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
644 static void
645 draw_shadow_rectangle (mw, window, x, y, width, height, erase_p, down_p)
646 XlwMenuWidget mw;
647 Window window;
648 int x;
649 int y;
650 int width;
651 int height;
652 int erase_p;
653 int down_p;
655 Display *dpy = XtDisplay (mw);
656 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
657 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
658 int thickness = mw->menu.shadow_thickness;
659 XPoint points [4];
661 if (!erase_p && down_p)
663 GC temp;
664 temp = top_gc;
665 top_gc = bottom_gc;
666 bottom_gc = temp;
669 points [0].x = x;
670 points [0].y = y;
671 points [1].x = x + width;
672 points [1].y = y;
673 points [2].x = x + width - thickness;
674 points [2].y = y + thickness;
675 points [3].x = x;
676 points [3].y = y + thickness;
677 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
678 points [0].x = x;
679 points [0].y = y + thickness;
680 points [1].x = x;
681 points [1].y = y + height;
682 points [2].x = x + thickness;
683 points [2].y = y + height - thickness;
684 points [3].x = x + thickness;
685 points [3].y = y + thickness;
686 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
687 points [0].x = x + width;
688 points [0].y = y;
689 points [1].x = x + width - thickness;
690 points [1].y = y + thickness;
691 points [2].x = x + width - thickness;
692 points [2].y = y + height - thickness;
693 points [3].x = x + width;
694 points [3].y = y + height - thickness;
695 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
696 points [0].x = x;
697 points [0].y = y + height;
698 points [1].x = x + width;
699 points [1].y = y + height;
700 points [2].x = x + width;
701 points [2].y = y + height - thickness;
702 points [3].x = x + thickness;
703 points [3].y = y + height - thickness;
704 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
708 static void
709 draw_shadow_rhombus (mw, window, x, y, width, height, erase_p, down_p)
710 XlwMenuWidget mw;
711 Window window;
712 int x;
713 int y;
714 int width;
715 int height;
716 int erase_p;
717 int down_p;
719 Display *dpy = XtDisplay (mw);
720 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
721 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
722 int thickness = mw->menu.shadow_thickness;
723 XPoint points [4];
725 if (!erase_p && down_p)
727 GC temp;
728 temp = top_gc;
729 top_gc = bottom_gc;
730 bottom_gc = temp;
733 points [0].x = x;
734 points [0].y = y + height / 2;
735 points [1].x = x + thickness;
736 points [1].y = y + height / 2;
737 points [2].x = x + width / 2;
738 points [2].y = y + thickness;
739 points [3].x = x + width / 2;
740 points [3].y = y;
741 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
742 points [0].x = x + width / 2;
743 points [0].y = y;
744 points [1].x = x + width / 2;
745 points [1].y = y + thickness;
746 points [2].x = x + width - thickness;
747 points [2].y = y + height / 2;
748 points [3].x = x + width;
749 points [3].y = y + height / 2;
750 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
751 points [0].x = x;
752 points [0].y = y + height / 2;
753 points [1].x = x + thickness;
754 points [1].y = y + height / 2;
755 points [2].x = x + width / 2;
756 points [2].y = y + height - thickness;
757 points [3].x = x + width / 2;
758 points [3].y = y + height;
759 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
760 points [0].x = x + width / 2;
761 points [0].y = y + height;
762 points [1].x = x + width / 2;
763 points [1].y = y + height - thickness;
764 points [2].x = x + width - thickness;
765 points [2].y = y + height / 2;
766 points [3].x = x + width;
767 points [3].y = y + height / 2;
768 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
772 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
773 top-left corner of the menu item. SELECTED_P non-zero means the
774 toggle button is selected. */
776 static void
777 draw_toggle (mw, window, x, y, selected_p)
778 XlwMenuWidget mw;
779 Window window;
780 int x, y, selected_p;
782 int width, height;
784 width = toggle_button_width (mw);
785 height = width;
786 x += mw->menu.horizontal_spacing;
787 y += (MENU_FONT_ASCENT (mw) - height) / 2;
788 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
792 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
793 top-left corner of the menu item. SELECTED_P non-zero means the
794 toggle button is selected. */
796 static void
797 draw_radio (mw, window, x, y, selected_p)
798 XlwMenuWidget mw;
799 Window window;
800 int x, y, selected_p;
802 int width, height;
804 width = radio_button_width (mw);
805 height = width;
806 x += mw->menu.horizontal_spacing;
807 y += (MENU_FONT_ASCENT (mw) - height) / 2;
808 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
812 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
813 top-left corner of the menu item. WIDTH is the width of the
814 separator to draw. TYPE is the separator type. */
816 static void
817 draw_separator (mw, window, x, y, width, type)
818 XlwMenuWidget mw;
819 Window window;
820 int x, y, width;
821 enum menu_separator type;
823 Display *dpy = XtDisplay (mw);
824 XGCValues xgcv;
826 switch (type)
828 case SEPARATOR_NO_LINE:
829 break;
831 case SEPARATOR_SINGLE_LINE:
832 XDrawLine (dpy, window, mw->menu.foreground_gc,
833 x, y, x + width, y);
834 break;
836 case SEPARATOR_DOUBLE_LINE:
837 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
838 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
839 break;
841 case SEPARATOR_SINGLE_DASHED_LINE:
842 xgcv.line_style = LineOnOffDash;
843 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
844 XDrawLine (dpy, window, mw->menu.foreground_gc,
845 x, y, x + width, y);
846 xgcv.line_style = LineSolid;
847 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
848 break;
850 case SEPARATOR_DOUBLE_DASHED_LINE:
851 draw_separator (mw, window, x, y, width,
852 SEPARATOR_SINGLE_DASHED_LINE);
853 draw_separator (mw, window, x, y + 2, width,
854 SEPARATOR_SINGLE_DASHED_LINE);
855 break;
857 case SEPARATOR_SHADOW_ETCHED_IN:
858 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
859 x, y, x + width, y);
860 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
861 x, y + 1, x + width, y + 1);
862 break;
864 case SEPARATOR_SHADOW_ETCHED_OUT:
865 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
866 x, y, x + width, y);
867 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
868 x, y + 1, x + width, y + 1);
869 break;
871 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
872 xgcv.line_style = LineOnOffDash;
873 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
874 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
875 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
876 xgcv.line_style = LineSolid;
877 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
878 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
879 break;
881 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
882 xgcv.line_style = LineOnOffDash;
883 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
884 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
885 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
886 xgcv.line_style = LineSolid;
887 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
888 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
889 break;
891 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
892 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
893 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
894 break;
896 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
897 draw_separator (mw, window, x, y, width,
898 SEPARATOR_SHADOW_ETCHED_OUT);
899 draw_separator (mw, window, x, y + 3, width,
900 SEPARATOR_SHADOW_ETCHED_OUT);
901 break;
903 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
904 xgcv.line_style = LineOnOffDash;
905 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
906 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
907 draw_separator (mw, window, x, y, width,
908 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
909 xgcv.line_style = LineSolid;
910 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
911 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
912 break;
914 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
915 xgcv.line_style = LineOnOffDash;
916 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
917 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
918 draw_separator (mw, window, x, y, width,
919 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
920 xgcv.line_style = LineSolid;
921 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
922 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
923 break;
925 default:
926 abort ();
931 /* Return the pixel height of menu separator SEPARATOR. */
933 static int
934 separator_height (separator)
935 enum menu_separator separator;
937 switch (separator)
939 case SEPARATOR_NO_LINE:
940 return 2;
942 case SEPARATOR_SINGLE_LINE:
943 case SEPARATOR_SINGLE_DASHED_LINE:
944 return 1;
946 case SEPARATOR_DOUBLE_LINE:
947 case SEPARATOR_DOUBLE_DASHED_LINE:
948 return 3;
950 case SEPARATOR_SHADOW_ETCHED_IN:
951 case SEPARATOR_SHADOW_ETCHED_OUT:
952 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
953 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
954 return 2;
956 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
957 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
958 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
959 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
960 return 5;
962 default:
963 abort ();
968 /* Display the menu item and increment where.x and where.y to show how large
969 the menu item was. */
971 static void
972 display_menu_item (mw, val, ws, where, highlighted_p, horizontal_p,
973 just_compute_p)
974 XlwMenuWidget mw;
975 widget_value* val;
976 window_state* ws;
977 XPoint* where;
978 Boolean highlighted_p;
979 Boolean horizontal_p;
980 Boolean just_compute_p;
982 GC deco_gc;
983 GC text_gc;
984 int font_height = MENU_FONT_HEIGHT (mw);
985 int font_ascent = MENU_FONT_ASCENT (mw);
986 int shadow = mw->menu.shadow_thickness;
987 int margin = mw->menu.margin;
988 int h_spacing = mw->menu.horizontal_spacing;
989 int v_spacing = mw->menu.vertical_spacing;
990 int label_width;
991 int rest_width;
992 int button_width;
993 int height;
994 int width;
995 enum menu_separator separator;
996 int separator_p = lw_separator_p (val->name, &separator, 0);
998 /* compute the sizes of the item */
999 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
1000 &button_width, &height);
1002 if (horizontal_p)
1003 width = label_width + rest_width;
1004 else
1006 label_width = ws->label_width;
1007 width = ws->width - 2 * shadow;
1010 /* Only highlight an enabled item that has a callback. */
1011 if (highlighted_p)
1012 if (!val->enabled || !(val->call_data || val->contents))
1013 highlighted_p = 0;
1015 /* do the drawing. */
1016 if (!just_compute_p)
1018 /* Add the shadow border of the containing menu */
1019 int x = where->x + shadow;
1020 int y = where->y + shadow;
1022 if (horizontal_p)
1024 x += margin;
1025 y += margin;
1028 /* pick the foreground and background GC. */
1029 if (val->enabled)
1030 text_gc = mw->menu.foreground_gc;
1031 else
1032 text_gc = mw->menu.disabled_gc;
1033 deco_gc = mw->menu.foreground_gc;
1035 if (separator_p)
1037 draw_separator (mw, ws->window, x, y, width, separator);
1039 else
1041 int x_offset = x + h_spacing + shadow;
1042 char* display_string = resource_widget_value (mw, val);
1043 draw_shadow_rectangle (mw, ws->window, x, y, width, height, True,
1044 False);
1046 /* Deal with centering a menu title. */
1047 if (!horizontal_p && !val->contents && !val->call_data)
1049 int l = string_width (mw, display_string);
1051 if (width > l)
1052 x_offset = (width - l) >> 1;
1054 else if (!horizontal_p && ws->button_width)
1055 x_offset += ws->button_width;
1058 #ifdef HAVE_X_I18N
1059 if (mw->menu.fontSet)
1060 XmbDrawString (XtDisplay (mw), ws->window, mw->menu.fontSet,
1061 text_gc, x_offset,
1062 y + v_spacing + shadow + font_ascent,
1063 display_string, strlen (display_string));
1064 else
1065 #endif
1066 XDrawString (XtDisplay (mw), ws->window,
1067 text_gc, x_offset,
1068 y + v_spacing + shadow + font_ascent,
1069 display_string, strlen (display_string));
1071 if (!horizontal_p)
1073 if (val->button_type == BUTTON_TYPE_TOGGLE)
1074 draw_toggle (mw, ws->window, x, y + v_spacing + shadow,
1075 val->selected);
1076 else if (val->button_type == BUTTON_TYPE_RADIO)
1077 draw_radio (mw, ws->window, x, y + v_spacing + shadow,
1078 val->selected);
1080 if (val->contents)
1082 int a_w = arrow_width (mw);
1083 draw_arrow (mw, ws->window, deco_gc,
1084 x + width - a_w
1085 - mw->menu.horizontal_spacing
1086 - mw->menu.shadow_thickness,
1087 y + v_spacing + shadow, a_w,
1088 highlighted_p);
1090 else if (val->key)
1092 #ifdef HAVE_X_I18N
1093 if (mw->menu.fontSet)
1094 XmbDrawString (XtDisplay (mw), ws->window,
1095 mw->menu.fontSet,
1096 text_gc,
1097 x + label_width + mw->menu.arrow_spacing,
1098 y + v_spacing + shadow + font_ascent,
1099 val->key, strlen (val->key));
1100 else
1101 #endif
1102 XDrawString (XtDisplay (mw), ws->window,
1103 text_gc,
1104 x + label_width + mw->menu.arrow_spacing,
1105 y + v_spacing + shadow + font_ascent,
1106 val->key, strlen (val->key));
1109 else
1111 XDrawRectangle (XtDisplay (mw), ws->window,
1112 mw->menu.background_gc,
1113 x + shadow, y + shadow,
1114 label_width + h_spacing - 1,
1115 font_height + 2 * v_spacing - 1);
1116 draw_shadow_rectangle (mw, ws->window, x, y, width, height,
1117 True, False);
1120 if (highlighted_p)
1121 draw_shadow_rectangle (mw, ws->window, x, y, width, height, False,
1122 False);
1126 where->x += width;
1127 where->y += height;
1130 static void
1131 display_menu (mw, level, just_compute_p, highlighted_pos, hit, hit_return,
1132 this, that)
1133 XlwMenuWidget mw;
1134 int level;
1135 Boolean just_compute_p;
1136 XPoint* highlighted_pos;
1137 XPoint* hit;
1138 widget_value** hit_return;
1139 widget_value* this;
1140 widget_value* that;
1142 widget_value* val;
1143 widget_value* following_item;
1144 window_state* ws;
1145 XPoint where;
1146 int horizontal_p = mw->menu.horizontal && (level == 0);
1147 int highlighted_p;
1148 int just_compute_this_one_p;
1149 /* This is set nonzero if the element containing HIGHLIGHTED_POS
1150 is disabled, so that we do not return any subsequent element either. */
1151 int no_return = 0;
1152 enum menu_separator separator;
1154 if (level >= mw->menu.old_depth)
1155 abort_gracefully ((Widget) mw);
1157 if (level < mw->menu.old_depth - 1)
1158 following_item = mw->menu.old_stack [level + 1];
1159 else
1160 following_item = NULL;
1162 if (hit)
1163 *hit_return = NULL;
1165 where.x = 0;
1166 where.y = 0;
1168 ws = &mw->menu.windows [level];
1169 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1171 highlighted_p = val == following_item;
1172 if (highlighted_p && highlighted_pos)
1174 if (horizontal_p)
1175 highlighted_pos->x = where.x;
1176 else
1177 highlighted_pos->y = where.y;
1180 just_compute_this_one_p =
1181 just_compute_p || ((this || that) && val != this && val != that);
1183 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1184 just_compute_this_one_p);
1186 if (highlighted_p && highlighted_pos)
1188 if (horizontal_p)
1189 highlighted_pos->y = where.y;
1190 else
1191 highlighted_pos->x = where.x;
1194 if (hit
1195 && !*hit_return
1196 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1197 && !lw_separator_p (val->name, &separator, 0)
1198 && !no_return)
1200 if (val->enabled)
1201 *hit_return = val;
1202 else
1203 no_return = 1;
1206 if (horizontal_p)
1207 where.y = 0;
1208 else
1209 where.x = 0;
1212 if (!just_compute_p)
1213 draw_shadow_rectangle (mw, ws->window, 0, 0, ws->width, ws->height,
1214 False, False);
1217 \f/* Motion code */
1218 static void
1219 set_new_state (mw, val, level)
1220 XlwMenuWidget mw;
1221 widget_value* val;
1222 int level;
1224 int i;
1226 mw->menu.new_depth = 0;
1227 for (i = 0; i < level; i++)
1228 push_new_stack (mw, mw->menu.old_stack [i]);
1229 push_new_stack (mw, val);
1232 static void
1233 make_windows_if_needed (mw, n)
1234 XlwMenuWidget mw;
1235 int n;
1237 int i;
1238 int start_at;
1239 XSetWindowAttributes xswa;
1240 int mask;
1241 Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1242 window_state* windows;
1244 if (mw->menu.windows_length >= n)
1245 return;
1247 xswa.save_under = True;
1248 xswa.override_redirect = True;
1249 xswa.background_pixel = mw->core.background_pixel;
1250 xswa.border_pixel = mw->core.border_pixel;
1251 xswa.event_mask =
1252 ExposureMask | PointerMotionMask | PointerMotionHintMask
1253 | ButtonReleaseMask | ButtonPressMask;
1254 xswa.cursor = mw->menu.cursor_shape;
1255 mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
1256 | CWEventMask | CWCursor;
1258 if (!mw->menu.windows)
1260 mw->menu.windows =
1261 (window_state*)XtMalloc (n * sizeof (window_state));
1262 start_at = 0;
1264 else
1266 mw->menu.windows =
1267 (window_state*)XtRealloc ((char*)mw->menu.windows,
1268 n * sizeof (window_state));
1269 start_at = mw->menu.windows_length;
1271 mw->menu.windows_length = n;
1273 windows = mw->menu.windows;
1275 for (i = start_at; i < n; i++)
1277 windows [i].x = 0;
1278 windows [i].y = 0;
1279 windows [i].width = 1;
1280 windows [i].height = 1;
1281 windows [i].window =
1282 XCreateWindow (XtDisplay (mw), root, 0, 0, 1, 1,
1283 0, 0, CopyFromParent, CopyFromParent, mask, &xswa);
1287 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1290 xlwmenu_window_p (w, window)
1291 Widget w;
1292 Window window;
1294 XlwMenuWidget mw = (XlwMenuWidget) w;
1295 int i;
1297 for (i = 0; i < mw->menu.windows_length; ++i)
1298 if (window == mw->menu.windows[i].window)
1299 break;
1301 return i < mw->menu.windows_length;
1304 /* Make the window fit in the screen */
1305 static void
1306 fit_to_screen (mw, ws, previous_ws, horizontal_p)
1307 XlwMenuWidget mw;
1308 window_state* ws;
1309 window_state* previous_ws;
1310 Boolean horizontal_p;
1312 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1313 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1314 /* 1 if we are unable to avoid an overlap between
1315 this menu and the parent menu in the X dimension. */
1316 int horizontal_overlap = 0;
1318 if (ws->x < 0)
1319 ws->x = 0;
1320 else if (ws->x + ws->width > screen_width)
1322 if (!horizontal_p)
1323 /* The addition of shadow-thickness for a sub-menu's position is
1324 to reflect a similar adjustment when the menu is displayed to
1325 the right of the invoking menu-item; it makes the sub-menu
1326 look more `attached' to the menu-item. */
1327 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1328 else
1329 ws->x = screen_width - ws->width;
1330 if (ws->x < 0)
1332 ws->x = 0;
1333 horizontal_overlap = 1;
1336 /* If we overlap in X, try to avoid overlap in Y. */
1337 if (horizontal_overlap
1338 && ws->y < previous_ws->y + previous_ws->height
1339 && previous_ws->y < ws->y + ws->height)
1341 /* Put this menu right below or right above PREVIOUS_WS
1342 if there's room. */
1343 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1344 ws->y = previous_ws->y + previous_ws->height;
1345 else if (previous_ws->y - ws->height > 0)
1346 ws->y = previous_ws->y - ws->height;
1349 if (ws->y < 0)
1350 ws->y = 0;
1351 else if (ws->y + ws->height > screen_height)
1353 if (horizontal_p)
1354 ws->y = previous_ws->y - ws->height;
1355 else
1356 ws->y = screen_height - ws->height;
1357 if (ws->y < 0)
1358 ws->y = 0;
1362 /* Updates old_stack from new_stack and redisplays. */
1363 static void
1364 remap_menubar (mw)
1365 XlwMenuWidget mw;
1367 int i;
1368 int last_same;
1369 XPoint selection_position;
1370 int old_depth = mw->menu.old_depth;
1371 int new_depth = mw->menu.new_depth;
1372 widget_value** old_stack;
1373 widget_value** new_stack;
1374 window_state* windows;
1375 widget_value* old_selection;
1376 widget_value* new_selection;
1378 /* Check that enough windows and old_stack are ready. */
1379 make_windows_if_needed (mw, new_depth);
1380 make_old_stack_space (mw, new_depth);
1381 windows = mw->menu.windows;
1382 old_stack = mw->menu.old_stack;
1383 new_stack = mw->menu.new_stack;
1385 /* compute the last identical different entry */
1386 for (i = 1; i < old_depth && i < new_depth; i++)
1387 if (old_stack [i] != new_stack [i])
1388 break;
1389 last_same = i - 1;
1391 /* Memorize the previously selected item to be able to refresh it */
1392 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1393 if (old_selection && !old_selection->enabled)
1394 old_selection = NULL;
1395 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1396 if (new_selection && !new_selection->enabled)
1397 new_selection = NULL;
1399 /* Call callback when the hightlighted item changes. */
1400 if (old_selection || new_selection)
1401 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1402 (XtPointer) new_selection);
1404 /* updates old_state from new_state. It has to be done now because
1405 display_menu (called below) uses the old_stack to know what to display. */
1406 for (i = last_same + 1; i < new_depth; i++)
1407 old_stack [i] = new_stack [i];
1408 mw->menu.old_depth = new_depth;
1410 /* refresh the last selection */
1411 selection_position.x = 0;
1412 selection_position.y = 0;
1413 display_menu (mw, last_same, new_selection == old_selection,
1414 &selection_position, NULL, NULL, old_selection, new_selection);
1416 /* Now place the new menus. */
1417 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1419 window_state *previous_ws = &windows[i - 1];
1420 window_state *ws = &windows[i];
1422 ws->x = (previous_ws->x + selection_position.x
1423 + mw->menu.shadow_thickness);
1424 if (mw->menu.horizontal && i == 1)
1425 ws->x += mw->menu.margin;
1427 #if 0
1428 if (!mw->menu.horizontal || i > 1)
1429 ws->x += mw->menu.shadow_thickness;
1430 #endif
1432 ws->y = (previous_ws->y + selection_position.y
1433 + mw->menu.shadow_thickness);
1434 if (mw->menu.horizontal && i == 1)
1435 ws->y += mw->menu.margin;
1437 size_menu (mw, i);
1439 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1441 XClearWindow (XtDisplay (mw), ws->window);
1442 XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y,
1443 ws->width, ws->height);
1444 XMapRaised (XtDisplay (mw), ws->window);
1445 display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL);
1448 /* unmap the menus that popped down */
1449 for (i = new_depth - 1; i < old_depth; i++)
1450 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1451 XUnmapWindow (XtDisplay (mw), windows[i].window);
1454 static Boolean
1455 motion_event_is_in_menu (mw, ev, level, relative_pos)
1456 XlwMenuWidget mw;
1457 XMotionEvent* ev;
1458 int level;
1459 XPoint* relative_pos;
1461 window_state* ws = &mw->menu.windows [level];
1462 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1463 int x = ws->x + shadow;
1464 int y = ws->y + shadow;
1465 relative_pos->x = ev->x_root - x;
1466 relative_pos->y = ev->y_root - y;
1467 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1468 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1471 static Boolean
1472 map_event_to_widget_value (mw, ev, val, level)
1473 XlwMenuWidget mw;
1474 XMotionEvent* ev;
1475 widget_value** val;
1476 int* level;
1478 int i;
1479 XPoint relative_pos;
1480 window_state* ws;
1482 *val = NULL;
1484 /* Find the window */
1485 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1487 ws = &mw->menu.windows [i];
1488 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1490 display_menu (mw, i, True, NULL, &relative_pos, val, NULL, NULL);
1492 if (*val)
1494 *level = i + 1;
1495 return True;
1499 return False;
1502 \f/* Procedures */
1503 static void
1504 make_drawing_gcs (mw)
1505 XlwMenuWidget mw;
1507 XGCValues xgcv;
1508 float scale;
1509 XtGCMask mask = GCForeground | GCBackground;
1511 #ifdef HAVE_X_I18N
1512 if (!mw->menu.fontSet)
1514 xgcv.font = mw->menu.font->fid;
1515 mask |= GCFont;
1517 #else
1518 xgcv.font = mw->menu.font->fid;
1519 mask |= GCFont;
1520 #endif
1521 xgcv.foreground = mw->menu.foreground;
1522 xgcv.background = mw->core.background_pixel;
1523 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1525 xgcv.foreground = mw->menu.button_foreground;
1526 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1528 xgcv.background = mw->core.background_pixel;
1530 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1532 /* Allocate color for disabled menu-items. */
1533 mw->menu.disabled_foreground = mw->menu.foreground;
1534 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1535 scale = 2.3;
1536 else
1537 scale = 0.55;
1539 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1540 mw->core.colormap,
1541 &mw->menu.disabled_foreground,
1542 scale,
1543 0x8000);
1545 if (mw->menu.foreground == mw->menu.disabled_foreground
1546 || mw->core.background_pixel == mw->menu.disabled_foreground)
1548 /* Too few colors, use stipple. */
1549 xgcv.foreground = mw->menu.foreground;
1550 xgcv.fill_style = FillStippled;
1551 xgcv.stipple = mw->menu.gray_pixmap;
1552 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1553 | GCFillStyle | GCStipple, &xgcv);
1555 else
1557 /* Many colors available, use disabled pixel. */
1558 xgcv.foreground = mw->menu.disabled_foreground;
1559 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1562 xgcv.foreground = mw->menu.button_foreground;
1563 xgcv.background = mw->core.background_pixel;
1564 xgcv.fill_style = FillStippled;
1565 xgcv.stipple = mw->menu.gray_pixmap;
1566 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1567 | GCFillStyle | GCStipple, &xgcv);
1569 xgcv.foreground = mw->core.background_pixel;
1570 xgcv.background = mw->menu.foreground;
1571 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1574 static void
1575 release_drawing_gcs (mw)
1576 XlwMenuWidget mw;
1578 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1579 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1580 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1581 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1582 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1583 /* let's get some segvs if we try to use these... */
1584 mw->menu.foreground_gc = (GC) -1;
1585 mw->menu.button_gc = (GC) -1;
1586 mw->menu.disabled_gc = (GC) -1;
1587 mw->menu.inactive_button_gc = (GC) -1;
1588 mw->menu.background_gc = (GC) -1;
1591 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1592 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1594 static void
1595 make_shadow_gcs (mw)
1596 XlwMenuWidget mw;
1598 XGCValues xgcv;
1599 unsigned long pm = 0;
1600 Display *dpy = XtDisplay ((Widget) mw);
1601 Screen *screen = XtScreen ((Widget) mw);
1602 Colormap cmap = mw->core.colormap;
1603 XColor topc, botc;
1604 int top_frobbed = 0, bottom_frobbed = 0;
1606 mw->menu.free_top_shadow_color_p = 0;
1607 mw->menu.free_bottom_shadow_color_p = 0;
1609 if (mw->menu.top_shadow_color == -1)
1610 mw->menu.top_shadow_color = mw->core.background_pixel;
1611 else
1612 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1614 if (mw->menu.bottom_shadow_color == -1)
1615 mw->menu.bottom_shadow_color = mw->menu.foreground;
1616 else
1617 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1619 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1620 mw->menu.top_shadow_color == mw->menu.foreground)
1622 topc.pixel = mw->core.background_pixel;
1623 #ifdef emacs
1624 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1625 &topc.pixel,
1626 1.2, 0x8000))
1627 #else
1628 XQueryColor (dpy, cmap, &topc);
1629 /* don't overflow/wrap! */
1630 topc.red = MINL (65535, topc.red * 1.2);
1631 topc.green = MINL (65535, topc.green * 1.2);
1632 topc.blue = MINL (65535, topc.blue * 1.2);
1633 if (XAllocColor (dpy, cmap, &topc))
1634 #endif
1636 mw->menu.top_shadow_color = topc.pixel;
1637 mw->menu.free_top_shadow_color_p = 1;
1638 top_frobbed = 1;
1641 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1642 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1644 botc.pixel = mw->core.background_pixel;
1645 #ifdef emacs
1646 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1647 &botc.pixel,
1648 0.6, 0x4000))
1649 #else
1650 XQueryColor (dpy, cmap, &botc);
1651 botc.red *= 0.6;
1652 botc.green *= 0.6;
1653 botc.blue *= 0.6;
1654 if (XAllocColor (dpy, cmap, &botc))
1655 #endif
1657 mw->menu.bottom_shadow_color = botc.pixel;
1658 mw->menu.free_bottom_shadow_color_p = 1;
1659 bottom_frobbed = 1;
1663 if (top_frobbed && bottom_frobbed)
1665 if (topc.pixel == botc.pixel)
1667 if (botc.pixel == mw->menu.foreground)
1669 if (mw->menu.free_top_shadow_color_p)
1671 x_free_dpy_colors (dpy, screen, cmap,
1672 &mw->menu.top_shadow_color, 1);
1673 mw->menu.free_top_shadow_color_p = 0;
1675 mw->menu.top_shadow_color = mw->core.background_pixel;
1677 else
1679 if (mw->menu.free_bottom_shadow_color_p)
1681 x_free_dpy_colors (dpy, screen, cmap,
1682 &mw->menu.bottom_shadow_color, 1);
1683 mw->menu.free_bottom_shadow_color_p = 0;
1685 mw->menu.bottom_shadow_color = mw->menu.foreground;
1690 if (!mw->menu.top_shadow_pixmap &&
1691 mw->menu.top_shadow_color == mw->core.background_pixel)
1693 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1694 if (mw->menu.free_top_shadow_color_p)
1696 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1697 mw->menu.free_top_shadow_color_p = 0;
1699 mw->menu.top_shadow_color = mw->menu.foreground;
1701 if (!mw->menu.bottom_shadow_pixmap &&
1702 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1704 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1705 if (mw->menu.free_bottom_shadow_color_p)
1707 x_free_dpy_colors (dpy, screen, cmap,
1708 &mw->menu.bottom_shadow_color, 1);
1709 mw->menu.free_bottom_shadow_color_p = 0;
1711 mw->menu.bottom_shadow_color = mw->menu.foreground;
1714 xgcv.fill_style = FillStippled;
1715 xgcv.foreground = mw->menu.top_shadow_color;
1716 xgcv.stipple = mw->menu.top_shadow_pixmap;
1717 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1718 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1720 xgcv.foreground = mw->menu.bottom_shadow_color;
1721 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1722 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1723 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1727 static void
1728 release_shadow_gcs (mw)
1729 XlwMenuWidget mw;
1731 Display *dpy = XtDisplay ((Widget) mw);
1732 Screen *screen = XtScreen ((Widget) mw);
1733 Colormap cmap = mw->core.colormap;
1734 Pixel px[2];
1735 int i = 0;
1737 if (mw->menu.free_top_shadow_color_p)
1738 px[i++] = mw->menu.top_shadow_color;
1739 if (mw->menu.free_bottom_shadow_color_p)
1740 px[i++] = mw->menu.bottom_shadow_color;
1741 if (i > 0)
1742 x_free_dpy_colors (dpy, screen, cmap, px, i);
1744 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1745 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1748 static void
1749 XlwMenuInitialize (request, mw, args, num_args)
1750 Widget request;
1751 XlwMenuWidget mw;
1752 ArgList args;
1753 Cardinal *num_args;
1755 /* Get the GCs and the widget size */
1757 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1758 Display* display = XtDisplay (mw);
1760 #if 0
1761 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1763 /* _XtCreate is freeing the object that was passed to us,
1764 so make a copy that we will actually keep. */
1765 lwlib_bcopy (mw->menu.contents, tem, sizeof (widget_value));
1766 mw->menu.contents = tem;
1767 #endif
1769 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1770 mw->menu.cursor = mw->menu.cursor_shape;
1772 mw->menu.gray_pixmap
1773 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1774 gray_bitmap_width, gray_bitmap_height,
1775 (unsigned long)1, (unsigned long)0, 1);
1777 /* I don't understand why this ends up 0 sometimes,
1778 but it does. This kludge works around it.
1779 Can anyone find a real fix? -- rms. */
1780 if (mw->menu.font == 0)
1781 mw->menu.font = xlwmenu_default_font;
1782 #ifdef HAVE_X_I18N
1783 if (mw->menu.fontSet)
1784 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1785 #endif
1787 make_drawing_gcs (mw);
1788 make_shadow_gcs (mw);
1790 mw->menu.popped_up = False;
1792 mw->menu.old_depth = 1;
1793 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1794 mw->menu.old_stack_length = 1;
1795 mw->menu.old_stack [0] = mw->menu.contents;
1797 mw->menu.new_depth = 0;
1798 mw->menu.new_stack = 0;
1799 mw->menu.new_stack_length = 0;
1800 push_new_stack (mw, mw->menu.contents);
1802 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1803 mw->menu.windows_length = 1;
1804 mw->menu.windows [0].x = 0;
1805 mw->menu.windows [0].y = 0;
1806 mw->menu.windows [0].width = 0;
1807 mw->menu.windows [0].height = 0;
1808 size_menu (mw, 0);
1810 mw->core.width = mw->menu.windows [0].width;
1811 mw->core.height = mw->menu.windows [0].height;
1814 static void
1815 XlwMenuClassInitialize ()
1819 static void
1820 XlwMenuRealize (w, valueMask, attributes)
1821 Widget w;
1822 Mask *valueMask;
1823 XSetWindowAttributes *attributes;
1825 XlwMenuWidget mw = (XlwMenuWidget)w;
1826 XSetWindowAttributes xswa;
1827 int mask;
1828 int count;
1830 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1831 (w, valueMask, attributes);
1833 xswa.save_under = True;
1834 xswa.cursor = mw->menu.cursor_shape;
1835 mask = CWSaveUnder | CWCursor;
1836 /* I sometimes get random BadCursor errors while creating the first
1837 frame on a display. I can not find their reason, but they are
1838 annoying so for now let's ignore any errors here. -- lorentey */
1839 #ifdef emacs
1840 count = x_catch_errors (XtDisplay (w));
1841 #endif
1842 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1843 #ifdef emacs
1844 x_uncatch_errors (XtDisplay (w), count);
1845 #endif
1847 mw->menu.windows [0].window = XtWindow (w);
1848 mw->menu.windows [0].x = w->core.x;
1849 mw->menu.windows [0].y = w->core.y;
1850 mw->menu.windows [0].width = w->core.width;
1851 mw->menu.windows [0].height = w->core.height;
1854 /* Only the toplevel menubar/popup is a widget so it's the only one that
1855 receives expose events through Xt. So we repaint all the other panes
1856 when receiving an Expose event. */
1857 static void
1858 XlwMenuRedisplay (w, ev, region)
1859 Widget w;
1860 XEvent* ev;
1861 Region region;
1863 XlwMenuWidget mw = (XlwMenuWidget)w;
1864 int i;
1866 /* If we have a depth beyond 1, it's because a submenu was displayed.
1867 If the submenu has been destroyed, set the depth back to 1. */
1868 if (submenu_destroyed)
1870 mw->menu.old_depth = 1;
1871 submenu_destroyed = 0;
1874 for (i = 0; i < mw->menu.old_depth; i++)
1875 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1879 /* Part of a hack to make the menu redisplay when a tooltip frame
1880 over a menu item is unmapped. */
1882 void
1883 xlwmenu_redisplay (w)
1884 Widget w;
1886 XlwMenuRedisplay (w, NULL, None);
1889 static void
1890 XlwMenuDestroy (w)
1891 Widget w;
1893 int i;
1894 XlwMenuWidget mw = (XlwMenuWidget) w;
1896 if (pointer_grabbed)
1897 ungrab_all ((Widget)w, CurrentTime);
1898 pointer_grabbed = 0;
1900 submenu_destroyed = 1;
1902 release_drawing_gcs (mw);
1903 release_shadow_gcs (mw);
1905 /* this doesn't come from the resource db but is created explicitly
1906 so we must free it ourselves. */
1907 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1908 mw->menu.gray_pixmap = (Pixmap) -1;
1910 #if 0
1911 /* Do free mw->menu.contents because nowadays we copy it
1912 during initialization. */
1913 XtFree (mw->menu.contents);
1914 #endif
1916 /* Don't free mw->menu.contents because that comes from our creator.
1917 The `*_stack' elements are just pointers into `contents' so leave
1918 that alone too. But free the stacks themselves. */
1919 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1920 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1922 /* Remember, you can't free anything that came from the resource
1923 database. This includes:
1924 mw->menu.cursor
1925 mw->menu.top_shadow_pixmap
1926 mw->menu.bottom_shadow_pixmap
1927 mw->menu.font
1928 Also the color cells of top_shadow_color, bottom_shadow_color,
1929 foreground, and button_foreground will never be freed until this
1930 client exits. Nice, eh?
1933 /* start from 1 because the one in slot 0 is w->core.window */
1934 for (i = 1; i < mw->menu.windows_length; i++)
1935 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1936 if (mw->menu.windows)
1937 XtFree ((char *) mw->menu.windows);
1940 static Boolean
1941 XlwMenuSetValues (current, request, new)
1942 Widget current;
1943 Widget request;
1944 Widget new;
1946 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1947 XlwMenuWidget newmw = (XlwMenuWidget)new;
1948 Boolean redisplay = False;
1949 int i;
1951 if (newmw->menu.contents
1952 && newmw->menu.contents->contents
1953 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1954 redisplay = True;
1955 /* Do redisplay if the contents are entirely eliminated. */
1956 if (newmw->menu.contents
1957 && newmw->menu.contents->contents == 0
1958 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1959 redisplay = True;
1961 if (newmw->core.background_pixel != oldmw->core.background_pixel
1962 || newmw->menu.foreground != oldmw->menu.foreground
1963 #ifdef HAVE_X_I18N
1964 || newmw->menu.fontSet != oldmw->menu.fontSet
1965 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
1966 #else
1967 || newmw->menu.font != oldmw->menu.font
1968 #endif
1971 release_drawing_gcs (newmw);
1972 make_drawing_gcs (newmw);
1974 release_shadow_gcs (newmw);
1975 /* Cause the shadow colors to be recalculated. */
1976 newmw->menu.top_shadow_color = -1;
1977 newmw->menu.bottom_shadow_color = -1;
1978 make_shadow_gcs (newmw);
1980 redisplay = True;
1982 if (XtIsRealized (current))
1983 /* If the menu is currently displayed, change the display. */
1984 for (i = 0; i < oldmw->menu.windows_length; i++)
1986 XSetWindowBackground (XtDisplay (oldmw),
1987 oldmw->menu.windows [i].window,
1988 newmw->core.background_pixel);
1989 /* clear windows and generate expose events */
1990 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
1991 0, 0, 0, 0, True);
1995 #ifdef HAVE_X_I18N
1996 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
1998 redisplay = True;
1999 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2001 #endif
2003 return redisplay;
2006 static void
2007 XlwMenuResize (w)
2008 Widget w;
2010 XlwMenuWidget mw = (XlwMenuWidget)w;
2012 if (mw->menu.popped_up)
2014 /* Don't allow the popup menu to resize itself. */
2015 mw->core.width = mw->menu.windows [0].width;
2016 mw->core.height = mw->menu.windows [0].height;
2017 mw->core.parent->core.width = mw->core.width ;
2018 mw->core.parent->core.height = mw->core.height ;
2020 else
2022 mw->menu.windows [0].width = mw->core.width;
2023 mw->menu.windows [0].height = mw->core.height;
2027 \f/* Action procedures */
2028 static void
2029 handle_single_motion_event (mw, ev)
2030 XlwMenuWidget mw;
2031 XMotionEvent* ev;
2033 widget_value* val;
2034 int level;
2036 if (!map_event_to_widget_value (mw, ev, &val, &level))
2037 pop_new_stack_if_no_contents (mw);
2038 else
2039 set_new_state (mw, val, level);
2040 remap_menubar (mw);
2042 /* Sync with the display. Makes it feel better on X terms. */
2043 XSync (XtDisplay (mw), False);
2046 static void
2047 handle_motion_event (mw, ev)
2048 XlwMenuWidget mw;
2049 XMotionEvent* ev;
2051 int x = ev->x_root;
2052 int y = ev->y_root;
2053 int state = ev->state;
2055 handle_single_motion_event (mw, ev);
2057 /* allow motion events to be generated again */
2058 if (ev->is_hint
2059 && XQueryPointer (XtDisplay (mw), ev->window,
2060 &ev->root, &ev->subwindow,
2061 &ev->x_root, &ev->y_root,
2062 &ev->x, &ev->y,
2063 &ev->state)
2064 && ev->state == state
2065 && (ev->x_root != x || ev->y_root != y))
2066 handle_single_motion_event (mw, ev);
2069 static void
2070 Start (w, ev, params, num_params)
2071 Widget w;
2072 XEvent *ev;
2073 String *params;
2074 Cardinal *num_params;
2076 XlwMenuWidget mw = (XlwMenuWidget)w;
2078 if (!mw->menu.popped_up)
2080 menu_post_event = *ev;
2081 /* If event is set to CurrentTime, get the last known time stamp.
2082 This is for calculating if (popup) menus should stay up after
2083 a fast click. */
2084 if (menu_post_event.xbutton.time == CurrentTime)
2085 menu_post_event.xbutton.time
2086 = XtLastTimestampProcessed (XtDisplay (w));
2088 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2090 else
2092 /* If we push a button while the menu is posted semipermanently,
2093 releasing the button should always pop the menu down. */
2094 next_release_must_exit = 1;
2096 /* notes the absolute position of the menubar window */
2097 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2098 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2100 /* handles the down like a move, slots are compatible */
2101 handle_motion_event (mw, &ev->xmotion);
2105 static void
2106 Drag (w, ev, params, num_params)
2107 Widget w;
2108 XEvent *ev;
2109 String *params;
2110 Cardinal *num_params;
2112 XlwMenuWidget mw = (XlwMenuWidget)w;
2113 if (mw->menu.popped_up)
2114 handle_motion_event (mw, &ev->xmotion);
2117 /* Do nothing.
2118 This is how we handle presses and releases of modifier keys. */
2119 static void
2120 Nothing (w, ev, params, num_params)
2121 Widget w;
2122 XEvent *ev;
2123 String *params;
2124 Cardinal *num_params;
2128 static widget_value *
2129 find_first_selectable (mw, item, skip_titles)
2130 XlwMenuWidget mw;
2131 widget_value *item;
2132 int skip_titles;
2134 widget_value *current = item;
2135 enum menu_separator separator;
2137 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2138 || (skip_titles && !current->call_data && !current->contents))
2139 if (current->next)
2140 current=current->next;
2141 else
2142 return NULL;
2144 return current;
2147 static widget_value *
2148 find_next_selectable (mw, item, skip_titles)
2149 XlwMenuWidget mw;
2150 widget_value *item;
2151 int skip_titles;
2153 widget_value *current = item;
2154 enum menu_separator separator;
2156 while (current->next && (current=current->next) &&
2157 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2158 || (skip_titles && !current->call_data && !current->contents)))
2161 if (current == item)
2163 if (mw->menu.old_depth < 2)
2164 return current;
2165 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2167 while (lw_separator_p (current->name, &separator, 0)
2168 || !current->enabled
2169 || (skip_titles && !current->call_data
2170 && !current->contents))
2172 if (current->next)
2173 current=current->next;
2175 if (current == item)
2176 break;
2181 return current;
2184 static widget_value *
2185 find_prev_selectable (mw, item, skip_titles)
2186 XlwMenuWidget mw;
2187 widget_value *item;
2188 int skip_titles;
2190 widget_value *current = item;
2191 widget_value *prev = item;
2193 while ((current=find_next_selectable (mw, current, skip_titles))
2194 != item)
2196 if (prev == current)
2197 break;
2198 prev=current;
2201 return prev;
2204 static void
2205 Down (w, ev, params, num_params)
2206 Widget w;
2207 XEvent *ev;
2208 String *params;
2209 Cardinal *num_params;
2211 XlwMenuWidget mw = (XlwMenuWidget) w;
2212 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2213 int popup_menu_p = mw->menu.top_depth == 1;
2215 /* Inside top-level menu-bar? */
2216 if (mw->menu.old_depth == mw->menu.top_depth)
2217 /* When <down> in the menu-bar is pressed, display the corresponding
2218 sub-menu and select the first selectable menu item there.
2219 If this is a popup menu, skip title item of the popup. */
2220 set_new_state (mw,
2221 find_first_selectable (mw,
2222 selected_item->contents,
2223 popup_menu_p),
2224 mw->menu.old_depth);
2225 else
2226 /* Highlight next possible (enabled and not separator) menu item. */
2227 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2228 mw->menu.old_depth - 1);
2230 remap_menubar (mw);
2233 static void
2234 Up (w, ev, params, num_params)
2235 Widget w;
2236 XEvent *ev;
2237 String *params;
2238 Cardinal *num_params;
2240 XlwMenuWidget mw = (XlwMenuWidget) w;
2241 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2242 int popup_menu_p = mw->menu.top_depth == 1;
2244 /* Inside top-level menu-bar? */
2245 if (mw->menu.old_depth == mw->menu.top_depth)
2247 /* FIXME: this is tricky. <up> in the menu-bar should select the
2248 last selectable item in the list. So we select the first
2249 selectable one and find the previous selectable item. Is there
2250 a better way? */
2251 /* If this is a popup menu, skip title item of the popup. */
2252 set_new_state (mw,
2253 find_first_selectable (mw,
2254 selected_item->contents,
2255 popup_menu_p),
2256 mw->menu.old_depth);
2257 remap_menubar (mw);
2258 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2259 set_new_state (mw,
2260 find_prev_selectable (mw,
2261 selected_item,
2262 popup_menu_p),
2263 mw->menu.old_depth - 1);
2265 else
2266 /* Highlight previous (enabled and not separator) menu item. */
2267 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2268 mw->menu.old_depth - 1);
2270 remap_menubar (mw);
2273 void
2274 Left (w, ev, params, num_params)
2275 Widget w;
2276 XEvent *ev;
2277 String *params;
2278 Cardinal *num_params;
2280 XlwMenuWidget mw = (XlwMenuWidget) w;
2281 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2283 /* Inside top-level menu-bar? */
2284 if (mw->menu.old_depth == mw->menu.top_depth)
2285 /* When <left> in the menu-bar is pressed, display the previous item on
2286 the menu-bar. If the current item is the first one, highlight the
2287 last item in the menubar (probably Help). */
2288 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2289 mw->menu.old_depth - 1);
2290 else if (mw->menu.old_depth == 1
2291 && selected_item->contents) /* Is this menu item expandable? */
2293 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2294 remap_menubar (mw);
2295 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2296 if (!selected_item->enabled && find_first_selectable (mw,
2297 selected_item,
2299 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2300 mw->menu.old_depth - 1);
2303 else
2305 pop_new_stack_if_no_contents (mw);
2306 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2307 mw->menu.old_depth - 2);
2310 remap_menubar (mw);
2313 void
2314 Right (w, ev, params, num_params)
2315 Widget w;
2316 XEvent *ev;
2317 String *params;
2318 Cardinal *num_params;
2320 XlwMenuWidget mw = (XlwMenuWidget) w;
2321 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2323 /* Inside top-level menu-bar? */
2324 if (mw->menu.old_depth == mw->menu.top_depth)
2325 /* When <right> in the menu-bar is pressed, display the next item on
2326 the menu-bar. If the current item is the last one, highlight the
2327 first item (probably File). */
2328 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2329 mw->menu.old_depth - 1);
2330 else if (selected_item->contents) /* Is this menu item expandable? */
2332 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2333 remap_menubar (mw);
2334 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2335 if (!selected_item->enabled && find_first_selectable (mw,
2336 selected_item,
2338 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2339 mw->menu.old_depth - 1);
2341 else
2343 pop_new_stack_if_no_contents (mw);
2344 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2345 mw->menu.old_depth - 2);
2348 remap_menubar (mw);
2351 /* Handle key press and release events while menu is popped up.
2352 Our action is to get rid of the menu. */
2353 static void
2354 Key (w, ev, params, num_params)
2355 Widget w;
2356 XEvent *ev;
2357 String *params;
2358 Cardinal *num_params;
2360 XlwMenuWidget mw = (XlwMenuWidget)w;
2362 /* Pop down everything. */
2363 mw->menu.new_depth = 1;
2364 remap_menubar (mw);
2366 if (mw->menu.popped_up)
2368 mw->menu.popped_up = False;
2369 ungrab_all ((Widget)mw, ev->xmotion.time);
2370 if (XtIsShell (XtParent ((Widget) mw)))
2371 XtPopdown (XtParent ((Widget) mw));
2372 else
2374 XtRemoveGrab ((Widget) mw);
2375 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2379 /* callback */
2380 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2383 static void
2384 Select (w, ev, params, num_params)
2385 Widget w;
2386 XEvent *ev;
2387 String *params;
2388 Cardinal *num_params;
2390 XlwMenuWidget mw = (XlwMenuWidget)w;
2391 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2393 /* If user releases the button quickly, without selecting anything,
2394 after the initial down-click that brought the menu up,
2395 do nothing. */
2396 if ((selected_item == 0
2397 || ((widget_value *) selected_item)->call_data == 0)
2398 && !next_release_must_exit
2399 && (ev->xbutton.time - menu_post_event.xbutton.time
2400 < XtGetMultiClickTime (XtDisplay (w))))
2401 return;
2403 /* pop down everything. */
2404 mw->menu.new_depth = 1;
2405 remap_menubar (mw);
2407 if (mw->menu.popped_up)
2409 mw->menu.popped_up = False;
2410 ungrab_all ((Widget)mw, ev->xmotion.time);
2411 if (XtIsShell (XtParent ((Widget) mw)))
2412 XtPopdown (XtParent ((Widget) mw));
2413 else
2415 XtRemoveGrab ((Widget) mw);
2416 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2420 /* callback */
2421 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2425 \f/* Special code to pop-up a menu */
2426 static void
2427 pop_up_menu (mw, event)
2428 XlwMenuWidget mw;
2429 XButtonPressedEvent* event;
2431 int x = event->x_root;
2432 int y = event->y_root;
2433 int w;
2434 int h;
2435 int borderwidth = mw->menu.shadow_thickness;
2436 Screen* screen = XtScreen (mw);
2437 Display *display = XtDisplay (mw);
2438 int count;
2440 next_release_must_exit = 0;
2442 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2444 if (XtIsShell (XtParent ((Widget)mw)))
2445 size_menu (mw, 0);
2447 w = mw->menu.windows [0].width;
2448 h = mw->menu.windows [0].height;
2450 x -= borderwidth;
2451 y -= borderwidth;
2452 if (x < borderwidth)
2453 x = borderwidth;
2454 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2455 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2456 if (y < borderwidth)
2457 y = borderwidth;
2458 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2459 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2461 mw->menu.popped_up = True;
2462 if (XtIsShell (XtParent ((Widget)mw)))
2464 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2465 XtParent ((Widget)mw)->core.border_width);
2466 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2467 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2468 mw->menu.windows [0].x = x + borderwidth;
2469 mw->menu.windows [0].y = y + borderwidth;
2470 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2472 else
2474 XEvent *ev = (XEvent *) event;
2476 XtAddGrab ((Widget) mw, True, True);
2478 /* notes the absolute position of the menubar window */
2479 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2480 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2481 mw->menu.top_depth = 2;
2484 #ifdef emacs
2485 count = x_catch_errors (display);
2486 #endif
2487 if (XtGrabPointer ((Widget)mw, False,
2488 (PointerMotionMask
2489 | PointerMotionHintMask
2490 | ButtonReleaseMask
2491 | ButtonPressMask),
2492 GrabModeAsync, GrabModeAsync, None,
2493 mw->menu.cursor_shape,
2494 event->time) == Success)
2496 if (! GRAB_KEYBOARD
2497 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2498 GrabModeAsync, event->time) == Success)
2500 XtSetKeyboardFocus((Widget)mw, None);
2501 pointer_grabbed = 1;
2503 else
2504 XtUngrabPointer ((Widget)mw, event->time);
2507 #ifdef emacs
2508 if (x_had_errors_p (display))
2510 pointer_grabbed = 0;
2511 XtUngrabPointer ((Widget)mw, event->time);
2513 x_uncatch_errors (display, count);
2514 #endif
2516 handle_motion_event (mw, (XMotionEvent*)event);
2519 /* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2520 (do not change this comment) */