(font-lock-extend-jit-lock-region-after-change): Add docstring.
[emacs.git] / lwlib / xlwmenu.c
blob77cc50e9d7ac1acd6e3e47928c61f8d683019332
1 /* Implements a lightweight menubar widget.
2 Copyright (C) 1992 Lucid, Inc.
3 Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
5 This file is part of the Lucid Widget Library.
7 The Lucid Widget Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
12 The Lucid Widget Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Emacs; see the file COPYING. If not, write to the
19 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA. */
22 /* Created by devin@lucid.com */
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include "lisp.h"
30 #include <stdio.h>
32 #include <sys/types.h>
33 #if (defined __sun) && !(defined SUNOS41)
34 #define SUNOS41
35 #include <X11/Xos.h>
36 #undef SUNOS41
37 #else
38 #include <X11/Xos.h>
39 #endif
40 #include <X11/IntrinsicP.h>
41 #include <X11/ObjectP.h>
42 #include <X11/StringDefs.h>
43 #include <X11/cursorfont.h>
44 #include "xlwmenuP.h"
46 #ifdef emacs
48 /* Defined in xfns.c. When config.h defines `static' as empty, we get
49 redefinition errors when gray_bitmap is included more than once, so
50 we're referring to the one include in xfns.c here. */
52 extern int gray_bitmap_width;
53 extern int gray_bitmap_height;
54 extern char *gray_bitmap_bits;
56 #include "xterm.h"
58 #else /* not emacs */
60 #include <X11/bitmaps/gray>
61 #define gray_bitmap_width gray_width
62 #define gray_bitmap_height gray_height
63 #define gray_bitmap_bits gray_bits
65 #endif /* not emacs */
67 static int pointer_grabbed;
68 static XEvent menu_post_event;
70 XFontStruct *xlwmenu_default_font;
72 static char
73 xlwMenuTranslations [] =
74 "<BtnDown>: start()\n\
75 <Motion>: drag()\n\
76 <BtnUp>: select()\n\
77 <Key>Shift_L: nothing()\n\
78 <Key>Shift_R: nothing()\n\
79 <Key>Meta_L: nothing()\n\
80 <Key>Meta_R: nothing()\n\
81 <Key>Control_L: nothing()\n\
82 <Key>Control_R: nothing()\n\
83 <Key>Hyper_L: nothing()\n\
84 <Key>Hyper_R: nothing()\n\
85 <Key>Super_L: nothing()\n\
86 <Key>Super_R: nothing()\n\
87 <Key>Alt_L: nothing()\n\
88 <Key>Alt_R: nothing()\n\
89 <Key>Caps_Lock: nothing()\n\
90 <Key>Shift_Lock: nothing()\n\
91 <KeyUp>Shift_L: nothing()\n\
92 <KeyUp>Shift_R: nothing()\n\
93 <KeyUp>Meta_L: nothing()\n\
94 <KeyUp>Meta_R: nothing()\n\
95 <KeyUp>Control_L: nothing()\n\
96 <KeyUp>Control_R: nothing()\n\
97 <KeyUp>Hyper_L: nothing()\n\
98 <KeyUp>Hyper_R: nothing()\n\
99 <KeyUp>Super_L: nothing()\n\
100 <KeyUp>Super_R: nothing()\n\
101 <KeyUp>Alt_L: nothing()\n\
102 <KeyUp>Alt_R: nothing()\n\
103 <KeyUp>Caps_Lock: nothing()\n\
104 <KeyUp>Shift_Lock:nothing()\n\
105 <Key>Return: select()\n\
106 <Key>Down: down()\n\
107 <Key>Up: up()\n\
108 <Key>Left: left()\n\
109 <Key>Right: right()\n\
110 <Key>: key()\n\
111 <KeyUp>: key()\n\
114 /* FIXME: Space should toggle toggleable menu item but not remove the menu
115 so you can toggle the next one without entering the menu again. */
117 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
119 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
121 #define offset(field) XtOffset(XlwMenuWidget, field)
122 static XtResource
123 xlwMenuResources[] =
125 #ifdef HAVE_X_I18N
126 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
127 offset(menu.fontSet), XtRFontSet, NULL},
128 #endif
129 {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
130 offset(menu.font), XtRString, "XtDefaultFont"},
131 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
132 offset(menu.foreground), XtRString, "XtDefaultForeground"},
133 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
134 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
135 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
136 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
137 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
138 offset(menu.margin), XtRImmediate, (XtPointer)1},
139 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
140 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
141 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
142 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
143 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
144 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
146 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
147 sizeof (Dimension), offset (menu.shadow_thickness),
148 XtRImmediate, (XtPointer)1},
149 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
150 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
151 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
152 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
153 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
154 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
155 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
156 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
158 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
159 offset(menu.open), XtRCallback, (XtPointer)NULL},
160 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
161 offset(menu.select), XtRCallback, (XtPointer)NULL},
162 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
163 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
164 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
165 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
166 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
167 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
168 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
169 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
171 #undef offset
173 static Boolean XlwMenuSetValues();
174 static void XlwMenuRealize();
175 static void XlwMenuRedisplay();
176 static void XlwMenuResize();
177 static void XlwMenuInitialize();
178 static void XlwMenuRedisplay();
179 static void XlwMenuDestroy();
180 static void XlwMenuClassInitialize();
181 static void Start();
182 static void Drag();
183 static void Down();
184 static void Up();
185 static void Left();
186 static void Right();
187 static void Select();
188 static void Key();
189 static void Nothing();
190 static int separator_height __P ((enum menu_separator));
191 static void pop_up_menu __P ((XlwMenuWidget, XButtonPressedEvent *));
194 static XtActionsRec
195 xlwMenuActionsList [] =
197 {"start", Start},
198 {"drag", Drag},
199 {"down", Down},
200 {"up", Up},
201 {"left", Left},
202 {"right", Right},
203 {"select", Select},
204 {"key", Key},
205 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
206 {"nothing", Nothing},
209 #define SuperClass ((CoreWidgetClass)&coreClassRec)
211 XlwMenuClassRec xlwMenuClassRec =
213 { /* CoreClass fields initialization */
214 (WidgetClass) SuperClass, /* superclass */
215 "XlwMenu", /* class_name */
216 sizeof(XlwMenuRec), /* size */
217 XlwMenuClassInitialize, /* class_initialize */
218 NULL, /* class_part_initialize */
219 FALSE, /* class_inited */
220 XlwMenuInitialize, /* initialize */
221 NULL, /* initialize_hook */
222 XlwMenuRealize, /* realize */
223 xlwMenuActionsList, /* actions */
224 XtNumber(xlwMenuActionsList), /* num_actions */
225 xlwMenuResources, /* resources */
226 XtNumber(xlwMenuResources), /* resource_count */
227 NULLQUARK, /* xrm_class */
228 TRUE, /* compress_motion */
229 XtExposeCompressMaximal, /* compress_exposure */
230 TRUE, /* compress_enterleave */
231 FALSE, /* visible_interest */
232 XlwMenuDestroy, /* destroy */
233 XlwMenuResize, /* resize */
234 XlwMenuRedisplay, /* expose */
235 XlwMenuSetValues, /* set_values */
236 NULL, /* set_values_hook */
237 XtInheritSetValuesAlmost, /* set_values_almost */
238 NULL, /* get_values_hook */
239 NULL, /* accept_focus */
240 XtVersion, /* version */
241 NULL, /* callback_private */
242 xlwMenuTranslations, /* tm_table */
243 XtInheritQueryGeometry, /* query_geometry */
244 XtInheritDisplayAccelerator, /* display_accelerator */
245 NULL /* extension */
246 }, /* XlwMenuClass fields initialization */
248 0 /* dummy */
252 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
254 int submenu_destroyed;
256 /* For debug, if installation-directory is non-nil this is not an installed
257 Emacs. In that case we do not grab the keyboard to make it easier to
258 debug. */
259 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
261 static int next_release_must_exit;
263 \f/* Utilities */
265 /* Ungrab pointer and keyboard */
266 static void
267 ungrab_all (w, ungrabtime)
268 Widget w;
269 Time ungrabtime;
271 XtUngrabPointer (w, ungrabtime);
272 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
275 /* Like abort, but remove grabs from widget W before. */
277 static void
278 abort_gracefully (w)
279 Widget w;
281 if (XtIsShell (XtParent (w)))
282 XtRemoveGrab (w);
283 ungrab_all (w, CurrentTime);
284 abort ();
287 static void
288 push_new_stack (mw, val)
289 XlwMenuWidget mw;
290 widget_value* val;
292 if (!mw->menu.new_stack)
294 mw->menu.new_stack_length = 10;
295 mw->menu.new_stack =
296 (widget_value**)XtCalloc (mw->menu.new_stack_length,
297 sizeof (widget_value*));
299 else if (mw->menu.new_depth == mw->menu.new_stack_length)
301 mw->menu.new_stack_length *= 2;
302 mw->menu.new_stack =
303 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
304 mw->menu.new_stack_length * sizeof (widget_value*));
306 mw->menu.new_stack [mw->menu.new_depth++] = val;
309 static void
310 pop_new_stack_if_no_contents (mw)
311 XlwMenuWidget mw;
313 if (mw->menu.new_depth > 1)
315 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
316 mw->menu.new_depth -= 1;
320 static void
321 make_old_stack_space (mw, n)
322 XlwMenuWidget mw;
323 int n;
325 if (!mw->menu.old_stack)
327 mw->menu.old_stack_length = 10;
328 mw->menu.old_stack =
329 (widget_value**)XtCalloc (mw->menu.old_stack_length,
330 sizeof (widget_value*));
332 else if (mw->menu.old_stack_length < n)
334 mw->menu.old_stack_length *= 2;
335 mw->menu.old_stack =
336 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
337 mw->menu.old_stack_length * sizeof (widget_value*));
341 \f/* Size code */
342 static int
343 string_width (mw, s)
344 XlwMenuWidget mw;
345 char *s;
347 XCharStruct xcs;
348 int drop;
349 #ifdef HAVE_X_I18N
350 XRectangle ink, logical;
351 if (mw->menu.fontSet)
353 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
354 return logical.width;
356 #endif
358 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
359 return xcs.width;
363 #ifdef HAVE_X_I18N
364 #define MENU_FONT_HEIGHT(mw) \
365 ((mw)->menu.fontSet != NULL \
366 ? (mw)->menu.font_extents->max_logical_extent.height \
367 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
368 #define MENU_FONT_ASCENT(mw) \
369 ((mw)->menu.fontSet != NULL \
370 ? - (mw)->menu.font_extents->max_logical_extent.y \
371 : (mw)->menu.font->ascent)
372 #else
373 #define MENU_FONT_HEIGHT(mw) \
374 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
375 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
376 #endif
378 static int
379 arrow_width (mw)
380 XlwMenuWidget mw;
382 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
385 /* Return the width of toggle buttons of widget MW. */
387 static int
388 toggle_button_width (mw)
389 XlwMenuWidget mw;
391 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
395 /* Return the width of radio buttons of widget MW. */
397 static int
398 radio_button_width (mw)
399 XlwMenuWidget mw;
401 return toggle_button_width (mw) * 1.41;
405 static XtResource
406 nameResource[] =
408 {"labelString", "LabelString", XtRString, sizeof(String),
409 0, XtRImmediate, 0},
412 static char*
413 resource_widget_value (mw, val)
414 XlwMenuWidget mw;
415 widget_value *val;
417 if (!val->toolkit_data)
419 char* resourced_name = NULL;
420 char* complete_name;
421 XtGetSubresources ((Widget) mw,
422 (XtPointer) &resourced_name,
423 val->name, val->name,
424 nameResource, 1, NULL, 0);
425 if (!resourced_name)
426 resourced_name = val->name;
427 if (!val->value)
429 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
430 strcpy (complete_name, resourced_name);
432 else
434 int complete_length =
435 strlen (resourced_name) + strlen (val->value) + 2;
436 complete_name = XtMalloc (complete_length);
437 *complete_name = 0;
438 strcat (complete_name, resourced_name);
439 strcat (complete_name, " ");
440 strcat (complete_name, val->value);
443 val->toolkit_data = complete_name;
444 val->free_toolkit_data = True;
446 return (char*)val->toolkit_data;
449 /* Returns the sizes of an item */
450 static void
451 size_menu_item (mw, val, horizontal_p, label_width, rest_width, button_width,
452 height)
453 XlwMenuWidget mw;
454 widget_value* val;
455 int horizontal_p;
456 int* label_width;
457 int* rest_width;
458 int* button_width;
459 int* height;
461 enum menu_separator separator;
463 if (lw_separator_p (val->name, &separator, 0))
465 *height = separator_height (separator);
466 *label_width = 1;
467 *rest_width = 0;
468 *button_width = 0;
470 else
472 *height = MENU_FONT_HEIGHT (mw)
473 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
475 *label_width =
476 string_width (mw, resource_widget_value (mw, val))
477 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
479 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
480 if (!horizontal_p)
482 if (val->contents)
483 /* Add width of the arrow displayed for submenus. */
484 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
485 else if (val->key)
486 /* Add width of key equivalent string. */
487 *rest_width += (string_width (mw, val->key)
488 + mw->menu.arrow_spacing);
490 if (val->button_type == BUTTON_TYPE_TOGGLE)
491 *button_width = (toggle_button_width (mw)
492 + mw->menu.horizontal_spacing);
493 else if (val->button_type == BUTTON_TYPE_RADIO)
494 *button_width = (radio_button_width (mw)
495 + mw->menu.horizontal_spacing);
500 static void
501 size_menu (mw, level)
502 XlwMenuWidget mw;
503 int level;
505 unsigned int label_width = 0;
506 int rest_width = 0;
507 int button_width = 0;
508 int max_rest_width = 0;
509 int max_button_width = 0;
510 unsigned int height = 0;
511 int horizontal_p = mw->menu.horizontal && (level == 0);
512 widget_value* val;
513 window_state* ws;
515 if (level >= mw->menu.old_depth)
516 abort_gracefully ((Widget) mw);
518 ws = &mw->menu.windows [level];
519 ws->width = 0;
520 ws->height = 0;
521 ws->label_width = 0;
522 ws->button_width = 0;
524 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
526 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
527 &button_width, &height);
528 if (horizontal_p)
530 ws->width += label_width + rest_width;
531 if (height > ws->height)
532 ws->height = height;
534 else
536 if (label_width > ws->label_width)
537 ws->label_width = label_width;
538 if (rest_width > max_rest_width)
539 max_rest_width = rest_width;
540 if (button_width > max_button_width)
541 max_button_width = button_width;
542 ws->height += height;
546 if (horizontal_p)
547 ws->label_width = ws->button_width = 0;
548 else
550 ws->width = ws->label_width + max_rest_width + max_button_width;
551 ws->button_width = max_button_width;
554 ws->width += 2 * mw->menu.shadow_thickness;
555 ws->height += 2 * mw->menu.shadow_thickness;
557 if (horizontal_p)
559 ws->width += 2 * mw->menu.margin;
560 ws->height += 2 * mw->menu.margin;
565 \f/* Display code */
567 static void
568 draw_arrow (mw, window, gc, x, y, width, down_p)
569 XlwMenuWidget mw;
570 Window window;
571 GC gc;
572 int x;
573 int y;
574 int width;
575 int down_p;
577 Display *dpy = XtDisplay (mw);
578 GC top_gc = mw->menu.shadow_top_gc;
579 GC bottom_gc = mw->menu.shadow_bottom_gc;
580 int thickness = mw->menu.shadow_thickness;
581 int height = width;
582 XPoint pt[10];
583 /* alpha = atan (0.5)
584 factor = (1 + sin (alpha)) / cos (alpha) */
585 double factor = 1.62;
586 int thickness2 = thickness * factor;
588 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
590 if (down_p)
592 GC temp;
593 temp = top_gc;
594 top_gc = bottom_gc;
595 bottom_gc = temp;
598 pt[0].x = x;
599 pt[0].y = y + height;
600 pt[1].x = x + thickness;
601 pt[1].y = y + height - thickness2;
602 pt[2].x = x + thickness2;
603 pt[2].y = y + thickness2;
604 pt[3].x = x;
605 pt[3].y = y;
606 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
608 pt[0].x = x;
609 pt[0].y = y;
610 pt[1].x = x + thickness;
611 pt[1].y = y + thickness2;
612 pt[2].x = x + width - thickness2;
613 pt[2].y = y + height / 2;
614 pt[3].x = x + width;
615 pt[3].y = y + height / 2;
616 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
618 pt[0].x = x;
619 pt[0].y = y + height;
620 pt[1].x = x + thickness;
621 pt[1].y = y + height - thickness2;
622 pt[2].x = x + width - thickness2;
623 pt[2].y = y + height / 2;
624 pt[3].x = x + width;
625 pt[3].y = y + height / 2;
626 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
631 static void
632 draw_shadow_rectangle (mw, window, x, y, width, height, erase_p, down_p)
633 XlwMenuWidget mw;
634 Window window;
635 int x;
636 int y;
637 int width;
638 int height;
639 int erase_p;
640 int down_p;
642 Display *dpy = XtDisplay (mw);
643 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
644 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
645 int thickness = mw->menu.shadow_thickness;
646 XPoint points [4];
648 if (!erase_p && down_p)
650 GC temp;
651 temp = top_gc;
652 top_gc = bottom_gc;
653 bottom_gc = temp;
656 points [0].x = x;
657 points [0].y = y;
658 points [1].x = x + width;
659 points [1].y = y;
660 points [2].x = x + width - thickness;
661 points [2].y = y + thickness;
662 points [3].x = x;
663 points [3].y = y + thickness;
664 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
665 points [0].x = x;
666 points [0].y = y + thickness;
667 points [1].x = x;
668 points [1].y = y + height;
669 points [2].x = x + thickness;
670 points [2].y = y + height - thickness;
671 points [3].x = x + thickness;
672 points [3].y = y + thickness;
673 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
674 points [0].x = x + width;
675 points [0].y = y;
676 points [1].x = x + width - thickness;
677 points [1].y = y + thickness;
678 points [2].x = x + width - thickness;
679 points [2].y = y + height - thickness;
680 points [3].x = x + width;
681 points [3].y = y + height - thickness;
682 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
683 points [0].x = x;
684 points [0].y = y + height;
685 points [1].x = x + width;
686 points [1].y = y + height;
687 points [2].x = x + width;
688 points [2].y = y + height - thickness;
689 points [3].x = x + thickness;
690 points [3].y = y + height - thickness;
691 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
695 static void
696 draw_shadow_rhombus (mw, window, x, y, width, height, erase_p, down_p)
697 XlwMenuWidget mw;
698 Window window;
699 int x;
700 int y;
701 int width;
702 int height;
703 int erase_p;
704 int down_p;
706 Display *dpy = XtDisplay (mw);
707 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
708 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
709 int thickness = mw->menu.shadow_thickness;
710 XPoint points [4];
712 if (!erase_p && down_p)
714 GC temp;
715 temp = top_gc;
716 top_gc = bottom_gc;
717 bottom_gc = temp;
720 points [0].x = x;
721 points [0].y = y + height / 2;
722 points [1].x = x + thickness;
723 points [1].y = y + height / 2;
724 points [2].x = x + width / 2;
725 points [2].y = y + thickness;
726 points [3].x = x + width / 2;
727 points [3].y = y;
728 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
729 points [0].x = x + width / 2;
730 points [0].y = y;
731 points [1].x = x + width / 2;
732 points [1].y = y + thickness;
733 points [2].x = x + width - thickness;
734 points [2].y = y + height / 2;
735 points [3].x = x + width;
736 points [3].y = y + height / 2;
737 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
738 points [0].x = x;
739 points [0].y = y + height / 2;
740 points [1].x = x + thickness;
741 points [1].y = y + height / 2;
742 points [2].x = x + width / 2;
743 points [2].y = y + height - thickness;
744 points [3].x = x + width / 2;
745 points [3].y = y + height;
746 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
747 points [0].x = x + width / 2;
748 points [0].y = y + height;
749 points [1].x = x + width / 2;
750 points [1].y = y + height - thickness;
751 points [2].x = x + width - thickness;
752 points [2].y = y + height / 2;
753 points [3].x = x + width;
754 points [3].y = y + height / 2;
755 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
759 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
760 top-left corner of the menu item. SELECTED_P non-zero means the
761 toggle button is selected. */
763 static void
764 draw_toggle (mw, window, x, y, selected_p)
765 XlwMenuWidget mw;
766 Window window;
767 int x, y, selected_p;
769 int width, height;
771 width = toggle_button_width (mw);
772 height = width;
773 x += mw->menu.horizontal_spacing;
774 y += (MENU_FONT_ASCENT (mw) - height) / 2;
775 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
779 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
780 top-left corner of the menu item. SELECTED_P non-zero means the
781 toggle button is selected. */
783 static void
784 draw_radio (mw, window, x, y, selected_p)
785 XlwMenuWidget mw;
786 Window window;
787 int x, y, selected_p;
789 int width, height;
791 width = radio_button_width (mw);
792 height = width;
793 x += mw->menu.horizontal_spacing;
794 y += (MENU_FONT_ASCENT (mw) - height) / 2;
795 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
799 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
800 top-left corner of the menu item. WIDTH is the width of the
801 separator to draw. TYPE is the separator type. */
803 static void
804 draw_separator (mw, window, x, y, width, type)
805 XlwMenuWidget mw;
806 Window window;
807 int x, y, width;
808 enum menu_separator type;
810 Display *dpy = XtDisplay (mw);
811 XGCValues xgcv;
813 switch (type)
815 case SEPARATOR_NO_LINE:
816 break;
818 case SEPARATOR_SINGLE_LINE:
819 XDrawLine (dpy, window, mw->menu.foreground_gc,
820 x, y, x + width, y);
821 break;
823 case SEPARATOR_DOUBLE_LINE:
824 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
825 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
826 break;
828 case SEPARATOR_SINGLE_DASHED_LINE:
829 xgcv.line_style = LineOnOffDash;
830 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
831 XDrawLine (dpy, window, mw->menu.foreground_gc,
832 x, y, x + width, y);
833 xgcv.line_style = LineSolid;
834 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
835 break;
837 case SEPARATOR_DOUBLE_DASHED_LINE:
838 draw_separator (mw, window, x, y, width,
839 SEPARATOR_SINGLE_DASHED_LINE);
840 draw_separator (mw, window, x, y + 2, width,
841 SEPARATOR_SINGLE_DASHED_LINE);
842 break;
844 case SEPARATOR_SHADOW_ETCHED_IN:
845 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
846 x, y, x + width, y);
847 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
848 x, y + 1, x + width, y + 1);
849 break;
851 case SEPARATOR_SHADOW_ETCHED_OUT:
852 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
853 x, y, x + width, y);
854 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
855 x, y + 1, x + width, y + 1);
856 break;
858 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
859 xgcv.line_style = LineOnOffDash;
860 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
861 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
862 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_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_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, SEPARATOR_SHADOW_ETCHED_OUT);
873 xgcv.line_style = LineSolid;
874 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
875 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
876 break;
878 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
879 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
880 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
881 break;
883 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
884 draw_separator (mw, window, x, y, width,
885 SEPARATOR_SHADOW_ETCHED_OUT);
886 draw_separator (mw, window, x, y + 3, width,
887 SEPARATOR_SHADOW_ETCHED_OUT);
888 break;
890 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
891 xgcv.line_style = LineOnOffDash;
892 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
893 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
894 draw_separator (mw, window, x, y, width,
895 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
896 xgcv.line_style = LineSolid;
897 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
898 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
899 break;
901 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
902 xgcv.line_style = LineOnOffDash;
903 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
904 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
905 draw_separator (mw, window, x, y, width,
906 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
907 xgcv.line_style = LineSolid;
908 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
909 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
910 break;
912 default:
913 abort ();
918 /* Return the pixel height of menu separator SEPARATOR. */
920 static int
921 separator_height (separator)
922 enum menu_separator separator;
924 switch (separator)
926 case SEPARATOR_NO_LINE:
927 return 2;
929 case SEPARATOR_SINGLE_LINE:
930 case SEPARATOR_SINGLE_DASHED_LINE:
931 return 1;
933 case SEPARATOR_DOUBLE_LINE:
934 case SEPARATOR_DOUBLE_DASHED_LINE:
935 return 3;
937 case SEPARATOR_SHADOW_ETCHED_IN:
938 case SEPARATOR_SHADOW_ETCHED_OUT:
939 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
940 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
941 return 2;
943 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
944 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
945 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
946 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
947 return 5;
949 default:
950 abort ();
955 /* Display the menu item and increment where.x and where.y to show how large
956 the menu item was. */
958 static void
959 display_menu_item (mw, val, ws, where, highlighted_p, horizontal_p,
960 just_compute_p)
961 XlwMenuWidget mw;
962 widget_value* val;
963 window_state* ws;
964 XPoint* where;
965 Boolean highlighted_p;
966 Boolean horizontal_p;
967 Boolean just_compute_p;
969 GC deco_gc;
970 GC text_gc;
971 int font_height = MENU_FONT_HEIGHT (mw);
972 int font_ascent = MENU_FONT_ASCENT (mw);
973 int shadow = mw->menu.shadow_thickness;
974 int margin = mw->menu.margin;
975 int h_spacing = mw->menu.horizontal_spacing;
976 int v_spacing = mw->menu.vertical_spacing;
977 int label_width;
978 int rest_width;
979 int button_width;
980 int height;
981 int width;
982 enum menu_separator separator;
983 int separator_p = lw_separator_p (val->name, &separator, 0);
985 /* compute the sizes of the item */
986 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
987 &button_width, &height);
989 if (horizontal_p)
990 width = label_width + rest_width;
991 else
993 label_width = ws->label_width;
994 width = ws->width - 2 * shadow;
997 /* Only highlight an enabled item that has a callback. */
998 if (highlighted_p)
999 if (!val->enabled || !(val->call_data || val->contents))
1000 highlighted_p = 0;
1002 /* do the drawing. */
1003 if (!just_compute_p)
1005 /* Add the shadow border of the containing menu */
1006 int x = where->x + shadow;
1007 int y = where->y + shadow;
1009 if (horizontal_p)
1011 x += margin;
1012 y += margin;
1015 /* pick the foreground and background GC. */
1016 if (val->enabled)
1017 text_gc = mw->menu.foreground_gc;
1018 else
1019 text_gc = mw->menu.disabled_gc;
1020 deco_gc = mw->menu.foreground_gc;
1022 if (separator_p)
1024 draw_separator (mw, ws->window, x, y, width, separator);
1026 else
1028 int x_offset = x + h_spacing + shadow;
1029 char* display_string = resource_widget_value (mw, val);
1030 draw_shadow_rectangle (mw, ws->window, x, y, width, height, True,
1031 False);
1033 /* Deal with centering a menu title. */
1034 if (!horizontal_p && !val->contents && !val->call_data)
1036 int l = string_width (mw, display_string);
1038 if (width > l)
1039 x_offset = (width - l) >> 1;
1041 else if (!horizontal_p && ws->button_width)
1042 x_offset += ws->button_width;
1045 #ifdef HAVE_X_I18N
1046 if (mw->menu.fontSet)
1047 XmbDrawString (XtDisplay (mw), ws->window, mw->menu.fontSet,
1048 text_gc, x_offset,
1049 y + v_spacing + shadow + font_ascent,
1050 display_string, strlen (display_string));
1051 else
1052 #endif
1053 XDrawString (XtDisplay (mw), ws->window,
1054 text_gc, x_offset,
1055 y + v_spacing + shadow + font_ascent,
1056 display_string, strlen (display_string));
1058 if (!horizontal_p)
1060 if (val->button_type == BUTTON_TYPE_TOGGLE)
1061 draw_toggle (mw, ws->window, x, y + v_spacing + shadow,
1062 val->selected);
1063 else if (val->button_type == BUTTON_TYPE_RADIO)
1064 draw_radio (mw, ws->window, x, y + v_spacing + shadow,
1065 val->selected);
1067 if (val->contents)
1069 int a_w = arrow_width (mw);
1070 draw_arrow (mw, ws->window, deco_gc,
1071 x + width - a_w
1072 - mw->menu.horizontal_spacing
1073 - mw->menu.shadow_thickness,
1074 y + v_spacing + shadow, a_w,
1075 highlighted_p);
1077 else if (val->key)
1079 #ifdef HAVE_X_I18N
1080 if (mw->menu.fontSet)
1081 XmbDrawString (XtDisplay (mw), ws->window,
1082 mw->menu.fontSet,
1083 text_gc,
1084 x + label_width + mw->menu.arrow_spacing,
1085 y + v_spacing + shadow + font_ascent,
1086 val->key, strlen (val->key));
1087 else
1088 #endif
1089 XDrawString (XtDisplay (mw), ws->window,
1090 text_gc,
1091 x + label_width + mw->menu.arrow_spacing,
1092 y + v_spacing + shadow + font_ascent,
1093 val->key, strlen (val->key));
1096 else
1098 XDrawRectangle (XtDisplay (mw), ws->window,
1099 mw->menu.background_gc,
1100 x + shadow, y + shadow,
1101 label_width + h_spacing - 1,
1102 font_height + 2 * v_spacing - 1);
1103 draw_shadow_rectangle (mw, ws->window, x, y, width, height,
1104 True, False);
1107 if (highlighted_p)
1108 draw_shadow_rectangle (mw, ws->window, x, y, width, height, False,
1109 False);
1113 where->x += width;
1114 where->y += height;
1117 static void
1118 display_menu (mw, level, just_compute_p, highlighted_pos, hit, hit_return,
1119 this, that)
1120 XlwMenuWidget mw;
1121 int level;
1122 Boolean just_compute_p;
1123 XPoint* highlighted_pos;
1124 XPoint* hit;
1125 widget_value** hit_return;
1126 widget_value* this;
1127 widget_value* that;
1129 widget_value* val;
1130 widget_value* following_item;
1131 window_state* ws;
1132 XPoint where;
1133 int horizontal_p = mw->menu.horizontal && (level == 0);
1134 int highlighted_p;
1135 int just_compute_this_one_p;
1136 /* This is set nonzero if the element containing HIGHLIGHTED_POS
1137 is disabled, so that we do not return any subsequent element either. */
1138 int no_return = 0;
1139 enum menu_separator separator;
1141 if (level >= mw->menu.old_depth)
1142 abort_gracefully ((Widget) mw);
1144 if (level < mw->menu.old_depth - 1)
1145 following_item = mw->menu.old_stack [level + 1];
1146 else
1147 following_item = NULL;
1149 if (hit)
1150 *hit_return = NULL;
1152 where.x = 0;
1153 where.y = 0;
1155 ws = &mw->menu.windows [level];
1156 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1158 highlighted_p = val == following_item;
1159 if (highlighted_p && highlighted_pos)
1161 if (horizontal_p)
1162 highlighted_pos->x = where.x;
1163 else
1164 highlighted_pos->y = where.y;
1167 just_compute_this_one_p =
1168 just_compute_p || ((this || that) && val != this && val != that);
1170 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1171 just_compute_this_one_p);
1173 if (highlighted_p && highlighted_pos)
1175 if (horizontal_p)
1176 highlighted_pos->y = where.y;
1177 else
1178 highlighted_pos->x = where.x;
1181 if (hit
1182 && !*hit_return
1183 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1184 && !lw_separator_p (val->name, &separator, 0)
1185 && !no_return)
1187 if (val->enabled)
1188 *hit_return = val;
1189 else
1190 no_return = 1;
1193 if (horizontal_p)
1194 where.y = 0;
1195 else
1196 where.x = 0;
1199 if (!just_compute_p)
1200 draw_shadow_rectangle (mw, ws->window, 0, 0, ws->width, ws->height,
1201 False, False);
1204 \f/* Motion code */
1205 static void
1206 set_new_state (mw, val, level)
1207 XlwMenuWidget mw;
1208 widget_value* val;
1209 int level;
1211 int i;
1213 mw->menu.new_depth = 0;
1214 for (i = 0; i < level; i++)
1215 push_new_stack (mw, mw->menu.old_stack [i]);
1216 push_new_stack (mw, val);
1219 static void
1220 make_windows_if_needed (mw, n)
1221 XlwMenuWidget mw;
1222 int n;
1224 int i;
1225 int start_at;
1226 XSetWindowAttributes xswa;
1227 int mask;
1228 Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1229 window_state* windows;
1231 if (mw->menu.windows_length >= n)
1232 return;
1234 xswa.save_under = True;
1235 xswa.override_redirect = True;
1236 xswa.background_pixel = mw->core.background_pixel;
1237 xswa.border_pixel = mw->core.border_pixel;
1238 xswa.event_mask =
1239 ExposureMask | PointerMotionMask | PointerMotionHintMask
1240 | ButtonReleaseMask | ButtonPressMask;
1241 xswa.cursor = mw->menu.cursor_shape;
1242 mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
1243 | CWEventMask | CWCursor;
1245 if (!mw->menu.windows)
1247 mw->menu.windows =
1248 (window_state*)XtMalloc (n * sizeof (window_state));
1249 start_at = 0;
1251 else
1253 mw->menu.windows =
1254 (window_state*)XtRealloc ((char*)mw->menu.windows,
1255 n * sizeof (window_state));
1256 start_at = mw->menu.windows_length;
1258 mw->menu.windows_length = n;
1260 windows = mw->menu.windows;
1262 for (i = start_at; i < n; i++)
1264 windows [i].x = 0;
1265 windows [i].y = 0;
1266 windows [i].width = 1;
1267 windows [i].height = 1;
1268 windows [i].window =
1269 XCreateWindow (XtDisplay (mw), root, 0, 0, 1, 1,
1270 0, 0, CopyFromParent, CopyFromParent, mask, &xswa);
1274 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1277 xlwmenu_window_p (w, window)
1278 Widget w;
1279 Window window;
1281 XlwMenuWidget mw = (XlwMenuWidget) w;
1282 int i;
1284 for (i = 0; i < mw->menu.windows_length; ++i)
1285 if (window == mw->menu.windows[i].window)
1286 break;
1288 return i < mw->menu.windows_length;
1291 /* Make the window fit in the screen */
1292 static void
1293 fit_to_screen (mw, ws, previous_ws, horizontal_p)
1294 XlwMenuWidget mw;
1295 window_state* ws;
1296 window_state* previous_ws;
1297 Boolean horizontal_p;
1299 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1300 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1301 /* 1 if we are unable to avoid an overlap between
1302 this menu and the parent menu in the X dimension. */
1303 int horizontal_overlap = 0;
1305 if (ws->x < 0)
1306 ws->x = 0;
1307 else if (ws->x + ws->width > screen_width)
1309 if (!horizontal_p)
1310 /* The addition of shadow-thickness for a sub-menu's position is
1311 to reflect a similar adjustment when the menu is displayed to
1312 the right of the invoking menu-item; it makes the sub-menu
1313 look more `attached' to the menu-item. */
1314 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1315 else
1316 ws->x = screen_width - ws->width;
1317 if (ws->x < 0)
1319 ws->x = 0;
1320 horizontal_overlap = 1;
1323 /* If we overlap in X, try to avoid overlap in Y. */
1324 if (horizontal_overlap
1325 && ws->y < previous_ws->y + previous_ws->height
1326 && previous_ws->y < ws->y + ws->height)
1328 /* Put this menu right below or right above PREVIOUS_WS
1329 if there's room. */
1330 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1331 ws->y = previous_ws->y + previous_ws->height;
1332 else if (previous_ws->y - ws->height > 0)
1333 ws->y = previous_ws->y - ws->height;
1336 if (ws->y < 0)
1337 ws->y = 0;
1338 else if (ws->y + ws->height > screen_height)
1340 if (horizontal_p)
1341 ws->y = previous_ws->y - ws->height;
1342 else
1343 ws->y = screen_height - ws->height;
1344 if (ws->y < 0)
1345 ws->y = 0;
1349 /* Updates old_stack from new_stack and redisplays. */
1350 static void
1351 remap_menubar (mw)
1352 XlwMenuWidget mw;
1354 int i;
1355 int last_same;
1356 XPoint selection_position;
1357 int old_depth = mw->menu.old_depth;
1358 int new_depth = mw->menu.new_depth;
1359 widget_value** old_stack;
1360 widget_value** new_stack;
1361 window_state* windows;
1362 widget_value* old_selection;
1363 widget_value* new_selection;
1365 /* Check that enough windows and old_stack are ready. */
1366 make_windows_if_needed (mw, new_depth);
1367 make_old_stack_space (mw, new_depth);
1368 windows = mw->menu.windows;
1369 old_stack = mw->menu.old_stack;
1370 new_stack = mw->menu.new_stack;
1372 /* compute the last identical different entry */
1373 for (i = 1; i < old_depth && i < new_depth; i++)
1374 if (old_stack [i] != new_stack [i])
1375 break;
1376 last_same = i - 1;
1378 /* Memorize the previously selected item to be able to refresh it */
1379 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1380 if (old_selection && !old_selection->enabled)
1381 old_selection = NULL;
1382 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1383 if (new_selection && !new_selection->enabled)
1384 new_selection = NULL;
1386 /* Call callback when the hightlighted item changes. */
1387 if (old_selection || new_selection)
1388 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1389 (XtPointer) new_selection);
1391 /* updates old_state from new_state. It has to be done now because
1392 display_menu (called below) uses the old_stack to know what to display. */
1393 for (i = last_same + 1; i < new_depth; i++)
1394 old_stack [i] = new_stack [i];
1395 mw->menu.old_depth = new_depth;
1397 /* refresh the last selection */
1398 selection_position.x = 0;
1399 selection_position.y = 0;
1400 display_menu (mw, last_same, new_selection == old_selection,
1401 &selection_position, NULL, NULL, old_selection, new_selection);
1403 /* Now place the new menus. */
1404 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1406 window_state *previous_ws = &windows[i - 1];
1407 window_state *ws = &windows[i];
1409 ws->x = (previous_ws->x + selection_position.x
1410 + mw->menu.shadow_thickness);
1411 if (mw->menu.horizontal && i == 1)
1412 ws->x += mw->menu.margin;
1414 #if 0
1415 if (!mw->menu.horizontal || i > 1)
1416 ws->x += mw->menu.shadow_thickness;
1417 #endif
1419 ws->y = (previous_ws->y + selection_position.y
1420 + mw->menu.shadow_thickness);
1421 if (mw->menu.horizontal && i == 1)
1422 ws->y += mw->menu.margin;
1424 size_menu (mw, i);
1426 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1428 XClearWindow (XtDisplay (mw), ws->window);
1429 XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y,
1430 ws->width, ws->height);
1431 XMapRaised (XtDisplay (mw), ws->window);
1432 display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL);
1435 /* unmap the menus that popped down */
1436 for (i = new_depth - 1; i < old_depth; i++)
1437 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1438 XUnmapWindow (XtDisplay (mw), windows[i].window);
1441 static Boolean
1442 motion_event_is_in_menu (mw, ev, level, relative_pos)
1443 XlwMenuWidget mw;
1444 XMotionEvent* ev;
1445 int level;
1446 XPoint* relative_pos;
1448 window_state* ws = &mw->menu.windows [level];
1449 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1450 int x = ws->x + shadow;
1451 int y = ws->y + shadow;
1452 relative_pos->x = ev->x_root - x;
1453 relative_pos->y = ev->y_root - y;
1454 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1455 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1458 static Boolean
1459 map_event_to_widget_value (mw, ev, val, level)
1460 XlwMenuWidget mw;
1461 XMotionEvent* ev;
1462 widget_value** val;
1463 int* level;
1465 int i;
1466 XPoint relative_pos;
1467 window_state* ws;
1469 *val = NULL;
1471 /* Find the window */
1472 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1474 ws = &mw->menu.windows [i];
1475 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1477 display_menu (mw, i, True, NULL, &relative_pos, val, NULL, NULL);
1479 if (*val)
1481 *level = i + 1;
1482 return True;
1486 return False;
1489 \f/* Procedures */
1490 static void
1491 make_drawing_gcs (mw)
1492 XlwMenuWidget mw;
1494 XGCValues xgcv;
1495 float scale;
1496 XtGCMask mask = GCForeground | GCBackground;
1498 #ifdef HAVE_X_I18N
1499 if (!mw->menu.fontSet)
1501 xgcv.font = mw->menu.font->fid;
1502 mask |= GCFont;
1504 #else
1505 xgcv.font = mw->menu.font->fid;
1506 mask |= GCFont;
1507 #endif
1508 xgcv.foreground = mw->menu.foreground;
1509 xgcv.background = mw->core.background_pixel;
1510 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1512 xgcv.foreground = mw->menu.button_foreground;
1513 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1515 xgcv.background = mw->core.background_pixel;
1517 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1519 /* Allocate color for disabled menu-items. */
1520 mw->menu.disabled_foreground = mw->menu.foreground;
1521 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1522 scale = 2.3;
1523 else
1524 scale = 0.55;
1526 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1527 mw->core.colormap,
1528 &mw->menu.disabled_foreground,
1529 scale,
1530 0x8000);
1532 if (mw->menu.foreground == mw->menu.disabled_foreground
1533 || mw->core.background_pixel == mw->menu.disabled_foreground)
1535 /* Too few colors, use stipple. */
1536 xgcv.foreground = mw->menu.foreground;
1537 xgcv.fill_style = FillStippled;
1538 xgcv.stipple = mw->menu.gray_pixmap;
1539 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1540 | GCFillStyle | GCStipple, &xgcv);
1542 else
1544 /* Many colors available, use disabled pixel. */
1545 xgcv.foreground = mw->menu.disabled_foreground;
1546 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1549 xgcv.foreground = mw->menu.button_foreground;
1550 xgcv.background = mw->core.background_pixel;
1551 xgcv.fill_style = FillStippled;
1552 xgcv.stipple = mw->menu.gray_pixmap;
1553 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1554 | GCFillStyle | GCStipple, &xgcv);
1556 xgcv.foreground = mw->core.background_pixel;
1557 xgcv.background = mw->menu.foreground;
1558 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1561 static void
1562 release_drawing_gcs (mw)
1563 XlwMenuWidget mw;
1565 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1566 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1567 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1568 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1569 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1570 /* let's get some segvs if we try to use these... */
1571 mw->menu.foreground_gc = (GC) -1;
1572 mw->menu.button_gc = (GC) -1;
1573 mw->menu.disabled_gc = (GC) -1;
1574 mw->menu.inactive_button_gc = (GC) -1;
1575 mw->menu.background_gc = (GC) -1;
1578 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1579 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1581 static void
1582 make_shadow_gcs (mw)
1583 XlwMenuWidget mw;
1585 XGCValues xgcv;
1586 unsigned long pm = 0;
1587 Display *dpy = XtDisplay ((Widget) mw);
1588 Screen *screen = XtScreen ((Widget) mw);
1589 Colormap cmap = mw->core.colormap;
1590 XColor topc, botc;
1591 int top_frobbed = 0, bottom_frobbed = 0;
1593 mw->menu.free_top_shadow_color_p = 0;
1594 mw->menu.free_bottom_shadow_color_p = 0;
1596 if (mw->menu.top_shadow_color == -1)
1597 mw->menu.top_shadow_color = mw->core.background_pixel;
1598 else
1599 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1601 if (mw->menu.bottom_shadow_color == -1)
1602 mw->menu.bottom_shadow_color = mw->menu.foreground;
1603 else
1604 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1606 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1607 mw->menu.top_shadow_color == mw->menu.foreground)
1609 topc.pixel = mw->core.background_pixel;
1610 #ifdef emacs
1611 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1612 &topc.pixel,
1613 1.2, 0x8000))
1614 #else
1615 XQueryColor (dpy, cmap, &topc);
1616 /* don't overflow/wrap! */
1617 topc.red = MINL (65535, topc.red * 1.2);
1618 topc.green = MINL (65535, topc.green * 1.2);
1619 topc.blue = MINL (65535, topc.blue * 1.2);
1620 if (XAllocColor (dpy, cmap, &topc))
1621 #endif
1623 mw->menu.top_shadow_color = topc.pixel;
1624 mw->menu.free_top_shadow_color_p = 1;
1625 top_frobbed = 1;
1628 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1629 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1631 botc.pixel = mw->core.background_pixel;
1632 #ifdef emacs
1633 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1634 &botc.pixel,
1635 0.6, 0x4000))
1636 #else
1637 XQueryColor (dpy, cmap, &botc);
1638 botc.red *= 0.6;
1639 botc.green *= 0.6;
1640 botc.blue *= 0.6;
1641 if (XAllocColor (dpy, cmap, &botc))
1642 #endif
1644 mw->menu.bottom_shadow_color = botc.pixel;
1645 mw->menu.free_bottom_shadow_color_p = 1;
1646 bottom_frobbed = 1;
1650 if (top_frobbed && bottom_frobbed)
1652 if (topc.pixel == botc.pixel)
1654 if (botc.pixel == mw->menu.foreground)
1656 if (mw->menu.free_top_shadow_color_p)
1658 x_free_dpy_colors (dpy, screen, cmap,
1659 &mw->menu.top_shadow_color, 1);
1660 mw->menu.free_top_shadow_color_p = 0;
1662 mw->menu.top_shadow_color = mw->core.background_pixel;
1664 else
1666 if (mw->menu.free_bottom_shadow_color_p)
1668 x_free_dpy_colors (dpy, screen, cmap,
1669 &mw->menu.bottom_shadow_color, 1);
1670 mw->menu.free_bottom_shadow_color_p = 0;
1672 mw->menu.bottom_shadow_color = mw->menu.foreground;
1677 if (!mw->menu.top_shadow_pixmap &&
1678 mw->menu.top_shadow_color == mw->core.background_pixel)
1680 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1681 if (mw->menu.free_top_shadow_color_p)
1683 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1684 mw->menu.free_top_shadow_color_p = 0;
1686 mw->menu.top_shadow_color = mw->menu.foreground;
1688 if (!mw->menu.bottom_shadow_pixmap &&
1689 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1691 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1692 if (mw->menu.free_bottom_shadow_color_p)
1694 x_free_dpy_colors (dpy, screen, cmap,
1695 &mw->menu.bottom_shadow_color, 1);
1696 mw->menu.free_bottom_shadow_color_p = 0;
1698 mw->menu.bottom_shadow_color = mw->menu.foreground;
1701 xgcv.fill_style = FillStippled;
1702 xgcv.foreground = mw->menu.top_shadow_color;
1703 xgcv.stipple = mw->menu.top_shadow_pixmap;
1704 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1705 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1707 xgcv.foreground = mw->menu.bottom_shadow_color;
1708 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1709 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1710 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1714 static void
1715 release_shadow_gcs (mw)
1716 XlwMenuWidget mw;
1718 Display *dpy = XtDisplay ((Widget) mw);
1719 Screen *screen = XtScreen ((Widget) mw);
1720 Colormap cmap = mw->core.colormap;
1721 Pixel px[2];
1722 int i = 0;
1724 if (mw->menu.free_top_shadow_color_p)
1725 px[i++] = mw->menu.top_shadow_color;
1726 if (mw->menu.free_bottom_shadow_color_p)
1727 px[i++] = mw->menu.bottom_shadow_color;
1728 if (i > 0)
1729 x_free_dpy_colors (dpy, screen, cmap, px, i);
1731 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1732 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1735 static void
1736 XlwMenuInitialize (request, mw, args, num_args)
1737 Widget request;
1738 XlwMenuWidget mw;
1739 ArgList args;
1740 Cardinal *num_args;
1742 /* Get the GCs and the widget size */
1744 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1745 Display* display = XtDisplay (mw);
1747 #if 0
1748 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1750 /* _XtCreate is freeing the object that was passed to us,
1751 so make a copy that we will actually keep. */
1752 lwlib_bcopy (mw->menu.contents, tem, sizeof (widget_value));
1753 mw->menu.contents = tem;
1754 #endif
1756 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1757 mw->menu.cursor = mw->menu.cursor_shape;
1759 mw->menu.gray_pixmap
1760 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1761 gray_bitmap_width, gray_bitmap_height,
1762 (unsigned long)1, (unsigned long)0, 1);
1764 /* I don't understand why this ends up 0 sometimes,
1765 but it does. This kludge works around it.
1766 Can anyone find a real fix? -- rms. */
1767 if (mw->menu.font == 0)
1768 mw->menu.font = xlwmenu_default_font;
1769 #ifdef HAVE_X_I18N
1770 if (mw->menu.fontSet)
1771 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1772 #endif
1774 make_drawing_gcs (mw);
1775 make_shadow_gcs (mw);
1777 mw->menu.popped_up = False;
1779 mw->menu.old_depth = 1;
1780 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1781 mw->menu.old_stack_length = 1;
1782 mw->menu.old_stack [0] = mw->menu.contents;
1784 mw->menu.new_depth = 0;
1785 mw->menu.new_stack = 0;
1786 mw->menu.new_stack_length = 0;
1787 push_new_stack (mw, mw->menu.contents);
1789 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1790 mw->menu.windows_length = 1;
1791 mw->menu.windows [0].x = 0;
1792 mw->menu.windows [0].y = 0;
1793 mw->menu.windows [0].width = 0;
1794 mw->menu.windows [0].height = 0;
1795 size_menu (mw, 0);
1797 mw->core.width = mw->menu.windows [0].width;
1798 mw->core.height = mw->menu.windows [0].height;
1801 static void
1802 XlwMenuClassInitialize ()
1806 static void
1807 XlwMenuRealize (w, valueMask, attributes)
1808 Widget w;
1809 Mask *valueMask;
1810 XSetWindowAttributes *attributes;
1812 XlwMenuWidget mw = (XlwMenuWidget)w;
1813 XSetWindowAttributes xswa;
1814 int mask;
1816 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1817 (w, valueMask, attributes);
1819 xswa.save_under = True;
1820 xswa.cursor = mw->menu.cursor_shape;
1821 mask = CWSaveUnder | CWCursor;
1822 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1824 mw->menu.windows [0].window = XtWindow (w);
1825 mw->menu.windows [0].x = w->core.x;
1826 mw->menu.windows [0].y = w->core.y;
1827 mw->menu.windows [0].width = w->core.width;
1828 mw->menu.windows [0].height = w->core.height;
1831 /* Only the toplevel menubar/popup is a widget so it's the only one that
1832 receives expose events through Xt. So we repaint all the other panes
1833 when receiving an Expose event. */
1834 static void
1835 XlwMenuRedisplay (w, ev, region)
1836 Widget w;
1837 XEvent* ev;
1838 Region region;
1840 XlwMenuWidget mw = (XlwMenuWidget)w;
1841 int i;
1843 /* If we have a depth beyond 1, it's because a submenu was displayed.
1844 If the submenu has been destroyed, set the depth back to 1. */
1845 if (submenu_destroyed)
1847 mw->menu.old_depth = 1;
1848 submenu_destroyed = 0;
1851 for (i = 0; i < mw->menu.old_depth; i++)
1852 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1856 /* Part of a hack to make the menu redisplay when a tooltip frame
1857 over a menu item is unmapped. */
1859 void
1860 xlwmenu_redisplay (w)
1861 Widget w;
1863 XlwMenuRedisplay (w, NULL, None);
1866 static void
1867 XlwMenuDestroy (w)
1868 Widget w;
1870 int i;
1871 XlwMenuWidget mw = (XlwMenuWidget) w;
1873 if (pointer_grabbed)
1874 ungrab_all ((Widget)w, CurrentTime);
1875 pointer_grabbed = 0;
1877 submenu_destroyed = 1;
1879 release_drawing_gcs (mw);
1880 release_shadow_gcs (mw);
1882 /* this doesn't come from the resource db but is created explicitly
1883 so we must free it ourselves. */
1884 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1885 mw->menu.gray_pixmap = (Pixmap) -1;
1887 #if 0
1888 /* Do free mw->menu.contents because nowadays we copy it
1889 during initialization. */
1890 XtFree (mw->menu.contents);
1891 #endif
1893 /* Don't free mw->menu.contents because that comes from our creator.
1894 The `*_stack' elements are just pointers into `contents' so leave
1895 that alone too. But free the stacks themselves. */
1896 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1897 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1899 /* Remember, you can't free anything that came from the resource
1900 database. This includes:
1901 mw->menu.cursor
1902 mw->menu.top_shadow_pixmap
1903 mw->menu.bottom_shadow_pixmap
1904 mw->menu.font
1905 Also the color cells of top_shadow_color, bottom_shadow_color,
1906 foreground, and button_foreground will never be freed until this
1907 client exits. Nice, eh?
1910 /* start from 1 because the one in slot 0 is w->core.window */
1911 for (i = 1; i < mw->menu.windows_length; i++)
1912 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1913 if (mw->menu.windows)
1914 XtFree ((char *) mw->menu.windows);
1917 static Boolean
1918 XlwMenuSetValues (current, request, new)
1919 Widget current;
1920 Widget request;
1921 Widget new;
1923 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1924 XlwMenuWidget newmw = (XlwMenuWidget)new;
1925 Boolean redisplay = False;
1926 int i;
1928 if (newmw->menu.contents
1929 && newmw->menu.contents->contents
1930 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1931 redisplay = True;
1932 /* Do redisplay if the contents are entirely eliminated. */
1933 if (newmw->menu.contents
1934 && newmw->menu.contents->contents == 0
1935 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1936 redisplay = True;
1938 if (newmw->core.background_pixel != oldmw->core.background_pixel
1939 || newmw->menu.foreground != oldmw->menu.foreground
1940 #ifdef HAVE_X_I18N
1941 || newmw->menu.fontSet != oldmw->menu.fontSet
1942 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
1943 #else
1944 || newmw->menu.font != oldmw->menu.font
1945 #endif
1948 release_drawing_gcs (newmw);
1949 make_drawing_gcs (newmw);
1951 release_shadow_gcs (newmw);
1952 /* Cause the shadow colors to be recalculated. */
1953 newmw->menu.top_shadow_color = -1;
1954 newmw->menu.bottom_shadow_color = -1;
1955 make_shadow_gcs (newmw);
1957 redisplay = True;
1959 if (XtIsRealized (current))
1960 /* If the menu is currently displayed, change the display. */
1961 for (i = 0; i < oldmw->menu.windows_length; i++)
1963 XSetWindowBackground (XtDisplay (oldmw),
1964 oldmw->menu.windows [i].window,
1965 newmw->core.background_pixel);
1966 /* clear windows and generate expose events */
1967 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
1968 0, 0, 0, 0, True);
1972 #ifdef HAVE_X_I18N
1973 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
1975 redisplay = True;
1976 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
1978 #endif
1980 return redisplay;
1983 static void
1984 XlwMenuResize (w)
1985 Widget w;
1987 XlwMenuWidget mw = (XlwMenuWidget)w;
1989 if (mw->menu.popped_up)
1991 /* Don't allow the popup menu to resize itself. */
1992 mw->core.width = mw->menu.windows [0].width;
1993 mw->core.height = mw->menu.windows [0].height;
1994 mw->core.parent->core.width = mw->core.width ;
1995 mw->core.parent->core.height = mw->core.height ;
1997 else
1999 mw->menu.windows [0].width = mw->core.width;
2000 mw->menu.windows [0].height = mw->core.height;
2004 \f/* Action procedures */
2005 static void
2006 handle_single_motion_event (mw, ev)
2007 XlwMenuWidget mw;
2008 XMotionEvent* ev;
2010 widget_value* val;
2011 int level;
2013 if (!map_event_to_widget_value (mw, ev, &val, &level))
2014 pop_new_stack_if_no_contents (mw);
2015 else
2016 set_new_state (mw, val, level);
2017 remap_menubar (mw);
2019 /* Sync with the display. Makes it feel better on X terms. */
2020 XSync (XtDisplay (mw), False);
2023 static void
2024 handle_motion_event (mw, ev)
2025 XlwMenuWidget mw;
2026 XMotionEvent* ev;
2028 int x = ev->x_root;
2029 int y = ev->y_root;
2030 int state = ev->state;
2032 handle_single_motion_event (mw, ev);
2034 /* allow motion events to be generated again */
2035 if (ev->is_hint
2036 && XQueryPointer (XtDisplay (mw), ev->window,
2037 &ev->root, &ev->subwindow,
2038 &ev->x_root, &ev->y_root,
2039 &ev->x, &ev->y,
2040 &ev->state)
2041 && ev->state == state
2042 && (ev->x_root != x || ev->y_root != y))
2043 handle_single_motion_event (mw, ev);
2046 static void
2047 Start (w, ev, params, num_params)
2048 Widget w;
2049 XEvent *ev;
2050 String *params;
2051 Cardinal *num_params;
2053 XlwMenuWidget mw = (XlwMenuWidget)w;
2055 if (!mw->menu.popped_up)
2057 menu_post_event = *ev;
2058 /* If event is set to CurrentTime, get the last known time stamp.
2059 This is for calculating if (popup) menus should stay up after
2060 a fast click. */
2061 if (menu_post_event.xbutton.time == CurrentTime)
2062 menu_post_event.xbutton.time
2063 = XtLastTimestampProcessed (XtDisplay (w));
2065 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2067 else
2069 /* If we push a button while the menu is posted semipermanently,
2070 releasing the button should always pop the menu down. */
2071 next_release_must_exit = 1;
2073 /* notes the absolute position of the menubar window */
2074 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2075 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2077 /* handles the down like a move, slots are compatible */
2078 ev->xmotion.is_hint = 0;
2079 handle_motion_event (mw, &ev->xmotion);
2083 static void
2084 Drag (w, ev, params, num_params)
2085 Widget w;
2086 XEvent *ev;
2087 String *params;
2088 Cardinal *num_params;
2090 XlwMenuWidget mw = (XlwMenuWidget)w;
2091 if (mw->menu.popped_up)
2092 handle_motion_event (mw, &ev->xmotion);
2095 /* Do nothing.
2096 This is how we handle presses and releases of modifier keys. */
2097 static void
2098 Nothing (w, ev, params, num_params)
2099 Widget w;
2100 XEvent *ev;
2101 String *params;
2102 Cardinal *num_params;
2106 static widget_value *
2107 find_first_selectable (mw, item, skip_titles)
2108 XlwMenuWidget mw;
2109 widget_value *item;
2110 int skip_titles;
2112 widget_value *current = item;
2113 enum menu_separator separator;
2115 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2116 || (skip_titles && !current->call_data && !current->contents))
2117 if (current->next)
2118 current=current->next;
2119 else
2120 return NULL;
2122 return current;
2125 static widget_value *
2126 find_next_selectable (mw, item, skip_titles)
2127 XlwMenuWidget mw;
2128 widget_value *item;
2129 int skip_titles;
2131 widget_value *current = item;
2132 enum menu_separator separator;
2134 while (current->next && (current=current->next) &&
2135 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2136 || (skip_titles && !current->call_data && !current->contents)))
2139 if (current == item)
2141 if (mw->menu.old_depth < 2)
2142 return current;
2143 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2145 while (lw_separator_p (current->name, &separator, 0)
2146 || !current->enabled
2147 || (skip_titles && !current->call_data
2148 && !current->contents))
2150 if (current->next)
2151 current=current->next;
2153 if (current == item)
2154 break;
2159 return current;
2162 static widget_value *
2163 find_prev_selectable (mw, item, skip_titles)
2164 XlwMenuWidget mw;
2165 widget_value *item;
2166 int skip_titles;
2168 widget_value *current = item;
2169 widget_value *prev = item;
2171 while ((current=find_next_selectable (mw, current, skip_titles))
2172 != item)
2174 if (prev == current)
2175 break;
2176 prev=current;
2179 return prev;
2182 static void
2183 Down (w, ev, params, num_params)
2184 Widget w;
2185 XEvent *ev;
2186 String *params;
2187 Cardinal *num_params;
2189 XlwMenuWidget mw = (XlwMenuWidget) w;
2190 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2191 int popup_menu_p = mw->menu.top_depth == 1;
2193 /* Inside top-level menu-bar? */
2194 if (mw->menu.old_depth == mw->menu.top_depth)
2195 /* When <down> in the menu-bar is pressed, display the corresponding
2196 sub-menu and select the first selectable menu item there.
2197 If this is a popup menu, skip title item of the popup. */
2198 set_new_state (mw,
2199 find_first_selectable (mw,
2200 selected_item->contents,
2201 popup_menu_p),
2202 mw->menu.old_depth);
2203 else
2204 /* Highlight next possible (enabled and not separator) menu item. */
2205 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2206 mw->menu.old_depth - 1);
2208 remap_menubar (mw);
2211 static void
2212 Up (w, ev, params, num_params)
2213 Widget w;
2214 XEvent *ev;
2215 String *params;
2216 Cardinal *num_params;
2218 XlwMenuWidget mw = (XlwMenuWidget) w;
2219 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2220 int popup_menu_p = mw->menu.top_depth == 1;
2222 /* Inside top-level menu-bar? */
2223 if (mw->menu.old_depth == mw->menu.top_depth)
2225 /* FIXME: this is tricky. <up> in the menu-bar should select the
2226 last selectable item in the list. So we select the first
2227 selectable one and find the previous selectable item. Is there
2228 a better way? */
2229 /* If this is a popup menu, skip title item of the popup. */
2230 set_new_state (mw,
2231 find_first_selectable (mw,
2232 selected_item->contents,
2233 popup_menu_p),
2234 mw->menu.old_depth);
2235 remap_menubar (mw);
2236 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2237 set_new_state (mw,
2238 find_prev_selectable (mw,
2239 selected_item,
2240 popup_menu_p),
2241 mw->menu.old_depth - 1);
2243 else
2244 /* Highlight previous (enabled and not separator) menu item. */
2245 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2246 mw->menu.old_depth - 1);
2248 remap_menubar (mw);
2251 void
2252 Left (w, ev, params, num_params)
2253 Widget w;
2254 XEvent *ev;
2255 String *params;
2256 Cardinal *num_params;
2258 XlwMenuWidget mw = (XlwMenuWidget) w;
2259 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2261 /* Inside top-level menu-bar? */
2262 if (mw->menu.old_depth == mw->menu.top_depth)
2263 /* When <left> in the menu-bar is pressed, display the previous item on
2264 the menu-bar. If the current item is the first one, highlight the
2265 last item in the menubar (probably Help). */
2266 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2267 mw->menu.old_depth - 1);
2268 else if (mw->menu.old_depth == 1
2269 && selected_item->contents) /* Is this menu item expandable? */
2271 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2272 remap_menubar (mw);
2273 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2274 if (!selected_item->enabled && find_first_selectable (mw,
2275 selected_item,
2277 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2278 mw->menu.old_depth - 1);
2281 else
2283 pop_new_stack_if_no_contents (mw);
2284 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2285 mw->menu.old_depth - 2);
2288 remap_menubar (mw);
2291 void
2292 Right (w, ev, params, num_params)
2293 Widget w;
2294 XEvent *ev;
2295 String *params;
2296 Cardinal *num_params;
2298 XlwMenuWidget mw = (XlwMenuWidget) w;
2299 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2301 /* Inside top-level menu-bar? */
2302 if (mw->menu.old_depth == mw->menu.top_depth)
2303 /* When <right> in the menu-bar is pressed, display the next item on
2304 the menu-bar. If the current item is the last one, highlight the
2305 first item (probably File). */
2306 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2307 mw->menu.old_depth - 1);
2308 else if (selected_item->contents) /* Is this menu item expandable? */
2310 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2311 remap_menubar (mw);
2312 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2313 if (!selected_item->enabled && find_first_selectable (mw,
2314 selected_item,
2316 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2317 mw->menu.old_depth - 1);
2319 else
2321 pop_new_stack_if_no_contents (mw);
2322 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2323 mw->menu.old_depth - 2);
2326 remap_menubar (mw);
2329 /* Handle key press and release events while menu is popped up.
2330 Our action is to get rid of the menu. */
2331 static void
2332 Key (w, ev, params, num_params)
2333 Widget w;
2334 XEvent *ev;
2335 String *params;
2336 Cardinal *num_params;
2338 XlwMenuWidget mw = (XlwMenuWidget)w;
2340 /* Pop down everything. */
2341 mw->menu.new_depth = 1;
2342 remap_menubar (mw);
2344 if (mw->menu.popped_up)
2346 mw->menu.popped_up = False;
2347 ungrab_all ((Widget)mw, ev->xmotion.time);
2348 if (XtIsShell (XtParent ((Widget) mw)))
2349 XtPopdown (XtParent ((Widget) mw));
2350 else
2352 XtRemoveGrab ((Widget) mw);
2353 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2357 /* callback */
2358 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2361 static void
2362 Select (w, ev, params, num_params)
2363 Widget w;
2364 XEvent *ev;
2365 String *params;
2366 Cardinal *num_params;
2368 XlwMenuWidget mw = (XlwMenuWidget)w;
2369 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2371 /* If user releases the button quickly, without selecting anything,
2372 after the initial down-click that brought the menu up,
2373 do nothing. */
2374 if ((selected_item == 0
2375 || ((widget_value *) selected_item)->call_data == 0)
2376 && !next_release_must_exit
2377 && (ev->xbutton.time - menu_post_event.xbutton.time
2378 < XtGetMultiClickTime (XtDisplay (w))))
2379 return;
2381 /* pop down everything. */
2382 mw->menu.new_depth = 1;
2383 remap_menubar (mw);
2385 if (mw->menu.popped_up)
2387 mw->menu.popped_up = False;
2388 ungrab_all ((Widget)mw, ev->xmotion.time);
2389 if (XtIsShell (XtParent ((Widget) mw)))
2390 XtPopdown (XtParent ((Widget) mw));
2391 else
2393 XtRemoveGrab ((Widget) mw);
2394 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2398 /* callback */
2399 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2403 \f/* Special code to pop-up a menu */
2404 static void
2405 pop_up_menu (mw, event)
2406 XlwMenuWidget mw;
2407 XButtonPressedEvent* event;
2409 int x = event->x_root;
2410 int y = event->y_root;
2411 int w;
2412 int h;
2413 int borderwidth = mw->menu.shadow_thickness;
2414 Screen* screen = XtScreen (mw);
2415 Display *display = XtDisplay (mw);
2417 next_release_must_exit = 0;
2419 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2421 if (XtIsShell (XtParent ((Widget)mw)))
2422 size_menu (mw, 0);
2424 w = mw->menu.windows [0].width;
2425 h = mw->menu.windows [0].height;
2427 x -= borderwidth;
2428 y -= borderwidth;
2429 if (x < borderwidth)
2430 x = borderwidth;
2431 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2432 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2433 if (y < borderwidth)
2434 y = borderwidth;
2435 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2436 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2438 mw->menu.popped_up = True;
2439 if (XtIsShell (XtParent ((Widget)mw)))
2441 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2442 XtParent ((Widget)mw)->core.border_width);
2443 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2444 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2445 mw->menu.windows [0].x = x + borderwidth;
2446 mw->menu.windows [0].y = y + borderwidth;
2447 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2449 else
2451 XEvent *ev = (XEvent *) event;
2453 XtAddGrab ((Widget) mw, True, True);
2455 /* notes the absolute position of the menubar window */
2456 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2457 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2458 mw->menu.top_depth = 2;
2461 #ifdef emacs
2462 x_catch_errors (display);
2463 #endif
2464 if (XtGrabPointer ((Widget)mw, False,
2465 (PointerMotionMask
2466 | PointerMotionHintMask
2467 | ButtonReleaseMask
2468 | ButtonPressMask),
2469 GrabModeAsync, GrabModeAsync, None,
2470 mw->menu.cursor_shape,
2471 event->time) == Success)
2473 if (! GRAB_KEYBOARD
2474 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2475 GrabModeAsync, event->time) == Success)
2477 XtSetKeyboardFocus((Widget)mw, None);
2478 pointer_grabbed = 1;
2480 else
2481 XtUngrabPointer ((Widget)mw, event->time);
2484 #ifdef emacs
2485 if (x_had_errors_p (display))
2487 pointer_grabbed = 0;
2488 XtUngrabPointer ((Widget)mw, event->time);
2490 x_uncatch_errors ();
2491 #endif
2493 ((XMotionEvent*)event)->is_hint = 0;
2494 handle_motion_event (mw, (XMotionEvent*)event);
2497 /* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2498 (do not change this comment) */