(url-handler-mode): Specify :group.
[emacs.git] / lwlib / xlwmenu.c
blob3fab2e80dbe66bb3a52969bfaf75cb3065144fc7
1 /* Implements a lightweight menubar widget.
2 Copyright (C) 1992 Lucid, Inc.
3 Copyright (C) 2002, 2005 Free Software Foundation, Inc.
5 This file is part of the Lucid Widget Library.
7 The Lucid Widget Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
12 The Lucid Widget Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Emacs; see the file COPYING. If not, write to the
19 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA. */
22 /* Created by devin@lucid.com */
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include "lisp.h"
30 #include <stdio.h>
32 #include <sys/types.h>
33 #if (defined __sun) && !(defined SUNOS41)
34 #define SUNOS41
35 #include <X11/Xos.h>
36 #undef SUNOS41
37 #else
38 #include <X11/Xos.h>
39 #endif
40 #include <X11/IntrinsicP.h>
41 #include <X11/ObjectP.h>
42 #include <X11/StringDefs.h>
43 #include <X11/cursorfont.h>
44 #include "xlwmenuP.h"
46 #ifdef emacs
48 /* Defined in xfns.c. When config.h defines `static' as empty, we get
49 redefinition errors when gray_bitmap is included more than once, so
50 we're referring to the one include in xfns.c here. */
52 extern int gray_bitmap_width;
53 extern int gray_bitmap_height;
54 extern char *gray_bitmap_bits;
56 /* Defined in xterm.c. */
57 extern int x_alloc_nearest_color_for_widget __P ((Widget, Colormap, XColor*));
58 extern int x_alloc_lighter_color_for_widget __P ((Widget, Display*, Colormap,
59 unsigned long *,
60 double, int));
61 extern int x_catch_errors __P ((Display*));
62 extern int x_uncatch_errors __P ((Display*, int));
63 extern int x_had_errors_p __P ((Display*));
64 extern int x_clear_errors __P ((Display*));
65 extern unsigned long x_copy_dpy_color __P ((Display *, Colormap,
66 unsigned long));
68 /* Defined in xfaces.c. */
69 extern void x_free_dpy_colors __P ((Display *, Screen *, Colormap,
70 unsigned long *pixels, int npixels));
71 #else /* not emacs */
73 #include <X11/bitmaps/gray>
74 #define gray_bitmap_width gray_width
75 #define gray_bitmap_height gray_height
76 #define gray_bitmap_bits gray_bits
78 #endif /* not emacs */
80 static int pointer_grabbed;
81 static XEvent menu_post_event;
83 XFontStruct *xlwmenu_default_font;
85 static char
86 xlwMenuTranslations [] =
87 "<BtnDown>: start()\n\
88 <Motion>: drag()\n\
89 <BtnUp>: select()\n\
90 <Key>Shift_L: nothing()\n\
91 <Key>Shift_R: nothing()\n\
92 <Key>Meta_L: nothing()\n\
93 <Key>Meta_R: nothing()\n\
94 <Key>Control_L: nothing()\n\
95 <Key>Control_R: nothing()\n\
96 <Key>Hyper_L: nothing()\n\
97 <Key>Hyper_R: nothing()\n\
98 <Key>Super_L: nothing()\n\
99 <Key>Super_R: nothing()\n\
100 <Key>Alt_L: nothing()\n\
101 <Key>Alt_R: nothing()\n\
102 <Key>Caps_Lock: nothing()\n\
103 <Key>Shift_Lock: nothing()\n\
104 <KeyUp>Shift_L: nothing()\n\
105 <KeyUp>Shift_R: nothing()\n\
106 <KeyUp>Meta_L: nothing()\n\
107 <KeyUp>Meta_R: nothing()\n\
108 <KeyUp>Control_L: nothing()\n\
109 <KeyUp>Control_R: nothing()\n\
110 <KeyUp>Hyper_L: nothing()\n\
111 <KeyUp>Hyper_R: nothing()\n\
112 <KeyUp>Super_L: nothing()\n\
113 <KeyUp>Super_R: nothing()\n\
114 <KeyUp>Alt_L: nothing()\n\
115 <KeyUp>Alt_R: nothing()\n\
116 <KeyUp>Caps_Lock: nothing()\n\
117 <KeyUp>Shift_Lock:nothing()\n\
118 <Key>Return: select()\n\
119 <Key>Down: down()\n\
120 <Key>Up: up()\n\
121 <Key>Left: left()\n\
122 <Key>Right: right()\n\
123 <Key>: key()\n\
124 <KeyUp>: key()\n\
127 /* FIXME: Space should toggle toggleable menu item but not remove the menu
128 so you can toggle the next one without entering the menu again. */
130 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
132 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
134 #define offset(field) XtOffset(XlwMenuWidget, field)
135 static XtResource
136 xlwMenuResources[] =
138 #ifdef HAVE_X_I18N
139 {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;
1829 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1830 (w, valueMask, attributes);
1832 xswa.save_under = True;
1833 xswa.cursor = mw->menu.cursor_shape;
1834 mask = CWSaveUnder | CWCursor;
1835 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1837 mw->menu.windows [0].window = XtWindow (w);
1838 mw->menu.windows [0].x = w->core.x;
1839 mw->menu.windows [0].y = w->core.y;
1840 mw->menu.windows [0].width = w->core.width;
1841 mw->menu.windows [0].height = w->core.height;
1844 /* Only the toplevel menubar/popup is a widget so it's the only one that
1845 receives expose events through Xt. So we repaint all the other panes
1846 when receiving an Expose event. */
1847 static void
1848 XlwMenuRedisplay (w, ev, region)
1849 Widget w;
1850 XEvent* ev;
1851 Region region;
1853 XlwMenuWidget mw = (XlwMenuWidget)w;
1854 int i;
1856 /* If we have a depth beyond 1, it's because a submenu was displayed.
1857 If the submenu has been destroyed, set the depth back to 1. */
1858 if (submenu_destroyed)
1860 mw->menu.old_depth = 1;
1861 submenu_destroyed = 0;
1864 for (i = 0; i < mw->menu.old_depth; i++)
1865 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1869 /* Part of a hack to make the menu redisplay when a tooltip frame
1870 over a menu item is unmapped. */
1872 void
1873 xlwmenu_redisplay (w)
1874 Widget w;
1876 XlwMenuRedisplay (w, NULL, None);
1879 static void
1880 XlwMenuDestroy (w)
1881 Widget w;
1883 int i;
1884 XlwMenuWidget mw = (XlwMenuWidget) w;
1886 if (pointer_grabbed)
1887 ungrab_all ((Widget)w, CurrentTime);
1888 pointer_grabbed = 0;
1890 submenu_destroyed = 1;
1892 release_drawing_gcs (mw);
1893 release_shadow_gcs (mw);
1895 /* this doesn't come from the resource db but is created explicitly
1896 so we must free it ourselves. */
1897 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1898 mw->menu.gray_pixmap = (Pixmap) -1;
1900 #if 0
1901 /* Do free mw->menu.contents because nowadays we copy it
1902 during initialization. */
1903 XtFree (mw->menu.contents);
1904 #endif
1906 /* Don't free mw->menu.contents because that comes from our creator.
1907 The `*_stack' elements are just pointers into `contents' so leave
1908 that alone too. But free the stacks themselves. */
1909 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1910 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1912 /* Remember, you can't free anything that came from the resource
1913 database. This includes:
1914 mw->menu.cursor
1915 mw->menu.top_shadow_pixmap
1916 mw->menu.bottom_shadow_pixmap
1917 mw->menu.font
1918 Also the color cells of top_shadow_color, bottom_shadow_color,
1919 foreground, and button_foreground will never be freed until this
1920 client exits. Nice, eh?
1923 /* start from 1 because the one in slot 0 is w->core.window */
1924 for (i = 1; i < mw->menu.windows_length; i++)
1925 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1926 if (mw->menu.windows)
1927 XtFree ((char *) mw->menu.windows);
1930 static Boolean
1931 XlwMenuSetValues (current, request, new)
1932 Widget current;
1933 Widget request;
1934 Widget new;
1936 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1937 XlwMenuWidget newmw = (XlwMenuWidget)new;
1938 Boolean redisplay = False;
1939 int i;
1941 if (newmw->menu.contents
1942 && newmw->menu.contents->contents
1943 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1944 redisplay = True;
1945 /* Do redisplay if the contents are entirely eliminated. */
1946 if (newmw->menu.contents
1947 && newmw->menu.contents->contents == 0
1948 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1949 redisplay = True;
1951 if (newmw->core.background_pixel != oldmw->core.background_pixel
1952 || newmw->menu.foreground != oldmw->menu.foreground
1953 #ifdef HAVE_X_I18N
1954 || newmw->menu.fontSet != oldmw->menu.fontSet
1955 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
1956 #else
1957 || newmw->menu.font != oldmw->menu.font
1958 #endif
1961 release_drawing_gcs (newmw);
1962 make_drawing_gcs (newmw);
1964 release_shadow_gcs (newmw);
1965 /* Cause the shadow colors to be recalculated. */
1966 newmw->menu.top_shadow_color = -1;
1967 newmw->menu.bottom_shadow_color = -1;
1968 make_shadow_gcs (newmw);
1970 redisplay = True;
1972 if (XtIsRealized (current))
1973 /* If the menu is currently displayed, change the display. */
1974 for (i = 0; i < oldmw->menu.windows_length; i++)
1976 XSetWindowBackground (XtDisplay (oldmw),
1977 oldmw->menu.windows [i].window,
1978 newmw->core.background_pixel);
1979 /* clear windows and generate expose events */
1980 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
1981 0, 0, 0, 0, True);
1985 #ifdef HAVE_X_I18N
1986 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
1988 redisplay = True;
1989 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
1991 #endif
1993 return redisplay;
1996 static void
1997 XlwMenuResize (w)
1998 Widget w;
2000 XlwMenuWidget mw = (XlwMenuWidget)w;
2002 if (mw->menu.popped_up)
2004 /* Don't allow the popup menu to resize itself. */
2005 mw->core.width = mw->menu.windows [0].width;
2006 mw->core.height = mw->menu.windows [0].height;
2007 mw->core.parent->core.width = mw->core.width ;
2008 mw->core.parent->core.height = mw->core.height ;
2010 else
2012 mw->menu.windows [0].width = mw->core.width;
2013 mw->menu.windows [0].height = mw->core.height;
2017 \f/* Action procedures */
2018 static void
2019 handle_single_motion_event (mw, ev)
2020 XlwMenuWidget mw;
2021 XMotionEvent* ev;
2023 widget_value* val;
2024 int level;
2026 if (!map_event_to_widget_value (mw, ev, &val, &level))
2027 pop_new_stack_if_no_contents (mw);
2028 else
2029 set_new_state (mw, val, level);
2030 remap_menubar (mw);
2032 /* Sync with the display. Makes it feel better on X terms. */
2033 XSync (XtDisplay (mw), False);
2036 static void
2037 handle_motion_event (mw, ev)
2038 XlwMenuWidget mw;
2039 XMotionEvent* ev;
2041 int x = ev->x_root;
2042 int y = ev->y_root;
2043 int state = ev->state;
2045 handle_single_motion_event (mw, ev);
2047 /* allow motion events to be generated again */
2048 if (ev->is_hint
2049 && XQueryPointer (XtDisplay (mw), ev->window,
2050 &ev->root, &ev->subwindow,
2051 &ev->x_root, &ev->y_root,
2052 &ev->x, &ev->y,
2053 &ev->state)
2054 && ev->state == state
2055 && (ev->x_root != x || ev->y_root != y))
2056 handle_single_motion_event (mw, ev);
2059 static void
2060 Start (w, ev, params, num_params)
2061 Widget w;
2062 XEvent *ev;
2063 String *params;
2064 Cardinal *num_params;
2066 XlwMenuWidget mw = (XlwMenuWidget)w;
2068 if (!mw->menu.popped_up)
2070 menu_post_event = *ev;
2071 /* If event is set to CurrentTime, get the last known time stamp.
2072 This is for calculating if (popup) menus should stay up after
2073 a fast click. */
2074 if (menu_post_event.xbutton.time == CurrentTime)
2075 menu_post_event.xbutton.time
2076 = XtLastTimestampProcessed (XtDisplay (w));
2078 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2080 else
2082 /* If we push a button while the menu is posted semipermanently,
2083 releasing the button should always pop the menu down. */
2084 next_release_must_exit = 1;
2086 /* notes the absolute position of the menubar window */
2087 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2088 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2090 /* handles the down like a move, slots are compatible */
2091 handle_motion_event (mw, &ev->xmotion);
2095 static void
2096 Drag (w, ev, params, num_params)
2097 Widget w;
2098 XEvent *ev;
2099 String *params;
2100 Cardinal *num_params;
2102 XlwMenuWidget mw = (XlwMenuWidget)w;
2103 if (mw->menu.popped_up)
2104 handle_motion_event (mw, &ev->xmotion);
2107 /* Do nothing.
2108 This is how we handle presses and releases of modifier keys. */
2109 static void
2110 Nothing (w, ev, params, num_params)
2111 Widget w;
2112 XEvent *ev;
2113 String *params;
2114 Cardinal *num_params;
2118 static widget_value *
2119 find_first_selectable (mw, item, skip_titles)
2120 XlwMenuWidget mw;
2121 widget_value *item;
2122 int skip_titles;
2124 widget_value *current = item;
2125 enum menu_separator separator;
2127 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2128 || (skip_titles && !current->call_data && !current->contents))
2129 if (current->next)
2130 current=current->next;
2131 else
2132 return NULL;
2134 return current;
2137 static widget_value *
2138 find_next_selectable (mw, item, skip_titles)
2139 XlwMenuWidget mw;
2140 widget_value *item;
2142 widget_value *current = item;
2143 enum menu_separator separator;
2145 while (current->next && (current=current->next) &&
2146 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2147 || (skip_titles && !current->call_data && !current->contents)))
2150 if (current == item)
2152 if (mw->menu.old_depth < 2)
2153 return current;
2154 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2156 while (lw_separator_p (current->name, &separator, 0)
2157 || !current->enabled
2158 || (skip_titles && !current->call_data
2159 && !current->contents))
2161 if (current->next)
2162 current=current->next;
2164 if (current == item)
2165 break;
2170 return current;
2173 static widget_value *
2174 find_prev_selectable (mw, item, skip_titles)
2175 XlwMenuWidget mw;
2176 widget_value *item;
2178 widget_value *current = item;
2179 widget_value *prev = item;
2181 while ((current=find_next_selectable (mw, current, skip_titles))
2182 != item)
2184 if (prev == current)
2185 break;
2186 prev=current;
2189 return prev;
2192 static void
2193 Down (w, ev, params, num_params)
2194 Widget w;
2195 XEvent *ev;
2196 String *params;
2197 Cardinal *num_params;
2199 XlwMenuWidget mw = (XlwMenuWidget) w;
2200 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2201 int popup_menu_p = mw->menu.top_depth == 1;
2203 /* Inside top-level menu-bar? */
2204 if (mw->menu.old_depth == mw->menu.top_depth)
2205 /* When <down> in the menu-bar is pressed, display the corresponding
2206 sub-menu and select the first selectable menu item there.
2207 If this is a popup menu, skip title item of the popup. */
2208 set_new_state (mw,
2209 find_first_selectable (mw,
2210 selected_item->contents,
2211 popup_menu_p),
2212 mw->menu.old_depth);
2213 else
2214 /* Highlight next possible (enabled and not separator) menu item. */
2215 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2216 mw->menu.old_depth - 1);
2218 remap_menubar (mw);
2221 static void
2222 Up (w, ev, params, num_params)
2223 Widget w;
2224 XEvent *ev;
2225 String *params;
2226 Cardinal *num_params;
2228 XlwMenuWidget mw = (XlwMenuWidget) w;
2229 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2230 int popup_menu_p = mw->menu.top_depth == 1;
2232 /* Inside top-level menu-bar? */
2233 if (mw->menu.old_depth == mw->menu.top_depth)
2235 /* FIXME: this is tricky. <up> in the menu-bar should select the
2236 last selectable item in the list. So we select the first
2237 selectable one and find the previous selectable item. Is there
2238 a better way? */
2239 /* If this is a popup menu, skip title item of the popup. */
2240 set_new_state (mw,
2241 find_first_selectable (mw,
2242 selected_item->contents,
2243 popup_menu_p),
2244 mw->menu.old_depth);
2245 remap_menubar (mw);
2246 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2247 set_new_state (mw,
2248 find_prev_selectable (mw,
2249 selected_item,
2250 popup_menu_p),
2251 mw->menu.old_depth - 1);
2253 else
2254 /* Highlight previous (enabled and not separator) menu item. */
2255 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2256 mw->menu.old_depth - 1);
2258 remap_menubar (mw);
2261 void
2262 Left (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 /* Inside top-level menu-bar? */
2272 if (mw->menu.old_depth == mw->menu.top_depth)
2273 /* When <left> in the menu-bar is pressed, display the previous item on
2274 the menu-bar. If the current item is the first one, highlight the
2275 last item in the menubar (probably Help). */
2276 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2277 mw->menu.old_depth - 1);
2278 else if (mw->menu.old_depth == 1
2279 && selected_item->contents) /* Is this menu item expandable? */
2281 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2282 remap_menubar (mw);
2283 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2284 if (!selected_item->enabled && find_first_selectable (mw,
2285 selected_item,
2287 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2288 mw->menu.old_depth - 1);
2291 else
2293 pop_new_stack_if_no_contents (mw);
2294 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2295 mw->menu.old_depth - 2);
2298 remap_menubar (mw);
2301 void
2302 Right (w, ev, params, num_params)
2303 Widget w;
2304 XEvent *ev;
2305 String *params;
2306 Cardinal *num_params;
2308 XlwMenuWidget mw = (XlwMenuWidget) w;
2309 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2311 /* Inside top-level menu-bar? */
2312 if (mw->menu.old_depth == mw->menu.top_depth)
2313 /* When <right> in the menu-bar is pressed, display the next item on
2314 the menu-bar. If the current item is the last one, highlight the
2315 first item (probably File). */
2316 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2317 mw->menu.old_depth - 1);
2318 else if (selected_item->contents) /* Is this menu item expandable? */
2320 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2321 remap_menubar (mw);
2322 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2323 if (!selected_item->enabled && find_first_selectable (mw,
2324 selected_item,
2326 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2327 mw->menu.old_depth - 1);
2329 else
2331 pop_new_stack_if_no_contents (mw);
2332 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2333 mw->menu.old_depth - 2);
2336 remap_menubar (mw);
2339 /* Handle key press and release events while menu is popped up.
2340 Our action is to get rid of the menu. */
2341 static void
2342 Key (w, ev, params, num_params)
2343 Widget w;
2344 XEvent *ev;
2345 String *params;
2346 Cardinal *num_params;
2348 XlwMenuWidget mw = (XlwMenuWidget)w;
2350 /* Pop down everything. */
2351 mw->menu.new_depth = 1;
2352 remap_menubar (mw);
2354 if (mw->menu.popped_up)
2356 mw->menu.popped_up = False;
2357 ungrab_all ((Widget)mw, ev->xmotion.time);
2358 if (XtIsShell (XtParent ((Widget) mw)))
2359 XtPopdown (XtParent ((Widget) mw));
2360 else
2362 XtRemoveGrab ((Widget) mw);
2363 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2367 /* callback */
2368 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2371 static void
2372 Select (w, ev, params, num_params)
2373 Widget w;
2374 XEvent *ev;
2375 String *params;
2376 Cardinal *num_params;
2378 XlwMenuWidget mw = (XlwMenuWidget)w;
2379 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2381 /* If user releases the button quickly, without selecting anything,
2382 after the initial down-click that brought the menu up,
2383 do nothing. */
2384 if ((selected_item == 0
2385 || ((widget_value *) selected_item)->call_data == 0)
2386 && !next_release_must_exit
2387 && (ev->xbutton.time - menu_post_event.xbutton.time
2388 < XtGetMultiClickTime (XtDisplay (w))))
2389 return;
2391 /* pop down everything. */
2392 mw->menu.new_depth = 1;
2393 remap_menubar (mw);
2395 if (mw->menu.popped_up)
2397 mw->menu.popped_up = False;
2398 ungrab_all ((Widget)mw, ev->xmotion.time);
2399 if (XtIsShell (XtParent ((Widget) mw)))
2400 XtPopdown (XtParent ((Widget) mw));
2401 else
2403 XtRemoveGrab ((Widget) mw);
2404 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2408 /* callback */
2409 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2413 \f/* Special code to pop-up a menu */
2414 static void
2415 pop_up_menu (mw, event)
2416 XlwMenuWidget mw;
2417 XButtonPressedEvent* event;
2419 int x = event->x_root;
2420 int y = event->y_root;
2421 int w;
2422 int h;
2423 int borderwidth = mw->menu.shadow_thickness;
2424 Screen* screen = XtScreen (mw);
2425 Display *display = XtDisplay (mw);
2426 int count;
2428 next_release_must_exit = 0;
2430 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2432 if (XtIsShell (XtParent ((Widget)mw)))
2433 size_menu (mw, 0);
2435 w = mw->menu.windows [0].width;
2436 h = mw->menu.windows [0].height;
2438 x -= borderwidth;
2439 y -= borderwidth;
2440 if (x < borderwidth)
2441 x = borderwidth;
2442 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2443 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2444 if (y < borderwidth)
2445 y = borderwidth;
2446 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2447 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2449 mw->menu.popped_up = True;
2450 if (XtIsShell (XtParent ((Widget)mw)))
2452 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2453 XtParent ((Widget)mw)->core.border_width);
2454 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2455 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2456 mw->menu.windows [0].x = x + borderwidth;
2457 mw->menu.windows [0].y = y + borderwidth;
2458 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2460 else
2462 XEvent *ev = (XEvent *) event;
2464 XtAddGrab ((Widget) mw, True, True);
2466 /* notes the absolute position of the menubar window */
2467 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2468 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2469 mw->menu.top_depth = 2;
2472 #ifdef emacs
2473 count = x_catch_errors (display);
2474 #endif
2475 if (XtGrabPointer ((Widget)mw, False,
2476 (PointerMotionMask
2477 | PointerMotionHintMask
2478 | ButtonReleaseMask
2479 | ButtonPressMask),
2480 GrabModeAsync, GrabModeAsync, None,
2481 mw->menu.cursor_shape,
2482 event->time) == Success)
2484 if (! GRAB_KEYBOARD
2485 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2486 GrabModeAsync, event->time) == Success)
2488 XtSetKeyboardFocus((Widget)mw, None);
2489 pointer_grabbed = 1;
2491 else
2492 XtUngrabPointer ((Widget)mw, event->time);
2495 #ifdef emacs
2496 if (x_had_errors_p (display))
2498 pointer_grabbed = 0;
2499 XtUngrabPointer ((Widget)mw, event->time);
2501 x_uncatch_errors (display, count);
2502 #endif
2504 handle_motion_event (mw, (XMotionEvent*)event);
2507 /* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2508 (do not change this comment) */