(find-file-noselect-1, set-visited-file-name):
[emacs.git] / lwlib / xlwmenu.c
blobfc73746dc8681ea6ce5279a46eb37ccae0c0f308
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 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
165 offset(menu.enter), XtRCallback, (XtPointer)NULL},
166 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
167 offset(menu.leave), XtRCallback, (XtPointer)NULL},
168 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
169 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
170 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
171 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
172 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
173 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
175 #undef offset
177 static Boolean XlwMenuSetValues();
178 static void XlwMenuRealize();
179 static void XlwMenuRedisplay();
180 static void XlwMenuResize();
181 static void XlwMenuInitialize();
182 static void XlwMenuRedisplay();
183 static void XlwMenuDestroy();
184 static void XlwMenuClassInitialize();
185 static void Start();
186 static void Drag();
187 static void Down();
188 static void Up();
189 static void Left();
190 static void Right();
191 static void Select();
192 static void Key();
193 static void Nothing();
194 static int separator_height __P ((enum menu_separator));
195 static void pop_up_menu __P ((XlwMenuWidget, XButtonPressedEvent *));
198 static XtActionsRec
199 xlwMenuActionsList [] =
201 {"start", Start},
202 {"drag", Drag},
203 {"down", Down},
204 {"up", Up},
205 {"left", Left},
206 {"right", Right},
207 {"select", Select},
208 {"key", Key},
209 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
210 {"nothing", Nothing},
213 #define SuperClass ((CoreWidgetClass)&coreClassRec)
215 XlwMenuClassRec xlwMenuClassRec =
217 { /* CoreClass fields initialization */
218 (WidgetClass) SuperClass, /* superclass */
219 "XlwMenu", /* class_name */
220 sizeof(XlwMenuRec), /* size */
221 XlwMenuClassInitialize, /* class_initialize */
222 NULL, /* class_part_initialize */
223 FALSE, /* class_inited */
224 XlwMenuInitialize, /* initialize */
225 NULL, /* initialize_hook */
226 XlwMenuRealize, /* realize */
227 xlwMenuActionsList, /* actions */
228 XtNumber(xlwMenuActionsList), /* num_actions */
229 xlwMenuResources, /* resources */
230 XtNumber(xlwMenuResources), /* resource_count */
231 NULLQUARK, /* xrm_class */
232 TRUE, /* compress_motion */
233 XtExposeCompressMaximal, /* compress_exposure */
234 TRUE, /* compress_enterleave */
235 FALSE, /* visible_interest */
236 XlwMenuDestroy, /* destroy */
237 XlwMenuResize, /* resize */
238 XlwMenuRedisplay, /* expose */
239 XlwMenuSetValues, /* set_values */
240 NULL, /* set_values_hook */
241 XtInheritSetValuesAlmost, /* set_values_almost */
242 NULL, /* get_values_hook */
243 NULL, /* accept_focus */
244 XtVersion, /* version */
245 NULL, /* callback_private */
246 xlwMenuTranslations, /* tm_table */
247 XtInheritQueryGeometry, /* query_geometry */
248 XtInheritDisplayAccelerator, /* display_accelerator */
249 NULL /* extension */
250 }, /* XlwMenuClass fields initialization */
252 0 /* dummy */
256 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
258 int submenu_destroyed;
260 /* For debug, if installation-directory is non-nil this is not an installed
261 Emacs. In that case we do not grab the keyboard to make it easier to
262 debug. */
263 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
265 static int next_release_must_exit;
267 \f/* Utilities */
269 /* Ungrab pointer and keyboard */
270 static void
271 ungrab_all (w, ungrabtime)
272 Widget w;
273 Time ungrabtime;
275 XtUngrabPointer (w, ungrabtime);
276 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
279 /* Like abort, but remove grabs from widget W before. */
281 static void
282 abort_gracefully (w)
283 Widget w;
285 if (XtIsShell (XtParent (w)))
286 XtRemoveGrab (w);
287 ungrab_all (w, CurrentTime);
288 abort ();
291 static void
292 push_new_stack (mw, val)
293 XlwMenuWidget mw;
294 widget_value* val;
296 if (!mw->menu.new_stack)
298 mw->menu.new_stack_length = 10;
299 mw->menu.new_stack =
300 (widget_value**)XtCalloc (mw->menu.new_stack_length,
301 sizeof (widget_value*));
303 else if (mw->menu.new_depth == mw->menu.new_stack_length)
305 mw->menu.new_stack_length *= 2;
306 mw->menu.new_stack =
307 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
308 mw->menu.new_stack_length * sizeof (widget_value*));
310 mw->menu.new_stack [mw->menu.new_depth++] = val;
313 static void
314 pop_new_stack_if_no_contents (mw)
315 XlwMenuWidget mw;
317 if (mw->menu.new_depth > 1)
319 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
320 mw->menu.new_depth -= 1;
324 static void
325 make_old_stack_space (mw, n)
326 XlwMenuWidget mw;
327 int n;
329 if (!mw->menu.old_stack)
331 mw->menu.old_stack_length = 10;
332 mw->menu.old_stack =
333 (widget_value**)XtCalloc (mw->menu.old_stack_length,
334 sizeof (widget_value*));
336 else if (mw->menu.old_stack_length < n)
338 mw->menu.old_stack_length *= 2;
339 mw->menu.old_stack =
340 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
341 mw->menu.old_stack_length * sizeof (widget_value*));
345 \f/* Size code */
346 static int
347 string_width (mw, s)
348 XlwMenuWidget mw;
349 char *s;
351 XCharStruct xcs;
352 int drop;
353 #ifdef HAVE_X_I18N
354 XRectangle ink, logical;
355 if (mw->menu.fontSet)
357 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
358 return logical.width;
360 #endif
362 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
363 return xcs.width;
367 #ifdef HAVE_X_I18N
368 #define MENU_FONT_HEIGHT(mw) \
369 ((mw)->menu.fontSet != NULL \
370 ? (mw)->menu.font_extents->max_logical_extent.height \
371 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
372 #define MENU_FONT_ASCENT(mw) \
373 ((mw)->menu.fontSet != NULL \
374 ? - (mw)->menu.font_extents->max_logical_extent.y \
375 : (mw)->menu.font->ascent)
376 #else
377 #define MENU_FONT_HEIGHT(mw) \
378 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
379 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
380 #endif
382 static int
383 arrow_width (mw)
384 XlwMenuWidget mw;
386 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
389 /* Return the width of toggle buttons of widget MW. */
391 static int
392 toggle_button_width (mw)
393 XlwMenuWidget mw;
395 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
399 /* Return the width of radio buttons of widget MW. */
401 static int
402 radio_button_width (mw)
403 XlwMenuWidget mw;
405 return toggle_button_width (mw) * 1.41;
409 static XtResource
410 nameResource[] =
412 {"labelString", "LabelString", XtRString, sizeof(String),
413 0, XtRImmediate, 0},
416 static char*
417 resource_widget_value (mw, val)
418 XlwMenuWidget mw;
419 widget_value *val;
421 if (!val->toolkit_data)
423 char* resourced_name = NULL;
424 char* complete_name;
425 XtGetSubresources ((Widget) mw,
426 (XtPointer) &resourced_name,
427 val->name, val->name,
428 nameResource, 1, NULL, 0);
429 if (!resourced_name)
430 resourced_name = val->name;
431 if (!val->value)
433 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
434 strcpy (complete_name, resourced_name);
436 else
438 int complete_length =
439 strlen (resourced_name) + strlen (val->value) + 2;
440 complete_name = XtMalloc (complete_length);
441 *complete_name = 0;
442 strcat (complete_name, resourced_name);
443 strcat (complete_name, " ");
444 strcat (complete_name, val->value);
447 val->toolkit_data = complete_name;
448 val->free_toolkit_data = True;
450 return (char*)val->toolkit_data;
453 /* Returns the sizes of an item */
454 static void
455 size_menu_item (mw, val, horizontal_p, label_width, rest_width, button_width,
456 height)
457 XlwMenuWidget mw;
458 widget_value* val;
459 int horizontal_p;
460 int* label_width;
461 int* rest_width;
462 int* button_width;
463 int* height;
465 enum menu_separator separator;
467 if (lw_separator_p (val->name, &separator, 0))
469 *height = separator_height (separator);
470 *label_width = 1;
471 *rest_width = 0;
472 *button_width = 0;
474 else
476 *height = MENU_FONT_HEIGHT (mw)
477 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
479 *label_width =
480 string_width (mw, resource_widget_value (mw, val))
481 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
483 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
484 if (!horizontal_p)
486 if (val->contents)
487 /* Add width of the arrow displayed for submenus. */
488 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
489 else if (val->key)
490 /* Add width of key equivalent string. */
491 *rest_width += (string_width (mw, val->key)
492 + mw->menu.arrow_spacing);
494 if (val->button_type == BUTTON_TYPE_TOGGLE)
495 *button_width = (toggle_button_width (mw)
496 + mw->menu.horizontal_spacing);
497 else if (val->button_type == BUTTON_TYPE_RADIO)
498 *button_width = (radio_button_width (mw)
499 + mw->menu.horizontal_spacing);
504 static void
505 size_menu (mw, level)
506 XlwMenuWidget mw;
507 int level;
509 unsigned int label_width = 0;
510 int rest_width = 0;
511 int button_width = 0;
512 int max_rest_width = 0;
513 int max_button_width = 0;
514 unsigned int height = 0;
515 int horizontal_p = mw->menu.horizontal && (level == 0);
516 widget_value* val;
517 window_state* ws;
519 if (level >= mw->menu.old_depth)
520 abort_gracefully ((Widget) mw);
522 ws = &mw->menu.windows [level];
523 ws->width = 0;
524 ws->height = 0;
525 ws->label_width = 0;
526 ws->button_width = 0;
528 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
530 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
531 &button_width, &height);
532 if (horizontal_p)
534 ws->width += label_width + rest_width;
535 if (height > ws->height)
536 ws->height = height;
538 else
540 if (label_width > ws->label_width)
541 ws->label_width = label_width;
542 if (rest_width > max_rest_width)
543 max_rest_width = rest_width;
544 if (button_width > max_button_width)
545 max_button_width = button_width;
546 ws->height += height;
550 if (horizontal_p)
551 ws->label_width = ws->button_width = 0;
552 else
554 ws->width = ws->label_width + max_rest_width + max_button_width;
555 ws->button_width = max_button_width;
558 ws->width += 2 * mw->menu.shadow_thickness;
559 ws->height += 2 * mw->menu.shadow_thickness;
561 if (horizontal_p)
563 ws->width += 2 * mw->menu.margin;
564 ws->height += 2 * mw->menu.margin;
569 \f/* Display code */
571 static void
572 draw_arrow (mw, window, gc, x, y, width, down_p)
573 XlwMenuWidget mw;
574 Window window;
575 GC gc;
576 int x;
577 int y;
578 int width;
579 int down_p;
581 Display *dpy = XtDisplay (mw);
582 GC top_gc = mw->menu.shadow_top_gc;
583 GC bottom_gc = mw->menu.shadow_bottom_gc;
584 int thickness = mw->menu.shadow_thickness;
585 int height = width;
586 XPoint pt[10];
587 /* alpha = atan (0.5)
588 factor = (1 + sin (alpha)) / cos (alpha) */
589 double factor = 1.62;
590 int thickness2 = thickness * factor;
592 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
594 if (down_p)
596 GC temp;
597 temp = top_gc;
598 top_gc = bottom_gc;
599 bottom_gc = temp;
602 pt[0].x = x;
603 pt[0].y = y + height;
604 pt[1].x = x + thickness;
605 pt[1].y = y + height - thickness2;
606 pt[2].x = x + thickness2;
607 pt[2].y = y + thickness2;
608 pt[3].x = x;
609 pt[3].y = y;
610 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
612 pt[0].x = x;
613 pt[0].y = y;
614 pt[1].x = x + thickness;
615 pt[1].y = y + thickness2;
616 pt[2].x = x + width - thickness2;
617 pt[2].y = y + height / 2;
618 pt[3].x = x + width;
619 pt[3].y = y + height / 2;
620 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
622 pt[0].x = x;
623 pt[0].y = y + height;
624 pt[1].x = x + thickness;
625 pt[1].y = y + height - thickness2;
626 pt[2].x = x + width - thickness2;
627 pt[2].y = y + height / 2;
628 pt[3].x = x + width;
629 pt[3].y = y + height / 2;
630 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
635 static void
636 draw_shadow_rectangle (mw, window, x, y, width, height, erase_p, down_p)
637 XlwMenuWidget mw;
638 Window window;
639 int x;
640 int y;
641 int width;
642 int height;
643 int erase_p;
644 int down_p;
646 Display *dpy = XtDisplay (mw);
647 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
648 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
649 int thickness = mw->menu.shadow_thickness;
650 XPoint points [4];
652 if (!erase_p && down_p)
654 GC temp;
655 temp = top_gc;
656 top_gc = bottom_gc;
657 bottom_gc = temp;
660 points [0].x = x;
661 points [0].y = y;
662 points [1].x = x + width;
663 points [1].y = y;
664 points [2].x = x + width - thickness;
665 points [2].y = y + thickness;
666 points [3].x = x;
667 points [3].y = y + thickness;
668 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
669 points [0].x = x;
670 points [0].y = y + thickness;
671 points [1].x = x;
672 points [1].y = y + height;
673 points [2].x = x + thickness;
674 points [2].y = y + height - thickness;
675 points [3].x = x + thickness;
676 points [3].y = y + thickness;
677 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
678 points [0].x = x + width;
679 points [0].y = y;
680 points [1].x = x + width - thickness;
681 points [1].y = y + thickness;
682 points [2].x = x + width - thickness;
683 points [2].y = y + height - thickness;
684 points [3].x = x + width;
685 points [3].y = y + height - thickness;
686 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
687 points [0].x = x;
688 points [0].y = y + height;
689 points [1].x = x + width;
690 points [1].y = y + height;
691 points [2].x = x + width;
692 points [2].y = y + height - thickness;
693 points [3].x = x + thickness;
694 points [3].y = y + height - thickness;
695 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
699 static void
700 draw_shadow_rhombus (mw, window, x, y, width, height, erase_p, down_p)
701 XlwMenuWidget mw;
702 Window window;
703 int x;
704 int y;
705 int width;
706 int height;
707 int erase_p;
708 int down_p;
710 Display *dpy = XtDisplay (mw);
711 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
712 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
713 int thickness = mw->menu.shadow_thickness;
714 XPoint points [4];
716 if (!erase_p && down_p)
718 GC temp;
719 temp = top_gc;
720 top_gc = bottom_gc;
721 bottom_gc = temp;
724 points [0].x = x;
725 points [0].y = y + height / 2;
726 points [1].x = x + thickness;
727 points [1].y = y + height / 2;
728 points [2].x = x + width / 2;
729 points [2].y = y + thickness;
730 points [3].x = x + width / 2;
731 points [3].y = y;
732 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
733 points [0].x = x + width / 2;
734 points [0].y = y;
735 points [1].x = x + width / 2;
736 points [1].y = y + thickness;
737 points [2].x = x + width - thickness;
738 points [2].y = y + height / 2;
739 points [3].x = x + width;
740 points [3].y = y + height / 2;
741 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
742 points [0].x = x;
743 points [0].y = y + height / 2;
744 points [1].x = x + thickness;
745 points [1].y = y + height / 2;
746 points [2].x = x + width / 2;
747 points [2].y = y + height - thickness;
748 points [3].x = x + width / 2;
749 points [3].y = y + height;
750 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
751 points [0].x = x + width / 2;
752 points [0].y = y + height;
753 points [1].x = x + width / 2;
754 points [1].y = y + height - thickness;
755 points [2].x = x + width - thickness;
756 points [2].y = y + height / 2;
757 points [3].x = x + width;
758 points [3].y = y + height / 2;
759 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
763 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
764 top-left corner of the menu item. SELECTED_P non-zero means the
765 toggle button is selected. */
767 static void
768 draw_toggle (mw, window, x, y, selected_p)
769 XlwMenuWidget mw;
770 Window window;
771 int x, y, selected_p;
773 int width, height;
775 width = toggle_button_width (mw);
776 height = width;
777 x += mw->menu.horizontal_spacing;
778 y += (MENU_FONT_ASCENT (mw) - height) / 2;
779 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
783 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
784 top-left corner of the menu item. SELECTED_P non-zero means the
785 toggle button is selected. */
787 static void
788 draw_radio (mw, window, x, y, selected_p)
789 XlwMenuWidget mw;
790 Window window;
791 int x, y, selected_p;
793 int width, height;
795 width = radio_button_width (mw);
796 height = width;
797 x += mw->menu.horizontal_spacing;
798 y += (MENU_FONT_ASCENT (mw) - height) / 2;
799 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
803 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
804 top-left corner of the menu item. WIDTH is the width of the
805 separator to draw. TYPE is the separator type. */
807 static void
808 draw_separator (mw, window, x, y, width, type)
809 XlwMenuWidget mw;
810 Window window;
811 int x, y, width;
812 enum menu_separator type;
814 Display *dpy = XtDisplay (mw);
815 XGCValues xgcv;
817 switch (type)
819 case SEPARATOR_NO_LINE:
820 break;
822 case SEPARATOR_SINGLE_LINE:
823 XDrawLine (dpy, window, mw->menu.foreground_gc,
824 x, y, x + width, y);
825 break;
827 case SEPARATOR_DOUBLE_LINE:
828 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
829 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
830 break;
832 case SEPARATOR_SINGLE_DASHED_LINE:
833 xgcv.line_style = LineOnOffDash;
834 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
835 XDrawLine (dpy, window, mw->menu.foreground_gc,
836 x, y, x + width, y);
837 xgcv.line_style = LineSolid;
838 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
839 break;
841 case SEPARATOR_DOUBLE_DASHED_LINE:
842 draw_separator (mw, window, x, y, width,
843 SEPARATOR_SINGLE_DASHED_LINE);
844 draw_separator (mw, window, x, y + 2, width,
845 SEPARATOR_SINGLE_DASHED_LINE);
846 break;
848 case SEPARATOR_SHADOW_ETCHED_IN:
849 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
850 x, y, x + width, y);
851 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
852 x, y + 1, x + width, y + 1);
853 break;
855 case SEPARATOR_SHADOW_ETCHED_OUT:
856 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
857 x, y, x + width, y);
858 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
859 x, y + 1, x + width, y + 1);
860 break;
862 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
863 xgcv.line_style = LineOnOffDash;
864 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
865 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
866 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
867 xgcv.line_style = LineSolid;
868 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
869 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
870 break;
872 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
873 xgcv.line_style = LineOnOffDash;
874 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
875 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
876 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
877 xgcv.line_style = LineSolid;
878 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
879 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
880 break;
882 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
883 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
884 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
885 break;
887 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
888 draw_separator (mw, window, x, y, width,
889 SEPARATOR_SHADOW_ETCHED_OUT);
890 draw_separator (mw, window, x, y + 3, width,
891 SEPARATOR_SHADOW_ETCHED_OUT);
892 break;
894 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
895 xgcv.line_style = LineOnOffDash;
896 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
897 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
898 draw_separator (mw, window, x, y, width,
899 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
900 xgcv.line_style = LineSolid;
901 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
902 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
903 break;
905 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
906 xgcv.line_style = LineOnOffDash;
907 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
908 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
909 draw_separator (mw, window, x, y, width,
910 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
911 xgcv.line_style = LineSolid;
912 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
913 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
914 break;
916 default:
917 abort ();
922 /* Return the pixel height of menu separator SEPARATOR. */
924 static int
925 separator_height (separator)
926 enum menu_separator separator;
928 switch (separator)
930 case SEPARATOR_NO_LINE:
931 return 2;
933 case SEPARATOR_SINGLE_LINE:
934 case SEPARATOR_SINGLE_DASHED_LINE:
935 return 1;
937 case SEPARATOR_DOUBLE_LINE:
938 case SEPARATOR_DOUBLE_DASHED_LINE:
939 return 3;
941 case SEPARATOR_SHADOW_ETCHED_IN:
942 case SEPARATOR_SHADOW_ETCHED_OUT:
943 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
944 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
945 return 2;
947 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
948 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
949 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
950 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
951 return 5;
953 default:
954 abort ();
959 /* Display the menu item and increment where.x and where.y to show how large
960 the menu item was. */
962 static void
963 display_menu_item (mw, val, ws, where, highlighted_p, horizontal_p,
964 just_compute_p)
965 XlwMenuWidget mw;
966 widget_value* val;
967 window_state* ws;
968 XPoint* where;
969 Boolean highlighted_p;
970 Boolean horizontal_p;
971 Boolean just_compute_p;
973 GC deco_gc;
974 GC text_gc;
975 int font_height = MENU_FONT_HEIGHT (mw);
976 int font_ascent = MENU_FONT_ASCENT (mw);
977 int shadow = mw->menu.shadow_thickness;
978 int margin = mw->menu.margin;
979 int h_spacing = mw->menu.horizontal_spacing;
980 int v_spacing = mw->menu.vertical_spacing;
981 int label_width;
982 int rest_width;
983 int button_width;
984 int height;
985 int width;
986 enum menu_separator separator;
987 int separator_p = lw_separator_p (val->name, &separator, 0);
989 /* compute the sizes of the item */
990 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
991 &button_width, &height);
993 if (horizontal_p)
994 width = label_width + rest_width;
995 else
997 label_width = ws->label_width;
998 width = ws->width - 2 * shadow;
1001 /* Only highlight an enabled item that has a callback. */
1002 if (highlighted_p)
1003 if (!val->enabled || !(val->call_data || val->contents))
1004 highlighted_p = 0;
1006 /* do the drawing. */
1007 if (!just_compute_p)
1009 /* Add the shadow border of the containing menu */
1010 int x = where->x + shadow;
1011 int y = where->y + shadow;
1013 if (horizontal_p)
1015 x += margin;
1016 y += margin;
1019 /* pick the foreground and background GC. */
1020 if (val->enabled)
1021 text_gc = mw->menu.foreground_gc;
1022 else
1023 text_gc = mw->menu.disabled_gc;
1024 deco_gc = mw->menu.foreground_gc;
1026 if (separator_p)
1028 draw_separator (mw, ws->window, x, y, width, separator);
1030 else
1032 int x_offset = x + h_spacing + shadow;
1033 char* display_string = resource_widget_value (mw, val);
1034 draw_shadow_rectangle (mw, ws->window, x, y, width, height, True,
1035 False);
1037 /* Deal with centering a menu title. */
1038 if (!horizontal_p && !val->contents && !val->call_data)
1040 int l = string_width (mw, display_string);
1042 if (width > l)
1043 x_offset = (width - l) >> 1;
1045 else if (!horizontal_p && ws->button_width)
1046 x_offset += ws->button_width;
1049 #ifdef HAVE_X_I18N
1050 if (mw->menu.fontSet)
1051 XmbDrawString (XtDisplay (mw), ws->window, mw->menu.fontSet,
1052 text_gc, x_offset,
1053 y + v_spacing + shadow + font_ascent,
1054 display_string, strlen (display_string));
1055 else
1056 #endif
1057 XDrawString (XtDisplay (mw), ws->window,
1058 text_gc, x_offset,
1059 y + v_spacing + shadow + font_ascent,
1060 display_string, strlen (display_string));
1062 if (!horizontal_p)
1064 if (val->button_type == BUTTON_TYPE_TOGGLE)
1065 draw_toggle (mw, ws->window, x, y + v_spacing + shadow,
1066 val->selected);
1067 else if (val->button_type == BUTTON_TYPE_RADIO)
1068 draw_radio (mw, ws->window, x, y + v_spacing + shadow,
1069 val->selected);
1071 if (val->contents)
1073 int a_w = arrow_width (mw);
1074 draw_arrow (mw, ws->window, deco_gc,
1075 x + width - a_w
1076 - mw->menu.horizontal_spacing
1077 - mw->menu.shadow_thickness,
1078 y + v_spacing + shadow, a_w,
1079 highlighted_p);
1081 else if (val->key)
1083 #ifdef HAVE_X_I18N
1084 if (mw->menu.fontSet)
1085 XmbDrawString (XtDisplay (mw), ws->window,
1086 mw->menu.fontSet,
1087 text_gc,
1088 x + label_width + mw->menu.arrow_spacing,
1089 y + v_spacing + shadow + font_ascent,
1090 val->key, strlen (val->key));
1091 else
1092 #endif
1093 XDrawString (XtDisplay (mw), ws->window,
1094 text_gc,
1095 x + label_width + mw->menu.arrow_spacing,
1096 y + v_spacing + shadow + font_ascent,
1097 val->key, strlen (val->key));
1100 else
1102 XDrawRectangle (XtDisplay (mw), ws->window,
1103 mw->menu.background_gc,
1104 x + shadow, y + shadow,
1105 label_width + h_spacing - 1,
1106 font_height + 2 * v_spacing - 1);
1107 draw_shadow_rectangle (mw, ws->window, x, y, width, height,
1108 True, False);
1111 if (highlighted_p)
1112 draw_shadow_rectangle (mw, ws->window, x, y, width, height, False,
1113 False);
1117 where->x += width;
1118 where->y += height;
1121 static void
1122 display_menu (mw, level, just_compute_p, highlighted_pos, hit, hit_return,
1123 this, that)
1124 XlwMenuWidget mw;
1125 int level;
1126 Boolean just_compute_p;
1127 XPoint* highlighted_pos;
1128 XPoint* hit;
1129 widget_value** hit_return;
1130 widget_value* this;
1131 widget_value* that;
1133 widget_value* val;
1134 widget_value* following_item;
1135 window_state* ws;
1136 XPoint where;
1137 int horizontal_p = mw->menu.horizontal && (level == 0);
1138 int highlighted_p;
1139 int just_compute_this_one_p;
1140 /* This is set nonzero if the element containing HIGHLIGHTED_POS
1141 is disabled, so that we do not return any subsequent element either. */
1142 int no_return = 0;
1143 enum menu_separator separator;
1145 if (level >= mw->menu.old_depth)
1146 abort_gracefully ((Widget) mw);
1148 if (level < mw->menu.old_depth - 1)
1149 following_item = mw->menu.old_stack [level + 1];
1150 else
1151 following_item = NULL;
1153 if (hit)
1154 *hit_return = NULL;
1156 where.x = 0;
1157 where.y = 0;
1159 ws = &mw->menu.windows [level];
1160 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1162 highlighted_p = val == following_item;
1163 if (highlighted_p && highlighted_pos)
1165 if (horizontal_p)
1166 highlighted_pos->x = where.x;
1167 else
1168 highlighted_pos->y = where.y;
1171 just_compute_this_one_p =
1172 just_compute_p || ((this || that) && val != this && val != that);
1174 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1175 just_compute_this_one_p);
1177 if (highlighted_p && highlighted_pos)
1179 if (horizontal_p)
1180 highlighted_pos->y = where.y;
1181 else
1182 highlighted_pos->x = where.x;
1185 if (hit
1186 && !*hit_return
1187 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1188 && !lw_separator_p (val->name, &separator, 0)
1189 && !no_return)
1191 if (val->enabled)
1192 *hit_return = val;
1193 else
1194 no_return = 1;
1195 if (mw->menu.inside_entry != val)
1197 if (mw->menu.inside_entry)
1198 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1199 (XtPointer) mw->menu.inside_entry);
1200 mw->menu.inside_entry = val;
1201 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1202 (XtPointer) mw->menu.inside_entry);
1206 if (horizontal_p)
1207 where.y = 0;
1208 else
1209 where.x = 0;
1212 if (!just_compute_p)
1213 draw_shadow_rectangle (mw, ws->window, 0, 0, ws->width, ws->height,
1214 False, False);
1217 \f/* Motion code */
1218 static void
1219 set_new_state (mw, val, level)
1220 XlwMenuWidget mw;
1221 widget_value* val;
1222 int level;
1224 int i;
1226 mw->menu.new_depth = 0;
1227 for (i = 0; i < level; i++)
1228 push_new_stack (mw, mw->menu.old_stack [i]);
1229 push_new_stack (mw, val);
1232 static void
1233 make_windows_if_needed (mw, n)
1234 XlwMenuWidget mw;
1235 int n;
1237 int i;
1238 int start_at;
1239 XSetWindowAttributes xswa;
1240 int mask;
1241 Window root = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1242 window_state* windows;
1244 if (mw->menu.windows_length >= n)
1245 return;
1247 xswa.save_under = True;
1248 xswa.override_redirect = True;
1249 xswa.background_pixel = mw->core.background_pixel;
1250 xswa.border_pixel = mw->core.border_pixel;
1251 xswa.event_mask =
1252 ExposureMask | PointerMotionMask | PointerMotionHintMask
1253 | ButtonReleaseMask | ButtonPressMask;
1254 xswa.cursor = mw->menu.cursor_shape;
1255 mask = CWSaveUnder | CWOverrideRedirect | CWBackPixel | CWBorderPixel
1256 | CWEventMask | CWCursor;
1258 if (!mw->menu.windows)
1260 mw->menu.windows =
1261 (window_state*)XtMalloc (n * sizeof (window_state));
1262 start_at = 0;
1264 else
1266 mw->menu.windows =
1267 (window_state*)XtRealloc ((char*)mw->menu.windows,
1268 n * sizeof (window_state));
1269 start_at = mw->menu.windows_length;
1271 mw->menu.windows_length = n;
1273 windows = mw->menu.windows;
1275 for (i = start_at; i < n; i++)
1277 windows [i].x = 0;
1278 windows [i].y = 0;
1279 windows [i].width = 1;
1280 windows [i].height = 1;
1281 windows [i].window =
1282 XCreateWindow (XtDisplay (mw), root, 0, 0, 1, 1,
1283 0, 0, CopyFromParent, CopyFromParent, mask, &xswa);
1287 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1290 xlwmenu_window_p (w, window)
1291 Widget w;
1292 Window window;
1294 XlwMenuWidget mw = (XlwMenuWidget) w;
1295 int i;
1297 for (i = 0; i < mw->menu.windows_length; ++i)
1298 if (window == mw->menu.windows[i].window)
1299 break;
1301 return i < mw->menu.windows_length;
1304 /* Make the window fit in the screen */
1305 static void
1306 fit_to_screen (mw, ws, previous_ws, horizontal_p)
1307 XlwMenuWidget mw;
1308 window_state* ws;
1309 window_state* previous_ws;
1310 Boolean horizontal_p;
1312 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1313 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1314 /* 1 if we are unable to avoid an overlap between
1315 this menu and the parent menu in the X dimension. */
1316 int horizontal_overlap = 0;
1318 if (ws->x < 0)
1319 ws->x = 0;
1320 else if (ws->x + ws->width > screen_width)
1322 if (!horizontal_p)
1323 /* The addition of shadow-thickness for a sub-menu's position is
1324 to reflect a similar adjustment when the menu is displayed to
1325 the right of the invoking menu-item; it makes the sub-menu
1326 look more `attached' to the menu-item. */
1327 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1328 else
1329 ws->x = screen_width - ws->width;
1330 if (ws->x < 0)
1332 ws->x = 0;
1333 horizontal_overlap = 1;
1336 /* If we overlap in X, try to avoid overlap in Y. */
1337 if (horizontal_overlap
1338 && ws->y < previous_ws->y + previous_ws->height
1339 && previous_ws->y < ws->y + ws->height)
1341 /* Put this menu right below or right above PREVIOUS_WS
1342 if there's room. */
1343 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1344 ws->y = previous_ws->y + previous_ws->height;
1345 else if (previous_ws->y - ws->height > 0)
1346 ws->y = previous_ws->y - ws->height;
1349 if (ws->y < 0)
1350 ws->y = 0;
1351 else if (ws->y + ws->height > screen_height)
1353 if (horizontal_p)
1354 ws->y = previous_ws->y - ws->height;
1355 else
1356 ws->y = screen_height - ws->height;
1357 if (ws->y < 0)
1358 ws->y = 0;
1362 /* Updates old_stack from new_stack and redisplays. */
1363 static void
1364 remap_menubar (mw)
1365 XlwMenuWidget mw;
1367 int i;
1368 int last_same;
1369 XPoint selection_position;
1370 int old_depth = mw->menu.old_depth;
1371 int new_depth = mw->menu.new_depth;
1372 widget_value** old_stack;
1373 widget_value** new_stack;
1374 window_state* windows;
1375 widget_value* old_selection;
1376 widget_value* new_selection;
1378 /* Check that enough windows and old_stack are ready. */
1379 make_windows_if_needed (mw, new_depth);
1380 make_old_stack_space (mw, new_depth);
1381 windows = mw->menu.windows;
1382 old_stack = mw->menu.old_stack;
1383 new_stack = mw->menu.new_stack;
1385 /* compute the last identical different entry */
1386 for (i = 1; i < old_depth && i < new_depth; i++)
1387 if (old_stack [i] != new_stack [i])
1388 break;
1389 last_same = i - 1;
1391 /* Memorize the previously selected item to be able to refresh it */
1392 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1393 if (old_selection && !old_selection->enabled)
1394 old_selection = NULL;
1395 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1396 if (new_selection && !new_selection->enabled)
1397 new_selection = NULL;
1399 /* Call callback when the hightlighted item changes. */
1400 if (old_selection || new_selection)
1401 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1402 (XtPointer) new_selection);
1404 /* updates old_state from new_state. It has to be done now because
1405 display_menu (called below) uses the old_stack to know what to display. */
1406 for (i = last_same + 1; i < new_depth; i++)
1407 old_stack [i] = new_stack [i];
1408 mw->menu.old_depth = new_depth;
1410 /* refresh the last selection */
1411 selection_position.x = 0;
1412 selection_position.y = 0;
1413 display_menu (mw, last_same, new_selection == old_selection,
1414 &selection_position, NULL, NULL, old_selection, new_selection);
1416 /* Now place the new menus. */
1417 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1419 window_state *previous_ws = &windows[i - 1];
1420 window_state *ws = &windows[i];
1422 ws->x = (previous_ws->x + selection_position.x
1423 + mw->menu.shadow_thickness);
1424 if (mw->menu.horizontal && i == 1)
1425 ws->x += mw->menu.margin;
1427 #if 0
1428 if (!mw->menu.horizontal || i > 1)
1429 ws->x += mw->menu.shadow_thickness;
1430 #endif
1432 ws->y = (previous_ws->y + selection_position.y
1433 + mw->menu.shadow_thickness);
1434 if (mw->menu.horizontal && i == 1)
1435 ws->y += mw->menu.margin;
1437 size_menu (mw, i);
1439 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1441 XClearWindow (XtDisplay (mw), ws->window);
1442 XMoveResizeWindow (XtDisplay (mw), ws->window, ws->x, ws->y,
1443 ws->width, ws->height);
1444 XMapRaised (XtDisplay (mw), ws->window);
1445 display_menu (mw, i, False, &selection_position, NULL, NULL, NULL, NULL);
1448 /* unmap the menus that popped down */
1449 for (i = new_depth - 1; i < old_depth; i++)
1450 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1451 XUnmapWindow (XtDisplay (mw), windows[i].window);
1454 static Boolean
1455 motion_event_is_in_menu (mw, ev, level, relative_pos)
1456 XlwMenuWidget mw;
1457 XMotionEvent* ev;
1458 int level;
1459 XPoint* relative_pos;
1461 window_state* ws = &mw->menu.windows [level];
1462 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1463 int x = ws->x + shadow;
1464 int y = ws->y + shadow;
1465 relative_pos->x = ev->x_root - x;
1466 relative_pos->y = ev->y_root - y;
1467 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1468 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1471 static Boolean
1472 map_event_to_widget_value (mw, ev, val, level)
1473 XlwMenuWidget mw;
1474 XMotionEvent* ev;
1475 widget_value** val;
1476 int* level;
1478 int i;
1479 XPoint relative_pos;
1480 window_state* ws;
1481 int inside = 0;
1483 *val = NULL;
1485 /* Find the window */
1486 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1488 ws = &mw->menu.windows [i];
1489 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1491 inside = 1;
1492 display_menu (mw, i, True, NULL, &relative_pos, val, NULL, NULL);
1494 if (*val)
1496 *level = i + 1;
1497 return True;
1502 if (!inside)
1504 if (mw->menu.inside_entry != NULL)
1505 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1506 (XtPointer) mw->menu.inside_entry);
1507 mw->menu.inside_entry = NULL;
1510 return False;
1513 \f/* Procedures */
1514 static void
1515 make_drawing_gcs (mw)
1516 XlwMenuWidget mw;
1518 XGCValues xgcv;
1519 float scale;
1520 XtGCMask mask = GCForeground | GCBackground;
1522 #ifdef HAVE_X_I18N
1523 if (!mw->menu.fontSet)
1525 xgcv.font = mw->menu.font->fid;
1526 mask |= GCFont;
1528 #else
1529 xgcv.font = mw->menu.font->fid;
1530 mask |= GCFont;
1531 #endif
1532 xgcv.foreground = mw->menu.foreground;
1533 xgcv.background = mw->core.background_pixel;
1534 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1536 xgcv.foreground = mw->menu.button_foreground;
1537 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1539 xgcv.background = mw->core.background_pixel;
1541 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1543 /* Allocate color for disabled menu-items. */
1544 mw->menu.disabled_foreground = mw->menu.foreground;
1545 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1546 scale = 2.3;
1547 else
1548 scale = 0.55;
1550 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1551 mw->core.colormap,
1552 &mw->menu.disabled_foreground,
1553 scale,
1554 0x8000);
1556 if (mw->menu.foreground == mw->menu.disabled_foreground
1557 || mw->core.background_pixel == mw->menu.disabled_foreground)
1559 /* Too few colors, use stipple. */
1560 xgcv.foreground = mw->menu.foreground;
1561 xgcv.fill_style = FillStippled;
1562 xgcv.stipple = mw->menu.gray_pixmap;
1563 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1564 | GCFillStyle | GCStipple, &xgcv);
1566 else
1568 /* Many colors available, use disabled pixel. */
1569 xgcv.foreground = mw->menu.disabled_foreground;
1570 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1573 xgcv.foreground = mw->menu.button_foreground;
1574 xgcv.background = mw->core.background_pixel;
1575 xgcv.fill_style = FillStippled;
1576 xgcv.stipple = mw->menu.gray_pixmap;
1577 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1578 | GCFillStyle | GCStipple, &xgcv);
1580 xgcv.foreground = mw->core.background_pixel;
1581 xgcv.background = mw->menu.foreground;
1582 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1585 static void
1586 release_drawing_gcs (mw)
1587 XlwMenuWidget mw;
1589 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1590 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1591 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1592 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1593 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1594 /* let's get some segvs if we try to use these... */
1595 mw->menu.foreground_gc = (GC) -1;
1596 mw->menu.button_gc = (GC) -1;
1597 mw->menu.disabled_gc = (GC) -1;
1598 mw->menu.inactive_button_gc = (GC) -1;
1599 mw->menu.background_gc = (GC) -1;
1602 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1603 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1605 static void
1606 make_shadow_gcs (mw)
1607 XlwMenuWidget mw;
1609 XGCValues xgcv;
1610 unsigned long pm = 0;
1611 Display *dpy = XtDisplay ((Widget) mw);
1612 Screen *screen = XtScreen ((Widget) mw);
1613 Colormap cmap = mw->core.colormap;
1614 XColor topc, botc;
1615 int top_frobbed = 0, bottom_frobbed = 0;
1617 mw->menu.free_top_shadow_color_p = 0;
1618 mw->menu.free_bottom_shadow_color_p = 0;
1620 if (mw->menu.top_shadow_color == -1)
1621 mw->menu.top_shadow_color = mw->core.background_pixel;
1622 else
1623 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1625 if (mw->menu.bottom_shadow_color == -1)
1626 mw->menu.bottom_shadow_color = mw->menu.foreground;
1627 else
1628 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1630 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1631 mw->menu.top_shadow_color == mw->menu.foreground)
1633 topc.pixel = mw->core.background_pixel;
1634 #ifdef emacs
1635 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1636 &topc.pixel,
1637 1.2, 0x8000))
1638 #else
1639 XQueryColor (dpy, cmap, &topc);
1640 /* don't overflow/wrap! */
1641 topc.red = MINL (65535, topc.red * 1.2);
1642 topc.green = MINL (65535, topc.green * 1.2);
1643 topc.blue = MINL (65535, topc.blue * 1.2);
1644 if (XAllocColor (dpy, cmap, &topc))
1645 #endif
1647 mw->menu.top_shadow_color = topc.pixel;
1648 mw->menu.free_top_shadow_color_p = 1;
1649 top_frobbed = 1;
1652 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1653 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1655 botc.pixel = mw->core.background_pixel;
1656 #ifdef emacs
1657 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1658 &botc.pixel,
1659 0.6, 0x4000))
1660 #else
1661 XQueryColor (dpy, cmap, &botc);
1662 botc.red *= 0.6;
1663 botc.green *= 0.6;
1664 botc.blue *= 0.6;
1665 if (XAllocColor (dpy, cmap, &botc))
1666 #endif
1668 mw->menu.bottom_shadow_color = botc.pixel;
1669 mw->menu.free_bottom_shadow_color_p = 1;
1670 bottom_frobbed = 1;
1674 if (top_frobbed && bottom_frobbed)
1676 if (topc.pixel == botc.pixel)
1678 if (botc.pixel == mw->menu.foreground)
1680 if (mw->menu.free_top_shadow_color_p)
1682 x_free_dpy_colors (dpy, screen, cmap,
1683 &mw->menu.top_shadow_color, 1);
1684 mw->menu.free_top_shadow_color_p = 0;
1686 mw->menu.top_shadow_color = mw->core.background_pixel;
1688 else
1690 if (mw->menu.free_bottom_shadow_color_p)
1692 x_free_dpy_colors (dpy, screen, cmap,
1693 &mw->menu.bottom_shadow_color, 1);
1694 mw->menu.free_bottom_shadow_color_p = 0;
1696 mw->menu.bottom_shadow_color = mw->menu.foreground;
1701 if (!mw->menu.top_shadow_pixmap &&
1702 mw->menu.top_shadow_color == mw->core.background_pixel)
1704 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1705 if (mw->menu.free_top_shadow_color_p)
1707 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1708 mw->menu.free_top_shadow_color_p = 0;
1710 mw->menu.top_shadow_color = mw->menu.foreground;
1712 if (!mw->menu.bottom_shadow_pixmap &&
1713 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1715 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1716 if (mw->menu.free_bottom_shadow_color_p)
1718 x_free_dpy_colors (dpy, screen, cmap,
1719 &mw->menu.bottom_shadow_color, 1);
1720 mw->menu.free_bottom_shadow_color_p = 0;
1722 mw->menu.bottom_shadow_color = mw->menu.foreground;
1725 xgcv.fill_style = FillStippled;
1726 xgcv.foreground = mw->menu.top_shadow_color;
1727 xgcv.stipple = mw->menu.top_shadow_pixmap;
1728 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1729 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1731 xgcv.foreground = mw->menu.bottom_shadow_color;
1732 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1733 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1734 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1738 static void
1739 release_shadow_gcs (mw)
1740 XlwMenuWidget mw;
1742 Display *dpy = XtDisplay ((Widget) mw);
1743 Screen *screen = XtScreen ((Widget) mw);
1744 Colormap cmap = mw->core.colormap;
1745 Pixel px[2];
1746 int i = 0;
1748 if (mw->menu.free_top_shadow_color_p)
1749 px[i++] = mw->menu.top_shadow_color;
1750 if (mw->menu.free_bottom_shadow_color_p)
1751 px[i++] = mw->menu.bottom_shadow_color;
1752 if (i > 0)
1753 x_free_dpy_colors (dpy, screen, cmap, px, i);
1755 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1756 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1759 static void
1760 XlwMenuInitialize (request, mw, args, num_args)
1761 Widget request;
1762 XlwMenuWidget mw;
1763 ArgList args;
1764 Cardinal *num_args;
1766 /* Get the GCs and the widget size */
1768 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1769 Display* display = XtDisplay (mw);
1771 #if 0
1772 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1774 /* _XtCreate is freeing the object that was passed to us,
1775 so make a copy that we will actually keep. */
1776 lwlib_bcopy (mw->menu.contents, tem, sizeof (widget_value));
1777 mw->menu.contents = tem;
1778 #endif
1780 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1781 mw->menu.cursor = mw->menu.cursor_shape;
1783 mw->menu.gray_pixmap
1784 = XCreatePixmapFromBitmapData (display, window, gray_bitmap_bits,
1785 gray_bitmap_width, gray_bitmap_height,
1786 (unsigned long)1, (unsigned long)0, 1);
1788 /* I don't understand why this ends up 0 sometimes,
1789 but it does. This kludge works around it.
1790 Can anyone find a real fix? -- rms. */
1791 if (mw->menu.font == 0)
1792 mw->menu.font = xlwmenu_default_font;
1793 #ifdef HAVE_X_I18N
1794 if (mw->menu.fontSet)
1795 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1796 #endif
1798 make_drawing_gcs (mw);
1799 make_shadow_gcs (mw);
1801 mw->menu.popped_up = False;
1803 mw->menu.old_depth = 1;
1804 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1805 mw->menu.old_stack_length = 1;
1806 mw->menu.old_stack [0] = mw->menu.contents;
1808 mw->menu.new_depth = 0;
1809 mw->menu.new_stack = 0;
1810 mw->menu.new_stack_length = 0;
1811 push_new_stack (mw, mw->menu.contents);
1813 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1814 mw->menu.windows_length = 1;
1815 mw->menu.windows [0].x = 0;
1816 mw->menu.windows [0].y = 0;
1817 mw->menu.windows [0].width = 0;
1818 mw->menu.windows [0].height = 0;
1819 size_menu (mw, 0);
1821 mw->core.width = mw->menu.windows [0].width;
1822 mw->core.height = mw->menu.windows [0].height;
1825 static void
1826 XlwMenuClassInitialize ()
1830 static void
1831 XlwMenuRealize (w, valueMask, attributes)
1832 Widget w;
1833 Mask *valueMask;
1834 XSetWindowAttributes *attributes;
1836 XlwMenuWidget mw = (XlwMenuWidget)w;
1837 XSetWindowAttributes xswa;
1838 int mask;
1840 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1841 (w, valueMask, attributes);
1843 xswa.save_under = True;
1844 xswa.cursor = mw->menu.cursor_shape;
1845 mask = CWSaveUnder | CWCursor;
1846 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1848 mw->menu.windows [0].window = XtWindow (w);
1849 mw->menu.windows [0].x = w->core.x;
1850 mw->menu.windows [0].y = w->core.y;
1851 mw->menu.windows [0].width = w->core.width;
1852 mw->menu.windows [0].height = w->core.height;
1855 /* Only the toplevel menubar/popup is a widget so it's the only one that
1856 receives expose events through Xt. So we repaint all the other panes
1857 when receiving an Expose event. */
1858 static void
1859 XlwMenuRedisplay (w, ev, region)
1860 Widget w;
1861 XEvent* ev;
1862 Region region;
1864 XlwMenuWidget mw = (XlwMenuWidget)w;
1865 int i;
1867 /* If we have a depth beyond 1, it's because a submenu was displayed.
1868 If the submenu has been destroyed, set the depth back to 1. */
1869 if (submenu_destroyed)
1871 mw->menu.old_depth = 1;
1872 submenu_destroyed = 0;
1875 for (i = 0; i < mw->menu.old_depth; i++)
1876 display_menu (mw, i, False, NULL, NULL, NULL, NULL, NULL);
1880 /* Part of a hack to make the menu redisplay when a tooltip frame
1881 over a menu item is unmapped. */
1883 void
1884 xlwmenu_redisplay (w)
1885 Widget w;
1887 XlwMenuRedisplay (w, NULL, None);
1890 static void
1891 XlwMenuDestroy (w)
1892 Widget w;
1894 int i;
1895 XlwMenuWidget mw = (XlwMenuWidget) w;
1897 if (pointer_grabbed)
1898 ungrab_all ((Widget)w, CurrentTime);
1899 pointer_grabbed = 0;
1901 submenu_destroyed = 1;
1903 release_drawing_gcs (mw);
1904 release_shadow_gcs (mw);
1906 /* this doesn't come from the resource db but is created explicitly
1907 so we must free it ourselves. */
1908 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
1909 mw->menu.gray_pixmap = (Pixmap) -1;
1911 #if 0
1912 /* Do free mw->menu.contents because nowadays we copy it
1913 during initialization. */
1914 XtFree (mw->menu.contents);
1915 #endif
1917 /* Don't free mw->menu.contents because that comes from our creator.
1918 The `*_stack' elements are just pointers into `contents' so leave
1919 that alone too. But free the stacks themselves. */
1920 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
1921 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
1923 /* Remember, you can't free anything that came from the resource
1924 database. This includes:
1925 mw->menu.cursor
1926 mw->menu.top_shadow_pixmap
1927 mw->menu.bottom_shadow_pixmap
1928 mw->menu.font
1929 Also the color cells of top_shadow_color, bottom_shadow_color,
1930 foreground, and button_foreground will never be freed until this
1931 client exits. Nice, eh?
1934 /* start from 1 because the one in slot 0 is w->core.window */
1935 for (i = 1; i < mw->menu.windows_length; i++)
1936 XDestroyWindow (XtDisplay (mw), mw->menu.windows [i].window);
1937 if (mw->menu.windows)
1938 XtFree ((char *) mw->menu.windows);
1941 static Boolean
1942 XlwMenuSetValues (current, request, new)
1943 Widget current;
1944 Widget request;
1945 Widget new;
1947 XlwMenuWidget oldmw = (XlwMenuWidget)current;
1948 XlwMenuWidget newmw = (XlwMenuWidget)new;
1949 Boolean redisplay = False;
1950 int i;
1952 if (newmw->menu.contents
1953 && newmw->menu.contents->contents
1954 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
1955 redisplay = True;
1956 /* Do redisplay if the contents are entirely eliminated. */
1957 if (newmw->menu.contents
1958 && newmw->menu.contents->contents == 0
1959 && newmw->menu.contents->change >= VISIBLE_CHANGE)
1960 redisplay = True;
1962 if (newmw->core.background_pixel != oldmw->core.background_pixel
1963 || newmw->menu.foreground != oldmw->menu.foreground
1964 #ifdef HAVE_X_I18N
1965 || newmw->menu.fontSet != oldmw->menu.fontSet
1966 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
1967 #else
1968 || newmw->menu.font != oldmw->menu.font
1969 #endif
1972 release_drawing_gcs (newmw);
1973 make_drawing_gcs (newmw);
1975 release_shadow_gcs (newmw);
1976 /* Cause the shadow colors to be recalculated. */
1977 newmw->menu.top_shadow_color = -1;
1978 newmw->menu.bottom_shadow_color = -1;
1979 make_shadow_gcs (newmw);
1981 redisplay = True;
1983 if (XtIsRealized (current))
1984 /* If the menu is currently displayed, change the display. */
1985 for (i = 0; i < oldmw->menu.windows_length; i++)
1987 XSetWindowBackground (XtDisplay (oldmw),
1988 oldmw->menu.windows [i].window,
1989 newmw->core.background_pixel);
1990 /* clear windows and generate expose events */
1991 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
1992 0, 0, 0, 0, True);
1996 #ifdef HAVE_X_I18N
1997 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
1999 redisplay = True;
2000 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2002 #endif
2004 return redisplay;
2007 static void
2008 XlwMenuResize (w)
2009 Widget w;
2011 XlwMenuWidget mw = (XlwMenuWidget)w;
2013 if (mw->menu.popped_up)
2015 /* Don't allow the popup menu to resize itself. */
2016 mw->core.width = mw->menu.windows [0].width;
2017 mw->core.height = mw->menu.windows [0].height;
2018 mw->core.parent->core.width = mw->core.width ;
2019 mw->core.parent->core.height = mw->core.height ;
2021 else
2023 mw->menu.windows [0].width = mw->core.width;
2024 mw->menu.windows [0].height = mw->core.height;
2028 \f/* Action procedures */
2029 static void
2030 handle_single_motion_event (mw, ev)
2031 XlwMenuWidget mw;
2032 XMotionEvent* ev;
2034 widget_value* val;
2035 int level;
2037 if (!map_event_to_widget_value (mw, ev, &val, &level))
2038 pop_new_stack_if_no_contents (mw);
2039 else
2040 set_new_state (mw, val, level);
2041 remap_menubar (mw);
2043 /* Sync with the display. Makes it feel better on X terms. */
2044 XSync (XtDisplay (mw), False);
2047 static void
2048 handle_motion_event (mw, ev)
2049 XlwMenuWidget mw;
2050 XMotionEvent* ev;
2052 int x = ev->x_root;
2053 int y = ev->y_root;
2054 int state = ev->state;
2056 handle_single_motion_event (mw, ev);
2058 /* allow motion events to be generated again */
2059 if (ev->is_hint
2060 && XQueryPointer (XtDisplay (mw), ev->window,
2061 &ev->root, &ev->subwindow,
2062 &ev->x_root, &ev->y_root,
2063 &ev->x, &ev->y,
2064 &ev->state)
2065 && ev->state == state
2066 && (ev->x_root != x || ev->y_root != y))
2067 handle_single_motion_event (mw, ev);
2070 static void
2071 Start (w, ev, params, num_params)
2072 Widget w;
2073 XEvent *ev;
2074 String *params;
2075 Cardinal *num_params;
2077 XlwMenuWidget mw = (XlwMenuWidget)w;
2079 if (!mw->menu.popped_up)
2081 menu_post_event = *ev;
2082 /* If event is set to CurrentTime, get the last known time stamp.
2083 This is for calculating if (popup) menus should stay up after
2084 a fast click. */
2085 if (menu_post_event.xbutton.time == CurrentTime)
2086 menu_post_event.xbutton.time
2087 = XtLastTimestampProcessed (XtDisplay (w));
2089 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2091 else
2093 /* If we push a button while the menu is posted semipermanently,
2094 releasing the button should always pop the menu down. */
2095 next_release_must_exit = 1;
2097 /* notes the absolute position of the menubar window */
2098 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2099 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2101 /* handles the down like a move, slots are compatible */
2102 ev->xmotion.is_hint = 0;
2103 handle_motion_event (mw, &ev->xmotion);
2107 static void
2108 Drag (w, ev, params, num_params)
2109 Widget w;
2110 XEvent *ev;
2111 String *params;
2112 Cardinal *num_params;
2114 XlwMenuWidget mw = (XlwMenuWidget)w;
2115 if (mw->menu.popped_up)
2116 handle_motion_event (mw, &ev->xmotion);
2119 /* Do nothing.
2120 This is how we handle presses and releases of modifier keys. */
2121 static void
2122 Nothing (w, ev, params, num_params)
2123 Widget w;
2124 XEvent *ev;
2125 String *params;
2126 Cardinal *num_params;
2130 static widget_value *
2131 find_first_selectable (mw, item, skip_titles)
2132 XlwMenuWidget mw;
2133 widget_value *item;
2134 int skip_titles;
2136 widget_value *current = item;
2137 enum menu_separator separator;
2139 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2140 || (skip_titles && !current->call_data && !current->contents))
2141 if (current->next)
2142 current=current->next;
2143 else
2144 return NULL;
2146 return current;
2149 static widget_value *
2150 find_next_selectable (mw, item, skip_titles)
2151 XlwMenuWidget mw;
2152 widget_value *item;
2153 int skip_titles;
2155 widget_value *current = item;
2156 enum menu_separator separator;
2158 while (current->next && (current=current->next) &&
2159 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2160 || (skip_titles && !current->call_data && !current->contents)))
2163 if (current == item)
2165 if (mw->menu.old_depth < 2)
2166 return current;
2167 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2169 while (lw_separator_p (current->name, &separator, 0)
2170 || !current->enabled
2171 || (skip_titles && !current->call_data
2172 && !current->contents))
2174 if (current->next)
2175 current=current->next;
2177 if (current == item)
2178 break;
2183 return current;
2186 static widget_value *
2187 find_prev_selectable (mw, item, skip_titles)
2188 XlwMenuWidget mw;
2189 widget_value *item;
2190 int skip_titles;
2192 widget_value *current = item;
2193 widget_value *prev = item;
2195 while ((current=find_next_selectable (mw, current, skip_titles))
2196 != item)
2198 if (prev == current)
2199 break;
2200 prev=current;
2203 return prev;
2206 static void
2207 Down (w, ev, params, num_params)
2208 Widget w;
2209 XEvent *ev;
2210 String *params;
2211 Cardinal *num_params;
2213 XlwMenuWidget mw = (XlwMenuWidget) w;
2214 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2215 int popup_menu_p = mw->menu.top_depth == 1;
2217 /* Inside top-level menu-bar? */
2218 if (mw->menu.old_depth == mw->menu.top_depth)
2219 /* When <down> in the menu-bar is pressed, display the corresponding
2220 sub-menu and select the first selectable menu item there.
2221 If this is a popup menu, skip title item of the popup. */
2222 set_new_state (mw,
2223 find_first_selectable (mw,
2224 selected_item->contents,
2225 popup_menu_p),
2226 mw->menu.old_depth);
2227 else
2228 /* Highlight next possible (enabled and not separator) menu item. */
2229 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2230 mw->menu.old_depth - 1);
2232 remap_menubar (mw);
2235 static void
2236 Up (w, ev, params, num_params)
2237 Widget w;
2238 XEvent *ev;
2239 String *params;
2240 Cardinal *num_params;
2242 XlwMenuWidget mw = (XlwMenuWidget) w;
2243 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2244 int popup_menu_p = mw->menu.top_depth == 1;
2246 /* Inside top-level menu-bar? */
2247 if (mw->menu.old_depth == mw->menu.top_depth)
2249 /* FIXME: this is tricky. <up> in the menu-bar should select the
2250 last selectable item in the list. So we select the first
2251 selectable one and find the previous selectable item. Is there
2252 a better way? */
2253 /* If this is a popup menu, skip title item of the popup. */
2254 set_new_state (mw,
2255 find_first_selectable (mw,
2256 selected_item->contents,
2257 popup_menu_p),
2258 mw->menu.old_depth);
2259 remap_menubar (mw);
2260 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2261 set_new_state (mw,
2262 find_prev_selectable (mw,
2263 selected_item,
2264 popup_menu_p),
2265 mw->menu.old_depth - 1);
2267 else
2268 /* Highlight previous (enabled and not separator) menu item. */
2269 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2270 mw->menu.old_depth - 1);
2272 remap_menubar (mw);
2275 void
2276 Left (w, ev, params, num_params)
2277 Widget w;
2278 XEvent *ev;
2279 String *params;
2280 Cardinal *num_params;
2282 XlwMenuWidget mw = (XlwMenuWidget) w;
2283 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2285 /* Inside top-level menu-bar? */
2286 if (mw->menu.old_depth == mw->menu.top_depth)
2287 /* When <left> in the menu-bar is pressed, display the previous item on
2288 the menu-bar. If the current item is the first one, highlight the
2289 last item in the menubar (probably Help). */
2290 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2291 mw->menu.old_depth - 1);
2292 else if (mw->menu.old_depth == 1
2293 && selected_item->contents) /* Is this menu item expandable? */
2295 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2296 remap_menubar (mw);
2297 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2298 if (!selected_item->enabled && find_first_selectable (mw,
2299 selected_item,
2301 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2302 mw->menu.old_depth - 1);
2305 else
2307 pop_new_stack_if_no_contents (mw);
2308 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2309 mw->menu.old_depth - 2);
2312 remap_menubar (mw);
2315 void
2316 Right (w, ev, params, num_params)
2317 Widget w;
2318 XEvent *ev;
2319 String *params;
2320 Cardinal *num_params;
2322 XlwMenuWidget mw = (XlwMenuWidget) w;
2323 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2325 /* Inside top-level menu-bar? */
2326 if (mw->menu.old_depth == mw->menu.top_depth)
2327 /* When <right> in the menu-bar is pressed, display the next item on
2328 the menu-bar. If the current item is the last one, highlight the
2329 first item (probably File). */
2330 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2331 mw->menu.old_depth - 1);
2332 else if (selected_item->contents) /* Is this menu item expandable? */
2334 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2335 remap_menubar (mw);
2336 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2337 if (!selected_item->enabled && find_first_selectable (mw,
2338 selected_item,
2340 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2341 mw->menu.old_depth - 1);
2343 else
2345 pop_new_stack_if_no_contents (mw);
2346 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2347 mw->menu.old_depth - 2);
2350 remap_menubar (mw);
2353 /* Handle key press and release events while menu is popped up.
2354 Our action is to get rid of the menu. */
2355 static void
2356 Key (w, ev, params, num_params)
2357 Widget w;
2358 XEvent *ev;
2359 String *params;
2360 Cardinal *num_params;
2362 XlwMenuWidget mw = (XlwMenuWidget)w;
2364 /* Pop down everything. */
2365 mw->menu.new_depth = 1;
2366 remap_menubar (mw);
2368 if (mw->menu.popped_up)
2370 mw->menu.popped_up = False;
2371 ungrab_all ((Widget)mw, ev->xmotion.time);
2372 if (XtIsShell (XtParent ((Widget) mw)))
2373 XtPopdown (XtParent ((Widget) mw));
2374 else
2376 XtRemoveGrab ((Widget) mw);
2377 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2381 /* callback */
2382 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2385 static void
2386 Select (w, ev, params, num_params)
2387 Widget w;
2388 XEvent *ev;
2389 String *params;
2390 Cardinal *num_params;
2392 XlwMenuWidget mw = (XlwMenuWidget)w;
2393 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2395 /* If user releases the button quickly, without selecting anything,
2396 after the initial down-click that brought the menu up,
2397 do nothing. */
2398 if ((selected_item == 0
2399 || ((widget_value *) selected_item)->call_data == 0)
2400 && !next_release_must_exit
2401 && (ev->xbutton.time - menu_post_event.xbutton.time
2402 < XtGetMultiClickTime (XtDisplay (w))))
2403 return;
2405 /* pop down everything. */
2406 mw->menu.new_depth = 1;
2407 remap_menubar (mw);
2409 if (mw->menu.popped_up)
2411 mw->menu.popped_up = False;
2412 ungrab_all ((Widget)mw, ev->xmotion.time);
2413 if (XtIsShell (XtParent ((Widget) mw)))
2414 XtPopdown (XtParent ((Widget) mw));
2415 else
2417 XtRemoveGrab ((Widget) mw);
2418 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2422 /* callback */
2423 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2427 \f/* Special code to pop-up a menu */
2428 static void
2429 pop_up_menu (mw, event)
2430 XlwMenuWidget mw;
2431 XButtonPressedEvent* event;
2433 int x = event->x_root;
2434 int y = event->y_root;
2435 int w;
2436 int h;
2437 int borderwidth = mw->menu.shadow_thickness;
2438 Screen* screen = XtScreen (mw);
2439 Display *display = XtDisplay (mw);
2441 next_release_must_exit = 0;
2443 mw->menu.inside_entry = NULL;
2444 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2446 if (XtIsShell (XtParent ((Widget)mw)))
2447 size_menu (mw, 0);
2449 w = mw->menu.windows [0].width;
2450 h = mw->menu.windows [0].height;
2452 x -= borderwidth;
2453 y -= borderwidth;
2454 if (x < borderwidth)
2455 x = borderwidth;
2456 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2457 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2458 if (y < borderwidth)
2459 y = borderwidth;
2460 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2461 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2463 mw->menu.popped_up = True;
2464 if (XtIsShell (XtParent ((Widget)mw)))
2466 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2467 XtParent ((Widget)mw)->core.border_width);
2468 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2469 display_menu (mw, 0, False, NULL, NULL, NULL, NULL, NULL);
2470 mw->menu.windows [0].x = x + borderwidth;
2471 mw->menu.windows [0].y = y + borderwidth;
2472 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2474 else
2476 XEvent *ev = (XEvent *) event;
2478 XtAddGrab ((Widget) mw, True, True);
2480 /* notes the absolute position of the menubar window */
2481 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2482 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2483 mw->menu.top_depth = 2;
2486 #ifdef emacs
2487 x_catch_errors (display);
2488 #endif
2489 if (XtGrabPointer ((Widget)mw, False,
2490 (PointerMotionMask
2491 | PointerMotionHintMask
2492 | ButtonReleaseMask
2493 | ButtonPressMask),
2494 GrabModeAsync, GrabModeAsync, None,
2495 mw->menu.cursor_shape,
2496 event->time) == Success)
2498 if (! GRAB_KEYBOARD
2499 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2500 GrabModeAsync, event->time) == Success)
2502 XtSetKeyboardFocus((Widget)mw, None);
2503 pointer_grabbed = 1;
2505 else
2506 XtUngrabPointer ((Widget)mw, event->time);
2509 #ifdef emacs
2510 if (x_had_errors_p (display))
2512 pointer_grabbed = 0;
2513 XtUngrabPointer ((Widget)mw, event->time);
2515 x_uncatch_errors ();
2516 #endif
2518 ((XMotionEvent*)event)->is_hint = 0;
2519 handle_motion_event (mw, (XMotionEvent*)event);
2522 /* arch-tag: 657f43dd-dfd0-4cc9-910c-52935f01176e
2523 (do not change this comment) */