(find_next_selectable): New function.
[emacs.git] / lwlib / xlwmenu.c
blobc63422bacc60b795106ad7d7f0503b1164523ac4
1 /* Implements a lightweight menubar widget.
2 Copyright (C) 1992 Lucid, Inc.
3 Copyright (C) 2002 Free Software Foundation, Inc.
5 This file is part of the Lucid Widget Library.
7 The Lucid Widget Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
12 The Lucid Widget Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Emacs; see the file COPYING. If not, write to the
19 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA. */
22 /* Created by devin@lucid.com */
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include "../src/lisp.h"
30 #include <stdio.h>
32 #include <sys/types.h>
33 #if (defined __sun) && !(defined SUNOS41)
34 #define SUNOS41
35 #include <X11/Xos.h>
36 #undef SUNOS41
37 #else
38 #include <X11/Xos.h>
39 #endif
40 #include <X11/IntrinsicP.h>
41 #include <X11/ObjectP.h>
42 #include <X11/StringDefs.h>
43 #include <X11/cursorfont.h>
44 #include "xlwmenuP.h"
46 #ifdef emacs
48 /* Defined in xfns.c. When config.h defines `static' as empty, we get
49 redefinition errors when gray_bitmap is included more than once, so
50 we're referring to the one include in xfns.c here. */
52 extern int gray_bitmap_width;
53 extern int gray_bitmap_height;
54 extern char *gray_bitmap_bits;
56 /* Defined in xterm.c. */
57 extern int x_alloc_nearest_color_for_widget __P ((Widget, Colormap, XColor*));
58 extern int x_alloc_lighter_color_for_widget __P ((Widget, Display*, Colormap,
59 unsigned long *,
60 double, int));
61 extern int x_catch_errors __P ((Display*));
62 extern int x_uncatch_errors __P ((Display*, int));
63 extern int x_had_errors_p __P ((Display*));
64 extern int x_clear_errors __P ((Display*));
65 extern unsigned long x_copy_dpy_color __P ((Display *, Colormap,
66 unsigned long));
68 /* Defined in xfaces.c. */
69 extern void x_free_dpy_colors __P ((Display *, Screen *, Colormap,
70 unsigned long *pixels, int npixels));
71 #else /* not emacs */
73 #include <X11/bitmaps/gray>
74 #define gray_bitmap_width gray_width
75 #define gray_bitmap_height gray_height
76 #define gray_bitmap_bits gray_bits
78 #endif /* not emacs */
80 static int pointer_grabbed;
81 static XEvent menu_post_event;
83 XFontStruct *xlwmenu_default_font;
85 static char
86 xlwMenuTranslations [] =
87 "<BtnDown>: start()\n\
88 <Motion>: drag()\n\
89 <BtnUp>: select()\n\
90 <Key>Shift_L: nothing()\n\
91 <Key>Shift_R: nothing()\n\
92 <Key>Meta_L: nothing()\n\
93 <Key>Meta_R: nothing()\n\
94 <Key>Control_L: nothing()\n\
95 <Key>Control_R: nothing()\n\
96 <Key>Hyper_L: nothing()\n\
97 <Key>Hyper_R: nothing()\n\
98 <Key>Super_L: nothing()\n\
99 <Key>Super_R: nothing()\n\
100 <Key>Alt_L: nothing()\n\
101 <Key>Alt_R: nothing()\n\
102 <Key>Caps_Lock: nothing()\n\
103 <Key>Shift_Lock: nothing()\n\
104 <KeyUp>Shift_L: nothing()\n\
105 <KeyUp>Shift_R: nothing()\n\
106 <KeyUp>Meta_L: nothing()\n\
107 <KeyUp>Meta_R: nothing()\n\
108 <KeyUp>Control_L: nothing()\n\
109 <KeyUp>Control_R: nothing()\n\
110 <KeyUp>Hyper_L: nothing()\n\
111 <KeyUp>Hyper_R: nothing()\n\
112 <KeyUp>Super_L: nothing()\n\
113 <KeyUp>Super_R: nothing()\n\
114 <KeyUp>Alt_L: nothing()\n\
115 <KeyUp>Alt_R: nothing()\n\
116 <KeyUp>Caps_Lock: nothing()\n\
117 <KeyUp>Shift_Lock:nothing()\n\
118 <Key>Return: select()\n\
119 <Key>Down: down()\n\
120 <Key>Up: up()\n\
121 <Key>Left: left()\n\
122 <Key>Right: right()\n\
123 <Key>: key()\n\
124 <KeyUp>: key()\n\
127 /* FIXME: Space should toggle toggleable menu item but not remove the menu
128 so you can toggle the next one without entering the menu again. */
130 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
132 /* FIXME: Should F10 enter to menu? Which one? File? */
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 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
143 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
144 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
145 offset(menu.margin), XtRImmediate, (XtPointer) 4},
146 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
147 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
148 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
149 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)1},
150 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
151 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
153 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
154 sizeof (Dimension), offset (menu.shadow_thickness),
155 XtRImmediate, (XtPointer) 2},
156 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
157 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
158 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
159 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
160 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
161 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
162 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
163 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
165 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
166 offset(menu.open), XtRCallback, (XtPointer)NULL},
167 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
168 offset(menu.select), XtRCallback, (XtPointer)NULL},
169 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
170 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
171 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
172 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
173 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
174 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
175 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
176 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
178 #undef offset
180 static Boolean XlwMenuSetValues();
181 static void XlwMenuRealize();
182 static void XlwMenuRedisplay();
183 static void XlwMenuResize();
184 static void XlwMenuInitialize();
185 static void XlwMenuRedisplay();
186 static void XlwMenuDestroy();
187 static void XlwMenuClassInitialize();
188 static void Start();
189 static void Drag();
190 static void Down();
191 static void Up();
192 static void Left();
193 static void Right();
194 static void Select();
195 static void Key();
196 static void Nothing();
197 static int separator_height ();
199 static XtActionsRec
200 xlwMenuActionsList [] =
202 {"start", Start},
203 {"drag", Drag},
204 {"down", Down},
205 {"up", Up},
206 {"left", Left},
207 {"right", Right},
208 {"select", Select},
209 {"key", Key},
210 {"nothing", Nothing},
213 #define SuperClass ((CoreWidgetClass)&coreClassRec)
215 XlwMenuClassRec xlwMenuClassRec =
217 { /* CoreClass fields initialization */
218 (WidgetClass) SuperClass, /* superclass */
219 "XlwMenu", /* class_name */
220 sizeof(XlwMenuRec), /* size */
221 XlwMenuClassInitialize, /* class_initialize */
222 NULL, /* class_part_initialize */
223 FALSE, /* class_inited */
224 XlwMenuInitialize, /* initialize */
225 NULL, /* initialize_hook */
226 XlwMenuRealize, /* realize */
227 xlwMenuActionsList, /* actions */
228 XtNumber(xlwMenuActionsList), /* num_actions */
229 xlwMenuResources, /* resources */
230 XtNumber(xlwMenuResources), /* resource_count */
231 NULLQUARK, /* xrm_class */
232 TRUE, /* compress_motion */
233 TRUE, /* compress_exposure */
234 TRUE, /* compress_enterleave */
235 FALSE, /* visible_interest */
236 XlwMenuDestroy, /* destroy */
237 XlwMenuResize, /* resize */
238 XlwMenuRedisplay, /* expose */
239 XlwMenuSetValues, /* set_values */
240 NULL, /* set_values_hook */
241 XtInheritSetValuesAlmost, /* set_values_almost */
242 NULL, /* get_values_hook */
243 NULL, /* accept_focus */
244 XtVersion, /* version */
245 NULL, /* callback_private */
246 xlwMenuTranslations, /* tm_table */
247 XtInheritQueryGeometry, /* query_geometry */
248 XtInheritDisplayAccelerator, /* display_accelerator */
249 NULL /* extension */
250 }, /* XlwMenuClass fields initialization */
252 0 /* dummy */
256 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
258 int submenu_destroyed;
260 static int next_release_must_exit;
262 \f/* Utilities */
265 /* Like abort, but remove grabs from widget W before. */
267 static void
268 abort_gracefully (w)
269 Widget w;
271 if (XtIsShell (XtParent (w)))
272 XtRemoveGrab (w);
273 XtUngrabPointer (w, CurrentTime);
274 abort ();
277 static void
278 push_new_stack (mw, val)
279 XlwMenuWidget mw;
280 widget_value* val;
282 if (!mw->menu.new_stack)
284 mw->menu.new_stack_length = 10;
285 mw->menu.new_stack =
286 (widget_value**)XtCalloc (mw->menu.new_stack_length,
287 sizeof (widget_value*));
289 else if (mw->menu.new_depth == mw->menu.new_stack_length)
291 mw->menu.new_stack_length *= 2;
292 mw->menu.new_stack =
293 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
294 mw->menu.new_stack_length * sizeof (widget_value*));
296 mw->menu.new_stack [mw->menu.new_depth++] = val;
299 static void
300 pop_new_stack_if_no_contents (mw)
301 XlwMenuWidget mw;
303 if (mw->menu.new_depth)
305 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
306 mw->menu.new_depth -= 1;
310 static void
311 make_old_stack_space (mw, n)
312 XlwMenuWidget mw;
313 int n;
315 if (!mw->menu.old_stack)
317 mw->menu.old_stack_length = 10;
318 mw->menu.old_stack =
319 (widget_value**)XtCalloc (mw->menu.old_stack_length,
320 sizeof (widget_value*));
322 else if (mw->menu.old_stack_length < n)
324 mw->menu.old_stack_length *= 2;
325 mw->menu.old_stack =
326 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
327 mw->menu.old_stack_length * sizeof (widget_value*));
331 \f/* Size code */
333 string_width (mw, s)
334 XlwMenuWidget mw;
335 char *s;
337 XCharStruct xcs;
338 int drop;
340 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
341 return xcs.width;
344 static int
345 arrow_width (mw)
346 XlwMenuWidget mw;
348 return (mw->menu.font->ascent * 3/4) | 1;
351 /* Return the width of toggle buttons of widget MW. */
353 static int
354 toggle_button_width (mw)
355 XlwMenuWidget mw;
357 return ((mw->menu.font->ascent + mw->menu.font->descent) * 2 / 3) | 1;
361 /* Return the width of radio buttons of widget MW. */
363 static int
364 radio_button_width (mw)
365 XlwMenuWidget mw;
367 return toggle_button_width (mw) * 1.41;
371 static XtResource
372 nameResource[] =
374 {"labelString", "LabelString", XtRString, sizeof(String),
375 0, XtRImmediate, 0},
378 static char*
379 resource_widget_value (mw, val)
380 XlwMenuWidget mw;
381 widget_value *val;
383 if (!val->toolkit_data)
385 char* resourced_name = NULL;
386 char* complete_name;
387 XtGetSubresources ((Widget) mw,
388 (XtPointer) &resourced_name,
389 val->name, val->name,
390 nameResource, 1, NULL, 0);
391 if (!resourced_name)
392 resourced_name = val->name;
393 if (!val->value)
395 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
396 strcpy (complete_name, resourced_name);
398 else
400 int complete_length =
401 strlen (resourced_name) + strlen (val->value) + 2;
402 complete_name = XtMalloc (complete_length);
403 *complete_name = 0;
404 strcat (complete_name, resourced_name);
405 strcat (complete_name, " ");
406 strcat (complete_name, val->value);
409 val->toolkit_data = complete_name;
410 val->free_toolkit_data = True;
412 return (char*)val->toolkit_data;
415 /* Returns the sizes of an item */
416 static void
417 size_menu_item (mw, val, horizontal_p, label_width, rest_width, button_width,
418 height)
419 XlwMenuWidget mw;
420 widget_value* val;
421 int horizontal_p;
422 int* label_width;
423 int* rest_width;
424 int* button_width;
425 int* height;
427 enum menu_separator separator;
429 if (lw_separator_p (val->name, &separator, 0))
431 *height = separator_height (separator);
432 *label_width = 1;
433 *rest_width = 0;
434 *button_width = 0;
436 else
438 *height =
439 mw->menu.font->ascent + mw->menu.font->descent
440 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
442 *label_width =
443 string_width (mw, resource_widget_value (mw, val))
444 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
446 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
447 if (!horizontal_p)
449 if (val->contents)
450 /* Add width of the arrow displayed for submenus. */
451 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
452 else if (val->key)
453 /* Add width of key equivalent string. */
454 *rest_width += (string_width (mw, val->key)
455 + mw->menu.arrow_spacing);
457 if (val->button_type == BUTTON_TYPE_TOGGLE)
458 *button_width = (toggle_button_width (mw)
459 + mw->menu.horizontal_spacing);
460 else if (val->button_type == BUTTON_TYPE_RADIO)
461 *button_width = (radio_button_width (mw)
462 + mw->menu.horizontal_spacing);
467 static void
468 size_menu (mw, level)
469 XlwMenuWidget mw;
470 int level;
472 unsigned int label_width = 0;
473 int rest_width = 0;
474 int button_width = 0;
475 int max_rest_width = 0;
476 int max_button_width = 0;
477 unsigned int height = 0;
478 int horizontal_p = mw->menu.horizontal && (level == 0);
479 widget_value* val;
480 window_state* ws;
482 if (level >= mw->menu.old_depth)
483 abort_gracefully ((Widget) mw);
485 ws = &mw->menu.windows [level];
486 ws->width = 0;
487 ws->height = 0;
488 ws->label_width = 0;
489 ws->button_width = 0;
491 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
493 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
494 &button_width, &height);
495 if (horizontal_p)
497 ws->width += label_width + rest_width;
498 if (height > ws->height)
499 ws->height = height;
501 else
503 if (label_width > ws->label_width)
504 ws->label_width = label_width;
505 if (rest_width > max_rest_width)
506 max_rest_width = rest_width;
507 if (button_width > max_button_width)
508 max_button_width = button_width;
509 ws->height += height;
513 if (horizontal_p)
514 ws->label_width = ws->button_width = 0;
515 else
517 ws->width = ws->label_width + max_rest_width + max_button_width;
518 ws->button_width = max_button_width;
521 ws->width += 2 * mw->menu.shadow_thickness;
522 ws->height += 2 * mw->menu.shadow_thickness;
524 if (horizontal_p)
526 ws->width += 2 * mw->menu.margin;
527 ws->height += 2 * mw->menu.margin;
532 \f/* Display code */
534 static void
535 draw_arrow (mw, window, gc, x, y, width, down_p)
536 XlwMenuWidget mw;
537 Window window;
538 GC gc;
539 int x;
540 int y;
541 int width;
542 int down_p;
544 Display *dpy = XtDisplay (mw);
545 GC top_gc = mw->menu.shadow_top_gc;
546 GC bottom_gc = mw->menu.shadow_bottom_gc;
547 int thickness = mw->menu.shadow_thickness;
548 int height = width;
549 XPoint pt[10];
550 /* alpha = atan (0.5)
551 factor = (1 + sin (alpha)) / cos (alpha) */
552 double factor = 1.62;
553 int thickness2 = thickness * factor;
555 y += (mw->menu.font->ascent + mw->menu.font->descent - height) / 2;
557 if (down_p)
559 GC temp;
560 temp = top_gc;
561 top_gc = bottom_gc;
562 bottom_gc = temp;
565 pt[0].x = x;
566 pt[0].y = y + height;
567 pt[1].x = x + thickness;
568 pt[1].y = y + height - thickness2;
569 pt[2].x = x + thickness2;
570 pt[2].y = y + thickness2;
571 pt[3].x = x;
572 pt[3].y = y;
573 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
575 pt[0].x = x;
576 pt[0].y = y;
577 pt[1].x = x + thickness;
578 pt[1].y = y + thickness2;
579 pt[2].x = x + width - thickness2;
580 pt[2].y = y + height / 2;
581 pt[3].x = x + width;
582 pt[3].y = y + height / 2;
583 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
585 pt[0].x = x;
586 pt[0].y = y + height;
587 pt[1].x = x + thickness;
588 pt[1].y = y + height - thickness2;
589 pt[2].x = x + width - thickness2;
590 pt[2].y = y + height / 2;
591 pt[3].x = x + width;
592 pt[3].y = y + height / 2;
593 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
598 static void
599 draw_shadow_rectangle (mw, window, x, y, width, height, erase_p, down_p)
600 XlwMenuWidget mw;
601 Window window;
602 int x;
603 int y;
604 int width;
605 int height;
606 int erase_p;
607 int down_p;
609 Display *dpy = XtDisplay (mw);
610 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
611 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
612 int thickness = mw->menu.shadow_thickness;
613 XPoint points [4];
615 if (!erase_p && down_p)
617 GC temp;
618 temp = top_gc;
619 top_gc = bottom_gc;
620 bottom_gc = temp;
623 points [0].x = x;
624 points [0].y = y;
625 points [1].x = x + width;
626 points [1].y = y;
627 points [2].x = x + width - thickness;
628 points [2].y = y + thickness;
629 points [3].x = x;
630 points [3].y = y + thickness;
631 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
632 points [0].x = x;
633 points [0].y = y + thickness;
634 points [1].x = x;
635 points [1].y = y + height;
636 points [2].x = x + thickness;
637 points [2].y = y + height - thickness;
638 points [3].x = x + thickness;
639 points [3].y = y + thickness;
640 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
641 points [0].x = x + width;
642 points [0].y = y;
643 points [1].x = x + width - thickness;
644 points [1].y = y + thickness;
645 points [2].x = x + width - thickness;
646 points [2].y = y + height - thickness;
647 points [3].x = x + width;
648 points [3].y = y + height - thickness;
649 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
650 points [0].x = x;
651 points [0].y = y + height;
652 points [1].x = x + width;
653 points [1].y = y + height;
654 points [2].x = x + width;
655 points [2].y = y + height - thickness;
656 points [3].x = x + thickness;
657 points [3].y = y + height - thickness;
658 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
662 static void
663 draw_shadow_rhombus (mw, window, x, y, width, height, erase_p, down_p)
664 XlwMenuWidget mw;
665 Window window;
666 int x;
667 int y;
668 int width;
669 int height;
670 int erase_p;
671 int down_p;
673 Display *dpy = XtDisplay (mw);
674 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
675 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
676 int thickness = mw->menu.shadow_thickness;
677 XPoint points [4];
679 if (!erase_p && down_p)
681 GC temp;
682 temp = top_gc;
683 top_gc = bottom_gc;
684 bottom_gc = temp;
687 points [0].x = x;
688 points [0].y = y + height / 2;
689 points [1].x = x + thickness;
690 points [1].y = y + height / 2;
691 points [2].x = x + width / 2;
692 points [2].y = y + thickness;
693 points [3].x = x + width / 2;
694 points [3].y = y;
695 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
696 points [0].x = x + width / 2;
697 points [0].y = y;
698 points [1].x = x + width / 2;
699 points [1].y = y + thickness;
700 points [2].x = x + width - thickness;
701 points [2].y = y + height / 2;
702 points [3].x = x + width;
703 points [3].y = y + height / 2;
704 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
705 points [0].x = x;
706 points [0].y = y + height / 2;
707 points [1].x = x + thickness;
708 points [1].y = y + height / 2;
709 points [2].x = x + width / 2;
710 points [2].y = y + height - thickness;
711 points [3].x = x + width / 2;
712 points [3].y = y + height;
713 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
714 points [0].x = x + width / 2;
715 points [0].y = y + height;
716 points [1].x = x + width / 2;
717 points [1].y = y + height - thickness;
718 points [2].x = x + width - thickness;
719 points [2].y = y + height / 2;
720 points [3].x = x + width;
721 points [3].y = y + height / 2;
722 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
726 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
727 top-left corner of the menu item. SELECTED_P non-zero means the
728 toggle button is selected. */
730 static void
731 draw_toggle (mw, window, x, y, selected_p)
732 XlwMenuWidget mw;
733 Window window;
734 int x, y, selected_p;
736 int width, height;
738 width = toggle_button_width (mw);
739 height = width;
740 x += mw->menu.horizontal_spacing;
741 y += (mw->menu.font->ascent - height) / 2;
742 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
746 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
747 top-left corner of the menu item. SELECTED_P non-zero means the
748 toggle button is selected. */
750 static void
751 draw_radio (mw, window, x, y, selected_p)
752 XlwMenuWidget mw;
753 Window window;
754 int x, y, selected_p;
756 int width, height;
758 width = radio_button_width (mw);
759 height = width;
760 x += mw->menu.horizontal_spacing;
761 y += (mw->menu.font->ascent - height) / 2;
762 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
766 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
767 top-left corner of the menu item. WIDTH is the width of the
768 separator to draw. TYPE is the separator type. */
770 static void
771 draw_separator (mw, window, x, y, width, type)
772 XlwMenuWidget mw;
773 Window window;
774 int x, y, width;
775 enum menu_separator type;
777 Display *dpy = XtDisplay (mw);
778 XGCValues xgcv;
780 switch (type)
782 case SEPARATOR_NO_LINE:
783 break;
785 case SEPARATOR_SINGLE_LINE:
786 XDrawLine (dpy, window, mw->menu.foreground_gc,
787 x, y, x + width, y);
788 break;
790 case SEPARATOR_DOUBLE_LINE:
791 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
792 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
793 break;
795 case SEPARATOR_SINGLE_DASHED_LINE:
796 xgcv.line_style = LineOnOffDash;
797 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
798 XDrawLine (dpy, window, mw->menu.foreground_gc,
799 x, y, x + width, y);
800 xgcv.line_style = LineSolid;
801 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
802 break;
804 case SEPARATOR_DOUBLE_DASHED_LINE:
805 draw_separator (mw, window, x, y, width,
806 SEPARATOR_SINGLE_DASHED_LINE);
807 draw_separator (mw, window, x, y + 2, width,
808 SEPARATOR_SINGLE_DASHED_LINE);
809 break;
811 case SEPARATOR_SHADOW_ETCHED_IN:
812 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
813 x, y, x + width, y);
814 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
815 x, y + 1, x + width, y + 1);
816 break;
818 case SEPARATOR_SHADOW_ETCHED_OUT:
819 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
820 x, y, x + width, y);
821 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
822 x, y + 1, x + width, y + 1);
823 break;
825 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
826 xgcv.line_style = LineOnOffDash;
827 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
828 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
829 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
830 xgcv.line_style = LineSolid;
831 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
832 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
833 break;
835 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
836 xgcv.line_style = LineOnOffDash;
837 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
838 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
839 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
840 xgcv.line_style = LineSolid;
841 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
842 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
843 break;
845 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
846 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
847 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
848 break;
850 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
851 draw_separator (mw, window, x, y, width,
852 SEPARATOR_SHADOW_ETCHED_OUT);
853 draw_separator (mw, window, x, y + 3, width,
854 SEPARATOR_SHADOW_ETCHED_OUT);
855 break;
857 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
858 xgcv.line_style = LineOnOffDash;
859 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
860 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
861 draw_separator (mw, window, x, y, width,
862 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
863 xgcv.line_style = LineSolid;
864 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
865 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
866 break;
868 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
869 xgcv.line_style = LineOnOffDash;
870 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
871 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
872 draw_separator (mw, window, x, y, width,
873 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
874 xgcv.line_style = LineSolid;
875 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
876 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
877 break;
879 default:
880 abort ();
885 /* Return the pixel height of menu separator SEPARATOR. */
887 static int
888 separator_height (separator)
889 enum menu_separator separator;
891 switch (separator)
893 case SEPARATOR_NO_LINE:
894 return 2;
896 case SEPARATOR_SINGLE_LINE:
897 case SEPARATOR_SINGLE_DASHED_LINE:
898 return 1;
900 case SEPARATOR_DOUBLE_LINE:
901 case SEPARATOR_DOUBLE_DASHED_LINE:
902 return 3;
904 case SEPARATOR_SHADOW_ETCHED_IN:
905 case SEPARATOR_SHADOW_ETCHED_OUT:
906 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
907 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
908 return 2;
910 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
911 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
912 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
913 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
914 return 5;
916 default:
917 abort ();
922 /* Display the menu item and increment where.x and where.y to show how large
923 the menu item was. */
925 static void
926 display_menu_item (mw, val, ws, where, highlighted_p, horizontal_p,
927 just_compute_p)
928 XlwMenuWidget mw;
929 widget_value* val;
930 window_state* ws;
931 XPoint* where;
932 Boolean highlighted_p;
933 Boolean horizontal_p;
934 Boolean just_compute_p;
936 GC deco_gc;
937 GC text_gc;
938 int font_ascent = mw->menu.font->ascent;
939 int font_descent = mw->menu.font->descent;
940 int shadow = mw->menu.shadow_thickness;
941 int margin = mw->menu.margin;
942 int h_spacing = mw->menu.horizontal_spacing;
943 int v_spacing = mw->menu.vertical_spacing;
944 int label_width;
945 int rest_width;
946 int button_width;
947 int height;
948 int width;
949 enum menu_separator separator;
950 int separator_p = lw_separator_p (val->name, &separator, 0);
952 /* compute the sizes of the item */
953 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
954 &button_width, &height);
956 if (horizontal_p)
957 width = label_width + rest_width;
958 else
960 label_width = ws->label_width;
961 width = ws->width - 2 * shadow;
964 /* Only highlight an enabled item that has a callback. */
965 if (highlighted_p)
966 if (!val->enabled || !(val->call_data || val->contents))
967 highlighted_p = 0;
969 /* do the drawing. */
970 if (!just_compute_p)
972 /* Add the shadow border of the containing menu */
973 int x = where->x + shadow;
974 int y = where->y + shadow;
976 if (horizontal_p)
978 x += margin;
979 y += margin;
982 /* pick the foreground and background GC. */
983 if (val->enabled)
984 text_gc = mw->menu.foreground_gc;
985 else
986 text_gc = mw->menu.inactive_gc;
987 deco_gc = mw->menu.foreground_gc;
989 if (separator_p)
991 draw_separator (mw, ws->window, x, y, width, separator);
993 else
995 int x_offset = x + h_spacing + shadow;
996 char* display_string = resource_widget_value (mw, val);
997 draw_shadow_rectangle (mw, ws->window, x, y, width, height, True,
998 False);
1000 /* Deal with centering a menu title. */
1001 if (!horizontal_p && !val->contents && !val->call_data)
1003 int l = string_width (mw, display_string);
1005 if (width > l)
1006 x_offset = (width - l) >> 1;
1008 else if (!horizontal_p && ws->button_width)
1009 x_offset += ws->button_width;
1012 XDrawString (XtDisplay (mw), ws->window, text_gc, x_offset,
1013 y + v_spacing + shadow + font_ascent,
1014 display_string, strlen (display_string));
1016 if (!horizontal_p)
1018 if (val->button_type == BUTTON_TYPE_TOGGLE)
1019 draw_toggle (mw, ws->window, x, y + v_spacing + shadow,
1020 val->selected);
1021 else if (val->button_type == BUTTON_TYPE_RADIO)
1022 draw_radio (mw, ws->window, x, y + v_spacing + shadow,
1023 val->selected);
1025 if (val->contents)
1027 int a_w = arrow_width (mw);
1028 draw_arrow (mw, ws->window, deco_gc,
1029 x + width - a_w
1030 - mw->menu.horizontal_spacing
1031 - mw->menu.shadow_thickness,
1032 y + v_spacing + shadow, a_w,
1033 highlighted_p);
1035 else if (val->key)
1037 XDrawString (XtDisplay (mw), ws->window, text_gc,
1038 x + label_width + mw->menu.arrow_spacing,
1039 y + v_spacing + shadow + font_ascent,
1040 val->key, strlen (val->key));
1043 else
1045 XDrawRectangle (XtDisplay (mw), ws->window,
1046 mw->menu.background_gc,
1047 x + shadow, y + shadow,
1048 label_width + h_spacing - 1,
1049 font_ascent + font_descent + 2 * v_spacing - 1);
1050 draw_shadow_rectangle (mw, ws->window, x, y, width, height,
1051 True, False);
1054 if (highlighted_p)
1055 draw_shadow_rectangle (mw, ws->window, x, y, width, height, False,
1056 False);
1060 where->x += width;
1061 where->y += height;
1064 static void
1065 display_menu (mw, level, just_compute_p, highlighted_pos, hit, hit_return,
1066 this, that)
1067 XlwMenuWidget mw;
1068 int level;
1069 Boolean just_compute_p;
1070 XPoint* highlighted_pos;
1071 XPoint* hit;
1072 widget_value** hit_return;
1073 widget_value* this;
1074 widget_value* that;
1076 widget_value* val;
1077 widget_value* following_item;
1078 window_state* ws;
1079 XPoint where;
1080 int horizontal_p = mw->menu.horizontal && (level == 0);
1081 int highlighted_p;
1082 int just_compute_this_one_p;
1083 /* This is set nonzero if the element containing HIGHLIGHTED_POS
1084 is disabled, so that we do not return any subsequent element either. */
1085 int no_return = 0;
1086 enum menu_separator separator;
1088 if (level >= mw->menu.old_depth)
1089 abort_gracefully ((Widget) mw);
1091 if (level < mw->menu.old_depth - 1)
1092 following_item = mw->menu.old_stack [level + 1];
1093 else
1094 following_item = NULL;
1096 if (hit)
1097 *hit_return = NULL;
1099 where.x = 0;
1100 where.y = 0;
1102 ws = &mw->menu.windows [level];
1103 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1105 highlighted_p = val == following_item;
1106 if (highlighted_p && highlighted_pos)
1108 if (horizontal_p)
1109 highlighted_pos->x = where.x;
1110 else
1111 highlighted_pos->y = where.y;
1114 just_compute_this_one_p =
1115 just_compute_p || ((this || that) && val != this && val != that);
1117 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1118 just_compute_this_one_p);
1120 if (highlighted_p && highlighted_pos)
1122 if (horizontal_p)
1123 highlighted_pos->y = where.y;
1124 else
1125 highlighted_pos->x = where.x;
1128 if (hit
1129 && !*hit_return
1130 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1131 && !lw_separator_p (val->name, &separator, 0)
1132 && !no_return)
1134 if (val->enabled)
1135 *hit_return = val;
1136 else
1137 no_return = 1;
1140 if (horizontal_p)
1141 where.y = 0;
1142 else
1143 where.x = 0;
1146 if (!just_compute_p)
1147 draw_shadow_rectangle (mw, ws->window, 0, 0, ws->width, ws->height,
1148 False, False);
1151 \f/* Motion code */
1152 static void
1153 set_new_state (mw, val, level)
1154 XlwMenuWidget mw;
1155 widget_value* val;
1156 int level;
1158 int i;
1160 mw->menu.new_depth = 0;
1161 for (i = 0; i < level; i++)
1162 push_new_stack (mw, mw->menu.old_stack [i]);
1163 push_new_stack (mw, val);
1166 static void
1167 make_windows_if_needed (mw, n)
1168 XlwMenuWidget mw;
1169 int n;
1171 int i;
1172 int start_at;
1173 XSetWindowAttributes xswa;
1174 int mask;
1175 Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1176 window_state* windows;
1178 if (mw->menu.windows_length >= n)
1179 return;
1181 xswa.save_under = True;
1182 xswa.override_redirect = True;
1183 xswa.background_pixel = mw->core.background_pixel;
1184 xswa.border_pixel = mw->core.border_pixel;
1185 xswa.event_mask =
1186 ExposureMask | PointerMotionMask | PointerMotionHintMask
1187 | ButtonReleaseMask | ButtonPressMask;
1188 xswa.cursor = mw->menu.cursor_shape;
1189 mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
1190 | CWEventMask | CWCursor;
1192 if (!mw->menu.windows)
1194 mw->menu.windows =
1195 (window_state*)XtMalloc (n * sizeof (window_state));
1196 start_at = 0;
1198 else
1200 mw->menu.windows =
1201 (window_state*)XtRealloc ((char*)mw->menu.windows,
1202 n * sizeof (window_state));
1203 start_at = mw->menu.windows_length;
1205 mw->menu.windows_length = n;
1207 windows = mw->menu.windows;
1209 for (i = start_at; i < n; i++)
1211 windows [i].x = 0;
1212 windows [i].y = 0;
1213 windows [i].width = 1;
1214 windows [i].height = 1;
1215 windows [i].window =
1216 XCreateWindow (XtDisplay (mw), root, 0, 0, 1, 1,
1217 0, 0, CopyFromParent, CopyFromParent, mask, &xswa);
1221 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1224 xlwmenu_window_p (w, window)
1225 Widget w;
1226 Window window;
1228 XlwMenuWidget mw = (XlwMenuWidget) w;
1229 int i;
1231 for (i = 0; i < mw->menu.windows_length; ++i)
1232 if (window == mw->menu.windows[i].window)
1233 break;
1235 return i < mw->menu.windows_length;
1238 /* Make the window fit in the screen */
1239 static void
1240 fit_to_screen (mw, ws, previous_ws, horizontal_p)
1241 XlwMenuWidget mw;
1242 window_state* ws;
1243 window_state* previous_ws;
1244 Boolean horizontal_p;
1246 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1247 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1248 /* 1 if we are unable to avoid an overlap between
1249 this menu and the parent menu in the X dimension. */
1250 int horizontal_overlap = 0;
1252 if (ws->x < 0)
1253 ws->x = 0;
1254 else if (ws->x + ws->width > screen_width)
1256 if (!horizontal_p)
1257 /* The addition of shadow-thickness for a sub-menu's position is
1258 to reflect a similar adjustment when the menu is displayed to
1259 the right of the invoking menu-item; it makes the sub-menu
1260 look more `attached' to the menu-item. */
1261 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1262 else
1263 ws->x = screen_width - ws->width;
1264 if (ws->x < 0)
1266 ws->x = 0;
1267 horizontal_overlap = 1;
1270 /* If we overlap in X, try to avoid overlap in Y. */
1271 if (horizontal_overlap
1272 && ws->y < previous_ws->y + previous_ws->height
1273 && previous_ws->y < ws->y + ws->height)
1275 /* Put this menu right below or right above PREVIOUS_WS
1276 if there's room. */
1277 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1278 ws->y = previous_ws->y + previous_ws->height;
1279 else if (previous_ws->y - ws->height > 0)
1280 ws->y = previous_ws->y - ws->height;
1283 if (ws->y < 0)
1284 ws->y = 0;
1285 else if (ws->y + ws->height > screen_height)
1287 if (horizontal_p)
1288 ws->y = previous_ws->y - ws->height;
1289 else
1290 ws->y = screen_height - ws->height;
1291 if (ws->y < 0)
1292 ws->y = 0;
1296 /* Updates old_stack from new_stack and redisplays. */
1297 static void
1298 remap_menubar (mw)
1299 XlwMenuWidget mw;
1301 int i;
1302 int last_same;
1303 XPoint selection_position;
1304 int old_depth = mw->menu.old_depth;
1305 int new_depth = mw->menu.new_depth;
1306 widget_value** old_stack;
1307 widget_value** new_stack;
1308 window_state* windows;
1309 widget_value* old_selection;
1310 widget_value* new_selection;
1312 /* Check that enough windows and old_stack are ready. */
1313 make_windows_if_needed (mw, new_depth);
1314 make_old_stack_space (mw, new_depth);
1315 windows = mw->menu.windows;
1316 old_stack = mw->menu.old_stack;
1317 new_stack = mw->menu.new_stack;
1319 /* compute the last identical different entry */
1320 for (i = 1; i < old_depth && i < new_depth; i++)
1321 if (old_stack [i] != new_stack [i])
1322 break;
1323 last_same = i - 1;
1325 /* Memorize the previously selected item to be able to refresh it */
1326 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1327 if (old_selection && !old_selection->enabled)
1328 old_selection = NULL;
1329 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1330 if (new_selection && !new_selection->enabled)
1331 new_selection = NULL;
1333 /* Call callback when the hightlighted item changes. */
1334 if (old_selection || new_selection)
1335 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1336 (XtPointer) new_selection);
1338 /* updates old_state from new_state. It has to be done now because
1339 display_menu (called below) uses the old_stack to know what to display. */
1340 for (i = last_same + 1; i < new_depth; i++)
1341 old_stack [i] = new_stack [i];
1342 mw->menu.old_depth = new_depth;
1344 /* refresh the last selection */
1345 selection_position.x = 0;
1346 selection_position.y = 0;
1347 display_menu (mw, last_same, new_selection == old_selection,
1348 &selection_position, NULL, NULL, old_selection, new_selection);
1350 /* Now place the new menus. */
1351 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1353 window_state *previous_ws = &windows[i - 1];
1354 window_state *ws = &windows[i];
1356 ws->x = (previous_ws->x + selection_position.x
1357 + mw->menu.shadow_thickness);
1358 if (mw->menu.horizontal && i == 1)
1359 ws->x += mw->menu.margin;
1361 #if 0
1362 if (!mw->menu.horizontal || i > 1)
1363 ws->x += mw->menu.shadow_thickness;
1364 #endif
1366 ws->y = (previous_ws->y + selection_position.y
1367 + mw->menu.shadow_thickness);
1368 if (mw->menu.horizontal && i == 1)
1369 ws->y += mw->menu.margin;
1371 size_menu (mw, i);
1373 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1375 XClearWindow (XtDisplay (mw), ws->window);
1376 XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y,
1377 ws->width, ws->height);
1378 XMapRaised (XtDisplay (mw), ws->window);
1379 display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL);
1382 /* unmap the menus that popped down */
1383 for (i = new_depth - 1; i < old_depth; i++)
1384 if (i >= new_depth || !new_stack[i]->contents)
1385 XUnmapWindow (XtDisplay (mw), windows[i].window);
1388 static Boolean
1389 motion_event_is_in_menu (mw, ev, level, relative_pos)
1390 XlwMenuWidget mw;
1391 XMotionEvent* ev;
1392 int level;
1393 XPoint* relative_pos;
1395 window_state* ws = &mw->menu.windows [level];
1396 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1397 int x = ws->x + shadow;
1398 int y = ws->y + shadow;
1399 relative_pos->x = ev->x_root - x;
1400 relative_pos->y = ev->y_root - y;
1401 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1402 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1405 static Boolean
1406 map_event_to_widget_value (mw, ev, val, level)
1407 XlwMenuWidget mw;
1408 XMotionEvent* ev;
1409 widget_value** val;
1410 int* level;
1412 int i;
1413 XPoint relative_pos;
1414 window_state* ws;
1416 *val = NULL;
1418 /* Find the window */
1419 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1421 ws = &mw->menu.windows [i];
1422 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1424 display_menu (mw, i, True, NULL, &relative_pos, val, NULL, NULL);
1426 if (*val)
1428 *level = i + 1;
1429 return True;
1433 return False;
1436 \f/* Procedures */
1437 static void
1438 make_drawing_gcs (mw)
1439 XlwMenuWidget mw;
1441 XGCValues xgcv;
1443 xgcv.font = mw->menu.font->fid;
1444 xgcv.foreground = mw->menu.foreground;
1445 xgcv.background = mw->core.background_pixel;
1446 mw->menu.foreground_gc = XtGetGC ((Widget)mw,
1447 GCFont | GCForeground | GCBackground,
1448 &xgcv);
1450 xgcv.font = mw->menu.font->fid;
1451 xgcv.foreground = mw->menu.button_foreground;
1452 xgcv.background = mw->core.background_pixel;
1453 mw->menu.button_gc = XtGetGC ((Widget)mw,
1454 GCFont | GCForeground | GCBackground,
1455 &xgcv);
1457 xgcv.font = mw->menu.font->fid;
1458 xgcv.foreground = mw->menu.foreground;
1459 xgcv.background = mw->core.background_pixel;
1460 xgcv.fill_style = FillStippled;
1461 xgcv.stipple = mw->menu.gray_pixmap;
1462 mw->menu.inactive_gc = XtGetGC ((Widget)mw,
1463 (GCFont | GCForeground | GCBackground
1464 | GCFillStyle | GCStipple), &xgcv);
1466 xgcv.font = mw->menu.font->fid;
1467 xgcv.foreground = mw->menu.button_foreground;
1468 xgcv.background = mw->core.background_pixel;
1469 xgcv.fill_style = FillStippled;
1470 xgcv.stipple = mw->menu.gray_pixmap;
1471 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw,
1472 (GCFont | GCForeground | GCBackground
1473 | GCFillStyle | GCStipple), &xgcv);
1475 xgcv.font = mw->menu.font->fid;
1476 xgcv.foreground = mw->core.background_pixel;
1477 xgcv.background = mw->menu.foreground;
1478 mw->menu.background_gc = XtGetGC ((Widget)mw,
1479 GCFont | GCForeground | GCBackground,
1480 &xgcv);
1483 static void
1484 release_drawing_gcs (mw)
1485 XlwMenuWidget mw;
1487 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1488 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1489 XtReleaseGC ((Widget) mw, mw->menu.inactive_gc);
1490 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1491 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1492 /* let's get some segvs if we try to use these... */
1493 mw->menu.foreground_gc = (GC) -1;
1494 mw->menu.button_gc = (GC) -1;
1495 mw->menu.inactive_gc = (GC) -1;
1496 mw->menu.inactive_button_gc = (GC) -1;
1497 mw->menu.background_gc = (GC) -1;
1500 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1501 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1503 static void
1504 make_shadow_gcs (mw)
1505 XlwMenuWidget mw;
1507 XGCValues xgcv;
1508 unsigned long pm = 0;
1509 Display *dpy = XtDisplay ((Widget) mw);
1510 Screen *screen = XtScreen ((Widget) mw);
1511 Colormap cmap = mw->core.colormap;
1512 XColor topc, botc;
1513 int top_frobbed = 0, bottom_frobbed = 0;
1515 mw->menu.free_top_shadow_color_p = 0;
1516 mw->menu.free_bottom_shadow_color_p = 0;
1518 if (mw->menu.top_shadow_color == -1)
1519 mw->menu.top_shadow_color = mw->core.background_pixel;
1520 else
1521 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1523 if (mw->menu.bottom_shadow_color == -1)
1524 mw->menu.bottom_shadow_color = mw->menu.foreground;
1525 else
1526 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1528 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1529 mw->menu.top_shadow_color == mw->menu.foreground)
1531 topc.pixel = mw->core.background_pixel;
1532 #ifdef emacs
1533 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1534 &topc.pixel,
1535 1.2, 0x8000))
1536 #else
1537 XQueryColor (dpy, cmap, &topc);
1538 /* don't overflow/wrap! */
1539 topc.red = MINL (65535, topc.red * 1.2);
1540 topc.green = MINL (65535, topc.green * 1.2);
1541 topc.blue = MINL (65535, topc.blue * 1.2);
1542 if (XAllocColor (dpy, cmap, &topc))
1543 #endif
1545 mw->menu.top_shadow_color = topc.pixel;
1546 mw->menu.free_top_shadow_color_p = 1;
1547 top_frobbed = 1;
1550 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1551 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1553 botc.pixel = mw->core.background_pixel;
1554 #ifdef emacs
1555 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1556 &botc.pixel,
1557 0.6, 0x4000))
1558 #else
1559 XQueryColor (dpy, cmap, &botc);
1560 botc.red *= 0.6;
1561 botc.green *= 0.6;
1562 botc.blue *= 0.6;
1563 if (XAllocColor (dpy, cmap, &botc))
1564 #endif
1566 mw->menu.bottom_shadow_color = botc.pixel;
1567 mw->menu.free_bottom_shadow_color_p = 1;
1568 bottom_frobbed = 1;
1572 if (top_frobbed && bottom_frobbed)
1574 if (topc.pixel == botc.pixel)
1576 if (botc.pixel == mw->menu.foreground)
1578 if (mw->menu.free_top_shadow_color_p)
1580 x_free_dpy_colors (dpy, screen, cmap,
1581 &mw->menu.top_shadow_color, 1);
1582 mw->menu.free_top_shadow_color_p = 0;
1584 mw->menu.top_shadow_color = mw->core.background_pixel;
1586 else
1588 if (mw->menu.free_bottom_shadow_color_p)
1590 x_free_dpy_colors (dpy, screen, cmap,
1591 &mw->menu.bottom_shadow_color, 1);
1592 mw->menu.free_bottom_shadow_color_p = 0;
1594 mw->menu.bottom_shadow_color = mw->menu.foreground;
1599 if (!mw->menu.top_shadow_pixmap &&
1600 mw->menu.top_shadow_color == mw->core.background_pixel)
1602 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1603 if (mw->menu.free_top_shadow_color_p)
1605 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1606 mw->menu.free_top_shadow_color_p = 0;
1608 mw->menu.top_shadow_color = mw->menu.foreground;
1610 if (!mw->menu.bottom_shadow_pixmap &&
1611 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1613 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1614 if (mw->menu.free_bottom_shadow_color_p)
1616 x_free_dpy_colors (dpy, screen, cmap,
1617 &mw->menu.bottom_shadow_color, 1);
1618 mw->menu.free_bottom_shadow_color_p = 0;
1620 mw->menu.bottom_shadow_color = mw->menu.foreground;
1623 xgcv.fill_style = FillStippled;
1624 xgcv.foreground = mw->menu.top_shadow_color;
1625 xgcv.stipple = mw->menu.top_shadow_pixmap;
1626 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1627 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1629 xgcv.foreground = mw->menu.bottom_shadow_color;
1630 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1631 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1632 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1636 static void
1637 release_shadow_gcs (mw)
1638 XlwMenuWidget mw;
1640 Display *dpy = XtDisplay ((Widget) mw);
1641 Screen *screen = XtScreen ((Widget) mw);
1642 Colormap cmap = mw->core.colormap;
1643 Pixel px[2];
1644 int i = 0;
1646 if (mw->menu.free_top_shadow_color_p)
1647 px[i++] = mw->menu.top_shadow_color;
1648 if (mw->menu.free_bottom_shadow_color_p)
1649 px[i++] = mw->menu.bottom_shadow_color;
1650 if (i > 0)
1651 x_free_dpy_colors (dpy, screen, cmap, px, i);
1653 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1654 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1657 static void
1658 XlwMenuInitialize (request, mw, args, num_args)
1659 Widget request;
1660 XlwMenuWidget mw;
1661 ArgList args;
1662 Cardinal *num_args;
1664 /* Get the GCs and the widget size */
1665 XSetWindowAttributes xswa;
1666 int mask;
1668 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1669 Display* display = XtDisplay (mw);
1671 #if 0
1672 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1674 /* _XtCreate is freeing the object that was passed to us,
1675 so make a copy that we will actually keep. */
1676 lwlib_bcopy (mw->menu.contents, tem, sizeof (widget_value));
1677 mw->menu.contents = tem;
1678 #endif
1680 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1681 mw->menu.cursor = mw->menu.cursor_shape;
1683 mw->menu.gray_pixmap
1684 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1685 gray_bitmap_width, gray_bitmap_height,
1686 (unsigned long)1, (unsigned long)0, 1);
1688 /* I don't understand why this ends up 0 sometimes,
1689 but it does. This kludge works around it.
1690 Can anyone find a real fix? -- rms. */
1691 if (mw->menu.font == 0)
1692 mw->menu.font = xlwmenu_default_font;
1694 make_drawing_gcs (mw);
1695 make_shadow_gcs (mw);
1697 xswa.background_pixel = mw->core.background_pixel;
1698 xswa.border_pixel = mw->core.border_pixel;
1699 mask = CWBackPixel | CWBorderPixel;
1701 mw->menu.popped_up = False;
1703 mw->menu.old_depth = 1;
1704 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1705 mw->menu.old_stack_length = 1;
1706 mw->menu.old_stack [0] = mw->menu.contents;
1708 mw->menu.new_depth = 0;
1709 mw->menu.new_stack = 0;
1710 mw->menu.new_stack_length = 0;
1711 push_new_stack (mw, mw->menu.contents);
1713 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1714 mw->menu.windows_length = 1;
1715 mw->menu.windows [0].x = 0;
1716 mw->menu.windows [0].y = 0;
1717 mw->menu.windows [0].width = 0;
1718 mw->menu.windows [0].height = 0;
1719 size_menu (mw, 0);
1721 mw->core.width = mw->menu.windows [0].width;
1722 mw->core.height = mw->menu.windows [0].height;
1725 static void
1726 XlwMenuClassInitialize ()
1730 static void
1731 XlwMenuRealize (w, valueMask, attributes)
1732 Widget w;
1733 Mask *valueMask;
1734 XSetWindowAttributes *attributes;
1736 XlwMenuWidget mw = (XlwMenuWidget)w;
1737 XSetWindowAttributes xswa;
1738 int mask;
1740 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1741 (w, valueMask, attributes);
1743 xswa.save_under = True;
1744 xswa.cursor = mw->menu.cursor_shape;
1745 mask = CWSaveUnder | CWCursor;
1746 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1748 mw->menu.windows [0].window = XtWindow (w);
1749 mw->menu.windows [0].x = w->core.x;
1750 mw->menu.windows [0].y = w->core.y;
1751 mw->menu.windows [0].width = w->core.width;
1752 mw->menu.windows [0].height = w->core.height;
1755 /* Only the toplevel menubar/popup is a widget so it's the only one that
1756 receives expose events through Xt. So we repaint all the other panes
1757 when receiving an Expose event. */
1758 static void
1759 XlwMenuRedisplay (w, ev, region)
1760 Widget w;
1761 XEvent* ev;
1762 Region region;
1764 XlwMenuWidget mw = (XlwMenuWidget)w;
1765 int i;
1767 /* If we have a depth beyond 1, it's because a submenu was displayed.
1768 If the submenu has been destroyed, set the depth back to 1. */
1769 if (submenu_destroyed)
1771 mw->menu.old_depth = 1;
1772 submenu_destroyed = 0;
1775 for (i = 0; i < mw->menu.old_depth; i++)
1776 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1780 /* Part of a hack to make the menu redisplay when a tooltip frame
1781 over a menu item is unmapped. */
1783 void
1784 xlwmenu_redisplay (w)
1785 Widget w;
1787 XlwMenuRedisplay (w, NULL, None);
1790 static void
1791 XlwMenuDestroy (w)
1792 Widget w;
1794 int i;
1795 XlwMenuWidget mw = (XlwMenuWidget) w;
1797 if (pointer_grabbed)
1798 XtUngrabPointer ((Widget)w, CurrentTime);
1799 pointer_grabbed = 0;
1801 submenu_destroyed = 1;
1803 release_drawing_gcs (mw);
1804 release_shadow_gcs (mw);
1806 /* this doesn't come from the resource db but is created explicitly
1807 so we must free it ourselves. */
1808 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1809 mw->menu.gray_pixmap = (Pixmap) -1;
1811 #if 0
1812 /* Do free mw->menu.contents because nowadays we copy it
1813 during initialization. */
1814 XtFree (mw->menu.contents);
1815 #endif
1817 /* Don't free mw->menu.contents because that comes from our creator.
1818 The `*_stack' elements are just pointers into `contents' so leave
1819 that alone too. But free the stacks themselves. */
1820 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1821 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1823 /* Remember, you can't free anything that came from the resource
1824 database. This includes:
1825 mw->menu.cursor
1826 mw->menu.top_shadow_pixmap
1827 mw->menu.bottom_shadow_pixmap
1828 mw->menu.font
1829 Also the color cells of top_shadow_color, bottom_shadow_color,
1830 foreground, and button_foreground will never be freed until this
1831 client exits. Nice, eh?
1834 /* start from 1 because the one in slot 0 is w->core.window */
1835 for (i = 1; i < mw->menu.windows_length; i++)
1836 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1837 if (mw->menu.windows)
1838 XtFree ((char *) mw->menu.windows);
1841 static Boolean
1842 XlwMenuSetValues (current, request, new)
1843 Widget current;
1844 Widget request;
1845 Widget new;
1847 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1848 XlwMenuWidget newmw = (XlwMenuWidget)new;
1849 Boolean redisplay = False;
1850 int i;
1852 if (newmw->menu.contents
1853 && newmw->menu.contents->contents
1854 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1855 redisplay = True;
1856 /* Do redisplay if the contents are entirely eliminated. */
1857 if (newmw->menu.contents
1858 && newmw->menu.contents->contents == 0
1859 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1860 redisplay = True;
1862 if (newmw->core.background_pixel != oldmw->core.background_pixel
1863 || newmw->menu.foreground != oldmw->menu.foreground
1864 || newmw->menu.font != oldmw->menu.font)
1866 release_drawing_gcs (newmw);
1867 make_drawing_gcs (newmw);
1869 release_shadow_gcs (newmw);
1870 /* Cause the shadow colors to be recalculated. */
1871 newmw->menu.top_shadow_color = -1;
1872 newmw->menu.bottom_shadow_color = -1;
1873 make_shadow_gcs (newmw);
1875 redisplay = True;
1877 if (XtIsRealized (current))
1878 /* If the menu is currently displayed, change the display. */
1879 for (i = 0; i < oldmw->menu.windows_length; i++)
1881 XSetWindowBackground (XtDisplay (oldmw),
1882 oldmw->menu.windows [i].window,
1883 newmw->core.background_pixel);
1884 /* clear windows and generate expose events */
1885 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
1886 0, 0, 0, 0, True);
1890 return redisplay;
1893 static void
1894 XlwMenuResize (w)
1895 Widget w;
1897 XlwMenuWidget mw = (XlwMenuWidget)w;
1899 if (mw->menu.popped_up)
1901 /* Don't allow the popup menu to resize itself. */
1902 mw->core.width = mw->menu.windows [0].width;
1903 mw->core.height = mw->menu.windows [0].height;
1904 mw->core.parent->core.width = mw->core.width ;
1905 mw->core.parent->core.height = mw->core.height ;
1907 else
1909 mw->menu.windows [0].width = mw->core.width;
1910 mw->menu.windows [0].height = mw->core.height;
1914 \f/* Action procedures */
1915 static void
1916 handle_single_motion_event (mw, ev)
1917 XlwMenuWidget mw;
1918 XMotionEvent* ev;
1920 widget_value* val;
1921 int level;
1923 if (!map_event_to_widget_value (mw, ev, &val, &level))
1924 pop_new_stack_if_no_contents (mw);
1925 else
1926 set_new_state (mw, val, level);
1927 remap_menubar (mw);
1929 /* Sync with the display. Makes it feel better on X terms. */
1930 XSync (XtDisplay (mw), False);
1933 static void
1934 handle_motion_event (mw, ev)
1935 XlwMenuWidget mw;
1936 XMotionEvent* ev;
1938 int x = ev->x_root;
1939 int y = ev->y_root;
1940 int state = ev->state;
1942 handle_single_motion_event (mw, ev);
1944 /* allow motion events to be generated again */
1945 if (ev->is_hint
1946 && XQueryPointer (XtDisplay (mw), ev->window,
1947 &ev->root, &ev->subwindow,
1948 &ev->x_root, &ev->y_root,
1949 &ev->x, &ev->y,
1950 &ev->state)
1951 && ev->state == state
1952 && (ev->x_root != x || ev->y_root != y))
1953 handle_single_motion_event (mw, ev);
1956 static void
1957 Start (w, ev, params, num_params)
1958 Widget w;
1959 XEvent *ev;
1960 String *params;
1961 Cardinal *num_params;
1963 XlwMenuWidget mw = (XlwMenuWidget)w;
1965 if (!mw->menu.popped_up)
1967 menu_post_event = *ev;
1968 pop_up_menu (mw, (XButtonPressedEvent*) ev);
1970 else
1972 /* If we push a button while the menu is posted semipermanently,
1973 releasing the button should always pop the menu down. */
1974 next_release_must_exit = 1;
1976 /* notes the absolute position of the menubar window */
1977 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
1978 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
1980 /* handles the down like a move, slots are compatible */
1981 handle_motion_event (mw, &ev->xmotion);
1985 static void
1986 Drag (w, ev, params, num_params)
1987 Widget w;
1988 XEvent *ev;
1989 String *params;
1990 Cardinal *num_params;
1992 XlwMenuWidget mw = (XlwMenuWidget)w;
1993 if (mw->menu.popped_up)
1994 handle_motion_event (mw, &ev->xmotion);
1997 /* Do nothing.
1998 This is how we handle presses and releases of modifier keys. */
1999 static void
2000 Nothing (w, ev, params, num_params)
2001 Widget w;
2002 XEvent *ev;
2003 String *params;
2004 Cardinal *num_params;
2008 widget_value *
2009 find_first_selectable (mw, item)
2010 XlwMenuWidget mw;
2011 widget_value *item;
2013 widget_value *current = item;
2014 enum menu_separator separator;
2016 while (lw_separator_p (current->name, &separator, 0) || !current->enabled)
2017 if (current->next)
2018 current=current->next;
2019 else
2020 return NULL;
2022 return current;
2025 widget_value *
2026 find_next_selectable (mw, item)
2027 XlwMenuWidget mw;
2028 widget_value *item;
2030 widget_value *current = item;
2031 enum menu_separator separator;
2033 while (current->next && (current=current->next) &&
2034 (lw_separator_p (current->name, &separator, 0) || !current->enabled))
2037 if (current == item)
2039 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2041 while (lw_separator_p (current->name, &separator, 0) || !current->enabled)
2043 if (current->next)
2044 current=current->next;
2046 if (current == item)
2047 break;
2052 return current;
2055 widget_value *
2056 find_prev_selectable (mw, item)
2057 XlwMenuWidget mw;
2058 widget_value *item;
2060 widget_value *current = item;
2061 widget_value *prev = item;
2063 while ((current=find_next_selectable (mw, current)) != item)
2065 if (prev == current)
2066 break;
2067 prev=current;
2070 return prev;
2073 static void
2074 Down (w, ev, params, num_params)
2075 Widget w;
2076 XEvent *ev;
2077 String *params;
2078 Cardinal *num_params;
2080 XlwMenuWidget mw = (XlwMenuWidget) w;
2081 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2083 /* Inside top-level menu-bar? */
2084 if (mw->menu.old_depth == 2)
2085 /* When <down> in the menu-bar is pressed, display the corresponding
2086 sub-menu and select the first selectable menu item there. */
2087 set_new_state (mw, find_first_selectable (mw, selected_item->contents), mw->menu.old_depth);
2088 else
2089 /* Highlight next possible (enabled and not separator) menu item. */
2090 set_new_state (mw, find_next_selectable (mw, selected_item), mw->menu.old_depth - 1);
2092 remap_menubar (mw);
2095 static void
2096 Up (w, ev, params, num_params)
2097 Widget w;
2098 XEvent *ev;
2099 String *params;
2100 Cardinal *num_params;
2102 XlwMenuWidget mw = (XlwMenuWidget) w;
2103 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2105 /* Inside top-level menu-bar? */
2106 if (mw->menu.old_depth == 2)
2108 /* FIXME: this is tricky. <up> in the menu-bar should select the
2109 last selectable item in the list. So we select the first
2110 selectable one and find the previous selectable item. Is there
2111 a better way? */
2112 set_new_state (mw, find_first_selectable (mw, selected_item->contents), mw->menu.old_depth);
2113 remap_menubar (mw);
2114 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2115 set_new_state (mw, find_prev_selectable (mw, selected_item), mw->menu.old_depth - 1);
2117 else
2118 /* Highlight previous (enabled and not separator) menu item. */
2119 set_new_state (mw, find_prev_selectable (mw, selected_item), mw->menu.old_depth - 1);
2121 remap_menubar (mw);
2124 static void
2125 Left (w, ev, params, num_params)
2126 Widget w;
2127 XEvent *ev;
2128 String *params;
2129 Cardinal *num_params;
2131 XlwMenuWidget mw = (XlwMenuWidget) w;
2132 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2134 /* Inside top-level menu-bar? */
2135 if (mw->menu.old_depth == 2)
2136 /* When <left> in the menu-bar is pressed, display the previous item on
2137 the menu-bar. If the current item is the first one, highlight the
2138 last item in the menubar (probably Help). */
2139 set_new_state (mw, find_prev_selectable (mw, selected_item), mw->menu.old_depth - 1);
2140 else
2142 pop_new_stack_if_no_contents (mw);
2143 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2], mw->menu.old_depth - 2);
2146 remap_menubar (mw);
2149 static void
2150 Right (w, ev, params, num_params)
2151 Widget w;
2152 XEvent *ev;
2153 String *params;
2154 Cardinal *num_params;
2156 XlwMenuWidget mw = (XlwMenuWidget) w;
2157 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2159 /* Inside top-level menu-bar? */
2160 if (mw->menu.old_depth == 2)
2161 /* When <right> in the menu-bar is pressed, display the next item on
2162 the menu-bar. If the current item is the last one, highlight the
2163 first item (probably File). */
2164 set_new_state (mw, find_next_selectable (mw, selected_item), mw->menu.old_depth - 1);
2165 else if (selected_item->contents) /* Is this menu item expandable? */
2167 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2168 remap_menubar (mw);
2169 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2170 if (!selected_item->enabled && find_first_selectable (mw, selected_item))
2171 set_new_state (mw, find_first_selectable (mw, selected_item), mw->menu.old_depth - 1);
2173 else
2175 pop_new_stack_if_no_contents (mw);
2176 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2], mw->menu.old_depth - 2);
2179 remap_menubar (mw);
2182 /* Handle key press and release events while menu is popped up.
2183 Our action is to get rid of the menu. */
2184 static void
2185 Key (w, ev, params, num_params)
2186 Widget w;
2187 XEvent *ev;
2188 String *params;
2189 Cardinal *num_params;
2191 XlwMenuWidget mw = (XlwMenuWidget)w;
2193 /* Pop down everything. */
2194 mw->menu.new_depth = 1;
2195 remap_menubar (mw);
2197 if (mw->menu.popped_up)
2199 mw->menu.popped_up = False;
2200 XtUngrabPointer ((Widget)mw, ev->xmotion.time);
2201 if (XtIsShell (XtParent ((Widget) mw)))
2202 XtPopdown (XtParent ((Widget) mw));
2203 else
2205 XtRemoveGrab ((Widget) mw);
2206 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2210 /* callback */
2211 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2214 static void
2215 Select (w, ev, params, num_params)
2216 Widget w;
2217 XEvent *ev;
2218 String *params;
2219 Cardinal *num_params;
2221 XlwMenuWidget mw = (XlwMenuWidget)w;
2222 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2224 /* If user releases the button quickly, without selecting anything,
2225 after the initial down-click that brought the menu up,
2226 do nothing. */
2227 if ((selected_item == 0
2228 || ((widget_value *) selected_item)->call_data == 0)
2229 && !next_release_must_exit
2230 && (ev->xbutton.time - menu_post_event.xbutton.time
2231 < XtGetMultiClickTime (XtDisplay (w))))
2232 return;
2234 /* pop down everything. */
2235 mw->menu.new_depth = 1;
2236 remap_menubar (mw);
2238 if (mw->menu.popped_up)
2240 mw->menu.popped_up = False;
2241 XtUngrabPointer ((Widget)mw, ev->xmotion.time);
2242 if (XtIsShell (XtParent ((Widget) mw)))
2243 XtPopdown (XtParent ((Widget) mw));
2244 else
2246 XtRemoveGrab ((Widget) mw);
2247 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2251 /* callback */
2252 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2256 \f/* Special code to pop-up a menu */
2257 void
2258 pop_up_menu (mw, event)
2259 XlwMenuWidget mw;
2260 XButtonPressedEvent* event;
2262 int x = event->x_root;
2263 int y = event->y_root;
2264 int w;
2265 int h;
2266 int borderwidth = mw->menu.shadow_thickness;
2267 Screen* screen = XtScreen (mw);
2268 Display *display = XtDisplay (mw);
2269 int count;
2271 next_release_must_exit = 0;
2273 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2275 if (XtIsShell (XtParent ((Widget)mw)))
2276 size_menu (mw, 0);
2278 w = mw->menu.windows [0].width;
2279 h = mw->menu.windows [0].height;
2281 x -= borderwidth;
2282 y -= borderwidth;
2283 if (x < borderwidth)
2284 x = borderwidth;
2285 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2286 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2287 if (y < borderwidth)
2288 y = borderwidth;
2289 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2290 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2292 mw->menu.popped_up = True;
2293 if (XtIsShell (XtParent ((Widget)mw)))
2295 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2296 XtParent ((Widget)mw)->core.border_width);
2297 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2298 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2299 mw->menu.windows [0].x = x + borderwidth;
2300 mw->menu.windows [0].y = y + borderwidth;
2302 else
2304 XEvent *ev = (XEvent *) event;
2306 XtAddGrab ((Widget) mw, True, True);
2308 /* notes the absolute position of the menubar window */
2309 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2310 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2313 #ifdef emacs
2314 count = x_catch_errors (display);
2315 #endif
2316 XtGrabPointer ((Widget)mw, False,
2317 (PointerMotionMask
2318 | PointerMotionHintMask
2319 | ButtonReleaseMask
2320 | ButtonPressMask),
2321 GrabModeAsync, GrabModeAsync, None,
2322 mw->menu.cursor_shape,
2323 event->time);
2324 pointer_grabbed = 1;
2325 #ifdef emacs
2326 if (x_had_errors_p (display))
2328 pointer_grabbed = 0;
2329 XtUngrabPointer ((Widget)mw, event->time);
2331 x_uncatch_errors (display, count);
2332 #endif
2334 handle_motion_event (mw, (XMotionEvent*)event);