(makefile-rule-action-regex): Backtrack less.
[emacs/old-mirror.git] / lwlib / xlwmenu.c
blob2ae7ae57e7909326a495e7bf87adfbc04ec7e516
1 /* Implements a lightweight menubar widget.
2 Copyright (C) 1992 Lucid, Inc.
3 Copyright (C) 1994, 1995, 1997, 1999, 2000, 2001, 2002, 2003, 2004,
4 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
6 This file is part of the Lucid Widget Library.
8 The Lucid Widget Library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2, or (at your option)
11 any later version.
13 The Lucid Widget Library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with GNU Emacs; see the file COPYING. If not, write to the
20 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA. */
23 /* Created by devin@lucid.com */
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
29 #include <setjmp.h>
30 #include "lisp.h"
32 #include <stdio.h>
34 #include <sys/types.h>
35 #if (defined __sun) && !(defined SUNOS41)
36 #define SUNOS41
37 #include <X11/Xos.h>
38 #undef SUNOS41
39 #else
40 #include <X11/Xos.h>
41 #endif
42 #include <X11/IntrinsicP.h>
43 #include <X11/ObjectP.h>
44 #include <X11/StringDefs.h>
45 #include <X11/cursorfont.h>
46 #include "xlwmenuP.h"
48 #ifdef emacs
50 /* Defined in xfns.c. When config.h defines `static' as empty, we get
51 redefinition errors when gray_bitmap is included more than once, so
52 we're referring to the one include in xfns.c here. */
54 extern int gray_bitmap_width;
55 extern int gray_bitmap_height;
56 extern char *gray_bitmap_bits;
58 #include "xterm.h"
60 #else /* not emacs */
62 #include <X11/bitmaps/gray>
63 #define gray_bitmap_width gray_width
64 #define gray_bitmap_height gray_height
65 #define gray_bitmap_bits gray_bits
67 #endif /* not emacs */
69 static int pointer_grabbed;
70 static XEvent menu_post_event;
72 XFontStruct *xlwmenu_default_font;
74 static char
75 xlwMenuTranslations [] =
76 "<BtnDown>: start()\n\
77 <Motion>: drag()\n\
78 <BtnUp>: select()\n\
79 <Key>Shift_L: nothing()\n\
80 <Key>Shift_R: nothing()\n\
81 <Key>Meta_L: nothing()\n\
82 <Key>Meta_R: nothing()\n\
83 <Key>Control_L: nothing()\n\
84 <Key>Control_R: nothing()\n\
85 <Key>Hyper_L: nothing()\n\
86 <Key>Hyper_R: nothing()\n\
87 <Key>Super_L: nothing()\n\
88 <Key>Super_R: nothing()\n\
89 <Key>Alt_L: nothing()\n\
90 <Key>Alt_R: nothing()\n\
91 <Key>Caps_Lock: nothing()\n\
92 <Key>Shift_Lock: nothing()\n\
93 <KeyUp>Shift_L: nothing()\n\
94 <KeyUp>Shift_R: nothing()\n\
95 <KeyUp>Meta_L: nothing()\n\
96 <KeyUp>Meta_R: nothing()\n\
97 <KeyUp>Control_L: nothing()\n\
98 <KeyUp>Control_R: nothing()\n\
99 <KeyUp>Hyper_L: nothing()\n\
100 <KeyUp>Hyper_R: nothing()\n\
101 <KeyUp>Super_L: nothing()\n\
102 <KeyUp>Super_R: nothing()\n\
103 <KeyUp>Alt_L: nothing()\n\
104 <KeyUp>Alt_R: nothing()\n\
105 <KeyUp>Caps_Lock: nothing()\n\
106 <KeyUp>Shift_Lock:nothing()\n\
107 <Key>Return: select()\n\
108 <Key>Down: down()\n\
109 <Key>Up: up()\n\
110 <Key>Left: left()\n\
111 <Key>Right: right()\n\
112 <Key>: key()\n\
113 <KeyUp>: key()\n\
116 /* FIXME: Space should toggle toggleable menu item but not remove the menu
117 so you can toggle the next one without entering the menu again. */
119 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
121 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
123 #define offset(field) XtOffset(XlwMenuWidget, field)
124 static XtResource
125 xlwMenuResources[] =
127 #ifdef HAVE_X_I18N
128 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
129 offset(menu.fontSet), XtRFontSet, NULL},
130 #endif
131 {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
132 offset(menu.font), XtRString, "XtDefaultFont"},
133 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
134 offset(menu.foreground), XtRString, "XtDefaultForeground"},
135 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
136 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
137 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
138 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
139 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
140 offset(menu.margin), XtRImmediate, (XtPointer)1},
141 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
142 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
143 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
144 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
145 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
146 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
148 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
149 sizeof (Dimension), offset (menu.shadow_thickness),
150 XtRImmediate, (XtPointer)1},
151 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
152 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
153 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
154 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
155 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
156 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
157 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
158 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
160 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
161 offset(menu.open), XtRCallback, (XtPointer)NULL},
162 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
163 offset(menu.select), XtRCallback, (XtPointer)NULL},
164 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
165 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
166 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
167 offset(menu.enter), XtRCallback, (XtPointer)NULL},
168 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
169 offset(menu.leave), XtRCallback, (XtPointer)NULL},
170 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
171 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
172 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
173 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
174 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
175 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
177 #undef offset
179 static Boolean XlwMenuSetValues();
180 static void XlwMenuRealize();
181 static void XlwMenuRedisplay();
182 static void XlwMenuResize();
183 static void XlwMenuInitialize();
184 static void XlwMenuRedisplay();
185 static void XlwMenuDestroy();
186 static void XlwMenuClassInitialize();
187 static void Start();
188 static void Drag();
189 static void Down();
190 static void Up();
191 static void Left();
192 static void Right();
193 static void Select();
194 static void Key();
195 static void Nothing();
196 static int separator_height __P ((enum menu_separator));
197 static void pop_up_menu __P ((XlwMenuWidget, XButtonPressedEvent *));
200 static XtActionsRec
201 xlwMenuActionsList [] =
203 {"start", Start},
204 {"drag", Drag},
205 {"down", Down},
206 {"up", Up},
207 {"left", Left},
208 {"right", Right},
209 {"select", Select},
210 {"key", Key},
211 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
212 {"nothing", Nothing},
215 #define SuperClass ((CoreWidgetClass)&coreClassRec)
217 XlwMenuClassRec xlwMenuClassRec =
219 { /* CoreClass fields initialization */
220 (WidgetClass) SuperClass, /* superclass */
221 "XlwMenu", /* class_name */
222 sizeof(XlwMenuRec), /* size */
223 XlwMenuClassInitialize, /* class_initialize */
224 NULL, /* class_part_initialize */
225 FALSE, /* class_inited */
226 XlwMenuInitialize, /* initialize */
227 NULL, /* initialize_hook */
228 XlwMenuRealize, /* realize */
229 xlwMenuActionsList, /* actions */
230 XtNumber(xlwMenuActionsList), /* num_actions */
231 xlwMenuResources, /* resources */
232 XtNumber(xlwMenuResources), /* resource_count */
233 NULLQUARK, /* xrm_class */
234 TRUE, /* compress_motion */
235 XtExposeCompressMaximal, /* compress_exposure */
236 TRUE, /* compress_enterleave */
237 FALSE, /* visible_interest */
238 XlwMenuDestroy, /* destroy */
239 XlwMenuResize, /* resize */
240 XlwMenuRedisplay, /* expose */
241 XlwMenuSetValues, /* set_values */
242 NULL, /* set_values_hook */
243 XtInheritSetValuesAlmost, /* set_values_almost */
244 NULL, /* get_values_hook */
245 NULL, /* accept_focus */
246 XtVersion, /* version */
247 NULL, /* callback_private */
248 xlwMenuTranslations, /* tm_table */
249 XtInheritQueryGeometry, /* query_geometry */
250 XtInheritDisplayAccelerator, /* display_accelerator */
251 NULL /* extension */
252 }, /* XlwMenuClass fields initialization */
254 0 /* dummy */
258 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
260 int submenu_destroyed;
262 /* For debug, if installation-directory is non-nil this is not an installed
263 Emacs. In that case we do not grab the keyboard to make it easier to
264 debug. */
265 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
267 static int next_release_must_exit;
269 \f/* Utilities */
271 /* Ungrab pointer and keyboard */
272 static void
273 ungrab_all (w, ungrabtime)
274 Widget w;
275 Time ungrabtime;
277 XtUngrabPointer (w, ungrabtime);
278 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
281 /* Like abort, but remove grabs from widget W before. */
283 static void
284 abort_gracefully (w)
285 Widget w;
287 if (XtIsShell (XtParent (w)))
288 XtRemoveGrab (w);
289 ungrab_all (w, CurrentTime);
290 abort ();
293 static void
294 push_new_stack (mw, val)
295 XlwMenuWidget mw;
296 widget_value* val;
298 if (!mw->menu.new_stack)
300 mw->menu.new_stack_length = 10;
301 mw->menu.new_stack =
302 (widget_value**)XtCalloc (mw->menu.new_stack_length,
303 sizeof (widget_value*));
305 else if (mw->menu.new_depth == mw->menu.new_stack_length)
307 mw->menu.new_stack_length *= 2;
308 mw->menu.new_stack =
309 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
310 mw->menu.new_stack_length * sizeof (widget_value*));
312 mw->menu.new_stack [mw->menu.new_depth++] = val;
315 static void
316 pop_new_stack_if_no_contents (mw)
317 XlwMenuWidget mw;
319 if (mw->menu.new_depth > 1)
321 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
322 mw->menu.new_depth -= 1;
326 static void
327 make_old_stack_space (mw, n)
328 XlwMenuWidget mw;
329 int n;
331 if (!mw->menu.old_stack)
333 mw->menu.old_stack_length = 10;
334 mw->menu.old_stack =
335 (widget_value**)XtCalloc (mw->menu.old_stack_length,
336 sizeof (widget_value*));
338 else if (mw->menu.old_stack_length < n)
340 mw->menu.old_stack_length *= 2;
341 mw->menu.old_stack =
342 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
343 mw->menu.old_stack_length * sizeof (widget_value*));
347 \f/* Size code */
348 static int
349 string_width (mw, s)
350 XlwMenuWidget mw;
351 char *s;
353 XCharStruct xcs;
354 int drop;
355 #ifdef HAVE_X_I18N
356 XRectangle ink, logical;
357 if (mw->menu.fontSet)
359 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
360 return logical.width;
362 #endif
364 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
365 return xcs.width;
369 #ifdef HAVE_X_I18N
370 #define MENU_FONT_HEIGHT(mw) \
371 ((mw)->menu.fontSet != NULL \
372 ? (mw)->menu.font_extents->max_logical_extent.height \
373 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
374 #define MENU_FONT_ASCENT(mw) \
375 ((mw)->menu.fontSet != NULL \
376 ? - (mw)->menu.font_extents->max_logical_extent.y \
377 : (mw)->menu.font->ascent)
378 #else
379 #define MENU_FONT_HEIGHT(mw) \
380 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
381 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
382 #endif
384 static int
385 arrow_width (mw)
386 XlwMenuWidget mw;
388 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
391 /* Return the width of toggle buttons of widget MW. */
393 static int
394 toggle_button_width (mw)
395 XlwMenuWidget mw;
397 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
401 /* Return the width of radio buttons of widget MW. */
403 static int
404 radio_button_width (mw)
405 XlwMenuWidget mw;
407 return toggle_button_width (mw) * 1.41;
411 static XtResource
412 nameResource[] =
414 {"labelString", "LabelString", XtRString, sizeof(String),
415 0, XtRImmediate, 0},
418 static char*
419 resource_widget_value (mw, val)
420 XlwMenuWidget mw;
421 widget_value *val;
423 if (!val->toolkit_data)
425 char* resourced_name = NULL;
426 char* complete_name;
427 XtGetSubresources ((Widget) mw,
428 (XtPointer) &resourced_name,
429 val->name, val->name,
430 nameResource, 1, NULL, 0);
431 if (!resourced_name)
432 resourced_name = val->name;
433 if (!val->value)
435 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
436 strcpy (complete_name, resourced_name);
438 else
440 int complete_length =
441 strlen (resourced_name) + strlen (val->value) + 2;
442 complete_name = XtMalloc (complete_length);
443 *complete_name = 0;
444 strcat (complete_name, resourced_name);
445 strcat (complete_name, " ");
446 strcat (complete_name, val->value);
449 val->toolkit_data = complete_name;
450 val->free_toolkit_data = True;
452 return (char*)val->toolkit_data;
455 /* Returns the sizes of an item */
456 static void
457 size_menu_item (mw, val, horizontal_p, label_width, rest_width, button_width,
458 height)
459 XlwMenuWidget mw;
460 widget_value* val;
461 int horizontal_p;
462 int* label_width;
463 int* rest_width;
464 int* button_width;
465 int* height;
467 enum menu_separator separator;
469 if (lw_separator_p (val->name, &separator, 0))
471 *height = separator_height (separator);
472 *label_width = 1;
473 *rest_width = 0;
474 *button_width = 0;
476 else
478 *height = MENU_FONT_HEIGHT (mw)
479 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
481 *label_width =
482 string_width (mw, resource_widget_value (mw, val))
483 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
485 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
486 if (!horizontal_p)
488 if (val->contents)
489 /* Add width of the arrow displayed for submenus. */
490 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
491 else if (val->key)
492 /* Add width of key equivalent string. */
493 *rest_width += (string_width (mw, val->key)
494 + mw->menu.arrow_spacing);
496 if (val->button_type == BUTTON_TYPE_TOGGLE)
497 *button_width = (toggle_button_width (mw)
498 + mw->menu.horizontal_spacing);
499 else if (val->button_type == BUTTON_TYPE_RADIO)
500 *button_width = (radio_button_width (mw)
501 + mw->menu.horizontal_spacing);
506 static void
507 size_menu (mw, level)
508 XlwMenuWidget mw;
509 int level;
511 unsigned int label_width = 0;
512 int rest_width = 0;
513 int button_width = 0;
514 int max_rest_width = 0;
515 int max_button_width = 0;
516 unsigned int height = 0;
517 int horizontal_p = mw->menu.horizontal && (level == 0);
518 widget_value* val;
519 window_state* ws;
521 if (level >= mw->menu.old_depth)
522 abort_gracefully ((Widget) mw);
524 ws = &mw->menu.windows [level];
525 ws->width = 0;
526 ws->height = 0;
527 ws->label_width = 0;
528 ws->button_width = 0;
530 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
532 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
533 &button_width, &height);
534 if (horizontal_p)
536 ws->width += label_width + rest_width;
537 if (height > ws->height)
538 ws->height = height;
540 else
542 if (label_width > ws->label_width)
543 ws->label_width = label_width;
544 if (rest_width > max_rest_width)
545 max_rest_width = rest_width;
546 if (button_width > max_button_width)
547 max_button_width = button_width;
548 ws->height += height;
552 if (horizontal_p)
553 ws->label_width = ws->button_width = 0;
554 else
556 ws->width = ws->label_width + max_rest_width + max_button_width;
557 ws->button_width = max_button_width;
560 ws->width += 2 * mw->menu.shadow_thickness;
561 ws->height += 2 * mw->menu.shadow_thickness;
563 if (horizontal_p)
565 ws->width += 2 * mw->menu.margin;
566 ws->height += 2 * mw->menu.margin;
571 \f/* Display code */
573 static void
574 draw_arrow (mw, window, gc, x, y, width, down_p)
575 XlwMenuWidget mw;
576 Window window;
577 GC gc;
578 int x;
579 int y;
580 int width;
581 int down_p;
583 Display *dpy = XtDisplay (mw);
584 GC top_gc = mw->menu.shadow_top_gc;
585 GC bottom_gc = mw->menu.shadow_bottom_gc;
586 int thickness = mw->menu.shadow_thickness;
587 int height = width;
588 XPoint pt[10];
589 /* alpha = atan (0.5)
590 factor = (1 + sin (alpha)) / cos (alpha) */
591 double factor = 1.62;
592 int thickness2 = thickness * factor;
594 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
596 if (down_p)
598 GC temp;
599 temp = top_gc;
600 top_gc = bottom_gc;
601 bottom_gc = temp;
604 pt[0].x = x;
605 pt[0].y = y + height;
606 pt[1].x = x + thickness;
607 pt[1].y = y + height - thickness2;
608 pt[2].x = x + thickness2;
609 pt[2].y = y + thickness2;
610 pt[3].x = x;
611 pt[3].y = y;
612 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
614 pt[0].x = x;
615 pt[0].y = y;
616 pt[1].x = x + thickness;
617 pt[1].y = y + thickness2;
618 pt[2].x = x + width - thickness2;
619 pt[2].y = y + height / 2;
620 pt[3].x = x + width;
621 pt[3].y = y + height / 2;
622 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
624 pt[0].x = x;
625 pt[0].y = y + height;
626 pt[1].x = x + thickness;
627 pt[1].y = y + height - thickness2;
628 pt[2].x = x + width - thickness2;
629 pt[2].y = y + height / 2;
630 pt[3].x = x + width;
631 pt[3].y = y + height / 2;
632 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
637 static void
638 draw_shadow_rectangle (mw, window, x, y, width, height, erase_p, down_p)
639 XlwMenuWidget mw;
640 Window window;
641 int x;
642 int y;
643 int width;
644 int height;
645 int erase_p;
646 int down_p;
648 Display *dpy = XtDisplay (mw);
649 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
650 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
651 int thickness = mw->menu.shadow_thickness;
652 XPoint points [4];
654 if (!erase_p && down_p)
656 GC temp;
657 temp = top_gc;
658 top_gc = bottom_gc;
659 bottom_gc = temp;
662 points [0].x = x;
663 points [0].y = y;
664 points [1].x = x + width;
665 points [1].y = y;
666 points [2].x = x + width - thickness;
667 points [2].y = y + thickness;
668 points [3].x = x;
669 points [3].y = y + thickness;
670 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
671 points [0].x = x;
672 points [0].y = y + thickness;
673 points [1].x = x;
674 points [1].y = y + height;
675 points [2].x = x + thickness;
676 points [2].y = y + height - thickness;
677 points [3].x = x + thickness;
678 points [3].y = y + thickness;
679 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
680 points [0].x = x + width;
681 points [0].y = y;
682 points [1].x = x + width - thickness;
683 points [1].y = y + thickness;
684 points [2].x = x + width - thickness;
685 points [2].y = y + height - thickness;
686 points [3].x = x + width;
687 points [3].y = y + height - thickness;
688 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
689 points [0].x = x;
690 points [0].y = y + height;
691 points [1].x = x + width;
692 points [1].y = y + height;
693 points [2].x = x + width;
694 points [2].y = y + height - thickness;
695 points [3].x = x + thickness;
696 points [3].y = y + height - thickness;
697 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
701 static void
702 draw_shadow_rhombus (mw, window, x, y, width, height, erase_p, down_p)
703 XlwMenuWidget mw;
704 Window window;
705 int x;
706 int y;
707 int width;
708 int height;
709 int erase_p;
710 int down_p;
712 Display *dpy = XtDisplay (mw);
713 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
714 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
715 int thickness = mw->menu.shadow_thickness;
716 XPoint points [4];
718 if (!erase_p && down_p)
720 GC temp;
721 temp = top_gc;
722 top_gc = bottom_gc;
723 bottom_gc = temp;
726 points [0].x = x;
727 points [0].y = y + height / 2;
728 points [1].x = x + thickness;
729 points [1].y = y + height / 2;
730 points [2].x = x + width / 2;
731 points [2].y = y + thickness;
732 points [3].x = x + width / 2;
733 points [3].y = y;
734 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
735 points [0].x = x + width / 2;
736 points [0].y = y;
737 points [1].x = x + width / 2;
738 points [1].y = y + thickness;
739 points [2].x = x + width - thickness;
740 points [2].y = y + height / 2;
741 points [3].x = x + width;
742 points [3].y = y + height / 2;
743 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
744 points [0].x = x;
745 points [0].y = y + height / 2;
746 points [1].x = x + thickness;
747 points [1].y = y + height / 2;
748 points [2].x = x + width / 2;
749 points [2].y = y + height - thickness;
750 points [3].x = x + width / 2;
751 points [3].y = y + height;
752 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
753 points [0].x = x + width / 2;
754 points [0].y = y + height;
755 points [1].x = x + width / 2;
756 points [1].y = y + height - thickness;
757 points [2].x = x + width - thickness;
758 points [2].y = y + height / 2;
759 points [3].x = x + width;
760 points [3].y = y + height / 2;
761 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
765 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
766 top-left corner of the menu item. SELECTED_P non-zero means the
767 toggle button is selected. */
769 static void
770 draw_toggle (mw, window, x, y, selected_p)
771 XlwMenuWidget mw;
772 Window window;
773 int x, y, selected_p;
775 int width, height;
777 width = toggle_button_width (mw);
778 height = width;
779 x += mw->menu.horizontal_spacing;
780 y += (MENU_FONT_ASCENT (mw) - height) / 2;
781 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
785 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
786 top-left corner of the menu item. SELECTED_P non-zero means the
787 toggle button is selected. */
789 static void
790 draw_radio (mw, window, x, y, selected_p)
791 XlwMenuWidget mw;
792 Window window;
793 int x, y, selected_p;
795 int width, height;
797 width = radio_button_width (mw);
798 height = width;
799 x += mw->menu.horizontal_spacing;
800 y += (MENU_FONT_ASCENT (mw) - height) / 2;
801 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
805 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
806 top-left corner of the menu item. WIDTH is the width of the
807 separator to draw. TYPE is the separator type. */
809 static void
810 draw_separator (mw, window, x, y, width, type)
811 XlwMenuWidget mw;
812 Window window;
813 int x, y, width;
814 enum menu_separator type;
816 Display *dpy = XtDisplay (mw);
817 XGCValues xgcv;
819 switch (type)
821 case SEPARATOR_NO_LINE:
822 break;
824 case SEPARATOR_SINGLE_LINE:
825 XDrawLine (dpy, window, mw->menu.foreground_gc,
826 x, y, x + width, y);
827 break;
829 case SEPARATOR_DOUBLE_LINE:
830 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
831 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
832 break;
834 case SEPARATOR_SINGLE_DASHED_LINE:
835 xgcv.line_style = LineOnOffDash;
836 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
837 XDrawLine (dpy, window, mw->menu.foreground_gc,
838 x, y, x + width, y);
839 xgcv.line_style = LineSolid;
840 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
841 break;
843 case SEPARATOR_DOUBLE_DASHED_LINE:
844 draw_separator (mw, window, x, y, width,
845 SEPARATOR_SINGLE_DASHED_LINE);
846 draw_separator (mw, window, x, y + 2, width,
847 SEPARATOR_SINGLE_DASHED_LINE);
848 break;
850 case SEPARATOR_SHADOW_ETCHED_IN:
851 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
852 x, y, x + width, y);
853 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
854 x, y + 1, x + width, y + 1);
855 break;
857 case SEPARATOR_SHADOW_ETCHED_OUT:
858 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
859 x, y, x + width, y);
860 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
861 x, y + 1, x + width, y + 1);
862 break;
864 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
865 xgcv.line_style = LineOnOffDash;
866 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
867 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
868 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
869 xgcv.line_style = LineSolid;
870 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
871 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
872 break;
874 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
875 xgcv.line_style = LineOnOffDash;
876 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
877 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
878 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
879 xgcv.line_style = LineSolid;
880 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
881 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
882 break;
884 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
885 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
886 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
887 break;
889 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
890 draw_separator (mw, window, x, y, width,
891 SEPARATOR_SHADOW_ETCHED_OUT);
892 draw_separator (mw, window, x, y + 3, width,
893 SEPARATOR_SHADOW_ETCHED_OUT);
894 break;
896 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
897 xgcv.line_style = LineOnOffDash;
898 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
899 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
900 draw_separator (mw, window, x, y, width,
901 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
902 xgcv.line_style = LineSolid;
903 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
904 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
905 break;
907 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
908 xgcv.line_style = LineOnOffDash;
909 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
910 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
911 draw_separator (mw, window, x, y, width,
912 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
913 xgcv.line_style = LineSolid;
914 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
915 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
916 break;
918 default:
919 abort ();
924 /* Return the pixel height of menu separator SEPARATOR. */
926 static int
927 separator_height (separator)
928 enum menu_separator separator;
930 switch (separator)
932 case SEPARATOR_NO_LINE:
933 return 2;
935 case SEPARATOR_SINGLE_LINE:
936 case SEPARATOR_SINGLE_DASHED_LINE:
937 return 1;
939 case SEPARATOR_DOUBLE_LINE:
940 case SEPARATOR_DOUBLE_DASHED_LINE:
941 return 3;
943 case SEPARATOR_SHADOW_ETCHED_IN:
944 case SEPARATOR_SHADOW_ETCHED_OUT:
945 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
946 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
947 return 2;
949 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
950 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
951 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
952 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
953 return 5;
955 default:
956 abort ();
961 /* Display the menu item and increment where.x and where.y to show how large
962 the menu item was. */
964 static void
965 display_menu_item (mw, val, ws, where, highlighted_p, horizontal_p,
966 just_compute_p)
967 XlwMenuWidget mw;
968 widget_value* val;
969 window_state* ws;
970 XPoint* where;
971 Boolean highlighted_p;
972 Boolean horizontal_p;
973 Boolean just_compute_p;
975 GC deco_gc;
976 GC text_gc;
977 int font_height = MENU_FONT_HEIGHT (mw);
978 int font_ascent = MENU_FONT_ASCENT (mw);
979 int shadow = mw->menu.shadow_thickness;
980 int margin = mw->menu.margin;
981 int h_spacing = mw->menu.horizontal_spacing;
982 int v_spacing = mw->menu.vertical_spacing;
983 int label_width;
984 int rest_width;
985 int button_width;
986 int height;
987 int width;
988 enum menu_separator separator;
989 int separator_p = lw_separator_p (val->name, &separator, 0);
991 /* compute the sizes of the item */
992 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
993 &button_width, &height);
995 if (horizontal_p)
996 width = label_width + rest_width;
997 else
999 label_width = ws->label_width;
1000 width = ws->width - 2 * shadow;
1003 /* Only highlight an enabled item that has a callback. */
1004 if (highlighted_p)
1005 if (!val->enabled || !(val->call_data || val->contents))
1006 highlighted_p = 0;
1008 /* do the drawing. */
1009 if (!just_compute_p)
1011 /* Add the shadow border of the containing menu */
1012 int x = where->x + shadow;
1013 int y = where->y + shadow;
1015 if (horizontal_p)
1017 x += margin;
1018 y += margin;
1021 /* pick the foreground and background GC. */
1022 if (val->enabled)
1023 text_gc = mw->menu.foreground_gc;
1024 else
1025 text_gc = mw->menu.disabled_gc;
1026 deco_gc = mw->menu.foreground_gc;
1028 if (separator_p)
1030 draw_separator (mw, ws->window, x, y, width, separator);
1032 else
1034 int x_offset = x + h_spacing + shadow;
1035 char* display_string = resource_widget_value (mw, val);
1036 draw_shadow_rectangle (mw, ws->window, x, y, width, height, True,
1037 False);
1039 /* Deal with centering a menu title. */
1040 if (!horizontal_p && !val->contents && !val->call_data)
1042 int l = string_width (mw, display_string);
1044 if (width > l)
1045 x_offset = (width - l) >> 1;
1047 else if (!horizontal_p && ws->button_width)
1048 x_offset += ws->button_width;
1051 #ifdef HAVE_X_I18N
1052 if (mw->menu.fontSet)
1053 XmbDrawString (XtDisplay (mw), ws->window, mw->menu.fontSet,
1054 text_gc, x_offset,
1055 y + v_spacing + shadow + font_ascent,
1056 display_string, strlen (display_string));
1057 else
1058 #endif
1059 XDrawString (XtDisplay (mw), ws->window,
1060 text_gc, x_offset,
1061 y + v_spacing + shadow + font_ascent,
1062 display_string, strlen (display_string));
1064 if (!horizontal_p)
1066 if (val->button_type == BUTTON_TYPE_TOGGLE)
1067 draw_toggle (mw, ws->window, x, y + v_spacing + shadow,
1068 val->selected);
1069 else if (val->button_type == BUTTON_TYPE_RADIO)
1070 draw_radio (mw, ws->window, x, y + v_spacing + shadow,
1071 val->selected);
1073 if (val->contents)
1075 int a_w = arrow_width (mw);
1076 draw_arrow (mw, ws->window, deco_gc,
1077 x + width - a_w
1078 - mw->menu.horizontal_spacing
1079 - mw->menu.shadow_thickness,
1080 y + v_spacing + shadow, a_w,
1081 highlighted_p);
1083 else if (val->key)
1085 #ifdef HAVE_X_I18N
1086 if (mw->menu.fontSet)
1087 XmbDrawString (XtDisplay (mw), ws->window,
1088 mw->menu.fontSet,
1089 text_gc,
1090 x + label_width + mw->menu.arrow_spacing,
1091 y + v_spacing + shadow + font_ascent,
1092 val->key, strlen (val->key));
1093 else
1094 #endif
1095 XDrawString (XtDisplay (mw), ws->window,
1096 text_gc,
1097 x + label_width + mw->menu.arrow_spacing,
1098 y + v_spacing + shadow + font_ascent,
1099 val->key, strlen (val->key));
1102 else
1104 XDrawRectangle (XtDisplay (mw), ws->window,
1105 mw->menu.background_gc,
1106 x + shadow, y + shadow,
1107 label_width + h_spacing - 1,
1108 font_height + 2 * v_spacing - 1);
1109 draw_shadow_rectangle (mw, ws->window, x, y, width, height,
1110 True, False);
1113 if (highlighted_p)
1114 draw_shadow_rectangle (mw, ws->window, x, y, width, height, False,
1115 False);
1119 where->x += width;
1120 where->y += height;
1123 static void
1124 display_menu (mw, level, just_compute_p, highlighted_pos, hit, hit_return,
1125 this, that)
1126 XlwMenuWidget mw;
1127 int level;
1128 Boolean just_compute_p;
1129 XPoint* highlighted_pos;
1130 XPoint* hit;
1131 widget_value** hit_return;
1132 widget_value* this;
1133 widget_value* that;
1135 widget_value* val;
1136 widget_value* following_item;
1137 window_state* ws;
1138 XPoint where;
1139 int horizontal_p = mw->menu.horizontal && (level == 0);
1140 int highlighted_p;
1141 int just_compute_this_one_p;
1142 /* This is set nonzero if the element containing HIGHLIGHTED_POS
1143 is disabled, so that we do not return any subsequent element either. */
1144 int no_return = 0;
1145 enum menu_separator separator;
1147 if (level >= mw->menu.old_depth)
1148 abort_gracefully ((Widget) mw);
1150 if (level < mw->menu.old_depth - 1)
1151 following_item = mw->menu.old_stack [level + 1];
1152 else
1153 following_item = NULL;
1155 if (hit)
1156 *hit_return = NULL;
1158 where.x = 0;
1159 where.y = 0;
1161 ws = &mw->menu.windows [level];
1162 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1164 highlighted_p = val == following_item;
1165 if (highlighted_p && highlighted_pos)
1167 if (horizontal_p)
1168 highlighted_pos->x = where.x;
1169 else
1170 highlighted_pos->y = where.y;
1173 just_compute_this_one_p =
1174 just_compute_p || ((this || that) && val != this && val != that);
1176 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1177 just_compute_this_one_p);
1179 if (highlighted_p && highlighted_pos)
1181 if (horizontal_p)
1182 highlighted_pos->y = where.y;
1183 else
1184 highlighted_pos->x = where.x;
1187 if (hit
1188 && !*hit_return
1189 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1190 && !lw_separator_p (val->name, &separator, 0)
1191 && !no_return)
1193 if (val->enabled)
1194 *hit_return = val;
1195 else
1196 no_return = 1;
1197 if (mw->menu.inside_entry != val)
1199 if (mw->menu.inside_entry)
1200 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1201 (XtPointer) mw->menu.inside_entry);
1202 mw->menu.inside_entry = val;
1203 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1204 (XtPointer) mw->menu.inside_entry);
1208 if (horizontal_p)
1209 where.y = 0;
1210 else
1211 where.x = 0;
1214 if (!just_compute_p)
1215 draw_shadow_rectangle (mw, ws->window, 0, 0, ws->width, ws->height,
1216 False, False);
1219 \f/* Motion code */
1220 static void
1221 set_new_state (mw, val, level)
1222 XlwMenuWidget mw;
1223 widget_value* val;
1224 int level;
1226 int i;
1228 mw->menu.new_depth = 0;
1229 for (i = 0; i < level; i++)
1230 push_new_stack (mw, mw->menu.old_stack [i]);
1231 push_new_stack (mw, val);
1234 static void
1235 make_windows_if_needed (mw, n)
1236 XlwMenuWidget mw;
1237 int n;
1239 int i;
1240 int start_at;
1241 XSetWindowAttributes xswa;
1242 int mask;
1243 Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1244 window_state* windows;
1246 if (mw->menu.windows_length >= n)
1247 return;
1249 xswa.save_under = True;
1250 xswa.override_redirect = True;
1251 xswa.background_pixel = mw->core.background_pixel;
1252 xswa.border_pixel = mw->core.border_pixel;
1253 xswa.event_mask =
1254 ExposureMask | PointerMotionMask | PointerMotionHintMask
1255 | ButtonReleaseMask | ButtonPressMask;
1256 xswa.cursor = mw->menu.cursor_shape;
1257 mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
1258 | CWEventMask | CWCursor;
1260 if (!mw->menu.windows)
1262 mw->menu.windows =
1263 (window_state*)XtMalloc (n * sizeof (window_state));
1264 start_at = 0;
1266 else
1268 mw->menu.windows =
1269 (window_state*)XtRealloc ((char*)mw->menu.windows,
1270 n * sizeof (window_state));
1271 start_at = mw->menu.windows_length;
1273 mw->menu.windows_length = n;
1275 windows = mw->menu.windows;
1277 for (i = start_at; i < n; i++)
1279 windows [i].x = 0;
1280 windows [i].y = 0;
1281 windows [i].width = 1;
1282 windows [i].height = 1;
1283 windows [i].window =
1284 XCreateWindow (XtDisplay (mw), root, 0, 0, 1, 1,
1285 0, 0, CopyFromParent, CopyFromParent, mask, &xswa);
1289 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1292 xlwmenu_window_p (w, window)
1293 Widget w;
1294 Window window;
1296 XlwMenuWidget mw = (XlwMenuWidget) w;
1297 int i;
1299 for (i = 0; i < mw->menu.windows_length; ++i)
1300 if (window == mw->menu.windows[i].window)
1301 break;
1303 return i < mw->menu.windows_length;
1306 /* Make the window fit in the screen */
1307 static void
1308 fit_to_screen (mw, ws, previous_ws, horizontal_p)
1309 XlwMenuWidget mw;
1310 window_state* ws;
1311 window_state* previous_ws;
1312 Boolean horizontal_p;
1314 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1315 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1316 /* 1 if we are unable to avoid an overlap between
1317 this menu and the parent menu in the X dimension. */
1318 int horizontal_overlap = 0;
1320 if (ws->x < 0)
1321 ws->x = 0;
1322 else if (ws->x + ws->width > screen_width)
1324 if (!horizontal_p)
1325 /* The addition of shadow-thickness for a sub-menu's position is
1326 to reflect a similar adjustment when the menu is displayed to
1327 the right of the invoking menu-item; it makes the sub-menu
1328 look more `attached' to the menu-item. */
1329 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1330 else
1331 ws->x = screen_width - ws->width;
1332 if (ws->x < 0)
1334 ws->x = 0;
1335 horizontal_overlap = 1;
1338 /* If we overlap in X, try to avoid overlap in Y. */
1339 if (horizontal_overlap
1340 && ws->y < previous_ws->y + previous_ws->height
1341 && previous_ws->y < ws->y + ws->height)
1343 /* Put this menu right below or right above PREVIOUS_WS
1344 if there's room. */
1345 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1346 ws->y = previous_ws->y + previous_ws->height;
1347 else if (previous_ws->y - ws->height > 0)
1348 ws->y = previous_ws->y - ws->height;
1351 if (ws->y < 0)
1352 ws->y = 0;
1353 else if (ws->y + ws->height > screen_height)
1355 if (horizontal_p)
1356 ws->y = previous_ws->y - ws->height;
1357 else
1358 ws->y = screen_height - ws->height;
1359 if (ws->y < 0)
1360 ws->y = 0;
1364 /* Updates old_stack from new_stack and redisplays. */
1365 static void
1366 remap_menubar (mw)
1367 XlwMenuWidget mw;
1369 int i;
1370 int last_same;
1371 XPoint selection_position;
1372 int old_depth = mw->menu.old_depth;
1373 int new_depth = mw->menu.new_depth;
1374 widget_value** old_stack;
1375 widget_value** new_stack;
1376 window_state* windows;
1377 widget_value* old_selection;
1378 widget_value* new_selection;
1380 /* Check that enough windows and old_stack are ready. */
1381 make_windows_if_needed (mw, new_depth);
1382 make_old_stack_space (mw, new_depth);
1383 windows = mw->menu.windows;
1384 old_stack = mw->menu.old_stack;
1385 new_stack = mw->menu.new_stack;
1387 /* compute the last identical different entry */
1388 for (i = 1; i < old_depth && i < new_depth; i++)
1389 if (old_stack [i] != new_stack [i])
1390 break;
1391 last_same = i - 1;
1393 /* Memorize the previously selected item to be able to refresh it */
1394 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1395 if (old_selection && !old_selection->enabled)
1396 old_selection = NULL;
1397 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1398 if (new_selection && !new_selection->enabled)
1399 new_selection = NULL;
1401 /* Call callback when the hightlighted item changes. */
1402 if (old_selection || new_selection)
1403 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1404 (XtPointer) new_selection);
1406 /* updates old_state from new_state. It has to be done now because
1407 display_menu (called below) uses the old_stack to know what to display. */
1408 for (i = last_same + 1; i < new_depth; i++)
1409 old_stack [i] = new_stack [i];
1410 mw->menu.old_depth = new_depth;
1412 /* refresh the last selection */
1413 selection_position.x = 0;
1414 selection_position.y = 0;
1415 display_menu (mw, last_same, new_selection == old_selection,
1416 &selection_position, NULL, NULL, old_selection, new_selection);
1418 /* Now place the new menus. */
1419 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1421 window_state *previous_ws = &windows[i - 1];
1422 window_state *ws = &windows[i];
1424 ws->x = (previous_ws->x + selection_position.x
1425 + mw->menu.shadow_thickness);
1426 if (mw->menu.horizontal && i == 1)
1427 ws->x += mw->menu.margin;
1429 #if 0
1430 if (!mw->menu.horizontal || i > 1)
1431 ws->x += mw->menu.shadow_thickness;
1432 #endif
1434 ws->y = (previous_ws->y + selection_position.y
1435 + mw->menu.shadow_thickness);
1436 if (mw->menu.horizontal && i == 1)
1437 ws->y += mw->menu.margin;
1439 size_menu (mw, i);
1441 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1443 XClearWindow (XtDisplay (mw), ws->window);
1444 XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y,
1445 ws->width, ws->height);
1446 XMapRaised (XtDisplay (mw), ws->window);
1447 display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL);
1450 /* unmap the menus that popped down */
1451 for (i = new_depth - 1; i < old_depth; i++)
1452 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1453 XUnmapWindow (XtDisplay (mw), windows[i].window);
1456 static Boolean
1457 motion_event_is_in_menu (mw, ev, level, relative_pos)
1458 XlwMenuWidget mw;
1459 XMotionEvent* ev;
1460 int level;
1461 XPoint* relative_pos;
1463 window_state* ws = &mw->menu.windows [level];
1464 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1465 int x = ws->x + shadow;
1466 int y = ws->y + shadow;
1467 relative_pos->x = ev->x_root - x;
1468 relative_pos->y = ev->y_root - y;
1469 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1470 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1473 static Boolean
1474 map_event_to_widget_value (mw, ev, val, level)
1475 XlwMenuWidget mw;
1476 XMotionEvent* ev;
1477 widget_value** val;
1478 int* level;
1480 int i;
1481 XPoint relative_pos;
1482 window_state* ws;
1483 int inside = 0;
1485 *val = NULL;
1487 /* Find the window */
1488 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1490 ws = &mw->menu.windows [i];
1491 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1493 inside = 1;
1494 display_menu (mw, i, True, NULL, &relative_pos, val, NULL, NULL);
1496 if (*val)
1498 *level = i + 1;
1499 return True;
1504 if (!inside)
1506 if (mw->menu.inside_entry != NULL)
1507 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1508 (XtPointer) mw->menu.inside_entry);
1509 mw->menu.inside_entry = NULL;
1512 return False;
1515 \f/* Procedures */
1516 static void
1517 make_drawing_gcs (mw)
1518 XlwMenuWidget mw;
1520 XGCValues xgcv;
1521 float scale;
1522 XtGCMask mask = GCForeground | GCBackground;
1524 #ifdef HAVE_X_I18N
1525 if (!mw->menu.fontSet)
1527 xgcv.font = mw->menu.font->fid;
1528 mask |= GCFont;
1530 #else
1531 xgcv.font = mw->menu.font->fid;
1532 mask |= GCFont;
1533 #endif
1534 xgcv.foreground = mw->menu.foreground;
1535 xgcv.background = mw->core.background_pixel;
1536 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1538 xgcv.foreground = mw->menu.button_foreground;
1539 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1541 xgcv.background = mw->core.background_pixel;
1543 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1545 /* Allocate color for disabled menu-items. */
1546 mw->menu.disabled_foreground = mw->menu.foreground;
1547 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1548 scale = 2.3;
1549 else
1550 scale = 0.55;
1552 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1553 mw->core.colormap,
1554 &mw->menu.disabled_foreground,
1555 scale,
1556 0x8000);
1558 if (mw->menu.foreground == mw->menu.disabled_foreground
1559 || mw->core.background_pixel == mw->menu.disabled_foreground)
1561 /* Too few colors, use stipple. */
1562 xgcv.foreground = mw->menu.foreground;
1563 xgcv.fill_style = FillStippled;
1564 xgcv.stipple = mw->menu.gray_pixmap;
1565 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1566 | GCFillStyle | GCStipple, &xgcv);
1568 else
1570 /* Many colors available, use disabled pixel. */
1571 xgcv.foreground = mw->menu.disabled_foreground;
1572 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1575 xgcv.foreground = mw->menu.button_foreground;
1576 xgcv.background = mw->core.background_pixel;
1577 xgcv.fill_style = FillStippled;
1578 xgcv.stipple = mw->menu.gray_pixmap;
1579 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1580 | GCFillStyle | GCStipple, &xgcv);
1582 xgcv.foreground = mw->core.background_pixel;
1583 xgcv.background = mw->menu.foreground;
1584 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1587 static void
1588 release_drawing_gcs (mw)
1589 XlwMenuWidget mw;
1591 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1592 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1593 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1594 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1595 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1596 /* let's get some segvs if we try to use these... */
1597 mw->menu.foreground_gc = (GC) -1;
1598 mw->menu.button_gc = (GC) -1;
1599 mw->menu.disabled_gc = (GC) -1;
1600 mw->menu.inactive_button_gc = (GC) -1;
1601 mw->menu.background_gc = (GC) -1;
1604 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1605 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1607 static void
1608 make_shadow_gcs (mw)
1609 XlwMenuWidget mw;
1611 XGCValues xgcv;
1612 unsigned long pm = 0;
1613 Display *dpy = XtDisplay ((Widget) mw);
1614 Screen *screen = XtScreen ((Widget) mw);
1615 Colormap cmap = mw->core.colormap;
1616 XColor topc, botc;
1617 int top_frobbed = 0, bottom_frobbed = 0;
1619 mw->menu.free_top_shadow_color_p = 0;
1620 mw->menu.free_bottom_shadow_color_p = 0;
1622 if (mw->menu.top_shadow_color == -1)
1623 mw->menu.top_shadow_color = mw->core.background_pixel;
1624 else
1625 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1627 if (mw->menu.bottom_shadow_color == -1)
1628 mw->menu.bottom_shadow_color = mw->menu.foreground;
1629 else
1630 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1632 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1633 mw->menu.top_shadow_color == mw->menu.foreground)
1635 topc.pixel = mw->core.background_pixel;
1636 #ifdef emacs
1637 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1638 &topc.pixel,
1639 1.2, 0x8000))
1640 #else
1641 XQueryColor (dpy, cmap, &topc);
1642 /* don't overflow/wrap! */
1643 topc.red = MINL (65535, topc.red * 1.2);
1644 topc.green = MINL (65535, topc.green * 1.2);
1645 topc.blue = MINL (65535, topc.blue * 1.2);
1646 if (XAllocColor (dpy, cmap, &topc))
1647 #endif
1649 mw->menu.top_shadow_color = topc.pixel;
1650 mw->menu.free_top_shadow_color_p = 1;
1651 top_frobbed = 1;
1654 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1655 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1657 botc.pixel = mw->core.background_pixel;
1658 #ifdef emacs
1659 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1660 &botc.pixel,
1661 0.6, 0x4000))
1662 #else
1663 XQueryColor (dpy, cmap, &botc);
1664 botc.red *= 0.6;
1665 botc.green *= 0.6;
1666 botc.blue *= 0.6;
1667 if (XAllocColor (dpy, cmap, &botc))
1668 #endif
1670 mw->menu.bottom_shadow_color = botc.pixel;
1671 mw->menu.free_bottom_shadow_color_p = 1;
1672 bottom_frobbed = 1;
1676 if (top_frobbed && bottom_frobbed)
1678 if (topc.pixel == botc.pixel)
1680 if (botc.pixel == mw->menu.foreground)
1682 if (mw->menu.free_top_shadow_color_p)
1684 x_free_dpy_colors (dpy, screen, cmap,
1685 &mw->menu.top_shadow_color, 1);
1686 mw->menu.free_top_shadow_color_p = 0;
1688 mw->menu.top_shadow_color = mw->core.background_pixel;
1690 else
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;
1703 if (!mw->menu.top_shadow_pixmap &&
1704 mw->menu.top_shadow_color == mw->core.background_pixel)
1706 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1707 if (mw->menu.free_top_shadow_color_p)
1709 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1710 mw->menu.free_top_shadow_color_p = 0;
1712 mw->menu.top_shadow_color = mw->menu.foreground;
1714 if (!mw->menu.bottom_shadow_pixmap &&
1715 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1717 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1718 if (mw->menu.free_bottom_shadow_color_p)
1720 x_free_dpy_colors (dpy, screen, cmap,
1721 &mw->menu.bottom_shadow_color, 1);
1722 mw->menu.free_bottom_shadow_color_p = 0;
1724 mw->menu.bottom_shadow_color = mw->menu.foreground;
1727 xgcv.fill_style = FillStippled;
1728 xgcv.foreground = mw->menu.top_shadow_color;
1729 xgcv.stipple = mw->menu.top_shadow_pixmap;
1730 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1731 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1733 xgcv.foreground = mw->menu.bottom_shadow_color;
1734 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1735 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1736 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1740 static void
1741 release_shadow_gcs (mw)
1742 XlwMenuWidget mw;
1744 Display *dpy = XtDisplay ((Widget) mw);
1745 Screen *screen = XtScreen ((Widget) mw);
1746 Colormap cmap = mw->core.colormap;
1747 Pixel px[2];
1748 int i = 0;
1750 if (mw->menu.free_top_shadow_color_p)
1751 px[i++] = mw->menu.top_shadow_color;
1752 if (mw->menu.free_bottom_shadow_color_p)
1753 px[i++] = mw->menu.bottom_shadow_color;
1754 if (i > 0)
1755 x_free_dpy_colors (dpy, screen, cmap, px, i);
1757 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1758 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1761 static void
1762 XlwMenuInitialize (request, mw, args, num_args)
1763 Widget request;
1764 XlwMenuWidget mw;
1765 ArgList args;
1766 Cardinal *num_args;
1768 /* Get the GCs and the widget size */
1770 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1771 Display* display = XtDisplay (mw);
1773 #if 0
1774 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1776 /* _XtCreate is freeing the object that was passed to us,
1777 so make a copy that we will actually keep. */
1778 lwlib_bcopy (mw->menu.contents, tem, sizeof (widget_value));
1779 mw->menu.contents = tem;
1780 #endif
1782 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1783 mw->menu.cursor = mw->menu.cursor_shape;
1785 mw->menu.gray_pixmap
1786 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1787 gray_bitmap_width, gray_bitmap_height,
1788 (unsigned long)1, (unsigned long)0, 1);
1790 /* I don't understand why this ends up 0 sometimes,
1791 but it does. This kludge works around it.
1792 Can anyone find a real fix? -- rms. */
1793 if (mw->menu.font == 0)
1794 mw->menu.font = xlwmenu_default_font;
1795 #ifdef HAVE_X_I18N
1796 if (mw->menu.fontSet)
1797 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1798 #endif
1800 make_drawing_gcs (mw);
1801 make_shadow_gcs (mw);
1803 mw->menu.popped_up = False;
1805 mw->menu.old_depth = 1;
1806 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1807 mw->menu.old_stack_length = 1;
1808 mw->menu.old_stack [0] = mw->menu.contents;
1810 mw->menu.new_depth = 0;
1811 mw->menu.new_stack = 0;
1812 mw->menu.new_stack_length = 0;
1813 push_new_stack (mw, mw->menu.contents);
1815 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1816 mw->menu.windows_length = 1;
1817 mw->menu.windows [0].x = 0;
1818 mw->menu.windows [0].y = 0;
1819 mw->menu.windows [0].width = 0;
1820 mw->menu.windows [0].height = 0;
1821 size_menu (mw, 0);
1823 mw->core.width = mw->menu.windows [0].width;
1824 mw->core.height = mw->menu.windows [0].height;
1827 static void
1828 XlwMenuClassInitialize ()
1832 static void
1833 XlwMenuRealize (w, valueMask, attributes)
1834 Widget w;
1835 Mask *valueMask;
1836 XSetWindowAttributes *attributes;
1838 XlwMenuWidget mw = (XlwMenuWidget)w;
1839 XSetWindowAttributes xswa;
1840 int mask;
1842 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1843 (w, valueMask, attributes);
1845 xswa.save_under = True;
1846 xswa.cursor = mw->menu.cursor_shape;
1847 mask = CWSaveUnder | CWCursor;
1848 /* I sometimes get random BadCursor errors while creating the first
1849 frame on a display. I can not find their reason, but they are
1850 annoying so for now let's ignore any errors here. -- lorentey */
1851 #ifdef emacs
1852 x_catch_errors (XtDisplay (w));
1853 #endif
1854 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1855 #ifdef emacs
1856 x_uncatch_errors ();
1857 #endif
1859 mw->menu.windows [0].window = XtWindow (w);
1860 mw->menu.windows [0].x = w->core.x;
1861 mw->menu.windows [0].y = w->core.y;
1862 mw->menu.windows [0].width = w->core.width;
1863 mw->menu.windows [0].height = w->core.height;
1866 /* Only the toplevel menubar/popup is a widget so it's the only one that
1867 receives expose events through Xt. So we repaint all the other panes
1868 when receiving an Expose event. */
1869 static void
1870 XlwMenuRedisplay (w, ev, region)
1871 Widget w;
1872 XEvent* ev;
1873 Region region;
1875 XlwMenuWidget mw = (XlwMenuWidget)w;
1876 int i;
1878 /* If we have a depth beyond 1, it's because a submenu was displayed.
1879 If the submenu has been destroyed, set the depth back to 1. */
1880 if (submenu_destroyed)
1882 mw->menu.old_depth = 1;
1883 submenu_destroyed = 0;
1886 for (i = 0; i < mw->menu.old_depth; i++)
1887 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1891 /* Part of a hack to make the menu redisplay when a tooltip frame
1892 over a menu item is unmapped. */
1894 void
1895 xlwmenu_redisplay (w)
1896 Widget w;
1898 XlwMenuRedisplay (w, NULL, None);
1901 static void
1902 XlwMenuDestroy (w)
1903 Widget w;
1905 int i;
1906 XlwMenuWidget mw = (XlwMenuWidget) w;
1908 if (pointer_grabbed)
1909 ungrab_all ((Widget)w, CurrentTime);
1910 pointer_grabbed = 0;
1912 submenu_destroyed = 1;
1914 release_drawing_gcs (mw);
1915 release_shadow_gcs (mw);
1917 /* this doesn't come from the resource db but is created explicitly
1918 so we must free it ourselves. */
1919 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1920 mw->menu.gray_pixmap = (Pixmap) -1;
1922 #if 0
1923 /* Do free mw->menu.contents because nowadays we copy it
1924 during initialization. */
1925 XtFree (mw->menu.contents);
1926 #endif
1928 /* Don't free mw->menu.contents because that comes from our creator.
1929 The `*_stack' elements are just pointers into `contents' so leave
1930 that alone too. But free the stacks themselves. */
1931 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1932 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1934 /* Remember, you can't free anything that came from the resource
1935 database. This includes:
1936 mw->menu.cursor
1937 mw->menu.top_shadow_pixmap
1938 mw->menu.bottom_shadow_pixmap
1939 mw->menu.font
1940 Also the color cells of top_shadow_color, bottom_shadow_color,
1941 foreground, and button_foreground will never be freed until this
1942 client exits. Nice, eh?
1945 /* start from 1 because the one in slot 0 is w->core.window */
1946 for (i = 1; i < mw->menu.windows_length; i++)
1947 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1948 if (mw->menu.windows)
1949 XtFree ((char *) mw->menu.windows);
1952 static Boolean
1953 XlwMenuSetValues (current, request, new)
1954 Widget current;
1955 Widget request;
1956 Widget new;
1958 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1959 XlwMenuWidget newmw = (XlwMenuWidget)new;
1960 Boolean redisplay = False;
1961 int i;
1963 if (newmw->menu.contents
1964 && newmw->menu.contents->contents
1965 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1966 redisplay = True;
1967 /* Do redisplay if the contents are entirely eliminated. */
1968 if (newmw->menu.contents
1969 && newmw->menu.contents->contents == 0
1970 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1971 redisplay = True;
1973 if (newmw->core.background_pixel != oldmw->core.background_pixel
1974 || newmw->menu.foreground != oldmw->menu.foreground
1975 #ifdef HAVE_X_I18N
1976 || newmw->menu.fontSet != oldmw->menu.fontSet
1977 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
1978 #else
1979 || newmw->menu.font != oldmw->menu.font
1980 #endif
1983 release_drawing_gcs (newmw);
1984 make_drawing_gcs (newmw);
1986 release_shadow_gcs (newmw);
1987 /* Cause the shadow colors to be recalculated. */
1988 newmw->menu.top_shadow_color = -1;
1989 newmw->menu.bottom_shadow_color = -1;
1990 make_shadow_gcs (newmw);
1992 redisplay = True;
1994 if (XtIsRealized (current))
1995 /* If the menu is currently displayed, change the display. */
1996 for (i = 0; i < oldmw->menu.windows_length; i++)
1998 XSetWindowBackground (XtDisplay (oldmw),
1999 oldmw->menu.windows [i].window,
2000 newmw->core.background_pixel);
2001 /* clear windows and generate expose events */
2002 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2003 0, 0, 0, 0, True);
2007 #ifdef HAVE_X_I18N
2008 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2010 redisplay = True;
2011 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2013 #endif
2015 return redisplay;
2018 static void
2019 XlwMenuResize (w)
2020 Widget w;
2022 XlwMenuWidget mw = (XlwMenuWidget)w;
2024 if (mw->menu.popped_up)
2026 /* Don't allow the popup menu to resize itself. */
2027 mw->core.width = mw->menu.windows [0].width;
2028 mw->core.height = mw->menu.windows [0].height;
2029 mw->core.parent->core.width = mw->core.width ;
2030 mw->core.parent->core.height = mw->core.height ;
2032 else
2034 mw->menu.windows [0].width = mw->core.width;
2035 mw->menu.windows [0].height = mw->core.height;
2039 \f/* Action procedures */
2040 static void
2041 handle_single_motion_event (mw, ev)
2042 XlwMenuWidget mw;
2043 XMotionEvent* ev;
2045 widget_value* val;
2046 int level;
2048 if (!map_event_to_widget_value (mw, ev, &val, &level))
2049 pop_new_stack_if_no_contents (mw);
2050 else
2051 set_new_state (mw, val, level);
2052 remap_menubar (mw);
2054 /* Sync with the display. Makes it feel better on X terms. */
2055 XSync (XtDisplay (mw), False);
2058 static void
2059 handle_motion_event (mw, ev)
2060 XlwMenuWidget mw;
2061 XMotionEvent* ev;
2063 int x = ev->x_root;
2064 int y = ev->y_root;
2065 int state = ev->state;
2067 handle_single_motion_event (mw, ev);
2069 /* allow motion events to be generated again */
2070 if (ev->is_hint
2071 && XQueryPointer (XtDisplay (mw), ev->window,
2072 &ev->root, &ev->subwindow,
2073 &ev->x_root, &ev->y_root,
2074 &ev->x, &ev->y,
2075 &ev->state)
2076 && ev->state == state
2077 && (ev->x_root != x || ev->y_root != y))
2078 handle_single_motion_event (mw, ev);
2081 static void
2082 Start (w, ev, params, num_params)
2083 Widget w;
2084 XEvent *ev;
2085 String *params;
2086 Cardinal *num_params;
2088 XlwMenuWidget mw = (XlwMenuWidget)w;
2090 if (!mw->menu.popped_up)
2092 menu_post_event = *ev;
2093 /* If event is set to CurrentTime, get the last known time stamp.
2094 This is for calculating if (popup) menus should stay up after
2095 a fast click. */
2096 if (menu_post_event.xbutton.time == CurrentTime)
2097 menu_post_event.xbutton.time
2098 = XtLastTimestampProcessed (XtDisplay (w));
2100 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2102 else
2104 /* If we push a button while the menu is posted semipermanently,
2105 releasing the button should always pop the menu down. */
2106 next_release_must_exit = 1;
2108 /* notes the absolute position of the menubar window */
2109 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2110 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2112 /* handles the down like a move, slots are compatible */
2113 ev->xmotion.is_hint = 0;
2114 handle_motion_event (mw, &ev->xmotion);
2118 static void
2119 Drag (w, ev, params, num_params)
2120 Widget w;
2121 XEvent *ev;
2122 String *params;
2123 Cardinal *num_params;
2125 XlwMenuWidget mw = (XlwMenuWidget)w;
2126 if (mw->menu.popped_up)
2127 handle_motion_event (mw, &ev->xmotion);
2130 /* Do nothing.
2131 This is how we handle presses and releases of modifier keys. */
2132 static void
2133 Nothing (w, ev, params, num_params)
2134 Widget w;
2135 XEvent *ev;
2136 String *params;
2137 Cardinal *num_params;
2141 static widget_value *
2142 find_first_selectable (mw, item, skip_titles)
2143 XlwMenuWidget mw;
2144 widget_value *item;
2145 int skip_titles;
2147 widget_value *current = item;
2148 enum menu_separator separator;
2150 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2151 || (skip_titles && !current->call_data && !current->contents))
2152 if (current->next)
2153 current=current->next;
2154 else
2155 return NULL;
2157 return current;
2160 static widget_value *
2161 find_next_selectable (mw, item, skip_titles)
2162 XlwMenuWidget mw;
2163 widget_value *item;
2164 int skip_titles;
2166 widget_value *current = item;
2167 enum menu_separator separator;
2169 while (current->next && (current=current->next) &&
2170 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2171 || (skip_titles && !current->call_data && !current->contents)))
2174 if (current == item)
2176 if (mw->menu.old_depth < 2)
2177 return current;
2178 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2180 while (lw_separator_p (current->name, &separator, 0)
2181 || !current->enabled
2182 || (skip_titles && !current->call_data
2183 && !current->contents))
2185 if (current->next)
2186 current=current->next;
2188 if (current == item)
2189 break;
2194 return current;
2197 static widget_value *
2198 find_prev_selectable (mw, item, skip_titles)
2199 XlwMenuWidget mw;
2200 widget_value *item;
2201 int skip_titles;
2203 widget_value *current = item;
2204 widget_value *prev = item;
2206 while ((current=find_next_selectable (mw, current, skip_titles))
2207 != item)
2209 if (prev == current)
2210 break;
2211 prev=current;
2214 return prev;
2217 static void
2218 Down (w, ev, params, num_params)
2219 Widget w;
2220 XEvent *ev;
2221 String *params;
2222 Cardinal *num_params;
2224 XlwMenuWidget mw = (XlwMenuWidget) w;
2225 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2226 int popup_menu_p = mw->menu.top_depth == 1;
2228 /* Inside top-level menu-bar? */
2229 if (mw->menu.old_depth == mw->menu.top_depth)
2230 /* When <down> in the menu-bar is pressed, display the corresponding
2231 sub-menu and select the first selectable menu item there.
2232 If this is a popup menu, skip title item of the popup. */
2233 set_new_state (mw,
2234 find_first_selectable (mw,
2235 selected_item->contents,
2236 popup_menu_p),
2237 mw->menu.old_depth);
2238 else
2239 /* Highlight next possible (enabled and not separator) menu item. */
2240 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2241 mw->menu.old_depth - 1);
2243 remap_menubar (mw);
2246 static void
2247 Up (w, ev, params, num_params)
2248 Widget w;
2249 XEvent *ev;
2250 String *params;
2251 Cardinal *num_params;
2253 XlwMenuWidget mw = (XlwMenuWidget) w;
2254 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2255 int popup_menu_p = mw->menu.top_depth == 1;
2257 /* Inside top-level menu-bar? */
2258 if (mw->menu.old_depth == mw->menu.top_depth)
2260 /* FIXME: this is tricky. <up> in the menu-bar should select the
2261 last selectable item in the list. So we select the first
2262 selectable one and find the previous selectable item. Is there
2263 a better way? */
2264 /* If this is a popup menu, skip title item of the popup. */
2265 set_new_state (mw,
2266 find_first_selectable (mw,
2267 selected_item->contents,
2268 popup_menu_p),
2269 mw->menu.old_depth);
2270 remap_menubar (mw);
2271 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2272 set_new_state (mw,
2273 find_prev_selectable (mw,
2274 selected_item,
2275 popup_menu_p),
2276 mw->menu.old_depth - 1);
2278 else
2279 /* Highlight previous (enabled and not separator) menu item. */
2280 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2281 mw->menu.old_depth - 1);
2283 remap_menubar (mw);
2286 void
2287 Left (w, ev, params, num_params)
2288 Widget w;
2289 XEvent *ev;
2290 String *params;
2291 Cardinal *num_params;
2293 XlwMenuWidget mw = (XlwMenuWidget) w;
2294 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2296 /* Inside top-level menu-bar? */
2297 if (mw->menu.old_depth == mw->menu.top_depth)
2298 /* When <left> in the menu-bar is pressed, display the previous item on
2299 the menu-bar. If the current item is the first one, highlight the
2300 last item in the menubar (probably Help). */
2301 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2302 mw->menu.old_depth - 1);
2303 else if (mw->menu.old_depth == 1
2304 && selected_item->contents) /* Is this menu item expandable? */
2306 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2307 remap_menubar (mw);
2308 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2309 if (!selected_item->enabled && find_first_selectable (mw,
2310 selected_item,
2312 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2313 mw->menu.old_depth - 1);
2316 else
2318 pop_new_stack_if_no_contents (mw);
2319 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2320 mw->menu.old_depth - 2);
2323 remap_menubar (mw);
2326 void
2327 Right (w, ev, params, num_params)
2328 Widget w;
2329 XEvent *ev;
2330 String *params;
2331 Cardinal *num_params;
2333 XlwMenuWidget mw = (XlwMenuWidget) w;
2334 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2336 /* Inside top-level menu-bar? */
2337 if (mw->menu.old_depth == mw->menu.top_depth)
2338 /* When <right> in the menu-bar is pressed, display the next item on
2339 the menu-bar. If the current item is the last one, highlight the
2340 first item (probably File). */
2341 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2342 mw->menu.old_depth - 1);
2343 else if (selected_item->contents) /* Is this menu item expandable? */
2345 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2346 remap_menubar (mw);
2347 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2348 if (!selected_item->enabled && find_first_selectable (mw,
2349 selected_item,
2351 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2352 mw->menu.old_depth - 1);
2354 else
2356 pop_new_stack_if_no_contents (mw);
2357 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2358 mw->menu.old_depth - 2);
2361 remap_menubar (mw);
2364 /* Handle key press and release events while menu is popped up.
2365 Our action is to get rid of the menu. */
2366 static void
2367 Key (w, ev, params, num_params)
2368 Widget w;
2369 XEvent *ev;
2370 String *params;
2371 Cardinal *num_params;
2373 XlwMenuWidget mw = (XlwMenuWidget)w;
2375 /* Pop down everything. */
2376 mw->menu.new_depth = 1;
2377 remap_menubar (mw);
2379 if (mw->menu.popped_up)
2381 mw->menu.popped_up = False;
2382 ungrab_all ((Widget)mw, ev->xmotion.time);
2383 if (XtIsShell (XtParent ((Widget) mw)))
2384 XtPopdown (XtParent ((Widget) mw));
2385 else
2387 XtRemoveGrab ((Widget) mw);
2388 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2392 /* callback */
2393 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2396 static void
2397 Select (w, ev, params, num_params)
2398 Widget w;
2399 XEvent *ev;
2400 String *params;
2401 Cardinal *num_params;
2403 XlwMenuWidget mw = (XlwMenuWidget)w;
2404 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2406 /* If user releases the button quickly, without selecting anything,
2407 after the initial down-click that brought the menu up,
2408 do nothing. */
2409 if ((selected_item == 0
2410 || ((widget_value *) selected_item)->call_data == 0)
2411 && !next_release_must_exit
2412 && (ev->xbutton.time - menu_post_event.xbutton.time
2413 < XtGetMultiClickTime (XtDisplay (w))))
2414 return;
2416 /* pop down everything. */
2417 mw->menu.new_depth = 1;
2418 remap_menubar (mw);
2420 if (mw->menu.popped_up)
2422 mw->menu.popped_up = False;
2423 ungrab_all ((Widget)mw, ev->xmotion.time);
2424 if (XtIsShell (XtParent ((Widget) mw)))
2425 XtPopdown (XtParent ((Widget) mw));
2426 else
2428 XtRemoveGrab ((Widget) mw);
2429 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2433 /* callback */
2434 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2438 \f/* Special code to pop-up a menu */
2439 static void
2440 pop_up_menu (mw, event)
2441 XlwMenuWidget mw;
2442 XButtonPressedEvent* event;
2444 int x = event->x_root;
2445 int y = event->y_root;
2446 int w;
2447 int h;
2448 int borderwidth = mw->menu.shadow_thickness;
2449 Screen* screen = XtScreen (mw);
2450 Display *display = XtDisplay (mw);
2452 next_release_must_exit = 0;
2454 mw->menu.inside_entry = NULL;
2455 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2457 if (XtIsShell (XtParent ((Widget)mw)))
2458 size_menu (mw, 0);
2460 w = mw->menu.windows [0].width;
2461 h = mw->menu.windows [0].height;
2463 x -= borderwidth;
2464 y -= borderwidth;
2465 if (x < borderwidth)
2466 x = borderwidth;
2467 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2468 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2469 if (y < borderwidth)
2470 y = borderwidth;
2471 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2472 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2474 mw->menu.popped_up = True;
2475 if (XtIsShell (XtParent ((Widget)mw)))
2477 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2478 XtParent ((Widget)mw)->core.border_width);
2479 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2480 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2481 mw->menu.windows [0].x = x + borderwidth;
2482 mw->menu.windows [0].y = y + borderwidth;
2483 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2485 else
2487 XEvent *ev = (XEvent *) event;
2489 XtAddGrab ((Widget) mw, True, True);
2491 /* notes the absolute position of the menubar window */
2492 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2493 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2494 mw->menu.top_depth = 2;
2497 #ifdef emacs
2498 x_catch_errors (display);
2499 #endif
2500 if (XtGrabPointer ((Widget)mw, False,
2501 (PointerMotionMask
2502 | PointerMotionHintMask
2503 | ButtonReleaseMask
2504 | ButtonPressMask),
2505 GrabModeAsync, GrabModeAsync, None,
2506 mw->menu.cursor_shape,
2507 event->time) == Success)
2509 if (! GRAB_KEYBOARD
2510 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2511 GrabModeAsync, event->time) == Success)
2513 XtSetKeyboardFocus((Widget)mw, None);
2514 pointer_grabbed = 1;
2516 else
2517 XtUngrabPointer ((Widget)mw, event->time);
2520 #ifdef emacs
2521 if (x_had_errors_p (display))
2523 pointer_grabbed = 0;
2524 XtUngrabPointer ((Widget)mw, event->time);
2526 x_uncatch_errors ();
2527 #endif
2529 ((XMotionEvent*)event)->is_hint = 0;
2530 handle_motion_event (mw, (XMotionEvent*)event);
2533 /* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2534 (do not change this comment) */