(find-file-noselect-1): Do not bind `inhibit-read-only' to t during
[emacs.git] / lwlib / xlwmenu.c
blob973fc6ec5d56dc93840fa3873a8c2ee0e86c19ae
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 {"nothing", Nothing},
217 #define SuperClass ((CoreWidgetClass)&coreClassRec)
219 XlwMenuClassRec xlwMenuClassRec =
221 { /* CoreClass fields initialization */
222 (WidgetClass) SuperClass, /* superclass */
223 "XlwMenu", /* class_name */
224 sizeof(XlwMenuRec), /* size */
225 XlwMenuClassInitialize, /* class_initialize */
226 NULL, /* class_part_initialize */
227 FALSE, /* class_inited */
228 XlwMenuInitialize, /* initialize */
229 NULL, /* initialize_hook */
230 XlwMenuRealize, /* realize */
231 xlwMenuActionsList, /* actions */
232 XtNumber(xlwMenuActionsList), /* num_actions */
233 xlwMenuResources, /* resources */
234 XtNumber(xlwMenuResources), /* resource_count */
235 NULLQUARK, /* xrm_class */
236 TRUE, /* compress_motion */
237 TRUE, /* compress_exposure */
238 TRUE, /* compress_enterleave */
239 FALSE, /* visible_interest */
240 XlwMenuDestroy, /* destroy */
241 XlwMenuResize, /* resize */
242 XlwMenuRedisplay, /* expose */
243 XlwMenuSetValues, /* set_values */
244 NULL, /* set_values_hook */
245 XtInheritSetValuesAlmost, /* set_values_almost */
246 NULL, /* get_values_hook */
247 NULL, /* accept_focus */
248 XtVersion, /* version */
249 NULL, /* callback_private */
250 xlwMenuTranslations, /* tm_table */
251 XtInheritQueryGeometry, /* query_geometry */
252 XtInheritDisplayAccelerator, /* display_accelerator */
253 NULL /* extension */
254 }, /* XlwMenuClass fields initialization */
256 0 /* dummy */
260 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
262 int submenu_destroyed;
264 /* For debug, if installation-directory is non-nil this is not an installed
265 Emacs. In that case we do not grab the keyboard to make it easier to
266 debug. */
267 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
269 static int next_release_must_exit;
271 \f/* Utilities */
273 /* Ungrab pointer and keyboard */
274 static void
275 ungrab_all (w, ungrabtime)
276 Widget w;
277 Time ungrabtime;
279 XtUngrabPointer (w, ungrabtime);
280 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
283 /* Like abort, but remove grabs from widget W before. */
285 static void
286 abort_gracefully (w)
287 Widget w;
289 if (XtIsShell (XtParent (w)))
290 XtRemoveGrab (w);
291 ungrab_all (w, CurrentTime);
292 abort ();
295 static void
296 push_new_stack (mw, val)
297 XlwMenuWidget mw;
298 widget_value* val;
300 if (!mw->menu.new_stack)
302 mw->menu.new_stack_length = 10;
303 mw->menu.new_stack =
304 (widget_value**)XtCalloc (mw->menu.new_stack_length,
305 sizeof (widget_value*));
307 else if (mw->menu.new_depth == mw->menu.new_stack_length)
309 mw->menu.new_stack_length *= 2;
310 mw->menu.new_stack =
311 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
312 mw->menu.new_stack_length * sizeof (widget_value*));
314 mw->menu.new_stack [mw->menu.new_depth++] = val;
317 static void
318 pop_new_stack_if_no_contents (mw)
319 XlwMenuWidget mw;
321 if (mw->menu.new_depth > 1)
323 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
324 mw->menu.new_depth -= 1;
328 static void
329 make_old_stack_space (mw, n)
330 XlwMenuWidget mw;
331 int n;
333 if (!mw->menu.old_stack)
335 mw->menu.old_stack_length = 10;
336 mw->menu.old_stack =
337 (widget_value**)XtCalloc (mw->menu.old_stack_length,
338 sizeof (widget_value*));
340 else if (mw->menu.old_stack_length < n)
342 mw->menu.old_stack_length *= 2;
343 mw->menu.old_stack =
344 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
345 mw->menu.old_stack_length * sizeof (widget_value*));
349 \f/* Size code */
351 string_width (mw, s)
352 XlwMenuWidget mw;
353 char *s;
355 XCharStruct xcs;
356 int drop;
358 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
359 return xcs.width;
362 static int
363 arrow_width (mw)
364 XlwMenuWidget mw;
366 return (mw->menu.font->ascent * 3/4) | 1;
369 /* Return the width of toggle buttons of widget MW. */
371 static int
372 toggle_button_width (mw)
373 XlwMenuWidget mw;
375 return ((mw->menu.font->ascent + mw->menu.font->descent) * 2 / 3) | 1;
379 /* Return the width of radio buttons of widget MW. */
381 static int
382 radio_button_width (mw)
383 XlwMenuWidget mw;
385 return toggle_button_width (mw) * 1.41;
389 static XtResource
390 nameResource[] =
392 {"labelString", "LabelString", XtRString, sizeof(String),
393 0, XtRImmediate, 0},
396 static char*
397 resource_widget_value (mw, val)
398 XlwMenuWidget mw;
399 widget_value *val;
401 if (!val->toolkit_data)
403 char* resourced_name = NULL;
404 char* complete_name;
405 XtGetSubresources ((Widget) mw,
406 (XtPointer) &resourced_name,
407 val->name, val->name,
408 nameResource, 1, NULL, 0);
409 if (!resourced_name)
410 resourced_name = val->name;
411 if (!val->value)
413 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
414 strcpy (complete_name, resourced_name);
416 else
418 int complete_length =
419 strlen (resourced_name) + strlen (val->value) + 2;
420 complete_name = XtMalloc (complete_length);
421 *complete_name = 0;
422 strcat (complete_name, resourced_name);
423 strcat (complete_name, " ");
424 strcat (complete_name, val->value);
427 val->toolkit_data = complete_name;
428 val->free_toolkit_data = True;
430 return (char*)val->toolkit_data;
433 /* Returns the sizes of an item */
434 static void
435 size_menu_item (mw, val, horizontal_p, label_width, rest_width, button_width,
436 height)
437 XlwMenuWidget mw;
438 widget_value* val;
439 int horizontal_p;
440 int* label_width;
441 int* rest_width;
442 int* button_width;
443 int* height;
445 enum menu_separator separator;
447 if (lw_separator_p (val->name, &separator, 0))
449 *height = separator_height (separator);
450 *label_width = 1;
451 *rest_width = 0;
452 *button_width = 0;
454 else
456 *height =
457 mw->menu.font->ascent + mw->menu.font->descent
458 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
460 *label_width =
461 string_width (mw, resource_widget_value (mw, val))
462 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
464 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
465 if (!horizontal_p)
467 if (val->contents)
468 /* Add width of the arrow displayed for submenus. */
469 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
470 else if (val->key)
471 /* Add width of key equivalent string. */
472 *rest_width += (string_width (mw, val->key)
473 + mw->menu.arrow_spacing);
475 if (val->button_type == BUTTON_TYPE_TOGGLE)
476 *button_width = (toggle_button_width (mw)
477 + mw->menu.horizontal_spacing);
478 else if (val->button_type == BUTTON_TYPE_RADIO)
479 *button_width = (radio_button_width (mw)
480 + mw->menu.horizontal_spacing);
485 static void
486 size_menu (mw, level)
487 XlwMenuWidget mw;
488 int level;
490 unsigned int label_width = 0;
491 int rest_width = 0;
492 int button_width = 0;
493 int max_rest_width = 0;
494 int max_button_width = 0;
495 unsigned int height = 0;
496 int horizontal_p = mw->menu.horizontal && (level == 0);
497 widget_value* val;
498 window_state* ws;
500 if (level >= mw->menu.old_depth)
501 abort_gracefully ((Widget) mw);
503 ws = &mw->menu.windows [level];
504 ws->width = 0;
505 ws->height = 0;
506 ws->label_width = 0;
507 ws->button_width = 0;
509 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
511 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
512 &button_width, &height);
513 if (horizontal_p)
515 ws->width += label_width + rest_width;
516 if (height > ws->height)
517 ws->height = height;
519 else
521 if (label_width > ws->label_width)
522 ws->label_width = label_width;
523 if (rest_width > max_rest_width)
524 max_rest_width = rest_width;
525 if (button_width > max_button_width)
526 max_button_width = button_width;
527 ws->height += height;
531 if (horizontal_p)
532 ws->label_width = ws->button_width = 0;
533 else
535 ws->width = ws->label_width + max_rest_width + max_button_width;
536 ws->button_width = max_button_width;
539 ws->width += 2 * mw->menu.shadow_thickness;
540 ws->height += 2 * mw->menu.shadow_thickness;
542 if (horizontal_p)
544 ws->width += 2 * mw->menu.margin;
545 ws->height += 2 * mw->menu.margin;
550 \f/* Display code */
552 static void
553 draw_arrow (mw, window, gc, x, y, width, down_p)
554 XlwMenuWidget mw;
555 Window window;
556 GC gc;
557 int x;
558 int y;
559 int width;
560 int down_p;
562 Display *dpy = XtDisplay (mw);
563 GC top_gc = mw->menu.shadow_top_gc;
564 GC bottom_gc = mw->menu.shadow_bottom_gc;
565 int thickness = mw->menu.shadow_thickness;
566 int height = width;
567 XPoint pt[10];
568 /* alpha = atan (0.5)
569 factor = (1 + sin (alpha)) / cos (alpha) */
570 double factor = 1.62;
571 int thickness2 = thickness * factor;
573 y += (mw->menu.font->ascent + mw->menu.font->descent - height) / 2;
575 if (down_p)
577 GC temp;
578 temp = top_gc;
579 top_gc = bottom_gc;
580 bottom_gc = temp;
583 pt[0].x = x;
584 pt[0].y = y + height;
585 pt[1].x = x + thickness;
586 pt[1].y = y + height - thickness2;
587 pt[2].x = x + thickness2;
588 pt[2].y = y + thickness2;
589 pt[3].x = x;
590 pt[3].y = y;
591 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
593 pt[0].x = x;
594 pt[0].y = y;
595 pt[1].x = x + thickness;
596 pt[1].y = y + thickness2;
597 pt[2].x = x + width - thickness2;
598 pt[2].y = y + height / 2;
599 pt[3].x = x + width;
600 pt[3].y = y + height / 2;
601 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
603 pt[0].x = x;
604 pt[0].y = y + height;
605 pt[1].x = x + thickness;
606 pt[1].y = y + height - thickness2;
607 pt[2].x = x + width - thickness2;
608 pt[2].y = y + height / 2;
609 pt[3].x = x + width;
610 pt[3].y = y + height / 2;
611 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
616 static void
617 draw_shadow_rectangle (mw, window, x, y, width, height, erase_p, down_p)
618 XlwMenuWidget mw;
619 Window window;
620 int x;
621 int y;
622 int width;
623 int height;
624 int erase_p;
625 int down_p;
627 Display *dpy = XtDisplay (mw);
628 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
629 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
630 int thickness = mw->menu.shadow_thickness;
631 XPoint points [4];
633 if (!erase_p && down_p)
635 GC temp;
636 temp = top_gc;
637 top_gc = bottom_gc;
638 bottom_gc = temp;
641 points [0].x = x;
642 points [0].y = y;
643 points [1].x = x + width;
644 points [1].y = y;
645 points [2].x = x + width - thickness;
646 points [2].y = y + thickness;
647 points [3].x = x;
648 points [3].y = y + thickness;
649 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
650 points [0].x = x;
651 points [0].y = y + thickness;
652 points [1].x = x;
653 points [1].y = y + height;
654 points [2].x = x + thickness;
655 points [2].y = y + height - thickness;
656 points [3].x = x + thickness;
657 points [3].y = y + thickness;
658 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
659 points [0].x = x + width;
660 points [0].y = y;
661 points [1].x = x + width - thickness;
662 points [1].y = y + thickness;
663 points [2].x = x + width - thickness;
664 points [2].y = y + height - thickness;
665 points [3].x = x + width;
666 points [3].y = y + height - thickness;
667 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
668 points [0].x = x;
669 points [0].y = y + height;
670 points [1].x = x + width;
671 points [1].y = y + height;
672 points [2].x = x + width;
673 points [2].y = y + height - thickness;
674 points [3].x = x + thickness;
675 points [3].y = y + height - thickness;
676 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
680 static void
681 draw_shadow_rhombus (mw, window, x, y, width, height, erase_p, down_p)
682 XlwMenuWidget mw;
683 Window window;
684 int x;
685 int y;
686 int width;
687 int height;
688 int erase_p;
689 int down_p;
691 Display *dpy = XtDisplay (mw);
692 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
693 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
694 int thickness = mw->menu.shadow_thickness;
695 XPoint points [4];
697 if (!erase_p && down_p)
699 GC temp;
700 temp = top_gc;
701 top_gc = bottom_gc;
702 bottom_gc = temp;
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 + thickness;
711 points [3].x = x + width / 2;
712 points [3].y = y;
713 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
714 points [0].x = x + width / 2;
715 points [0].y = y;
716 points [1].x = x + width / 2;
717 points [1].y = y + 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, top_gc, points, 4, Convex, CoordModeOrigin);
723 points [0].x = x;
724 points [0].y = y + height / 2;
725 points [1].x = x + thickness;
726 points [1].y = y + height / 2;
727 points [2].x = x + width / 2;
728 points [2].y = y + height - thickness;
729 points [3].x = x + width / 2;
730 points [3].y = y + height;
731 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
732 points [0].x = x + width / 2;
733 points [0].y = y + height;
734 points [1].x = x + width / 2;
735 points [1].y = y + height - thickness;
736 points [2].x = x + width - thickness;
737 points [2].y = y + height / 2;
738 points [3].x = x + width;
739 points [3].y = y + height / 2;
740 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
744 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
745 top-left corner of the menu item. SELECTED_P non-zero means the
746 toggle button is selected. */
748 static void
749 draw_toggle (mw, window, x, y, selected_p)
750 XlwMenuWidget mw;
751 Window window;
752 int x, y, selected_p;
754 int width, height;
756 width = toggle_button_width (mw);
757 height = width;
758 x += mw->menu.horizontal_spacing;
759 y += (mw->menu.font->ascent - height) / 2;
760 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
764 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
765 top-left corner of the menu item. SELECTED_P non-zero means the
766 toggle button is selected. */
768 static void
769 draw_radio (mw, window, x, y, selected_p)
770 XlwMenuWidget mw;
771 Window window;
772 int x, y, selected_p;
774 int width, height;
776 width = radio_button_width (mw);
777 height = width;
778 x += mw->menu.horizontal_spacing;
779 y += (mw->menu.font->ascent - height) / 2;
780 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
784 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
785 top-left corner of the menu item. WIDTH is the width of the
786 separator to draw. TYPE is the separator type. */
788 static void
789 draw_separator (mw, window, x, y, width, type)
790 XlwMenuWidget mw;
791 Window window;
792 int x, y, width;
793 enum menu_separator type;
795 Display *dpy = XtDisplay (mw);
796 XGCValues xgcv;
798 switch (type)
800 case SEPARATOR_NO_LINE:
801 break;
803 case SEPARATOR_SINGLE_LINE:
804 XDrawLine (dpy, window, mw->menu.foreground_gc,
805 x, y, x + width, y);
806 break;
808 case SEPARATOR_DOUBLE_LINE:
809 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
810 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
811 break;
813 case SEPARATOR_SINGLE_DASHED_LINE:
814 xgcv.line_style = LineOnOffDash;
815 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
816 XDrawLine (dpy, window, mw->menu.foreground_gc,
817 x, y, x + width, y);
818 xgcv.line_style = LineSolid;
819 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
820 break;
822 case SEPARATOR_DOUBLE_DASHED_LINE:
823 draw_separator (mw, window, x, y, width,
824 SEPARATOR_SINGLE_DASHED_LINE);
825 draw_separator (mw, window, x, y + 2, width,
826 SEPARATOR_SINGLE_DASHED_LINE);
827 break;
829 case SEPARATOR_SHADOW_ETCHED_IN:
830 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
831 x, y, x + width, y);
832 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
833 x, y + 1, x + width, y + 1);
834 break;
836 case SEPARATOR_SHADOW_ETCHED_OUT:
837 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
838 x, y, x + width, y);
839 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
840 x, y + 1, x + width, y + 1);
841 break;
843 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
844 xgcv.line_style = LineOnOffDash;
845 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
846 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
847 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
848 xgcv.line_style = LineSolid;
849 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
850 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
851 break;
853 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
854 xgcv.line_style = LineOnOffDash;
855 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
856 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
857 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
858 xgcv.line_style = LineSolid;
859 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
860 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
861 break;
863 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
864 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
865 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
866 break;
868 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
869 draw_separator (mw, window, x, y, width,
870 SEPARATOR_SHADOW_ETCHED_OUT);
871 draw_separator (mw, window, x, y + 3, width,
872 SEPARATOR_SHADOW_ETCHED_OUT);
873 break;
875 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
876 xgcv.line_style = LineOnOffDash;
877 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
878 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
879 draw_separator (mw, window, x, y, width,
880 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
881 xgcv.line_style = LineSolid;
882 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
883 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
884 break;
886 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
887 xgcv.line_style = LineOnOffDash;
888 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
889 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
890 draw_separator (mw, window, x, y, width,
891 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
892 xgcv.line_style = LineSolid;
893 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
894 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
895 break;
897 default:
898 abort ();
903 /* Return the pixel height of menu separator SEPARATOR. */
905 static int
906 separator_height (separator)
907 enum menu_separator separator;
909 switch (separator)
911 case SEPARATOR_NO_LINE:
912 return 2;
914 case SEPARATOR_SINGLE_LINE:
915 case SEPARATOR_SINGLE_DASHED_LINE:
916 return 1;
918 case SEPARATOR_DOUBLE_LINE:
919 case SEPARATOR_DOUBLE_DASHED_LINE:
920 return 3;
922 case SEPARATOR_SHADOW_ETCHED_IN:
923 case SEPARATOR_SHADOW_ETCHED_OUT:
924 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
925 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
926 return 2;
928 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
929 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
930 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
931 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
932 return 5;
934 default:
935 abort ();
940 /* Display the menu item and increment where.x and where.y to show how large
941 the menu item was. */
943 static void
944 display_menu_item (mw, val, ws, where, highlighted_p, horizontal_p,
945 just_compute_p)
946 XlwMenuWidget mw;
947 widget_value* val;
948 window_state* ws;
949 XPoint* where;
950 Boolean highlighted_p;
951 Boolean horizontal_p;
952 Boolean just_compute_p;
954 GC deco_gc;
955 GC text_gc;
956 int font_ascent = mw->menu.font->ascent;
957 int font_descent = mw->menu.font->descent;
958 int shadow = mw->menu.shadow_thickness;
959 int margin = mw->menu.margin;
960 int h_spacing = mw->menu.horizontal_spacing;
961 int v_spacing = mw->menu.vertical_spacing;
962 int label_width;
963 int rest_width;
964 int button_width;
965 int height;
966 int width;
967 enum menu_separator separator;
968 int separator_p = lw_separator_p (val->name, &separator, 0);
970 /* compute the sizes of the item */
971 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
972 &button_width, &height);
974 if (horizontal_p)
975 width = label_width + rest_width;
976 else
978 label_width = ws->label_width;
979 width = ws->width - 2 * shadow;
982 /* Only highlight an enabled item that has a callback. */
983 if (highlighted_p)
984 if (!val->enabled || !(val->call_data || val->contents))
985 highlighted_p = 0;
987 /* do the drawing. */
988 if (!just_compute_p)
990 /* Add the shadow border of the containing menu */
991 int x = where->x + shadow;
992 int y = where->y + shadow;
994 if (horizontal_p)
996 x += margin;
997 y += margin;
1000 /* pick the foreground and background GC. */
1001 if (val->enabled)
1002 text_gc = mw->menu.foreground_gc;
1003 else
1004 text_gc = mw->menu.disabled_gc;
1005 deco_gc = mw->menu.foreground_gc;
1007 if (separator_p)
1009 draw_separator (mw, ws->window, x, y, width, separator);
1011 else
1013 int x_offset = x + h_spacing + shadow;
1014 char* display_string = resource_widget_value (mw, val);
1015 draw_shadow_rectangle (mw, ws->window, x, y, width, height, True,
1016 False);
1018 /* Deal with centering a menu title. */
1019 if (!horizontal_p && !val->contents && !val->call_data)
1021 int l = string_width (mw, display_string);
1023 if (width > l)
1024 x_offset = (width - l) >> 1;
1026 else if (!horizontal_p && ws->button_width)
1027 x_offset += ws->button_width;
1030 XDrawString (XtDisplay (mw), ws->window, text_gc, x_offset,
1031 y + v_spacing + shadow + font_ascent,
1032 display_string, strlen (display_string));
1034 if (!horizontal_p)
1036 if (val->button_type == BUTTON_TYPE_TOGGLE)
1037 draw_toggle (mw, ws->window, x, y + v_spacing + shadow,
1038 val->selected);
1039 else if (val->button_type == BUTTON_TYPE_RADIO)
1040 draw_radio (mw, ws->window, x, y + v_spacing + shadow,
1041 val->selected);
1043 if (val->contents)
1045 int a_w = arrow_width (mw);
1046 draw_arrow (mw, ws->window, deco_gc,
1047 x + width - a_w
1048 - mw->menu.horizontal_spacing
1049 - mw->menu.shadow_thickness,
1050 y + v_spacing + shadow, a_w,
1051 highlighted_p);
1053 else if (val->key)
1055 XDrawString (XtDisplay (mw), ws->window, text_gc,
1056 x + label_width + mw->menu.arrow_spacing,
1057 y + v_spacing + shadow + font_ascent,
1058 val->key, strlen (val->key));
1061 else
1063 XDrawRectangle (XtDisplay (mw), ws->window,
1064 mw->menu.background_gc,
1065 x + shadow, y + shadow,
1066 label_width + h_spacing - 1,
1067 font_ascent + font_descent + 2 * v_spacing - 1);
1068 draw_shadow_rectangle (mw, ws->window, x, y, width, height,
1069 True, False);
1072 if (highlighted_p)
1073 draw_shadow_rectangle (mw, ws->window, x, y, width, height, False,
1074 False);
1078 where->x += width;
1079 where->y += height;
1082 static void
1083 display_menu (mw, level, just_compute_p, highlighted_pos, hit, hit_return,
1084 this, that)
1085 XlwMenuWidget mw;
1086 int level;
1087 Boolean just_compute_p;
1088 XPoint* highlighted_pos;
1089 XPoint* hit;
1090 widget_value** hit_return;
1091 widget_value* this;
1092 widget_value* that;
1094 widget_value* val;
1095 widget_value* following_item;
1096 window_state* ws;
1097 XPoint where;
1098 int horizontal_p = mw->menu.horizontal && (level == 0);
1099 int highlighted_p;
1100 int just_compute_this_one_p;
1101 /* This is set nonzero if the element containing HIGHLIGHTED_POS
1102 is disabled, so that we do not return any subsequent element either. */
1103 int no_return = 0;
1104 enum menu_separator separator;
1106 if (level >= mw->menu.old_depth)
1107 abort_gracefully ((Widget) mw);
1109 if (level < mw->menu.old_depth - 1)
1110 following_item = mw->menu.old_stack [level + 1];
1111 else
1112 following_item = NULL;
1114 if (hit)
1115 *hit_return = NULL;
1117 where.x = 0;
1118 where.y = 0;
1120 ws = &mw->menu.windows [level];
1121 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1123 highlighted_p = val == following_item;
1124 if (highlighted_p && highlighted_pos)
1126 if (horizontal_p)
1127 highlighted_pos->x = where.x;
1128 else
1129 highlighted_pos->y = where.y;
1132 just_compute_this_one_p =
1133 just_compute_p || ((this || that) && val != this && val != that);
1135 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1136 just_compute_this_one_p);
1138 if (highlighted_p && highlighted_pos)
1140 if (horizontal_p)
1141 highlighted_pos->y = where.y;
1142 else
1143 highlighted_pos->x = where.x;
1146 if (hit
1147 && !*hit_return
1148 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1149 && !lw_separator_p (val->name, &separator, 0)
1150 && !no_return)
1152 if (val->enabled)
1153 *hit_return = val;
1154 else
1155 no_return = 1;
1158 if (horizontal_p)
1159 where.y = 0;
1160 else
1161 where.x = 0;
1164 if (!just_compute_p)
1165 draw_shadow_rectangle (mw, ws->window, 0, 0, ws->width, ws->height,
1166 False, False);
1169 \f/* Motion code */
1170 static void
1171 set_new_state (mw, val, level)
1172 XlwMenuWidget mw;
1173 widget_value* val;
1174 int level;
1176 int i;
1178 mw->menu.new_depth = 0;
1179 for (i = 0; i < level; i++)
1180 push_new_stack (mw, mw->menu.old_stack [i]);
1181 push_new_stack (mw, val);
1184 static void
1185 make_windows_if_needed (mw, n)
1186 XlwMenuWidget mw;
1187 int n;
1189 int i;
1190 int start_at;
1191 XSetWindowAttributes xswa;
1192 int mask;
1193 Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1194 window_state* windows;
1196 if (mw->menu.windows_length >= n)
1197 return;
1199 xswa.save_under = True;
1200 xswa.override_redirect = True;
1201 xswa.background_pixel = mw->core.background_pixel;
1202 xswa.border_pixel = mw->core.border_pixel;
1203 xswa.event_mask =
1204 ExposureMask | PointerMotionMask | PointerMotionHintMask
1205 | ButtonReleaseMask | ButtonPressMask;
1206 xswa.cursor = mw->menu.cursor_shape;
1207 mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
1208 | CWEventMask | CWCursor;
1210 if (!mw->menu.windows)
1212 mw->menu.windows =
1213 (window_state*)XtMalloc (n * sizeof (window_state));
1214 start_at = 0;
1216 else
1218 mw->menu.windows =
1219 (window_state*)XtRealloc ((char*)mw->menu.windows,
1220 n * sizeof (window_state));
1221 start_at = mw->menu.windows_length;
1223 mw->menu.windows_length = n;
1225 windows = mw->menu.windows;
1227 for (i = start_at; i < n; i++)
1229 windows [i].x = 0;
1230 windows [i].y = 0;
1231 windows [i].width = 1;
1232 windows [i].height = 1;
1233 windows [i].window =
1234 XCreateWindow (XtDisplay (mw), root, 0, 0, 1, 1,
1235 0, 0, CopyFromParent, CopyFromParent, mask, &xswa);
1239 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1242 xlwmenu_window_p (w, window)
1243 Widget w;
1244 Window window;
1246 XlwMenuWidget mw = (XlwMenuWidget) w;
1247 int i;
1249 for (i = 0; i < mw->menu.windows_length; ++i)
1250 if (window == mw->menu.windows[i].window)
1251 break;
1253 return i < mw->menu.windows_length;
1256 /* Make the window fit in the screen */
1257 static void
1258 fit_to_screen (mw, ws, previous_ws, horizontal_p)
1259 XlwMenuWidget mw;
1260 window_state* ws;
1261 window_state* previous_ws;
1262 Boolean horizontal_p;
1264 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1265 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1266 /* 1 if we are unable to avoid an overlap between
1267 this menu and the parent menu in the X dimension. */
1268 int horizontal_overlap = 0;
1270 if (ws->x < 0)
1271 ws->x = 0;
1272 else if (ws->x + ws->width > screen_width)
1274 if (!horizontal_p)
1275 /* The addition of shadow-thickness for a sub-menu's position is
1276 to reflect a similar adjustment when the menu is displayed to
1277 the right of the invoking menu-item; it makes the sub-menu
1278 look more `attached' to the menu-item. */
1279 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1280 else
1281 ws->x = screen_width - ws->width;
1282 if (ws->x < 0)
1284 ws->x = 0;
1285 horizontal_overlap = 1;
1288 /* If we overlap in X, try to avoid overlap in Y. */
1289 if (horizontal_overlap
1290 && ws->y < previous_ws->y + previous_ws->height
1291 && previous_ws->y < ws->y + ws->height)
1293 /* Put this menu right below or right above PREVIOUS_WS
1294 if there's room. */
1295 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1296 ws->y = previous_ws->y + previous_ws->height;
1297 else if (previous_ws->y - ws->height > 0)
1298 ws->y = previous_ws->y - ws->height;
1301 if (ws->y < 0)
1302 ws->y = 0;
1303 else if (ws->y + ws->height > screen_height)
1305 if (horizontal_p)
1306 ws->y = previous_ws->y - ws->height;
1307 else
1308 ws->y = screen_height - ws->height;
1309 if (ws->y < 0)
1310 ws->y = 0;
1314 /* Updates old_stack from new_stack and redisplays. */
1315 static void
1316 remap_menubar (mw)
1317 XlwMenuWidget mw;
1319 int i;
1320 int last_same;
1321 XPoint selection_position;
1322 int old_depth = mw->menu.old_depth;
1323 int new_depth = mw->menu.new_depth;
1324 widget_value** old_stack;
1325 widget_value** new_stack;
1326 window_state* windows;
1327 widget_value* old_selection;
1328 widget_value* new_selection;
1330 /* Check that enough windows and old_stack are ready. */
1331 make_windows_if_needed (mw, new_depth);
1332 make_old_stack_space (mw, new_depth);
1333 windows = mw->menu.windows;
1334 old_stack = mw->menu.old_stack;
1335 new_stack = mw->menu.new_stack;
1337 /* compute the last identical different entry */
1338 for (i = 1; i < old_depth && i < new_depth; i++)
1339 if (old_stack [i] != new_stack [i])
1340 break;
1341 last_same = i - 1;
1343 /* Memorize the previously selected item to be able to refresh it */
1344 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1345 if (old_selection && !old_selection->enabled)
1346 old_selection = NULL;
1347 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1348 if (new_selection && !new_selection->enabled)
1349 new_selection = NULL;
1351 /* Call callback when the hightlighted item changes. */
1352 if (old_selection || new_selection)
1353 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1354 (XtPointer) new_selection);
1356 /* updates old_state from new_state. It has to be done now because
1357 display_menu (called below) uses the old_stack to know what to display. */
1358 for (i = last_same + 1; i < new_depth; i++)
1359 old_stack [i] = new_stack [i];
1360 mw->menu.old_depth = new_depth;
1362 /* refresh the last selection */
1363 selection_position.x = 0;
1364 selection_position.y = 0;
1365 display_menu (mw, last_same, new_selection == old_selection,
1366 &selection_position, NULL, NULL, old_selection, new_selection);
1368 /* Now place the new menus. */
1369 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1371 window_state *previous_ws = &windows[i - 1];
1372 window_state *ws = &windows[i];
1374 ws->x = (previous_ws->x + selection_position.x
1375 + mw->menu.shadow_thickness);
1376 if (mw->menu.horizontal && i == 1)
1377 ws->x += mw->menu.margin;
1379 #if 0
1380 if (!mw->menu.horizontal || i > 1)
1381 ws->x += mw->menu.shadow_thickness;
1382 #endif
1384 ws->y = (previous_ws->y + selection_position.y
1385 + mw->menu.shadow_thickness);
1386 if (mw->menu.horizontal && i == 1)
1387 ws->y += mw->menu.margin;
1389 size_menu (mw, i);
1391 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1393 XClearWindow (XtDisplay (mw), ws->window);
1394 XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y,
1395 ws->width, ws->height);
1396 XMapRaised (XtDisplay (mw), ws->window);
1397 display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL);
1400 /* unmap the menus that popped down */
1401 for (i = new_depth - 1; i < old_depth; i++)
1402 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1403 XUnmapWindow (XtDisplay (mw), windows[i].window);
1406 static Boolean
1407 motion_event_is_in_menu (mw, ev, level, relative_pos)
1408 XlwMenuWidget mw;
1409 XMotionEvent* ev;
1410 int level;
1411 XPoint* relative_pos;
1413 window_state* ws = &mw->menu.windows [level];
1414 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1415 int x = ws->x + shadow;
1416 int y = ws->y + shadow;
1417 relative_pos->x = ev->x_root - x;
1418 relative_pos->y = ev->y_root - y;
1419 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1420 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1423 static Boolean
1424 map_event_to_widget_value (mw, ev, val, level)
1425 XlwMenuWidget mw;
1426 XMotionEvent* ev;
1427 widget_value** val;
1428 int* level;
1430 int i;
1431 XPoint relative_pos;
1432 window_state* ws;
1434 *val = NULL;
1436 /* Find the window */
1437 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1439 ws = &mw->menu.windows [i];
1440 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1442 display_menu (mw, i, True, NULL, &relative_pos, val, NULL, NULL);
1444 if (*val)
1446 *level = i + 1;
1447 return True;
1451 return False;
1454 \f/* Procedures */
1455 static void
1456 make_drawing_gcs (mw)
1457 XlwMenuWidget mw;
1459 XGCValues xgcv;
1460 float scale;
1462 xgcv.font = mw->menu.font->fid;
1463 xgcv.foreground = mw->menu.foreground;
1464 xgcv.background = mw->core.background_pixel;
1465 mw->menu.foreground_gc = XtGetGC ((Widget)mw,
1466 GCFont | GCForeground | GCBackground,
1467 &xgcv);
1469 xgcv.font = mw->menu.font->fid;
1470 xgcv.foreground = mw->menu.button_foreground;
1471 xgcv.background = mw->core.background_pixel;
1472 mw->menu.button_gc = XtGetGC ((Widget)mw,
1473 GCFont | GCForeground | GCBackground,
1474 &xgcv);
1476 xgcv.font = mw->menu.font->fid;
1477 xgcv.background = mw->core.background_pixel;
1479 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1481 /* Allocate color for disabled menu-items. */
1482 mw->menu.disabled_foreground = mw->menu.foreground;
1483 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1484 scale = 2.3;
1485 else
1486 scale = 0.55;
1488 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1489 mw->core.colormap,
1490 &mw->menu.disabled_foreground,
1491 scale,
1492 0x8000);
1494 if (mw->menu.foreground == mw->menu.disabled_foreground
1495 || mw->core.background_pixel == mw->menu.disabled_foreground)
1497 /* Too few colors, use stipple. */
1498 xgcv.foreground = mw->menu.foreground;
1499 xgcv.fill_style = FillStippled;
1500 xgcv.stipple = mw->menu.gray_pixmap;
1501 mw->menu.disabled_gc = XtGetGC ((Widget)mw,
1502 (GCFont | GCForeground | GCBackground
1503 | GCFillStyle | GCStipple), &xgcv);
1505 else
1507 /* Many colors available, use disabled pixel. */
1508 xgcv.foreground = mw->menu.disabled_foreground;
1509 mw->menu.disabled_gc = XtGetGC ((Widget)mw,
1510 (GCFont | GCForeground | GCBackground), &xgcv);
1513 xgcv.font = mw->menu.font->fid;
1514 xgcv.foreground = mw->menu.button_foreground;
1515 xgcv.background = mw->core.background_pixel;
1516 xgcv.fill_style = FillStippled;
1517 xgcv.stipple = mw->menu.gray_pixmap;
1518 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw,
1519 (GCFont | GCForeground | GCBackground
1520 | GCFillStyle | GCStipple), &xgcv);
1522 xgcv.font = mw->menu.font->fid;
1523 xgcv.foreground = mw->core.background_pixel;
1524 xgcv.background = mw->menu.foreground;
1525 mw->menu.background_gc = XtGetGC ((Widget)mw,
1526 GCFont | GCForeground | GCBackground,
1527 &xgcv);
1530 static void
1531 release_drawing_gcs (mw)
1532 XlwMenuWidget mw;
1534 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1535 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1536 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1537 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1538 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1539 /* let's get some segvs if we try to use these... */
1540 mw->menu.foreground_gc = (GC) -1;
1541 mw->menu.button_gc = (GC) -1;
1542 mw->menu.disabled_gc = (GC) -1;
1543 mw->menu.inactive_button_gc = (GC) -1;
1544 mw->menu.background_gc = (GC) -1;
1547 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1548 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1550 static void
1551 make_shadow_gcs (mw)
1552 XlwMenuWidget mw;
1554 XGCValues xgcv;
1555 unsigned long pm = 0;
1556 Display *dpy = XtDisplay ((Widget) mw);
1557 Screen *screen = XtScreen ((Widget) mw);
1558 Colormap cmap = mw->core.colormap;
1559 XColor topc, botc;
1560 int top_frobbed = 0, bottom_frobbed = 0;
1562 mw->menu.free_top_shadow_color_p = 0;
1563 mw->menu.free_bottom_shadow_color_p = 0;
1565 if (mw->menu.top_shadow_color == -1)
1566 mw->menu.top_shadow_color = mw->core.background_pixel;
1567 else
1568 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1570 if (mw->menu.bottom_shadow_color == -1)
1571 mw->menu.bottom_shadow_color = mw->menu.foreground;
1572 else
1573 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1575 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1576 mw->menu.top_shadow_color == mw->menu.foreground)
1578 topc.pixel = mw->core.background_pixel;
1579 #ifdef emacs
1580 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1581 &topc.pixel,
1582 1.2, 0x8000))
1583 #else
1584 XQueryColor (dpy, cmap, &topc);
1585 /* don't overflow/wrap! */
1586 topc.red = MINL (65535, topc.red * 1.2);
1587 topc.green = MINL (65535, topc.green * 1.2);
1588 topc.blue = MINL (65535, topc.blue * 1.2);
1589 if (XAllocColor (dpy, cmap, &topc))
1590 #endif
1592 mw->menu.top_shadow_color = topc.pixel;
1593 mw->menu.free_top_shadow_color_p = 1;
1594 top_frobbed = 1;
1597 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1598 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1600 botc.pixel = mw->core.background_pixel;
1601 #ifdef emacs
1602 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1603 &botc.pixel,
1604 0.6, 0x4000))
1605 #else
1606 XQueryColor (dpy, cmap, &botc);
1607 botc.red *= 0.6;
1608 botc.green *= 0.6;
1609 botc.blue *= 0.6;
1610 if (XAllocColor (dpy, cmap, &botc))
1611 #endif
1613 mw->menu.bottom_shadow_color = botc.pixel;
1614 mw->menu.free_bottom_shadow_color_p = 1;
1615 bottom_frobbed = 1;
1619 if (top_frobbed && bottom_frobbed)
1621 if (topc.pixel == botc.pixel)
1623 if (botc.pixel == mw->menu.foreground)
1625 if (mw->menu.free_top_shadow_color_p)
1627 x_free_dpy_colors (dpy, screen, cmap,
1628 &mw->menu.top_shadow_color, 1);
1629 mw->menu.free_top_shadow_color_p = 0;
1631 mw->menu.top_shadow_color = mw->core.background_pixel;
1633 else
1635 if (mw->menu.free_bottom_shadow_color_p)
1637 x_free_dpy_colors (dpy, screen, cmap,
1638 &mw->menu.bottom_shadow_color, 1);
1639 mw->menu.free_bottom_shadow_color_p = 0;
1641 mw->menu.bottom_shadow_color = mw->menu.foreground;
1646 if (!mw->menu.top_shadow_pixmap &&
1647 mw->menu.top_shadow_color == mw->core.background_pixel)
1649 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1650 if (mw->menu.free_top_shadow_color_p)
1652 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1653 mw->menu.free_top_shadow_color_p = 0;
1655 mw->menu.top_shadow_color = mw->menu.foreground;
1657 if (!mw->menu.bottom_shadow_pixmap &&
1658 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1660 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1661 if (mw->menu.free_bottom_shadow_color_p)
1663 x_free_dpy_colors (dpy, screen, cmap,
1664 &mw->menu.bottom_shadow_color, 1);
1665 mw->menu.free_bottom_shadow_color_p = 0;
1667 mw->menu.bottom_shadow_color = mw->menu.foreground;
1670 xgcv.fill_style = FillStippled;
1671 xgcv.foreground = mw->menu.top_shadow_color;
1672 xgcv.stipple = mw->menu.top_shadow_pixmap;
1673 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1674 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1676 xgcv.foreground = mw->menu.bottom_shadow_color;
1677 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1678 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1679 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1683 static void
1684 release_shadow_gcs (mw)
1685 XlwMenuWidget mw;
1687 Display *dpy = XtDisplay ((Widget) mw);
1688 Screen *screen = XtScreen ((Widget) mw);
1689 Colormap cmap = mw->core.colormap;
1690 Pixel px[2];
1691 int i = 0;
1693 if (mw->menu.free_top_shadow_color_p)
1694 px[i++] = mw->menu.top_shadow_color;
1695 if (mw->menu.free_bottom_shadow_color_p)
1696 px[i++] = mw->menu.bottom_shadow_color;
1697 if (i > 0)
1698 x_free_dpy_colors (dpy, screen, cmap, px, i);
1700 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1701 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1704 static void
1705 XlwMenuInitialize (request, mw, args, num_args)
1706 Widget request;
1707 XlwMenuWidget mw;
1708 ArgList args;
1709 Cardinal *num_args;
1711 /* Get the GCs and the widget size */
1713 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1714 Display* display = XtDisplay (mw);
1716 #if 0
1717 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1719 /* _XtCreate is freeing the object that was passed to us,
1720 so make a copy that we will actually keep. */
1721 lwlib_bcopy (mw->menu.contents, tem, sizeof (widget_value));
1722 mw->menu.contents = tem;
1723 #endif
1725 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1726 mw->menu.cursor = mw->menu.cursor_shape;
1728 mw->menu.gray_pixmap
1729 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1730 gray_bitmap_width, gray_bitmap_height,
1731 (unsigned long)1, (unsigned long)0, 1);
1733 /* I don't understand why this ends up 0 sometimes,
1734 but it does. This kludge works around it.
1735 Can anyone find a real fix? -- rms. */
1736 if (mw->menu.font == 0)
1737 mw->menu.font = xlwmenu_default_font;
1739 make_drawing_gcs (mw);
1740 make_shadow_gcs (mw);
1742 mw->menu.popped_up = False;
1744 mw->menu.old_depth = 1;
1745 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1746 mw->menu.old_stack_length = 1;
1747 mw->menu.old_stack [0] = mw->menu.contents;
1749 mw->menu.new_depth = 0;
1750 mw->menu.new_stack = 0;
1751 mw->menu.new_stack_length = 0;
1752 push_new_stack (mw, mw->menu.contents);
1754 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1755 mw->menu.windows_length = 1;
1756 mw->menu.windows [0].x = 0;
1757 mw->menu.windows [0].y = 0;
1758 mw->menu.windows [0].width = 0;
1759 mw->menu.windows [0].height = 0;
1760 size_menu (mw, 0);
1762 mw->core.width = mw->menu.windows [0].width;
1763 mw->core.height = mw->menu.windows [0].height;
1766 static void
1767 XlwMenuClassInitialize ()
1771 static void
1772 XlwMenuRealize (w, valueMask, attributes)
1773 Widget w;
1774 Mask *valueMask;
1775 XSetWindowAttributes *attributes;
1777 XlwMenuWidget mw = (XlwMenuWidget)w;
1778 XSetWindowAttributes xswa;
1779 int mask;
1781 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1782 (w, valueMask, attributes);
1784 xswa.save_under = True;
1785 xswa.cursor = mw->menu.cursor_shape;
1786 mask = CWSaveUnder | CWCursor;
1787 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1789 mw->menu.windows [0].window = XtWindow (w);
1790 mw->menu.windows [0].x = w->core.x;
1791 mw->menu.windows [0].y = w->core.y;
1792 mw->menu.windows [0].width = w->core.width;
1793 mw->menu.windows [0].height = w->core.height;
1796 /* Only the toplevel menubar/popup is a widget so it's the only one that
1797 receives expose events through Xt. So we repaint all the other panes
1798 when receiving an Expose event. */
1799 static void
1800 XlwMenuRedisplay (w, ev, region)
1801 Widget w;
1802 XEvent* ev;
1803 Region region;
1805 XlwMenuWidget mw = (XlwMenuWidget)w;
1806 int i;
1808 /* If we have a depth beyond 1, it's because a submenu was displayed.
1809 If the submenu has been destroyed, set the depth back to 1. */
1810 if (submenu_destroyed)
1812 mw->menu.old_depth = 1;
1813 submenu_destroyed = 0;
1816 for (i = 0; i < mw->menu.old_depth; i++)
1817 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1821 /* Part of a hack to make the menu redisplay when a tooltip frame
1822 over a menu item is unmapped. */
1824 void
1825 xlwmenu_redisplay (w)
1826 Widget w;
1828 XlwMenuRedisplay (w, NULL, None);
1831 static void
1832 XlwMenuDestroy (w)
1833 Widget w;
1835 int i;
1836 XlwMenuWidget mw = (XlwMenuWidget) w;
1838 if (pointer_grabbed)
1839 ungrab_all ((Widget)w, CurrentTime);
1840 pointer_grabbed = 0;
1842 submenu_destroyed = 1;
1844 release_drawing_gcs (mw);
1845 release_shadow_gcs (mw);
1847 /* this doesn't come from the resource db but is created explicitly
1848 so we must free it ourselves. */
1849 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1850 mw->menu.gray_pixmap = (Pixmap) -1;
1852 #if 0
1853 /* Do free mw->menu.contents because nowadays we copy it
1854 during initialization. */
1855 XtFree (mw->menu.contents);
1856 #endif
1858 /* Don't free mw->menu.contents because that comes from our creator.
1859 The `*_stack' elements are just pointers into `contents' so leave
1860 that alone too. But free the stacks themselves. */
1861 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1862 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1864 /* Remember, you can't free anything that came from the resource
1865 database. This includes:
1866 mw->menu.cursor
1867 mw->menu.top_shadow_pixmap
1868 mw->menu.bottom_shadow_pixmap
1869 mw->menu.font
1870 Also the color cells of top_shadow_color, bottom_shadow_color,
1871 foreground, and button_foreground will never be freed until this
1872 client exits. Nice, eh?
1875 /* start from 1 because the one in slot 0 is w->core.window */
1876 for (i = 1; i < mw->menu.windows_length; i++)
1877 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1878 if (mw->menu.windows)
1879 XtFree ((char *) mw->menu.windows);
1882 static Boolean
1883 XlwMenuSetValues (current, request, new)
1884 Widget current;
1885 Widget request;
1886 Widget new;
1888 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1889 XlwMenuWidget newmw = (XlwMenuWidget)new;
1890 Boolean redisplay = False;
1891 int i;
1893 if (newmw->menu.contents
1894 && newmw->menu.contents->contents
1895 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1896 redisplay = True;
1897 /* Do redisplay if the contents are entirely eliminated. */
1898 if (newmw->menu.contents
1899 && newmw->menu.contents->contents == 0
1900 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1901 redisplay = True;
1903 if (newmw->core.background_pixel != oldmw->core.background_pixel
1904 || newmw->menu.foreground != oldmw->menu.foreground
1905 || newmw->menu.font != oldmw->menu.font)
1907 release_drawing_gcs (newmw);
1908 make_drawing_gcs (newmw);
1910 release_shadow_gcs (newmw);
1911 /* Cause the shadow colors to be recalculated. */
1912 newmw->menu.top_shadow_color = -1;
1913 newmw->menu.bottom_shadow_color = -1;
1914 make_shadow_gcs (newmw);
1916 redisplay = True;
1918 if (XtIsRealized (current))
1919 /* If the menu is currently displayed, change the display. */
1920 for (i = 0; i < oldmw->menu.windows_length; i++)
1922 XSetWindowBackground (XtDisplay (oldmw),
1923 oldmw->menu.windows [i].window,
1924 newmw->core.background_pixel);
1925 /* clear windows and generate expose events */
1926 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
1927 0, 0, 0, 0, True);
1931 return redisplay;
1934 static void
1935 XlwMenuResize (w)
1936 Widget w;
1938 XlwMenuWidget mw = (XlwMenuWidget)w;
1940 if (mw->menu.popped_up)
1942 /* Don't allow the popup menu to resize itself. */
1943 mw->core.width = mw->menu.windows [0].width;
1944 mw->core.height = mw->menu.windows [0].height;
1945 mw->core.parent->core.width = mw->core.width ;
1946 mw->core.parent->core.height = mw->core.height ;
1948 else
1950 mw->menu.windows [0].width = mw->core.width;
1951 mw->menu.windows [0].height = mw->core.height;
1955 \f/* Action procedures */
1956 static void
1957 handle_single_motion_event (mw, ev)
1958 XlwMenuWidget mw;
1959 XMotionEvent* ev;
1961 widget_value* val;
1962 int level;
1964 if (!map_event_to_widget_value (mw, ev, &val, &level))
1965 pop_new_stack_if_no_contents (mw);
1966 else
1967 set_new_state (mw, val, level);
1968 remap_menubar (mw);
1970 /* Sync with the display. Makes it feel better on X terms. */
1971 XSync (XtDisplay (mw), False);
1974 static void
1975 handle_motion_event (mw, ev)
1976 XlwMenuWidget mw;
1977 XMotionEvent* ev;
1979 int x = ev->x_root;
1980 int y = ev->y_root;
1981 int state = ev->state;
1983 handle_single_motion_event (mw, ev);
1985 /* allow motion events to be generated again */
1986 if (ev->is_hint
1987 && XQueryPointer (XtDisplay (mw), ev->window,
1988 &ev->root, &ev->subwindow,
1989 &ev->x_root, &ev->y_root,
1990 &ev->x, &ev->y,
1991 &ev->state)
1992 && ev->state == state
1993 && (ev->x_root != x || ev->y_root != y))
1994 handle_single_motion_event (mw, ev);
1997 static void
1998 Start (w, ev, params, num_params)
1999 Widget w;
2000 XEvent *ev;
2001 String *params;
2002 Cardinal *num_params;
2004 XlwMenuWidget mw = (XlwMenuWidget)w;
2006 if (!mw->menu.popped_up)
2008 menu_post_event = *ev;
2009 /* If event is set to CurrentTime, get the last known time stamp.
2010 This is for calculating if (popup) menus should stay up after
2011 a fast click. */
2012 if (menu_post_event.xbutton.time == CurrentTime)
2013 menu_post_event.xbutton.time
2014 = XtLastTimestampProcessed (XtDisplay (w));
2016 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2018 else
2020 /* If we push a button while the menu is posted semipermanently,
2021 releasing the button should always pop the menu down. */
2022 next_release_must_exit = 1;
2024 /* notes the absolute position of the menubar window */
2025 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2026 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2028 /* handles the down like a move, slots are compatible */
2029 handle_motion_event (mw, &ev->xmotion);
2033 static void
2034 Drag (w, ev, params, num_params)
2035 Widget w;
2036 XEvent *ev;
2037 String *params;
2038 Cardinal *num_params;
2040 XlwMenuWidget mw = (XlwMenuWidget)w;
2041 if (mw->menu.popped_up)
2042 handle_motion_event (mw, &ev->xmotion);
2045 /* Do nothing.
2046 This is how we handle presses and releases of modifier keys. */
2047 static void
2048 Nothing (w, ev, params, num_params)
2049 Widget w;
2050 XEvent *ev;
2051 String *params;
2052 Cardinal *num_params;
2056 static widget_value *
2057 find_first_selectable (mw, item, skip_no_call_data)
2058 XlwMenuWidget mw;
2059 widget_value *item;
2060 int skip_no_call_data;
2062 widget_value *current = item;
2063 enum menu_separator separator;
2065 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2066 || (skip_no_call_data && !current->call_data))
2067 if (current->next)
2068 current=current->next;
2069 else
2070 return NULL;
2072 return current;
2075 static widget_value *
2076 find_next_selectable (mw, item, skip_no_call_data)
2077 XlwMenuWidget mw;
2078 widget_value *item;
2080 widget_value *current = item;
2081 enum menu_separator separator;
2083 while (current->next && (current=current->next) &&
2084 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2085 || (skip_no_call_data && !current->call_data)))
2088 if (current == item)
2090 if (mw->menu.old_depth < 2)
2091 return current;
2092 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2094 while (lw_separator_p (current->name, &separator, 0)
2095 || !current->enabled
2096 || (skip_no_call_data && !current->call_data))
2098 if (current->next)
2099 current=current->next;
2101 if (current == item)
2102 break;
2107 return current;
2110 static widget_value *
2111 find_prev_selectable (mw, item, skip_no_call_data)
2112 XlwMenuWidget mw;
2113 widget_value *item;
2115 widget_value *current = item;
2116 widget_value *prev = item;
2118 while ((current=find_next_selectable (mw, current, skip_no_call_data))
2119 != item)
2121 if (prev == current)
2122 break;
2123 prev=current;
2126 return prev;
2129 static void
2130 Down (w, ev, params, num_params)
2131 Widget w;
2132 XEvent *ev;
2133 String *params;
2134 Cardinal *num_params;
2136 XlwMenuWidget mw = (XlwMenuWidget) w;
2137 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2138 int popup_menu_p = mw->menu.top_depth == 1;
2140 /* Inside top-level menu-bar? */
2141 if (mw->menu.old_depth == mw->menu.top_depth)
2142 /* When <down> in the menu-bar is pressed, display the corresponding
2143 sub-menu and select the first selectable menu item there.
2144 If this is a popup menu, skip items with zero call data (title of
2145 the popup). */
2146 set_new_state (mw,
2147 find_first_selectable (mw,
2148 selected_item->contents,
2149 popup_menu_p),
2150 mw->menu.old_depth);
2151 else
2152 /* Highlight next possible (enabled and not separator) menu item. */
2153 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2154 mw->menu.old_depth - 1);
2156 remap_menubar (mw);
2159 static void
2160 Up (w, ev, params, num_params)
2161 Widget w;
2162 XEvent *ev;
2163 String *params;
2164 Cardinal *num_params;
2166 XlwMenuWidget mw = (XlwMenuWidget) w;
2167 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2168 int popup_menu_p = mw->menu.top_depth == 1;
2170 /* Inside top-level menu-bar? */
2171 if (mw->menu.old_depth == mw->menu.top_depth)
2173 /* FIXME: this is tricky. <up> in the menu-bar should select the
2174 last selectable item in the list. So we select the first
2175 selectable one and find the previous selectable item. Is there
2176 a better way? */
2177 /* If this is a popup menu, skip items with zero call data (title of
2178 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) */