(back_to_previous_visible_line_start): Reset iterator
[emacs.git] / lwlib / xlwmenu.c
blob43ae8ee2ea005bd821c029b9ed882cde4be4c15b
1 /* Implements a lightweight menubar widget.
2 Copyright (C) 1992 Lucid, Inc.
3 Copyright (C) 2002 Free Software Foundation, Inc.
5 This file is part of the Lucid Widget Library.
7 The Lucid Widget Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
12 The Lucid Widget Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Emacs; see the file COPYING. If not, write to the
19 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA. */
22 /* Created by devin@lucid.com */
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include "lisp.h"
30 #include <stdio.h>
32 #include <sys/types.h>
33 #if (defined __sun) && !(defined SUNOS41)
34 #define SUNOS41
35 #include <X11/Xos.h>
36 #undef SUNOS41
37 #else
38 #include <X11/Xos.h>
39 #endif
40 #include <X11/IntrinsicP.h>
41 #include <X11/ObjectP.h>
42 #include <X11/StringDefs.h>
43 #include <X11/cursorfont.h>
44 #include "xlwmenuP.h"
46 #ifdef emacs
48 /* Defined in xfns.c. When config.h defines `static' as empty, we get
49 redefinition errors when gray_bitmap is included more than once, so
50 we're referring to the one include in xfns.c here. */
52 extern int gray_bitmap_width;
53 extern int gray_bitmap_height;
54 extern char *gray_bitmap_bits;
56 /* Defined in xterm.c. */
57 extern int x_alloc_nearest_color_for_widget __P ((Widget, Colormap, XColor*));
58 extern int x_alloc_lighter_color_for_widget __P ((Widget, Display*, Colormap,
59 unsigned long *,
60 double, int));
61 extern int x_catch_errors __P ((Display*));
62 extern int x_uncatch_errors __P ((Display*, int));
63 extern int x_had_errors_p __P ((Display*));
64 extern int x_clear_errors __P ((Display*));
65 extern unsigned long x_copy_dpy_color __P ((Display *, Colormap,
66 unsigned long));
68 /* Defined in xfaces.c. */
69 extern void x_free_dpy_colors __P ((Display *, Screen *, Colormap,
70 unsigned long *pixels, int npixels));
71 #else /* not emacs */
73 #include <X11/bitmaps/gray>
74 #define gray_bitmap_width gray_width
75 #define gray_bitmap_height gray_height
76 #define gray_bitmap_bits gray_bits
78 #endif /* not emacs */
80 static int pointer_grabbed;
81 static XEvent menu_post_event;
83 XFontStruct *xlwmenu_default_font;
85 static char
86 xlwMenuTranslations [] =
87 "<BtnDown>: start()\n\
88 <Motion>: drag()\n\
89 <BtnUp>: select()\n\
90 <Key>Shift_L: nothing()\n\
91 <Key>Shift_R: nothing()\n\
92 <Key>Meta_L: nothing()\n\
93 <Key>Meta_R: nothing()\n\
94 <Key>Control_L: nothing()\n\
95 <Key>Control_R: nothing()\n\
96 <Key>Hyper_L: nothing()\n\
97 <Key>Hyper_R: nothing()\n\
98 <Key>Super_L: nothing()\n\
99 <Key>Super_R: nothing()\n\
100 <Key>Alt_L: nothing()\n\
101 <Key>Alt_R: nothing()\n\
102 <Key>Caps_Lock: nothing()\n\
103 <Key>Shift_Lock: nothing()\n\
104 <KeyUp>Shift_L: nothing()\n\
105 <KeyUp>Shift_R: nothing()\n\
106 <KeyUp>Meta_L: nothing()\n\
107 <KeyUp>Meta_R: nothing()\n\
108 <KeyUp>Control_L: nothing()\n\
109 <KeyUp>Control_R: nothing()\n\
110 <KeyUp>Hyper_L: nothing()\n\
111 <KeyUp>Hyper_R: nothing()\n\
112 <KeyUp>Super_L: nothing()\n\
113 <KeyUp>Super_R: nothing()\n\
114 <KeyUp>Alt_L: nothing()\n\
115 <KeyUp>Alt_R: nothing()\n\
116 <KeyUp>Caps_Lock: nothing()\n\
117 <KeyUp>Shift_Lock:nothing()\n\
118 <Key>Return: select()\n\
119 <Key>Down: down()\n\
120 <Key>Up: up()\n\
121 <Key>Left: left()\n\
122 <Key>Right: right()\n\
123 <Key>: key()\n\
124 <KeyUp>: key()\n\
127 /* FIXME: Space should toggle toggleable menu item but not remove the menu
128 so you can toggle the next one without entering the menu again. */
130 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
132 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
134 #define offset(field) XtOffset(XlwMenuWidget, field)
135 static XtResource
136 xlwMenuResources[] =
138 {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
139 offset(menu.font),XtRString, "XtDefaultFont"},
140 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
141 offset(menu.foreground), XtRString, "XtDefaultForeground"},
142 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
143 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
144 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
145 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
146 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
147 offset(menu.margin), XtRImmediate, (XtPointer)1},
148 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
149 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
150 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
151 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
152 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
153 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
155 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
156 sizeof (Dimension), offset (menu.shadow_thickness),
157 XtRImmediate, (XtPointer)1},
158 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
159 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
160 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
161 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
162 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
163 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
164 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
165 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
167 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
168 offset(menu.open), XtRCallback, (XtPointer)NULL},
169 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
170 offset(menu.select), XtRCallback, (XtPointer)NULL},
171 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
172 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
173 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
174 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
175 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
176 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
177 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
178 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
180 #undef offset
182 static Boolean XlwMenuSetValues();
183 static void XlwMenuRealize();
184 static void XlwMenuRedisplay();
185 static void XlwMenuResize();
186 static void XlwMenuInitialize();
187 static void XlwMenuRedisplay();
188 static void XlwMenuDestroy();
189 static void XlwMenuClassInitialize();
190 static void Start();
191 static void Drag();
192 static void Down();
193 static void Up();
194 static void Left();
195 static void Right();
196 static void Select();
197 static void Key();
198 static void Nothing();
199 static int separator_height __P ((enum menu_separator));
200 static void pop_up_menu __P ((XlwMenuWidget, XButtonPressedEvent *));
203 static XtActionsRec
204 xlwMenuActionsList [] =
206 {"start", Start},
207 {"drag", Drag},
208 {"down", Down},
209 {"up", Up},
210 {"left", Left},
211 {"right", Right},
212 {"select", Select},
213 {"key", Key},
214 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
215 {"nothing", Nothing},
218 #define SuperClass ((CoreWidgetClass)&coreClassRec)
220 XlwMenuClassRec xlwMenuClassRec =
222 { /* CoreClass fields initialization */
223 (WidgetClass) SuperClass, /* superclass */
224 "XlwMenu", /* class_name */
225 sizeof(XlwMenuRec), /* size */
226 XlwMenuClassInitialize, /* class_initialize */
227 NULL, /* class_part_initialize */
228 FALSE, /* class_inited */
229 XlwMenuInitialize, /* initialize */
230 NULL, /* initialize_hook */
231 XlwMenuRealize, /* realize */
232 xlwMenuActionsList, /* actions */
233 XtNumber(xlwMenuActionsList), /* num_actions */
234 xlwMenuResources, /* resources */
235 XtNumber(xlwMenuResources), /* resource_count */
236 NULLQUARK, /* xrm_class */
237 TRUE, /* compress_motion */
238 TRUE, /* compress_exposure */
239 TRUE, /* compress_enterleave */
240 FALSE, /* visible_interest */
241 XlwMenuDestroy, /* destroy */
242 XlwMenuResize, /* resize */
243 XlwMenuRedisplay, /* expose */
244 XlwMenuSetValues, /* set_values */
245 NULL, /* set_values_hook */
246 XtInheritSetValuesAlmost, /* set_values_almost */
247 NULL, /* get_values_hook */
248 NULL, /* accept_focus */
249 XtVersion, /* version */
250 NULL, /* callback_private */
251 xlwMenuTranslations, /* tm_table */
252 XtInheritQueryGeometry, /* query_geometry */
253 XtInheritDisplayAccelerator, /* display_accelerator */
254 NULL /* extension */
255 }, /* XlwMenuClass fields initialization */
257 0 /* dummy */
261 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
263 int submenu_destroyed;
265 /* For debug, if installation-directory is non-nil this is not an installed
266 Emacs. In that case we do not grab the keyboard to make it easier to
267 debug. */
268 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
270 static int next_release_must_exit;
272 \f/* Utilities */
274 /* Ungrab pointer and keyboard */
275 static void
276 ungrab_all (w, ungrabtime)
277 Widget w;
278 Time ungrabtime;
280 XtUngrabPointer (w, ungrabtime);
281 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
284 /* Like abort, but remove grabs from widget W before. */
286 static void
287 abort_gracefully (w)
288 Widget w;
290 if (XtIsShell (XtParent (w)))
291 XtRemoveGrab (w);
292 ungrab_all (w, CurrentTime);
293 abort ();
296 static void
297 push_new_stack (mw, val)
298 XlwMenuWidget mw;
299 widget_value* val;
301 if (!mw->menu.new_stack)
303 mw->menu.new_stack_length = 10;
304 mw->menu.new_stack =
305 (widget_value**)XtCalloc (mw->menu.new_stack_length,
306 sizeof (widget_value*));
308 else if (mw->menu.new_depth == mw->menu.new_stack_length)
310 mw->menu.new_stack_length *= 2;
311 mw->menu.new_stack =
312 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
313 mw->menu.new_stack_length * sizeof (widget_value*));
315 mw->menu.new_stack [mw->menu.new_depth++] = val;
318 static void
319 pop_new_stack_if_no_contents (mw)
320 XlwMenuWidget mw;
322 if (mw->menu.new_depth > 1)
324 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
325 mw->menu.new_depth -= 1;
329 static void
330 make_old_stack_space (mw, n)
331 XlwMenuWidget mw;
332 int n;
334 if (!mw->menu.old_stack)
336 mw->menu.old_stack_length = 10;
337 mw->menu.old_stack =
338 (widget_value**)XtCalloc (mw->menu.old_stack_length,
339 sizeof (widget_value*));
341 else if (mw->menu.old_stack_length < n)
343 mw->menu.old_stack_length *= 2;
344 mw->menu.old_stack =
345 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
346 mw->menu.old_stack_length * sizeof (widget_value*));
350 \f/* Size code */
352 string_width (mw, s)
353 XlwMenuWidget mw;
354 char *s;
356 XCharStruct xcs;
357 int drop;
359 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
360 return xcs.width;
363 static int
364 arrow_width (mw)
365 XlwMenuWidget mw;
367 return (mw->menu.font->ascent * 3/4) | 1;
370 /* Return the width of toggle buttons of widget MW. */
372 static int
373 toggle_button_width (mw)
374 XlwMenuWidget mw;
376 return ((mw->menu.font->ascent + mw->menu.font->descent) * 2 / 3) | 1;
380 /* Return the width of radio buttons of widget MW. */
382 static int
383 radio_button_width (mw)
384 XlwMenuWidget mw;
386 return toggle_button_width (mw) * 1.41;
390 static XtResource
391 nameResource[] =
393 {"labelString", "LabelString", XtRString, sizeof(String),
394 0, XtRImmediate, 0},
397 static char*
398 resource_widget_value (mw, val)
399 XlwMenuWidget mw;
400 widget_value *val;
402 if (!val->toolkit_data)
404 char* resourced_name = NULL;
405 char* complete_name;
406 XtGetSubresources ((Widget) mw,
407 (XtPointer) &resourced_name,
408 val->name, val->name,
409 nameResource, 1, NULL, 0);
410 if (!resourced_name)
411 resourced_name = val->name;
412 if (!val->value)
414 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
415 strcpy (complete_name, resourced_name);
417 else
419 int complete_length =
420 strlen (resourced_name) + strlen (val->value) + 2;
421 complete_name = XtMalloc (complete_length);
422 *complete_name = 0;
423 strcat (complete_name, resourced_name);
424 strcat (complete_name, " ");
425 strcat (complete_name, val->value);
428 val->toolkit_data = complete_name;
429 val->free_toolkit_data = True;
431 return (char*)val->toolkit_data;
434 /* Returns the sizes of an item */
435 static void
436 size_menu_item (mw, val, horizontal_p, label_width, rest_width, button_width,
437 height)
438 XlwMenuWidget mw;
439 widget_value* val;
440 int horizontal_p;
441 int* label_width;
442 int* rest_width;
443 int* button_width;
444 int* height;
446 enum menu_separator separator;
448 if (lw_separator_p (val->name, &separator, 0))
450 *height = separator_height (separator);
451 *label_width = 1;
452 *rest_width = 0;
453 *button_width = 0;
455 else
457 *height =
458 mw->menu.font->ascent + mw->menu.font->descent
459 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
461 *label_width =
462 string_width (mw, resource_widget_value (mw, val))
463 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
465 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
466 if (!horizontal_p)
468 if (val->contents)
469 /* Add width of the arrow displayed for submenus. */
470 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
471 else if (val->key)
472 /* Add width of key equivalent string. */
473 *rest_width += (string_width (mw, val->key)
474 + mw->menu.arrow_spacing);
476 if (val->button_type == BUTTON_TYPE_TOGGLE)
477 *button_width = (toggle_button_width (mw)
478 + mw->menu.horizontal_spacing);
479 else if (val->button_type == BUTTON_TYPE_RADIO)
480 *button_width = (radio_button_width (mw)
481 + mw->menu.horizontal_spacing);
486 static void
487 size_menu (mw, level)
488 XlwMenuWidget mw;
489 int level;
491 unsigned int label_width = 0;
492 int rest_width = 0;
493 int button_width = 0;
494 int max_rest_width = 0;
495 int max_button_width = 0;
496 unsigned int height = 0;
497 int horizontal_p = mw->menu.horizontal && (level == 0);
498 widget_value* val;
499 window_state* ws;
501 if (level >= mw->menu.old_depth)
502 abort_gracefully ((Widget) mw);
504 ws = &mw->menu.windows [level];
505 ws->width = 0;
506 ws->height = 0;
507 ws->label_width = 0;
508 ws->button_width = 0;
510 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
512 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
513 &button_width, &height);
514 if (horizontal_p)
516 ws->width += label_width + rest_width;
517 if (height > ws->height)
518 ws->height = height;
520 else
522 if (label_width > ws->label_width)
523 ws->label_width = label_width;
524 if (rest_width > max_rest_width)
525 max_rest_width = rest_width;
526 if (button_width > max_button_width)
527 max_button_width = button_width;
528 ws->height += height;
532 if (horizontal_p)
533 ws->label_width = ws->button_width = 0;
534 else
536 ws->width = ws->label_width + max_rest_width + max_button_width;
537 ws->button_width = max_button_width;
540 ws->width += 2 * mw->menu.shadow_thickness;
541 ws->height += 2 * mw->menu.shadow_thickness;
543 if (horizontal_p)
545 ws->width += 2 * mw->menu.margin;
546 ws->height += 2 * mw->menu.margin;
551 \f/* Display code */
553 static void
554 draw_arrow (mw, window, gc, x, y, width, down_p)
555 XlwMenuWidget mw;
556 Window window;
557 GC gc;
558 int x;
559 int y;
560 int width;
561 int down_p;
563 Display *dpy = XtDisplay (mw);
564 GC top_gc = mw->menu.shadow_top_gc;
565 GC bottom_gc = mw->menu.shadow_bottom_gc;
566 int thickness = mw->menu.shadow_thickness;
567 int height = width;
568 XPoint pt[10];
569 /* alpha = atan (0.5)
570 factor = (1 + sin (alpha)) / cos (alpha) */
571 double factor = 1.62;
572 int thickness2 = thickness * factor;
574 y += (mw->menu.font->ascent + mw->menu.font->descent - height) / 2;
576 if (down_p)
578 GC temp;
579 temp = top_gc;
580 top_gc = bottom_gc;
581 bottom_gc = temp;
584 pt[0].x = x;
585 pt[0].y = y + height;
586 pt[1].x = x + thickness;
587 pt[1].y = y + height - thickness2;
588 pt[2].x = x + thickness2;
589 pt[2].y = y + thickness2;
590 pt[3].x = x;
591 pt[3].y = y;
592 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
594 pt[0].x = x;
595 pt[0].y = y;
596 pt[1].x = x + thickness;
597 pt[1].y = y + thickness2;
598 pt[2].x = x + width - thickness2;
599 pt[2].y = y + height / 2;
600 pt[3].x = x + width;
601 pt[3].y = y + height / 2;
602 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
604 pt[0].x = x;
605 pt[0].y = y + height;
606 pt[1].x = x + thickness;
607 pt[1].y = y + height - thickness2;
608 pt[2].x = x + width - thickness2;
609 pt[2].y = y + height / 2;
610 pt[3].x = x + width;
611 pt[3].y = y + height / 2;
612 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
617 static void
618 draw_shadow_rectangle (mw, window, x, y, width, height, erase_p, down_p)
619 XlwMenuWidget mw;
620 Window window;
621 int x;
622 int y;
623 int width;
624 int height;
625 int erase_p;
626 int down_p;
628 Display *dpy = XtDisplay (mw);
629 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
630 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
631 int thickness = mw->menu.shadow_thickness;
632 XPoint points [4];
634 if (!erase_p && down_p)
636 GC temp;
637 temp = top_gc;
638 top_gc = bottom_gc;
639 bottom_gc = temp;
642 points [0].x = x;
643 points [0].y = y;
644 points [1].x = x + width;
645 points [1].y = y;
646 points [2].x = x + width - thickness;
647 points [2].y = y + thickness;
648 points [3].x = x;
649 points [3].y = y + thickness;
650 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
651 points [0].x = x;
652 points [0].y = y + thickness;
653 points [1].x = x;
654 points [1].y = y + height;
655 points [2].x = x + thickness;
656 points [2].y = y + height - thickness;
657 points [3].x = x + thickness;
658 points [3].y = y + thickness;
659 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
660 points [0].x = x + width;
661 points [0].y = y;
662 points [1].x = x + width - thickness;
663 points [1].y = y + thickness;
664 points [2].x = x + width - thickness;
665 points [2].y = y + height - thickness;
666 points [3].x = x + width;
667 points [3].y = y + height - thickness;
668 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
669 points [0].x = x;
670 points [0].y = y + height;
671 points [1].x = x + width;
672 points [1].y = y + height;
673 points [2].x = x + width;
674 points [2].y = y + height - thickness;
675 points [3].x = x + thickness;
676 points [3].y = y + height - thickness;
677 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
681 static void
682 draw_shadow_rhombus (mw, window, x, y, width, height, erase_p, down_p)
683 XlwMenuWidget mw;
684 Window window;
685 int x;
686 int y;
687 int width;
688 int height;
689 int erase_p;
690 int down_p;
692 Display *dpy = XtDisplay (mw);
693 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
694 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
695 int thickness = mw->menu.shadow_thickness;
696 XPoint points [4];
698 if (!erase_p && down_p)
700 GC temp;
701 temp = top_gc;
702 top_gc = bottom_gc;
703 bottom_gc = temp;
706 points [0].x = x;
707 points [0].y = y + height / 2;
708 points [1].x = x + thickness;
709 points [1].y = y + height / 2;
710 points [2].x = x + width / 2;
711 points [2].y = y + thickness;
712 points [3].x = x + width / 2;
713 points [3].y = y;
714 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
715 points [0].x = x + width / 2;
716 points [0].y = y;
717 points [1].x = x + width / 2;
718 points [1].y = y + thickness;
719 points [2].x = x + width - thickness;
720 points [2].y = y + height / 2;
721 points [3].x = x + width;
722 points [3].y = y + height / 2;
723 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
724 points [0].x = x;
725 points [0].y = y + height / 2;
726 points [1].x = x + thickness;
727 points [1].y = y + height / 2;
728 points [2].x = x + width / 2;
729 points [2].y = y + height - thickness;
730 points [3].x = x + width / 2;
731 points [3].y = y + height;
732 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
733 points [0].x = x + width / 2;
734 points [0].y = y + height;
735 points [1].x = x + width / 2;
736 points [1].y = y + height - thickness;
737 points [2].x = x + width - thickness;
738 points [2].y = y + height / 2;
739 points [3].x = x + width;
740 points [3].y = y + height / 2;
741 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
745 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
746 top-left corner of the menu item. SELECTED_P non-zero means the
747 toggle button is selected. */
749 static void
750 draw_toggle (mw, window, x, y, selected_p)
751 XlwMenuWidget mw;
752 Window window;
753 int x, y, selected_p;
755 int width, height;
757 width = toggle_button_width (mw);
758 height = width;
759 x += mw->menu.horizontal_spacing;
760 y += (mw->menu.font->ascent - height) / 2;
761 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
765 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
766 top-left corner of the menu item. SELECTED_P non-zero means the
767 toggle button is selected. */
769 static void
770 draw_radio (mw, window, x, y, selected_p)
771 XlwMenuWidget mw;
772 Window window;
773 int x, y, selected_p;
775 int width, height;
777 width = radio_button_width (mw);
778 height = width;
779 x += mw->menu.horizontal_spacing;
780 y += (mw->menu.font->ascent - height) / 2;
781 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
785 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
786 top-left corner of the menu item. WIDTH is the width of the
787 separator to draw. TYPE is the separator type. */
789 static void
790 draw_separator (mw, window, x, y, width, type)
791 XlwMenuWidget mw;
792 Window window;
793 int x, y, width;
794 enum menu_separator type;
796 Display *dpy = XtDisplay (mw);
797 XGCValues xgcv;
799 switch (type)
801 case SEPARATOR_NO_LINE:
802 break;
804 case SEPARATOR_SINGLE_LINE:
805 XDrawLine (dpy, window, mw->menu.foreground_gc,
806 x, y, x + width, y);
807 break;
809 case SEPARATOR_DOUBLE_LINE:
810 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
811 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
812 break;
814 case SEPARATOR_SINGLE_DASHED_LINE:
815 xgcv.line_style = LineOnOffDash;
816 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
817 XDrawLine (dpy, window, mw->menu.foreground_gc,
818 x, y, x + width, y);
819 xgcv.line_style = LineSolid;
820 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
821 break;
823 case SEPARATOR_DOUBLE_DASHED_LINE:
824 draw_separator (mw, window, x, y, width,
825 SEPARATOR_SINGLE_DASHED_LINE);
826 draw_separator (mw, window, x, y + 2, width,
827 SEPARATOR_SINGLE_DASHED_LINE);
828 break;
830 case SEPARATOR_SHADOW_ETCHED_IN:
831 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
832 x, y, x + width, y);
833 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
834 x, y + 1, x + width, y + 1);
835 break;
837 case SEPARATOR_SHADOW_ETCHED_OUT:
838 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
839 x, y, x + width, y);
840 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
841 x, y + 1, x + width, y + 1);
842 break;
844 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
845 xgcv.line_style = LineOnOffDash;
846 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
847 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
848 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
849 xgcv.line_style = LineSolid;
850 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
851 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
852 break;
854 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
855 xgcv.line_style = LineOnOffDash;
856 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
857 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
858 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
859 xgcv.line_style = LineSolid;
860 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
861 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
862 break;
864 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
865 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
866 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
867 break;
869 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
870 draw_separator (mw, window, x, y, width,
871 SEPARATOR_SHADOW_ETCHED_OUT);
872 draw_separator (mw, window, x, y + 3, width,
873 SEPARATOR_SHADOW_ETCHED_OUT);
874 break;
876 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
877 xgcv.line_style = LineOnOffDash;
878 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
879 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
880 draw_separator (mw, window, x, y, width,
881 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
882 xgcv.line_style = LineSolid;
883 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
884 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
885 break;
887 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
888 xgcv.line_style = LineOnOffDash;
889 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
890 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
891 draw_separator (mw, window, x, y, width,
892 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
893 xgcv.line_style = LineSolid;
894 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
895 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
896 break;
898 default:
899 abort ();
904 /* Return the pixel height of menu separator SEPARATOR. */
906 static int
907 separator_height (separator)
908 enum menu_separator separator;
910 switch (separator)
912 case SEPARATOR_NO_LINE:
913 return 2;
915 case SEPARATOR_SINGLE_LINE:
916 case SEPARATOR_SINGLE_DASHED_LINE:
917 return 1;
919 case SEPARATOR_DOUBLE_LINE:
920 case SEPARATOR_DOUBLE_DASHED_LINE:
921 return 3;
923 case SEPARATOR_SHADOW_ETCHED_IN:
924 case SEPARATOR_SHADOW_ETCHED_OUT:
925 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
926 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
927 return 2;
929 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
930 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
931 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
932 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
933 return 5;
935 default:
936 abort ();
941 /* Display the menu item and increment where.x and where.y to show how large
942 the menu item was. */
944 static void
945 display_menu_item (mw, val, ws, where, highlighted_p, horizontal_p,
946 just_compute_p)
947 XlwMenuWidget mw;
948 widget_value* val;
949 window_state* ws;
950 XPoint* where;
951 Boolean highlighted_p;
952 Boolean horizontal_p;
953 Boolean just_compute_p;
955 GC deco_gc;
956 GC text_gc;
957 int font_ascent = mw->menu.font->ascent;
958 int font_descent = mw->menu.font->descent;
959 int shadow = mw->menu.shadow_thickness;
960 int margin = mw->menu.margin;
961 int h_spacing = mw->menu.horizontal_spacing;
962 int v_spacing = mw->menu.vertical_spacing;
963 int label_width;
964 int rest_width;
965 int button_width;
966 int height;
967 int width;
968 enum menu_separator separator;
969 int separator_p = lw_separator_p (val->name, &separator, 0);
971 /* compute the sizes of the item */
972 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
973 &button_width, &height);
975 if (horizontal_p)
976 width = label_width + rest_width;
977 else
979 label_width = ws->label_width;
980 width = ws->width - 2 * shadow;
983 /* Only highlight an enabled item that has a callback. */
984 if (highlighted_p)
985 if (!val->enabled || !(val->call_data || val->contents))
986 highlighted_p = 0;
988 /* do the drawing. */
989 if (!just_compute_p)
991 /* Add the shadow border of the containing menu */
992 int x = where->x + shadow;
993 int y = where->y + shadow;
995 if (horizontal_p)
997 x += margin;
998 y += margin;
1001 /* pick the foreground and background GC. */
1002 if (val->enabled)
1003 text_gc = mw->menu.foreground_gc;
1004 else
1005 text_gc = mw->menu.disabled_gc;
1006 deco_gc = mw->menu.foreground_gc;
1008 if (separator_p)
1010 draw_separator (mw, ws->window, x, y, width, separator);
1012 else
1014 int x_offset = x + h_spacing + shadow;
1015 char* display_string = resource_widget_value (mw, val);
1016 draw_shadow_rectangle (mw, ws->window, x, y, width, height, True,
1017 False);
1019 /* Deal with centering a menu title. */
1020 if (!horizontal_p && !val->contents && !val->call_data)
1022 int l = string_width (mw, display_string);
1024 if (width > l)
1025 x_offset = (width - l) >> 1;
1027 else if (!horizontal_p && ws->button_width)
1028 x_offset += ws->button_width;
1031 XDrawString (XtDisplay (mw), ws->window, text_gc, x_offset,
1032 y + v_spacing + shadow + font_ascent,
1033 display_string, strlen (display_string));
1035 if (!horizontal_p)
1037 if (val->button_type == BUTTON_TYPE_TOGGLE)
1038 draw_toggle (mw, ws->window, x, y + v_spacing + shadow,
1039 val->selected);
1040 else if (val->button_type == BUTTON_TYPE_RADIO)
1041 draw_radio (mw, ws->window, x, y + v_spacing + shadow,
1042 val->selected);
1044 if (val->contents)
1046 int a_w = arrow_width (mw);
1047 draw_arrow (mw, ws->window, deco_gc,
1048 x + width - a_w
1049 - mw->menu.horizontal_spacing
1050 - mw->menu.shadow_thickness,
1051 y + v_spacing + shadow, a_w,
1052 highlighted_p);
1054 else if (val->key)
1056 XDrawString (XtDisplay (mw), ws->window, text_gc,
1057 x + label_width + mw->menu.arrow_spacing,
1058 y + v_spacing + shadow + font_ascent,
1059 val->key, strlen (val->key));
1062 else
1064 XDrawRectangle (XtDisplay (mw), ws->window,
1065 mw->menu.background_gc,
1066 x + shadow, y + shadow,
1067 label_width + h_spacing - 1,
1068 font_ascent + font_descent + 2 * v_spacing - 1);
1069 draw_shadow_rectangle (mw, ws->window, x, y, width, height,
1070 True, False);
1073 if (highlighted_p)
1074 draw_shadow_rectangle (mw, ws->window, x, y, width, height, False,
1075 False);
1079 where->x += width;
1080 where->y += height;
1083 static void
1084 display_menu (mw, level, just_compute_p, highlighted_pos, hit, hit_return,
1085 this, that)
1086 XlwMenuWidget mw;
1087 int level;
1088 Boolean just_compute_p;
1089 XPoint* highlighted_pos;
1090 XPoint* hit;
1091 widget_value** hit_return;
1092 widget_value* this;
1093 widget_value* that;
1095 widget_value* val;
1096 widget_value* following_item;
1097 window_state* ws;
1098 XPoint where;
1099 int horizontal_p = mw->menu.horizontal && (level == 0);
1100 int highlighted_p;
1101 int just_compute_this_one_p;
1102 /* This is set nonzero if the element containing HIGHLIGHTED_POS
1103 is disabled, so that we do not return any subsequent element either. */
1104 int no_return = 0;
1105 enum menu_separator separator;
1107 if (level >= mw->menu.old_depth)
1108 abort_gracefully ((Widget) mw);
1110 if (level < mw->menu.old_depth - 1)
1111 following_item = mw->menu.old_stack [level + 1];
1112 else
1113 following_item = NULL;
1115 if (hit)
1116 *hit_return = NULL;
1118 where.x = 0;
1119 where.y = 0;
1121 ws = &mw->menu.windows [level];
1122 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1124 highlighted_p = val == following_item;
1125 if (highlighted_p && highlighted_pos)
1127 if (horizontal_p)
1128 highlighted_pos->x = where.x;
1129 else
1130 highlighted_pos->y = where.y;
1133 just_compute_this_one_p =
1134 just_compute_p || ((this || that) && val != this && val != that);
1136 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1137 just_compute_this_one_p);
1139 if (highlighted_p && highlighted_pos)
1141 if (horizontal_p)
1142 highlighted_pos->y = where.y;
1143 else
1144 highlighted_pos->x = where.x;
1147 if (hit
1148 && !*hit_return
1149 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1150 && !lw_separator_p (val->name, &separator, 0)
1151 && !no_return)
1153 if (val->enabled)
1154 *hit_return = val;
1155 else
1156 no_return = 1;
1159 if (horizontal_p)
1160 where.y = 0;
1161 else
1162 where.x = 0;
1165 if (!just_compute_p)
1166 draw_shadow_rectangle (mw, ws->window, 0, 0, ws->width, ws->height,
1167 False, False);
1170 \f/* Motion code */
1171 static void
1172 set_new_state (mw, val, level)
1173 XlwMenuWidget mw;
1174 widget_value* val;
1175 int level;
1177 int i;
1179 mw->menu.new_depth = 0;
1180 for (i = 0; i < level; i++)
1181 push_new_stack (mw, mw->menu.old_stack [i]);
1182 push_new_stack (mw, val);
1185 static void
1186 make_windows_if_needed (mw, n)
1187 XlwMenuWidget mw;
1188 int n;
1190 int i;
1191 int start_at;
1192 XSetWindowAttributes xswa;
1193 int mask;
1194 Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1195 window_state* windows;
1197 if (mw->menu.windows_length >= n)
1198 return;
1200 xswa.save_under = True;
1201 xswa.override_redirect = True;
1202 xswa.background_pixel = mw->core.background_pixel;
1203 xswa.border_pixel = mw->core.border_pixel;
1204 xswa.event_mask =
1205 ExposureMask | PointerMotionMask | PointerMotionHintMask
1206 | ButtonReleaseMask | ButtonPressMask;
1207 xswa.cursor = mw->menu.cursor_shape;
1208 mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
1209 | CWEventMask | CWCursor;
1211 if (!mw->menu.windows)
1213 mw->menu.windows =
1214 (window_state*)XtMalloc (n * sizeof (window_state));
1215 start_at = 0;
1217 else
1219 mw->menu.windows =
1220 (window_state*)XtRealloc ((char*)mw->menu.windows,
1221 n * sizeof (window_state));
1222 start_at = mw->menu.windows_length;
1224 mw->menu.windows_length = n;
1226 windows = mw->menu.windows;
1228 for (i = start_at; i < n; i++)
1230 windows [i].x = 0;
1231 windows [i].y = 0;
1232 windows [i].width = 1;
1233 windows [i].height = 1;
1234 windows [i].window =
1235 XCreateWindow (XtDisplay (mw), root, 0, 0, 1, 1,
1236 0, 0, CopyFromParent, CopyFromParent, mask, &xswa);
1240 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1243 xlwmenu_window_p (w, window)
1244 Widget w;
1245 Window window;
1247 XlwMenuWidget mw = (XlwMenuWidget) w;
1248 int i;
1250 for (i = 0; i < mw->menu.windows_length; ++i)
1251 if (window == mw->menu.windows[i].window)
1252 break;
1254 return i < mw->menu.windows_length;
1257 /* Make the window fit in the screen */
1258 static void
1259 fit_to_screen (mw, ws, previous_ws, horizontal_p)
1260 XlwMenuWidget mw;
1261 window_state* ws;
1262 window_state* previous_ws;
1263 Boolean horizontal_p;
1265 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1266 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1267 /* 1 if we are unable to avoid an overlap between
1268 this menu and the parent menu in the X dimension. */
1269 int horizontal_overlap = 0;
1271 if (ws->x < 0)
1272 ws->x = 0;
1273 else if (ws->x + ws->width > screen_width)
1275 if (!horizontal_p)
1276 /* The addition of shadow-thickness for a sub-menu's position is
1277 to reflect a similar adjustment when the menu is displayed to
1278 the right of the invoking menu-item; it makes the sub-menu
1279 look more `attached' to the menu-item. */
1280 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1281 else
1282 ws->x = screen_width - ws->width;
1283 if (ws->x < 0)
1285 ws->x = 0;
1286 horizontal_overlap = 1;
1289 /* If we overlap in X, try to avoid overlap in Y. */
1290 if (horizontal_overlap
1291 && ws->y < previous_ws->y + previous_ws->height
1292 && previous_ws->y < ws->y + ws->height)
1294 /* Put this menu right below or right above PREVIOUS_WS
1295 if there's room. */
1296 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1297 ws->y = previous_ws->y + previous_ws->height;
1298 else if (previous_ws->y - ws->height > 0)
1299 ws->y = previous_ws->y - ws->height;
1302 if (ws->y < 0)
1303 ws->y = 0;
1304 else if (ws->y + ws->height > screen_height)
1306 if (horizontal_p)
1307 ws->y = previous_ws->y - ws->height;
1308 else
1309 ws->y = screen_height - ws->height;
1310 if (ws->y < 0)
1311 ws->y = 0;
1315 /* Updates old_stack from new_stack and redisplays. */
1316 static void
1317 remap_menubar (mw)
1318 XlwMenuWidget mw;
1320 int i;
1321 int last_same;
1322 XPoint selection_position;
1323 int old_depth = mw->menu.old_depth;
1324 int new_depth = mw->menu.new_depth;
1325 widget_value** old_stack;
1326 widget_value** new_stack;
1327 window_state* windows;
1328 widget_value* old_selection;
1329 widget_value* new_selection;
1331 /* Check that enough windows and old_stack are ready. */
1332 make_windows_if_needed (mw, new_depth);
1333 make_old_stack_space (mw, new_depth);
1334 windows = mw->menu.windows;
1335 old_stack = mw->menu.old_stack;
1336 new_stack = mw->menu.new_stack;
1338 /* compute the last identical different entry */
1339 for (i = 1; i < old_depth && i < new_depth; i++)
1340 if (old_stack [i] != new_stack [i])
1341 break;
1342 last_same = i - 1;
1344 /* Memorize the previously selected item to be able to refresh it */
1345 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1346 if (old_selection && !old_selection->enabled)
1347 old_selection = NULL;
1348 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1349 if (new_selection && !new_selection->enabled)
1350 new_selection = NULL;
1352 /* Call callback when the hightlighted item changes. */
1353 if (old_selection || new_selection)
1354 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1355 (XtPointer) new_selection);
1357 /* updates old_state from new_state. It has to be done now because
1358 display_menu (called below) uses the old_stack to know what to display. */
1359 for (i = last_same + 1; i < new_depth; i++)
1360 old_stack [i] = new_stack [i];
1361 mw->menu.old_depth = new_depth;
1363 /* refresh the last selection */
1364 selection_position.x = 0;
1365 selection_position.y = 0;
1366 display_menu (mw, last_same, new_selection == old_selection,
1367 &selection_position, NULL, NULL, old_selection, new_selection);
1369 /* Now place the new menus. */
1370 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1372 window_state *previous_ws = &windows[i - 1];
1373 window_state *ws = &windows[i];
1375 ws->x = (previous_ws->x + selection_position.x
1376 + mw->menu.shadow_thickness);
1377 if (mw->menu.horizontal && i == 1)
1378 ws->x += mw->menu.margin;
1380 #if 0
1381 if (!mw->menu.horizontal || i > 1)
1382 ws->x += mw->menu.shadow_thickness;
1383 #endif
1385 ws->y = (previous_ws->y + selection_position.y
1386 + mw->menu.shadow_thickness);
1387 if (mw->menu.horizontal && i == 1)
1388 ws->y += mw->menu.margin;
1390 size_menu (mw, i);
1392 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1394 XClearWindow (XtDisplay (mw), ws->window);
1395 XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y,
1396 ws->width, ws->height);
1397 XMapRaised (XtDisplay (mw), ws->window);
1398 display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL);
1401 /* unmap the menus that popped down */
1402 for (i = new_depth - 1; i < old_depth; i++)
1403 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1404 XUnmapWindow (XtDisplay (mw), windows[i].window);
1407 static Boolean
1408 motion_event_is_in_menu (mw, ev, level, relative_pos)
1409 XlwMenuWidget mw;
1410 XMotionEvent* ev;
1411 int level;
1412 XPoint* relative_pos;
1414 window_state* ws = &mw->menu.windows [level];
1415 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1416 int x = ws->x + shadow;
1417 int y = ws->y + shadow;
1418 relative_pos->x = ev->x_root - x;
1419 relative_pos->y = ev->y_root - y;
1420 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1421 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1424 static Boolean
1425 map_event_to_widget_value (mw, ev, val, level)
1426 XlwMenuWidget mw;
1427 XMotionEvent* ev;
1428 widget_value** val;
1429 int* level;
1431 int i;
1432 XPoint relative_pos;
1433 window_state* ws;
1435 *val = NULL;
1437 /* Find the window */
1438 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1440 ws = &mw->menu.windows [i];
1441 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1443 display_menu (mw, i, True, NULL, &relative_pos, val, NULL, NULL);
1445 if (*val)
1447 *level = i + 1;
1448 return True;
1452 return False;
1455 \f/* Procedures */
1456 static void
1457 make_drawing_gcs (mw)
1458 XlwMenuWidget mw;
1460 XGCValues xgcv;
1461 float scale;
1463 xgcv.font = mw->menu.font->fid;
1464 xgcv.foreground = mw->menu.foreground;
1465 xgcv.background = mw->core.background_pixel;
1466 mw->menu.foreground_gc = XtGetGC ((Widget)mw,
1467 GCFont | GCForeground | GCBackground,
1468 &xgcv);
1470 xgcv.font = mw->menu.font->fid;
1471 xgcv.foreground = mw->menu.button_foreground;
1472 xgcv.background = mw->core.background_pixel;
1473 mw->menu.button_gc = XtGetGC ((Widget)mw,
1474 GCFont | GCForeground | GCBackground,
1475 &xgcv);
1477 xgcv.font = mw->menu.font->fid;
1478 xgcv.background = mw->core.background_pixel;
1480 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1482 /* Allocate color for disabled menu-items. */
1483 mw->menu.disabled_foreground = mw->menu.foreground;
1484 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1485 scale = 2.3;
1486 else
1487 scale = 0.55;
1489 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1490 mw->core.colormap,
1491 &mw->menu.disabled_foreground,
1492 scale,
1493 0x8000);
1495 if (mw->menu.foreground == mw->menu.disabled_foreground
1496 || mw->core.background_pixel == mw->menu.disabled_foreground)
1498 /* Too few colors, use stipple. */
1499 xgcv.foreground = mw->menu.foreground;
1500 xgcv.fill_style = FillStippled;
1501 xgcv.stipple = mw->menu.gray_pixmap;
1502 mw->menu.disabled_gc = XtGetGC ((Widget)mw,
1503 (GCFont | GCForeground | GCBackground
1504 | GCFillStyle | GCStipple), &xgcv);
1506 else
1508 /* Many colors available, use disabled pixel. */
1509 xgcv.foreground = mw->menu.disabled_foreground;
1510 mw->menu.disabled_gc = XtGetGC ((Widget)mw,
1511 (GCFont | GCForeground | GCBackground), &xgcv);
1514 xgcv.font = mw->menu.font->fid;
1515 xgcv.foreground = mw->menu.button_foreground;
1516 xgcv.background = mw->core.background_pixel;
1517 xgcv.fill_style = FillStippled;
1518 xgcv.stipple = mw->menu.gray_pixmap;
1519 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw,
1520 (GCFont | GCForeground | GCBackground
1521 | GCFillStyle | GCStipple), &xgcv);
1523 xgcv.font = mw->menu.font->fid;
1524 xgcv.foreground = mw->core.background_pixel;
1525 xgcv.background = mw->menu.foreground;
1526 mw->menu.background_gc = XtGetGC ((Widget)mw,
1527 GCFont | GCForeground | GCBackground,
1528 &xgcv);
1531 static void
1532 release_drawing_gcs (mw)
1533 XlwMenuWidget mw;
1535 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1536 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1537 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1538 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1539 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1540 /* let's get some segvs if we try to use these... */
1541 mw->menu.foreground_gc = (GC) -1;
1542 mw->menu.button_gc = (GC) -1;
1543 mw->menu.disabled_gc = (GC) -1;
1544 mw->menu.inactive_button_gc = (GC) -1;
1545 mw->menu.background_gc = (GC) -1;
1548 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1549 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1551 static void
1552 make_shadow_gcs (mw)
1553 XlwMenuWidget mw;
1555 XGCValues xgcv;
1556 unsigned long pm = 0;
1557 Display *dpy = XtDisplay ((Widget) mw);
1558 Screen *screen = XtScreen ((Widget) mw);
1559 Colormap cmap = mw->core.colormap;
1560 XColor topc, botc;
1561 int top_frobbed = 0, bottom_frobbed = 0;
1563 mw->menu.free_top_shadow_color_p = 0;
1564 mw->menu.free_bottom_shadow_color_p = 0;
1566 if (mw->menu.top_shadow_color == -1)
1567 mw->menu.top_shadow_color = mw->core.background_pixel;
1568 else
1569 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1571 if (mw->menu.bottom_shadow_color == -1)
1572 mw->menu.bottom_shadow_color = mw->menu.foreground;
1573 else
1574 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1576 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1577 mw->menu.top_shadow_color == mw->menu.foreground)
1579 topc.pixel = mw->core.background_pixel;
1580 #ifdef emacs
1581 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1582 &topc.pixel,
1583 1.2, 0x8000))
1584 #else
1585 XQueryColor (dpy, cmap, &topc);
1586 /* don't overflow/wrap! */
1587 topc.red = MINL (65535, topc.red * 1.2);
1588 topc.green = MINL (65535, topc.green * 1.2);
1589 topc.blue = MINL (65535, topc.blue * 1.2);
1590 if (XAllocColor (dpy, cmap, &topc))
1591 #endif
1593 mw->menu.top_shadow_color = topc.pixel;
1594 mw->menu.free_top_shadow_color_p = 1;
1595 top_frobbed = 1;
1598 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1599 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1601 botc.pixel = mw->core.background_pixel;
1602 #ifdef emacs
1603 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1604 &botc.pixel,
1605 0.6, 0x4000))
1606 #else
1607 XQueryColor (dpy, cmap, &botc);
1608 botc.red *= 0.6;
1609 botc.green *= 0.6;
1610 botc.blue *= 0.6;
1611 if (XAllocColor (dpy, cmap, &botc))
1612 #endif
1614 mw->menu.bottom_shadow_color = botc.pixel;
1615 mw->menu.free_bottom_shadow_color_p = 1;
1616 bottom_frobbed = 1;
1620 if (top_frobbed && bottom_frobbed)
1622 if (topc.pixel == botc.pixel)
1624 if (botc.pixel == mw->menu.foreground)
1626 if (mw->menu.free_top_shadow_color_p)
1628 x_free_dpy_colors (dpy, screen, cmap,
1629 &mw->menu.top_shadow_color, 1);
1630 mw->menu.free_top_shadow_color_p = 0;
1632 mw->menu.top_shadow_color = mw->core.background_pixel;
1634 else
1636 if (mw->menu.free_bottom_shadow_color_p)
1638 x_free_dpy_colors (dpy, screen, cmap,
1639 &mw->menu.bottom_shadow_color, 1);
1640 mw->menu.free_bottom_shadow_color_p = 0;
1642 mw->menu.bottom_shadow_color = mw->menu.foreground;
1647 if (!mw->menu.top_shadow_pixmap &&
1648 mw->menu.top_shadow_color == mw->core.background_pixel)
1650 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1651 if (mw->menu.free_top_shadow_color_p)
1653 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1654 mw->menu.free_top_shadow_color_p = 0;
1656 mw->menu.top_shadow_color = mw->menu.foreground;
1658 if (!mw->menu.bottom_shadow_pixmap &&
1659 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1661 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1662 if (mw->menu.free_bottom_shadow_color_p)
1664 x_free_dpy_colors (dpy, screen, cmap,
1665 &mw->menu.bottom_shadow_color, 1);
1666 mw->menu.free_bottom_shadow_color_p = 0;
1668 mw->menu.bottom_shadow_color = mw->menu.foreground;
1671 xgcv.fill_style = FillStippled;
1672 xgcv.foreground = mw->menu.top_shadow_color;
1673 xgcv.stipple = mw->menu.top_shadow_pixmap;
1674 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1675 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1677 xgcv.foreground = mw->menu.bottom_shadow_color;
1678 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1679 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1680 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1684 static void
1685 release_shadow_gcs (mw)
1686 XlwMenuWidget mw;
1688 Display *dpy = XtDisplay ((Widget) mw);
1689 Screen *screen = XtScreen ((Widget) mw);
1690 Colormap cmap = mw->core.colormap;
1691 Pixel px[2];
1692 int i = 0;
1694 if (mw->menu.free_top_shadow_color_p)
1695 px[i++] = mw->menu.top_shadow_color;
1696 if (mw->menu.free_bottom_shadow_color_p)
1697 px[i++] = mw->menu.bottom_shadow_color;
1698 if (i > 0)
1699 x_free_dpy_colors (dpy, screen, cmap, px, i);
1701 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1702 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1705 static void
1706 XlwMenuInitialize (request, mw, args, num_args)
1707 Widget request;
1708 XlwMenuWidget mw;
1709 ArgList args;
1710 Cardinal *num_args;
1712 /* Get the GCs and the widget size */
1714 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1715 Display* display = XtDisplay (mw);
1717 #if 0
1718 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1720 /* _XtCreate is freeing the object that was passed to us,
1721 so make a copy that we will actually keep. */
1722 lwlib_bcopy (mw->menu.contents, tem, sizeof (widget_value));
1723 mw->menu.contents = tem;
1724 #endif
1726 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1727 mw->menu.cursor = mw->menu.cursor_shape;
1729 mw->menu.gray_pixmap
1730 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1731 gray_bitmap_width, gray_bitmap_height,
1732 (unsigned long)1, (unsigned long)0, 1);
1734 /* I don't understand why this ends up 0 sometimes,
1735 but it does. This kludge works around it.
1736 Can anyone find a real fix? -- rms. */
1737 if (mw->menu.font == 0)
1738 mw->menu.font = xlwmenu_default_font;
1740 make_drawing_gcs (mw);
1741 make_shadow_gcs (mw);
1743 mw->menu.popped_up = False;
1745 mw->menu.old_depth = 1;
1746 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1747 mw->menu.old_stack_length = 1;
1748 mw->menu.old_stack [0] = mw->menu.contents;
1750 mw->menu.new_depth = 0;
1751 mw->menu.new_stack = 0;
1752 mw->menu.new_stack_length = 0;
1753 push_new_stack (mw, mw->menu.contents);
1755 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1756 mw->menu.windows_length = 1;
1757 mw->menu.windows [0].x = 0;
1758 mw->menu.windows [0].y = 0;
1759 mw->menu.windows [0].width = 0;
1760 mw->menu.windows [0].height = 0;
1761 size_menu (mw, 0);
1763 mw->core.width = mw->menu.windows [0].width;
1764 mw->core.height = mw->menu.windows [0].height;
1767 static void
1768 XlwMenuClassInitialize ()
1772 static void
1773 XlwMenuRealize (w, valueMask, attributes)
1774 Widget w;
1775 Mask *valueMask;
1776 XSetWindowAttributes *attributes;
1778 XlwMenuWidget mw = (XlwMenuWidget)w;
1779 XSetWindowAttributes xswa;
1780 int mask;
1782 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1783 (w, valueMask, attributes);
1785 xswa.save_under = True;
1786 xswa.cursor = mw->menu.cursor_shape;
1787 mask = CWSaveUnder | CWCursor;
1788 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1790 mw->menu.windows [0].window = XtWindow (w);
1791 mw->menu.windows [0].x = w->core.x;
1792 mw->menu.windows [0].y = w->core.y;
1793 mw->menu.windows [0].width = w->core.width;
1794 mw->menu.windows [0].height = w->core.height;
1797 /* Only the toplevel menubar/popup is a widget so it's the only one that
1798 receives expose events through Xt. So we repaint all the other panes
1799 when receiving an Expose event. */
1800 static void
1801 XlwMenuRedisplay (w, ev, region)
1802 Widget w;
1803 XEvent* ev;
1804 Region region;
1806 XlwMenuWidget mw = (XlwMenuWidget)w;
1807 int i;
1809 /* If we have a depth beyond 1, it's because a submenu was displayed.
1810 If the submenu has been destroyed, set the depth back to 1. */
1811 if (submenu_destroyed)
1813 mw->menu.old_depth = 1;
1814 submenu_destroyed = 0;
1817 for (i = 0; i < mw->menu.old_depth; i++)
1818 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1822 /* Part of a hack to make the menu redisplay when a tooltip frame
1823 over a menu item is unmapped. */
1825 void
1826 xlwmenu_redisplay (w)
1827 Widget w;
1829 XlwMenuRedisplay (w, NULL, None);
1832 static void
1833 XlwMenuDestroy (w)
1834 Widget w;
1836 int i;
1837 XlwMenuWidget mw = (XlwMenuWidget) w;
1839 if (pointer_grabbed)
1840 ungrab_all ((Widget)w, CurrentTime);
1841 pointer_grabbed = 0;
1843 submenu_destroyed = 1;
1845 release_drawing_gcs (mw);
1846 release_shadow_gcs (mw);
1848 /* this doesn't come from the resource db but is created explicitly
1849 so we must free it ourselves. */
1850 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1851 mw->menu.gray_pixmap = (Pixmap) -1;
1853 #if 0
1854 /* Do free mw->menu.contents because nowadays we copy it
1855 during initialization. */
1856 XtFree (mw->menu.contents);
1857 #endif
1859 /* Don't free mw->menu.contents because that comes from our creator.
1860 The `*_stack' elements are just pointers into `contents' so leave
1861 that alone too. But free the stacks themselves. */
1862 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1863 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1865 /* Remember, you can't free anything that came from the resource
1866 database. This includes:
1867 mw->menu.cursor
1868 mw->menu.top_shadow_pixmap
1869 mw->menu.bottom_shadow_pixmap
1870 mw->menu.font
1871 Also the color cells of top_shadow_color, bottom_shadow_color,
1872 foreground, and button_foreground will never be freed until this
1873 client exits. Nice, eh?
1876 /* start from 1 because the one in slot 0 is w->core.window */
1877 for (i = 1; i < mw->menu.windows_length; i++)
1878 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1879 if (mw->menu.windows)
1880 XtFree ((char *) mw->menu.windows);
1883 static Boolean
1884 XlwMenuSetValues (current, request, new)
1885 Widget current;
1886 Widget request;
1887 Widget new;
1889 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1890 XlwMenuWidget newmw = (XlwMenuWidget)new;
1891 Boolean redisplay = False;
1892 int i;
1894 if (newmw->menu.contents
1895 && newmw->menu.contents->contents
1896 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1897 redisplay = True;
1898 /* Do redisplay if the contents are entirely eliminated. */
1899 if (newmw->menu.contents
1900 && newmw->menu.contents->contents == 0
1901 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1902 redisplay = True;
1904 if (newmw->core.background_pixel != oldmw->core.background_pixel
1905 || newmw->menu.foreground != oldmw->menu.foreground
1906 || newmw->menu.font != oldmw->menu.font)
1908 release_drawing_gcs (newmw);
1909 make_drawing_gcs (newmw);
1911 release_shadow_gcs (newmw);
1912 /* Cause the shadow colors to be recalculated. */
1913 newmw->menu.top_shadow_color = -1;
1914 newmw->menu.bottom_shadow_color = -1;
1915 make_shadow_gcs (newmw);
1917 redisplay = True;
1919 if (XtIsRealized (current))
1920 /* If the menu is currently displayed, change the display. */
1921 for (i = 0; i < oldmw->menu.windows_length; i++)
1923 XSetWindowBackground (XtDisplay (oldmw),
1924 oldmw->menu.windows [i].window,
1925 newmw->core.background_pixel);
1926 /* clear windows and generate expose events */
1927 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
1928 0, 0, 0, 0, True);
1932 return redisplay;
1935 static void
1936 XlwMenuResize (w)
1937 Widget w;
1939 XlwMenuWidget mw = (XlwMenuWidget)w;
1941 if (mw->menu.popped_up)
1943 /* Don't allow the popup menu to resize itself. */
1944 mw->core.width = mw->menu.windows [0].width;
1945 mw->core.height = mw->menu.windows [0].height;
1946 mw->core.parent->core.width = mw->core.width ;
1947 mw->core.parent->core.height = mw->core.height ;
1949 else
1951 mw->menu.windows [0].width = mw->core.width;
1952 mw->menu.windows [0].height = mw->core.height;
1956 \f/* Action procedures */
1957 static void
1958 handle_single_motion_event (mw, ev)
1959 XlwMenuWidget mw;
1960 XMotionEvent* ev;
1962 widget_value* val;
1963 int level;
1965 if (!map_event_to_widget_value (mw, ev, &val, &level))
1966 pop_new_stack_if_no_contents (mw);
1967 else
1968 set_new_state (mw, val, level);
1969 remap_menubar (mw);
1971 /* Sync with the display. Makes it feel better on X terms. */
1972 XSync (XtDisplay (mw), False);
1975 static void
1976 handle_motion_event (mw, ev)
1977 XlwMenuWidget mw;
1978 XMotionEvent* ev;
1980 int x = ev->x_root;
1981 int y = ev->y_root;
1982 int state = ev->state;
1984 handle_single_motion_event (mw, ev);
1986 /* allow motion events to be generated again */
1987 if (ev->is_hint
1988 && XQueryPointer (XtDisplay (mw), ev->window,
1989 &ev->root, &ev->subwindow,
1990 &ev->x_root, &ev->y_root,
1991 &ev->x, &ev->y,
1992 &ev->state)
1993 && ev->state == state
1994 && (ev->x_root != x || ev->y_root != y))
1995 handle_single_motion_event (mw, ev);
1998 static void
1999 Start (w, ev, params, num_params)
2000 Widget w;
2001 XEvent *ev;
2002 String *params;
2003 Cardinal *num_params;
2005 XlwMenuWidget mw = (XlwMenuWidget)w;
2007 if (!mw->menu.popped_up)
2009 menu_post_event = *ev;
2010 /* If event is set to CurrentTime, get the last known time stamp.
2011 This is for calculating if (popup) menus should stay up after
2012 a fast click. */
2013 if (menu_post_event.xbutton.time == CurrentTime)
2014 menu_post_event.xbutton.time
2015 = XtLastTimestampProcessed (XtDisplay (w));
2017 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2019 else
2021 /* If we push a button while the menu is posted semipermanently,
2022 releasing the button should always pop the menu down. */
2023 next_release_must_exit = 1;
2025 /* notes the absolute position of the menubar window */
2026 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2027 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2029 /* handles the down like a move, slots are compatible */
2030 handle_motion_event (mw, &ev->xmotion);
2034 static void
2035 Drag (w, ev, params, num_params)
2036 Widget w;
2037 XEvent *ev;
2038 String *params;
2039 Cardinal *num_params;
2041 XlwMenuWidget mw = (XlwMenuWidget)w;
2042 if (mw->menu.popped_up)
2043 handle_motion_event (mw, &ev->xmotion);
2046 /* Do nothing.
2047 This is how we handle presses and releases of modifier keys. */
2048 static void
2049 Nothing (w, ev, params, num_params)
2050 Widget w;
2051 XEvent *ev;
2052 String *params;
2053 Cardinal *num_params;
2057 static widget_value *
2058 find_first_selectable (mw, item, skip_titles)
2059 XlwMenuWidget mw;
2060 widget_value *item;
2061 int skip_titles;
2063 widget_value *current = item;
2064 enum menu_separator separator;
2066 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2067 || (skip_titles && !current->call_data && !current->contents))
2068 if (current->next)
2069 current=current->next;
2070 else
2071 return NULL;
2073 return current;
2076 static widget_value *
2077 find_next_selectable (mw, item, skip_titles)
2078 XlwMenuWidget mw;
2079 widget_value *item;
2081 widget_value *current = item;
2082 enum menu_separator separator;
2084 while (current->next && (current=current->next) &&
2085 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2086 || (skip_titles && !current->call_data && !current->contents)))
2089 if (current == item)
2091 if (mw->menu.old_depth < 2)
2092 return current;
2093 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2095 while (lw_separator_p (current->name, &separator, 0)
2096 || !current->enabled
2097 || (skip_titles && !current->call_data
2098 && !current->contents))
2100 if (current->next)
2101 current=current->next;
2103 if (current == item)
2104 break;
2109 return current;
2112 static widget_value *
2113 find_prev_selectable (mw, item, skip_titles)
2114 XlwMenuWidget mw;
2115 widget_value *item;
2117 widget_value *current = item;
2118 widget_value *prev = item;
2120 while ((current=find_next_selectable (mw, current, skip_titles))
2121 != item)
2123 if (prev == current)
2124 break;
2125 prev=current;
2128 return prev;
2131 static void
2132 Down (w, ev, params, num_params)
2133 Widget w;
2134 XEvent *ev;
2135 String *params;
2136 Cardinal *num_params;
2138 XlwMenuWidget mw = (XlwMenuWidget) w;
2139 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2140 int popup_menu_p = mw->menu.top_depth == 1;
2142 /* Inside top-level menu-bar? */
2143 if (mw->menu.old_depth == mw->menu.top_depth)
2144 /* When <down> in the menu-bar is pressed, display the corresponding
2145 sub-menu and select the first selectable menu item there.
2146 If this is a popup menu, skip title item of the popup. */
2147 set_new_state (mw,
2148 find_first_selectable (mw,
2149 selected_item->contents,
2150 popup_menu_p),
2151 mw->menu.old_depth);
2152 else
2153 /* Highlight next possible (enabled and not separator) menu item. */
2154 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2155 mw->menu.old_depth - 1);
2157 remap_menubar (mw);
2160 static void
2161 Up (w, ev, params, num_params)
2162 Widget w;
2163 XEvent *ev;
2164 String *params;
2165 Cardinal *num_params;
2167 XlwMenuWidget mw = (XlwMenuWidget) w;
2168 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2169 int popup_menu_p = mw->menu.top_depth == 1;
2171 /* Inside top-level menu-bar? */
2172 if (mw->menu.old_depth == mw->menu.top_depth)
2174 /* FIXME: this is tricky. <up> in the menu-bar should select the
2175 last selectable item in the list. So we select the first
2176 selectable one and find the previous selectable item. Is there
2177 a better way? */
2178 /* If this is a popup menu, skip title item of the popup. */
2179 set_new_state (mw,
2180 find_first_selectable (mw,
2181 selected_item->contents,
2182 popup_menu_p),
2183 mw->menu.old_depth);
2184 remap_menubar (mw);
2185 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2186 set_new_state (mw,
2187 find_prev_selectable (mw,
2188 selected_item,
2189 popup_menu_p),
2190 mw->menu.old_depth - 1);
2192 else
2193 /* Highlight previous (enabled and not separator) menu item. */
2194 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2195 mw->menu.old_depth - 1);
2197 remap_menubar (mw);
2200 void
2201 Left (w, ev, params, num_params)
2202 Widget w;
2203 XEvent *ev;
2204 String *params;
2205 Cardinal *num_params;
2207 XlwMenuWidget mw = (XlwMenuWidget) w;
2208 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2210 /* Inside top-level menu-bar? */
2211 if (mw->menu.old_depth == mw->menu.top_depth)
2212 /* When <left> in the menu-bar is pressed, display the previous item on
2213 the menu-bar. If the current item is the first one, highlight the
2214 last item in the menubar (probably Help). */
2215 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2216 mw->menu.old_depth - 1);
2217 else if (mw->menu.old_depth == 1
2218 && selected_item->contents) /* Is this menu item expandable? */
2220 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2221 remap_menubar (mw);
2222 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2223 if (!selected_item->enabled && find_first_selectable (mw,
2224 selected_item,
2226 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2227 mw->menu.old_depth - 1);
2230 else
2232 pop_new_stack_if_no_contents (mw);
2233 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2234 mw->menu.old_depth - 2);
2237 remap_menubar (mw);
2240 void
2241 Right (w, ev, params, num_params)
2242 Widget w;
2243 XEvent *ev;
2244 String *params;
2245 Cardinal *num_params;
2247 XlwMenuWidget mw = (XlwMenuWidget) w;
2248 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2250 /* Inside top-level menu-bar? */
2251 if (mw->menu.old_depth == mw->menu.top_depth)
2252 /* When <right> in the menu-bar is pressed, display the next item on
2253 the menu-bar. If the current item is the last one, highlight the
2254 first item (probably File). */
2255 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2256 mw->menu.old_depth - 1);
2257 else if (selected_item->contents) /* Is this menu item expandable? */
2259 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2260 remap_menubar (mw);
2261 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2262 if (!selected_item->enabled && find_first_selectable (mw,
2263 selected_item,
2265 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2266 mw->menu.old_depth - 1);
2268 else
2270 pop_new_stack_if_no_contents (mw);
2271 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2272 mw->menu.old_depth - 2);
2275 remap_menubar (mw);
2278 /* Handle key press and release events while menu is popped up.
2279 Our action is to get rid of the menu. */
2280 static void
2281 Key (w, ev, params, num_params)
2282 Widget w;
2283 XEvent *ev;
2284 String *params;
2285 Cardinal *num_params;
2287 XlwMenuWidget mw = (XlwMenuWidget)w;
2289 /* Pop down everything. */
2290 mw->menu.new_depth = 1;
2291 remap_menubar (mw);
2293 if (mw->menu.popped_up)
2295 mw->menu.popped_up = False;
2296 ungrab_all ((Widget)mw, ev->xmotion.time);
2297 if (XtIsShell (XtParent ((Widget) mw)))
2298 XtPopdown (XtParent ((Widget) mw));
2299 else
2301 XtRemoveGrab ((Widget) mw);
2302 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2306 /* callback */
2307 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2310 static void
2311 Select (w, ev, params, num_params)
2312 Widget w;
2313 XEvent *ev;
2314 String *params;
2315 Cardinal *num_params;
2317 XlwMenuWidget mw = (XlwMenuWidget)w;
2318 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2320 /* If user releases the button quickly, without selecting anything,
2321 after the initial down-click that brought the menu up,
2322 do nothing. */
2323 if ((selected_item == 0
2324 || ((widget_value *) selected_item)->call_data == 0)
2325 && !next_release_must_exit
2326 && (ev->xbutton.time - menu_post_event.xbutton.time
2327 < XtGetMultiClickTime (XtDisplay (w))))
2328 return;
2330 /* pop down everything. */
2331 mw->menu.new_depth = 1;
2332 remap_menubar (mw);
2334 if (mw->menu.popped_up)
2336 mw->menu.popped_up = False;
2337 ungrab_all ((Widget)mw, ev->xmotion.time);
2338 if (XtIsShell (XtParent ((Widget) mw)))
2339 XtPopdown (XtParent ((Widget) mw));
2340 else
2342 XtRemoveGrab ((Widget) mw);
2343 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2347 /* callback */
2348 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2352 \f/* Special code to pop-up a menu */
2353 static void
2354 pop_up_menu (mw, event)
2355 XlwMenuWidget mw;
2356 XButtonPressedEvent* event;
2358 int x = event->x_root;
2359 int y = event->y_root;
2360 int w;
2361 int h;
2362 int borderwidth = mw->menu.shadow_thickness;
2363 Screen* screen = XtScreen (mw);
2364 Display *display = XtDisplay (mw);
2365 int count;
2367 next_release_must_exit = 0;
2369 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2371 if (XtIsShell (XtParent ((Widget)mw)))
2372 size_menu (mw, 0);
2374 w = mw->menu.windows [0].width;
2375 h = mw->menu.windows [0].height;
2377 x -= borderwidth;
2378 y -= borderwidth;
2379 if (x < borderwidth)
2380 x = borderwidth;
2381 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2382 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2383 if (y < borderwidth)
2384 y = borderwidth;
2385 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2386 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2388 mw->menu.popped_up = True;
2389 if (XtIsShell (XtParent ((Widget)mw)))
2391 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2392 XtParent ((Widget)mw)->core.border_width);
2393 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2394 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2395 mw->menu.windows [0].x = x + borderwidth;
2396 mw->menu.windows [0].y = y + borderwidth;
2397 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2399 else
2401 XEvent *ev = (XEvent *) event;
2403 XtAddGrab ((Widget) mw, True, True);
2405 /* notes the absolute position of the menubar window */
2406 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2407 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2408 mw->menu.top_depth = 2;
2411 #ifdef emacs
2412 count = x_catch_errors (display);
2413 #endif
2414 if (XtGrabPointer ((Widget)mw, False,
2415 (PointerMotionMask
2416 | PointerMotionHintMask
2417 | ButtonReleaseMask
2418 | ButtonPressMask),
2419 GrabModeAsync, GrabModeAsync, None,
2420 mw->menu.cursor_shape,
2421 event->time) == Success)
2423 if (! GRAB_KEYBOARD
2424 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2425 GrabModeAsync, event->time) == Success)
2427 XtSetKeyboardFocus((Widget)mw, None);
2428 pointer_grabbed = 1;
2430 else
2431 XtUngrabPointer ((Widget)mw, event->time);
2434 #ifdef emacs
2435 if (x_had_errors_p (display))
2437 pointer_grabbed = 0;
2438 XtUngrabPointer ((Widget)mw, event->time);
2440 x_uncatch_errors (display, count);
2441 #endif
2443 handle_motion_event (mw, (XMotionEvent*)event);
2446 /* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2447 (do not change this comment) */