* cal-x.el (calendar-dedicate-diary): Drop support for deleted aliases.
[emacs.git] / lwlib / xlwmenu.c
blob6f4db562e0ba6833737b81a557b97574844c3ae7
1 /* Implements a lightweight menubar widget.
3 Copyright (C) 1992 Lucid, Inc.
4 Copyright (C) 1994-1995, 1997, 1999-2014 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. If not, see <http://www.gnu.org/licenses/>. */
21 /* Created by devin@lucid.com */
23 #include <config.h>
25 #include <setjmp.h>
26 #include <lisp.h>
28 #include <stdio.h>
30 #include <sys/types.h>
31 #if (defined __sun) && !(defined SUNOS41)
32 #define SUNOS41
33 #include <X11/Xos.h>
34 #undef SUNOS41
35 #else
36 #include <X11/Xos.h>
37 #endif
38 #include <X11/IntrinsicP.h>
39 #include <X11/ObjectP.h>
40 #include <X11/StringDefs.h>
41 #include <X11/cursorfont.h>
42 #include <X11/Shell.h>
43 #include "xlwmenuP.h"
45 #ifdef emacs
47 #include <xterm.h>
48 #include "bitmaps/gray.xbm"
50 #else /* not emacs */
52 #include <X11/bitmaps/gray>
54 #endif /* not emacs */
56 static int pointer_grabbed;
57 static XEvent menu_post_event;
59 static char
60 xlwMenuTranslations [] =
61 "<BtnDown>: start()\n\
62 <Motion>: drag()\n\
63 <BtnUp>: select()\n\
64 <Key>Shift_L: nothing()\n\
65 <Key>Shift_R: nothing()\n\
66 <Key>Meta_L: nothing()\n\
67 <Key>Meta_R: nothing()\n\
68 <Key>Control_L: nothing()\n\
69 <Key>Control_R: nothing()\n\
70 <Key>Hyper_L: nothing()\n\
71 <Key>Hyper_R: nothing()\n\
72 <Key>Super_L: nothing()\n\
73 <Key>Super_R: nothing()\n\
74 <Key>Alt_L: nothing()\n\
75 <Key>Alt_R: nothing()\n\
76 <Key>Caps_Lock: nothing()\n\
77 <Key>Shift_Lock: nothing()\n\
78 <KeyUp>Shift_L: nothing()\n\
79 <KeyUp>Shift_R: nothing()\n\
80 <KeyUp>Meta_L: nothing()\n\
81 <KeyUp>Meta_R: nothing()\n\
82 <KeyUp>Control_L: nothing()\n\
83 <KeyUp>Control_R: nothing()\n\
84 <KeyUp>Hyper_L: nothing()\n\
85 <KeyUp>Hyper_R: nothing()\n\
86 <KeyUp>Super_L: nothing()\n\
87 <KeyUp>Super_R: nothing()\n\
88 <KeyUp>Alt_L: nothing()\n\
89 <KeyUp>Alt_R: nothing()\n\
90 <KeyUp>Caps_Lock: nothing()\n\
91 <KeyUp>Shift_Lock:nothing()\n\
92 <Key>Return: select()\n\
93 <Key>Down: down()\n\
94 <Key>Up: up()\n\
95 <Key>Left: left()\n\
96 <Key>Right: right()\n\
97 <Key>: key()\n\
98 <KeyUp>: key()\n\
101 /* FIXME: Space should toggle togglable menu item but not remove the menu
102 so you can toggle the next one without entering the menu again. */
104 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
106 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
108 #define offset(field) XtOffset(XlwMenuWidget, field)
109 static XtResource
110 xlwMenuResources[] =
112 #ifdef HAVE_X_I18N
113 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
114 offset(menu.fontSet), XtRFontSet, NULL},
115 #endif
116 #ifdef HAVE_XFT
117 #define DEFAULT_FONTNAME "Sans-10"
118 #else
119 #define DEFAULT_FONTNAME "XtDefaultFont"
120 #endif
121 {XtNfont, XtCFont, XtRString, sizeof(String),
122 offset(menu.fontName), XtRString, DEFAULT_FONTNAME },
123 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
124 offset(menu.foreground), XtRString, "XtDefaultForeground"},
125 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
126 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
127 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
128 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
129 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
130 offset(menu.margin), XtRImmediate, (XtPointer)1},
131 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
132 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
133 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
134 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
135 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
136 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
138 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
139 sizeof (Dimension), offset (menu.shadow_thickness),
140 XtRImmediate, (XtPointer)1},
141 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
142 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
143 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
144 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
145 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
146 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
147 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
148 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
150 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
151 offset(menu.open), XtRCallback, (XtPointer)NULL},
152 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
153 offset(menu.select), XtRCallback, (XtPointer)NULL},
154 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
155 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
156 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
157 offset(menu.enter), XtRCallback, (XtPointer)NULL},
158 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
159 offset(menu.leave), XtRCallback, (XtPointer)NULL},
160 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
161 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
162 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
163 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
164 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
165 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
167 #undef offset
169 static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
170 ArgList args, Cardinal *num_args);
171 static void XlwMenuRealize(Widget, Mask *, XSetWindowAttributes *);
172 static void XlwMenuResize(Widget w);
173 static void XlwMenuInitialize(Widget, Widget, ArgList, Cardinal *);
174 static void XlwMenuRedisplay(Widget w, XEvent *ev, Region region);
175 static void XlwMenuDestroy(Widget w);
176 static void XlwMenuClassInitialize(void);
177 static void Start(Widget w, XEvent *ev, String *params, Cardinal *num_params);
178 static void Drag(Widget w, XEvent *ev, String *params, Cardinal *num_params);
179 static void Down(Widget w, XEvent *ev, String *params, Cardinal *num_params);
180 static void Up(Widget w, XEvent *ev, String *params, Cardinal *num_params);
181 static void Left(Widget w, XEvent *ev, String *params, Cardinal *num_params);
182 static void Right(Widget w, XEvent *ev, String *params, Cardinal *num_params);
183 static void Select(Widget w, XEvent *ev, String *params, Cardinal *num_params);
184 static void Key(Widget w, XEvent *ev, String *params, Cardinal *num_params);
185 static void Nothing(Widget w, XEvent *ev, String *params, Cardinal *num_params);
186 static int separator_height (enum menu_separator);
187 static void pop_up_menu (XlwMenuWidget, XButtonPressedEvent *);
189 static XtActionsRec
190 xlwMenuActionsList [] =
192 {"start", Start},
193 {"drag", Drag},
194 {"down", Down},
195 {"up", Up},
196 {"left", Left},
197 {"right", Right},
198 {"select", Select},
199 {"key", Key},
200 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
201 {"nothing", Nothing},
204 #define SuperClass ((CoreWidgetClass)&coreClassRec)
206 XlwMenuClassRec xlwMenuClassRec =
208 { /* CoreClass fields initialization */
209 (WidgetClass) SuperClass, /* superclass */
210 "XlwMenu", /* class_name */
211 sizeof(XlwMenuRec), /* size */
212 XlwMenuClassInitialize, /* class_initialize */
213 NULL, /* class_part_initialize */
214 FALSE, /* class_inited */
215 XlwMenuInitialize, /* initialize */
216 NULL, /* initialize_hook */
217 XlwMenuRealize, /* realize */
218 xlwMenuActionsList, /* actions */
219 XtNumber(xlwMenuActionsList), /* num_actions */
220 xlwMenuResources, /* resources */
221 XtNumber(xlwMenuResources), /* resource_count */
222 NULLQUARK, /* xrm_class */
223 TRUE, /* compress_motion */
224 XtExposeCompressMaximal, /* compress_exposure */
225 TRUE, /* compress_enterleave */
226 FALSE, /* visible_interest */
227 XlwMenuDestroy, /* destroy */
228 XlwMenuResize, /* resize */
229 XlwMenuRedisplay, /* expose */
230 XlwMenuSetValues, /* set_values */
231 NULL, /* set_values_hook */
232 XtInheritSetValuesAlmost, /* set_values_almost */
233 NULL, /* get_values_hook */
234 NULL, /* accept_focus */
235 XtVersion, /* version */
236 NULL, /* callback_private */
237 xlwMenuTranslations, /* tm_table */
238 XtInheritQueryGeometry, /* query_geometry */
239 XtInheritDisplayAccelerator, /* display_accelerator */
240 NULL /* extension */
241 }, /* XlwMenuClass fields initialization */
243 0 /* dummy */
247 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
249 int submenu_destroyed;
251 /* For debug, if installation-directory is non-nil this is not an installed
252 Emacs. In that case we do not grab the keyboard to make it easier to
253 debug. */
254 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
256 static int next_release_must_exit;
258 \f/* Utilities */
260 /* Ungrab pointer and keyboard */
261 static void
262 ungrab_all (Widget w, Time ungrabtime)
264 XtUngrabPointer (w, ungrabtime);
265 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
268 /* Like abort, but remove grabs from widget W before. */
270 static _Noreturn void
271 abort_gracefully (Widget w)
273 if (XtIsShell (XtParent (w)))
274 XtRemoveGrab (w);
275 ungrab_all (w, CurrentTime);
276 abort ();
279 static void
280 push_new_stack (XlwMenuWidget mw, widget_value *val)
282 if (!mw->menu.new_stack)
284 mw->menu.new_stack_length = 10;
285 mw->menu.new_stack =
286 (widget_value**)XtCalloc (mw->menu.new_stack_length,
287 sizeof (widget_value*));
289 else if (mw->menu.new_depth == mw->menu.new_stack_length)
291 mw->menu.new_stack_length *= 2;
292 mw->menu.new_stack =
293 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
294 mw->menu.new_stack_length * sizeof (widget_value*));
296 mw->menu.new_stack [mw->menu.new_depth++] = val;
299 static void
300 pop_new_stack_if_no_contents (XlwMenuWidget mw)
302 if (mw->menu.new_depth > 1)
304 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
305 mw->menu.new_depth -= 1;
309 static void
310 make_old_stack_space (XlwMenuWidget mw, int n)
312 if (!mw->menu.old_stack)
314 mw->menu.old_stack_length = 10;
315 mw->menu.old_stack =
316 (widget_value**)XtCalloc (mw->menu.old_stack_length,
317 sizeof (widget_value*));
319 else if (mw->menu.old_stack_length < n)
321 mw->menu.old_stack_length *= 2;
322 mw->menu.old_stack =
323 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
324 mw->menu.old_stack_length * sizeof (widget_value*));
328 \f/* Size code */
329 static int
330 string_width (XlwMenuWidget mw, char *s)
332 XCharStruct xcs;
333 int drop;
334 #ifdef HAVE_XFT
335 if (mw->menu.xft_font)
337 XGlyphInfo gi;
338 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
339 (FcChar8 *) s,
340 strlen (s), &gi);
341 return gi.width;
343 #endif
344 #ifdef HAVE_X_I18N
345 if (mw->menu.fontSet)
347 XRectangle ink, logical;
348 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
349 return logical.width;
351 #endif
353 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
354 return xcs.width;
358 #ifdef HAVE_XFT
359 #define MENU_FONT_HEIGHT(mw) \
360 ((mw)->menu.xft_font != NULL \
361 ? (mw)->menu.xft_font->height \
362 : ((mw)->menu.fontSet != NULL \
363 ? (mw)->menu.font_extents->max_logical_extent.height \
364 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
365 #define MENU_FONT_ASCENT(mw) \
366 ((mw)->menu.xft_font != NULL \
367 ? (mw)->menu.xft_font->ascent \
368 : ((mw)->menu.fontSet != NULL \
369 ? - (mw)->menu.font_extents->max_logical_extent.y \
370 : (mw)->menu.font->ascent))
371 #else
372 #ifdef HAVE_X_I18N
373 #define MENU_FONT_HEIGHT(mw) \
374 ((mw)->menu.fontSet != NULL \
375 ? (mw)->menu.font_extents->max_logical_extent.height \
376 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
377 #define MENU_FONT_ASCENT(mw) \
378 ((mw)->menu.fontSet != NULL \
379 ? - (mw)->menu.font_extents->max_logical_extent.y \
380 : (mw)->menu.font->ascent)
381 #else
382 #define MENU_FONT_HEIGHT(mw) \
383 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
384 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
385 #endif
386 #endif
388 static int
389 arrow_width (XlwMenuWidget mw)
391 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
394 /* Return the width of toggle buttons of widget MW. */
396 static int
397 toggle_button_width (XlwMenuWidget mw)
399 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
403 /* Return the width of radio buttons of widget MW. */
405 static int
406 radio_button_width (XlwMenuWidget mw)
408 return toggle_button_width (mw) * 1.41;
412 static XtResource
413 nameResource[] =
415 {"labelString", "LabelString", XtRString, sizeof(String),
416 0, XtRImmediate, 0},
419 static char*
420 resource_widget_value (XlwMenuWidget mw, widget_value *val)
422 if (!val->toolkit_data)
424 char* resourced_name = NULL;
425 char* complete_name;
426 XtGetSubresources ((Widget) mw,
427 (XtPointer) &resourced_name,
428 val->name, val->name,
429 nameResource, 1, NULL, 0);
430 if (!resourced_name)
431 resourced_name = val->name;
432 if (!val->value)
434 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
435 strcpy (complete_name, resourced_name);
437 else
439 int complete_length =
440 strlen (resourced_name) + strlen (val->value) + 2;
441 complete_name = XtMalloc (complete_length);
442 *complete_name = 0;
443 strcat (complete_name, resourced_name);
444 strcat (complete_name, " ");
445 strcat (complete_name, val->value);
448 val->toolkit_data = complete_name;
449 val->free_toolkit_data = True;
451 return (char*)val->toolkit_data;
454 /* Returns the sizes of an item */
455 static void
456 size_menu_item (XlwMenuWidget mw,
457 widget_value* val,
458 int horizontal_p,
459 int* label_width,
460 int* rest_width,
461 int* button_width,
462 int* height)
464 enum menu_separator separator;
466 if (lw_separator_p (val->name, &separator, 0))
468 *height = separator_height (separator);
469 *label_width = 1;
470 *rest_width = 0;
471 *button_width = 0;
473 else
475 *height = MENU_FONT_HEIGHT (mw)
476 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
478 *label_width =
479 string_width (mw, resource_widget_value (mw, val))
480 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
482 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
483 if (!horizontal_p)
485 if (val->contents)
486 /* Add width of the arrow displayed for submenus. */
487 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
488 else if (val->key)
489 /* Add width of key equivalent string. */
490 *rest_width += (string_width (mw, val->key)
491 + mw->menu.arrow_spacing);
493 if (val->button_type == BUTTON_TYPE_TOGGLE)
494 *button_width = (toggle_button_width (mw)
495 + mw->menu.horizontal_spacing);
496 else if (val->button_type == BUTTON_TYPE_RADIO)
497 *button_width = (radio_button_width (mw)
498 + mw->menu.horizontal_spacing);
503 static void
504 size_menu (XlwMenuWidget mw, int level)
506 int label_width = 0;
507 int rest_width = 0;
508 int button_width = 0;
509 int max_rest_width = 0;
510 int max_button_width = 0;
511 int height = 0;
512 int horizontal_p = mw->menu.horizontal && (level == 0);
513 widget_value* val;
514 window_state* ws;
516 if (level >= mw->menu.old_depth)
517 abort_gracefully ((Widget) mw);
519 ws = &mw->menu.windows [level];
520 ws->width = 0;
521 ws->height = 0;
522 ws->label_width = 0;
523 ws->button_width = 0;
525 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
527 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
528 &button_width, &height);
529 if (horizontal_p)
531 ws->width += label_width + rest_width;
532 if (height > ws->height)
533 ws->height = height;
535 else
537 if (label_width > ws->label_width)
538 ws->label_width = label_width;
539 if (rest_width > max_rest_width)
540 max_rest_width = rest_width;
541 if (button_width > max_button_width)
542 max_button_width = button_width;
543 ws->height += height;
547 if (horizontal_p)
548 ws->label_width = ws->button_width = 0;
549 else
551 ws->width = ws->label_width + max_rest_width + max_button_width;
552 ws->button_width = max_button_width;
555 ws->width += 2 * mw->menu.shadow_thickness;
556 ws->height += 2 * mw->menu.shadow_thickness;
557 ws->max_rest_width = max_rest_width;
559 if (horizontal_p)
561 ws->width += 2 * mw->menu.margin;
562 ws->height += 2 * mw->menu.margin;
567 \f/* Display code */
569 static void
570 draw_arrow (XlwMenuWidget mw,
571 Window window,
572 GC gc,
573 int x,
574 int y,
575 int width,
576 int down_p)
578 Display *dpy = XtDisplay (mw);
579 GC top_gc = mw->menu.shadow_top_gc;
580 GC bottom_gc = mw->menu.shadow_bottom_gc;
581 int thickness = mw->menu.shadow_thickness;
582 int height = width;
583 XPoint pt[10];
584 /* alpha = atan (0.5)
585 factor = (1 + sin (alpha)) / cos (alpha) */
586 double factor = 1.62;
587 int thickness2 = thickness * factor;
589 y += (MENU_FONT_HEIGHT (mw) - height) / 2;
591 if (down_p)
593 GC temp;
594 temp = top_gc;
595 top_gc = bottom_gc;
596 bottom_gc = temp;
599 pt[0].x = x;
600 pt[0].y = y + height;
601 pt[1].x = x + thickness;
602 pt[1].y = y + height - thickness2;
603 pt[2].x = x + thickness2;
604 pt[2].y = y + thickness2;
605 pt[3].x = x;
606 pt[3].y = y;
607 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
609 pt[0].x = x;
610 pt[0].y = y;
611 pt[1].x = x + thickness;
612 pt[1].y = y + thickness2;
613 pt[2].x = x + width - thickness2;
614 pt[2].y = y + height / 2;
615 pt[3].x = x + width;
616 pt[3].y = y + height / 2;
617 XFillPolygon (dpy, window, top_gc, pt, 4, Convex, CoordModeOrigin);
619 pt[0].x = x;
620 pt[0].y = y + height;
621 pt[1].x = x + thickness;
622 pt[1].y = y + height - thickness2;
623 pt[2].x = x + width - thickness2;
624 pt[2].y = y + height / 2;
625 pt[3].x = x + width;
626 pt[3].y = y + height / 2;
627 XFillPolygon (dpy, window, bottom_gc, pt, 4, Convex, CoordModeOrigin);
632 static void
633 draw_shadow_rectangle (XlwMenuWidget mw,
634 Window window,
635 int x,
636 int y,
637 int width,
638 int height,
639 int erase_p,
640 int down_p)
642 Display *dpy = XtDisplay (mw);
643 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
644 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
645 int thickness = mw->menu.shadow_thickness;
646 XPoint points [4];
648 if (!erase_p && down_p)
650 GC temp;
651 temp = top_gc;
652 top_gc = bottom_gc;
653 bottom_gc = temp;
656 points [0].x = x;
657 points [0].y = y;
658 points [1].x = x + width;
659 points [1].y = y;
660 points [2].x = x + width - thickness;
661 points [2].y = y + thickness;
662 points [3].x = x;
663 points [3].y = y + thickness;
664 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
665 points [0].x = x;
666 points [0].y = y + thickness;
667 points [1].x = x;
668 points [1].y = y + height;
669 points [2].x = x + thickness;
670 points [2].y = y + height - thickness;
671 points [3].x = x + thickness;
672 points [3].y = y + thickness;
673 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
674 points [0].x = x + width;
675 points [0].y = y;
676 points [1].x = x + width - thickness;
677 points [1].y = y + thickness;
678 points [2].x = x + width - thickness;
679 points [2].y = y + height - thickness;
680 points [3].x = x + width;
681 points [3].y = y + height - thickness;
682 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
683 points [0].x = x;
684 points [0].y = y + height;
685 points [1].x = x + width;
686 points [1].y = y + height;
687 points [2].x = x + width;
688 points [2].y = y + height - thickness;
689 points [3].x = x + thickness;
690 points [3].y = y + height - thickness;
691 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
695 static void
696 draw_shadow_rhombus (XlwMenuWidget mw,
697 Window window,
698 int x,
699 int y,
700 int width,
701 int height,
702 int erase_p,
703 int down_p)
705 Display *dpy = XtDisplay (mw);
706 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
707 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
708 int thickness = mw->menu.shadow_thickness;
709 XPoint points [4];
711 if (!erase_p && down_p)
713 GC temp;
714 temp = top_gc;
715 top_gc = bottom_gc;
716 bottom_gc = temp;
719 points [0].x = x;
720 points [0].y = y + height / 2;
721 points [1].x = x + thickness;
722 points [1].y = y + height / 2;
723 points [2].x = x + width / 2;
724 points [2].y = y + thickness;
725 points [3].x = x + width / 2;
726 points [3].y = y;
727 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
728 points [0].x = x + width / 2;
729 points [0].y = y;
730 points [1].x = x + width / 2;
731 points [1].y = y + thickness;
732 points [2].x = x + width - thickness;
733 points [2].y = y + height / 2;
734 points [3].x = x + width;
735 points [3].y = y + height / 2;
736 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
737 points [0].x = x;
738 points [0].y = y + height / 2;
739 points [1].x = x + thickness;
740 points [1].y = y + height / 2;
741 points [2].x = x + width / 2;
742 points [2].y = y + height - thickness;
743 points [3].x = x + width / 2;
744 points [3].y = y + height;
745 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
746 points [0].x = x + width / 2;
747 points [0].y = y + height;
748 points [1].x = x + width / 2;
749 points [1].y = y + height - thickness;
750 points [2].x = x + width - thickness;
751 points [2].y = y + height / 2;
752 points [3].x = x + width;
753 points [3].y = y + height / 2;
754 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
758 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
759 top-left corner of the menu item. SELECTED_P non-zero means the
760 toggle button is selected. */
762 static void
763 draw_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
765 int width, height;
767 width = toggle_button_width (mw);
768 height = width;
769 x += mw->menu.horizontal_spacing;
770 y += (MENU_FONT_ASCENT (mw) - height) / 2;
771 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
775 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
776 top-left corner of the menu item. SELECTED_P non-zero means the
777 toggle button is selected. */
779 static void
780 draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
782 int width, height;
784 width = radio_button_width (mw);
785 height = width;
786 x += mw->menu.horizontal_spacing;
787 y += (MENU_FONT_ASCENT (mw) - height) / 2;
788 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
792 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
793 top-left corner of the menu item. WIDTH is the width of the
794 separator to draw. TYPE is the separator type. */
796 static void
797 draw_separator (XlwMenuWidget mw,
798 Window window,
799 int x,
800 int y,
801 int width,
802 enum menu_separator type)
804 Display *dpy = XtDisplay (mw);
805 XGCValues xgcv;
807 switch (type)
809 case SEPARATOR_NO_LINE:
810 break;
812 case SEPARATOR_SINGLE_LINE:
813 XDrawLine (dpy, window, mw->menu.foreground_gc,
814 x, y, x + width, y);
815 break;
817 case SEPARATOR_DOUBLE_LINE:
818 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
819 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
820 break;
822 case SEPARATOR_SINGLE_DASHED_LINE:
823 xgcv.line_style = LineOnOffDash;
824 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
825 XDrawLine (dpy, window, mw->menu.foreground_gc,
826 x, y, x + width, y);
827 xgcv.line_style = LineSolid;
828 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
829 break;
831 case SEPARATOR_DOUBLE_DASHED_LINE:
832 draw_separator (mw, window, x, y, width,
833 SEPARATOR_SINGLE_DASHED_LINE);
834 draw_separator (mw, window, x, y + 2, width,
835 SEPARATOR_SINGLE_DASHED_LINE);
836 break;
838 case SEPARATOR_SHADOW_ETCHED_IN:
839 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
840 x, y, x + width, y);
841 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
842 x, y + 1, x + width, y + 1);
843 break;
845 case SEPARATOR_SHADOW_ETCHED_OUT:
846 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
847 x, y, x + width, y);
848 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
849 x, y + 1, x + width, y + 1);
850 break;
852 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
853 xgcv.line_style = LineOnOffDash;
854 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
855 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
856 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
857 xgcv.line_style = LineSolid;
858 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
859 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
860 break;
862 case SEPARATOR_SHADOW_ETCHED_OUT_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_OUT);
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_DOUBLE_ETCHED_IN:
873 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
874 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
875 break;
877 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
878 draw_separator (mw, window, x, y, width,
879 SEPARATOR_SHADOW_ETCHED_OUT);
880 draw_separator (mw, window, x, y + 3, width,
881 SEPARATOR_SHADOW_ETCHED_OUT);
882 break;
884 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
885 xgcv.line_style = LineOnOffDash;
886 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
887 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
888 draw_separator (mw, window, x, y, width,
889 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
890 xgcv.line_style = LineSolid;
891 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
892 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
893 break;
895 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
896 xgcv.line_style = LineOnOffDash;
897 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
898 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
899 draw_separator (mw, window, x, y, width,
900 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
901 xgcv.line_style = LineSolid;
902 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
903 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
904 break;
906 default:
907 abort ();
912 /* Return the pixel height of menu separator SEPARATOR. */
914 static int
915 separator_height (enum menu_separator separator)
917 switch (separator)
919 case SEPARATOR_NO_LINE:
920 return 2;
922 case SEPARATOR_SINGLE_LINE:
923 case SEPARATOR_SINGLE_DASHED_LINE:
924 return 1;
926 case SEPARATOR_DOUBLE_LINE:
927 case SEPARATOR_DOUBLE_DASHED_LINE:
928 return 3;
930 case SEPARATOR_SHADOW_ETCHED_IN:
931 case SEPARATOR_SHADOW_ETCHED_OUT:
932 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
933 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
934 return 2;
936 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
937 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
938 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
939 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
940 return 5;
942 default:
943 abort ();
948 /* Display the menu item and increment where.x and where.y to show how large
949 the menu item was. */
951 static void
952 display_menu_item (XlwMenuWidget mw,
953 widget_value* val,
954 window_state* ws,
955 XPoint* where,
956 Boolean highlighted_p,
957 Boolean horizontal_p,
958 Boolean just_compute_p)
960 GC deco_gc;
961 GC text_gc;
962 int font_height = MENU_FONT_HEIGHT (mw);
963 int font_ascent = MENU_FONT_ASCENT (mw);
964 int shadow = mw->menu.shadow_thickness;
965 int margin = mw->menu.margin;
966 int h_spacing = mw->menu.horizontal_spacing;
967 int v_spacing = mw->menu.vertical_spacing;
968 int label_width;
969 int rest_width;
970 int button_width;
971 int height;
972 int width;
973 enum menu_separator separator;
974 int separator_p = lw_separator_p (val->name, &separator, 0);
975 #ifdef HAVE_XFT
976 XftColor *xftfg;
977 #endif
979 /* compute the sizes of the item */
980 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
981 &button_width, &height);
983 if (horizontal_p)
984 width = label_width + rest_width;
985 else
987 label_width = ws->label_width;
988 width = ws->width - 2 * shadow;
991 /* Only highlight an enabled item that has a callback. */
992 if (highlighted_p)
993 if (!val->enabled || !(val->call_data || val->contents))
994 highlighted_p = 0;
996 /* do the drawing. */
997 if (!just_compute_p)
999 /* Add the shadow border of the containing menu */
1000 int x = where->x + shadow;
1001 int y = where->y + shadow;
1003 if (horizontal_p)
1005 x += margin;
1006 y += margin;
1009 /* pick the foreground and background GC. */
1010 if (val->enabled)
1011 text_gc = mw->menu.foreground_gc;
1012 else
1013 text_gc = mw->menu.disabled_gc;
1014 deco_gc = mw->menu.foreground_gc;
1015 #ifdef HAVE_XFT
1016 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1017 #endif
1019 if (separator_p)
1021 draw_separator (mw, ws->pixmap, x, y, width, separator);
1023 else
1025 int x_offset = x + h_spacing + shadow;
1026 char* display_string = resource_widget_value (mw, val);
1027 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
1028 False);
1030 /* Deal with centering a menu title. */
1031 if (!horizontal_p && !val->contents && !val->call_data)
1033 int l = string_width (mw, display_string);
1035 if (width > l)
1036 x_offset = (width - l) >> 1;
1038 else if (!horizontal_p && ws->button_width)
1039 x_offset += ws->button_width;
1042 #ifdef HAVE_XFT
1043 if (ws->xft_draw)
1045 int draw_y = y + v_spacing + shadow;
1046 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1047 mw->menu.xft_font,
1048 x_offset, draw_y + font_ascent,
1049 (unsigned char *) display_string,
1050 strlen (display_string));
1052 else
1053 #endif
1054 #ifdef HAVE_X_I18N
1055 if (mw->menu.fontSet)
1056 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
1057 text_gc, x_offset,
1058 y + v_spacing + shadow + font_ascent,
1059 display_string, strlen (display_string));
1060 else
1061 #endif
1062 XDrawString (XtDisplay (mw), ws->pixmap,
1063 text_gc, x_offset,
1064 y + v_spacing + shadow + font_ascent,
1065 display_string, strlen (display_string));
1067 if (!horizontal_p)
1069 if (val->button_type == BUTTON_TYPE_TOGGLE)
1070 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
1071 val->selected);
1072 else if (val->button_type == BUTTON_TYPE_RADIO)
1073 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
1074 val->selected);
1076 if (val->contents)
1078 int a_w = arrow_width (mw);
1079 draw_arrow (mw, ws->pixmap, deco_gc,
1080 x + width - a_w
1081 - mw->menu.horizontal_spacing
1082 - mw->menu.shadow_thickness,
1083 y + v_spacing + shadow, a_w,
1084 highlighted_p);
1086 else if (val->key)
1088 #ifdef HAVE_XFT
1089 if (ws->xft_draw)
1091 int draw_x = ws->width - ws->max_rest_width
1092 + mw->menu.arrow_spacing;
1093 int draw_y = y + v_spacing + shadow + font_ascent;
1094 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1095 mw->menu.xft_font,
1096 draw_x, draw_y,
1097 (unsigned char *) val->key,
1098 strlen (val->key));
1100 else
1101 #endif
1102 #ifdef HAVE_X_I18N
1103 if (mw->menu.fontSet)
1104 XmbDrawString (XtDisplay (mw), ws->pixmap,
1105 mw->menu.fontSet,
1106 text_gc,
1107 x + label_width + mw->menu.arrow_spacing,
1108 y + v_spacing + shadow + font_ascent,
1109 val->key, strlen (val->key));
1110 else
1111 #endif
1112 XDrawString (XtDisplay (mw), ws->pixmap,
1113 text_gc,
1114 x + label_width + mw->menu.arrow_spacing,
1115 y + v_spacing + shadow + font_ascent,
1116 val->key, strlen (val->key));
1119 else
1121 XDrawRectangle (XtDisplay (mw), ws->pixmap,
1122 mw->menu.background_gc,
1123 x + shadow, y + shadow,
1124 label_width + h_spacing - 1,
1125 font_height + 2 * v_spacing - 1);
1126 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
1127 True, False);
1130 if (highlighted_p)
1131 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
1132 False);
1136 where->x += width;
1137 where->y += height;
1140 static void
1141 display_menu (XlwMenuWidget mw,
1142 int level,
1143 Boolean just_compute_p,
1144 XPoint *highlighted_pos,
1145 XPoint *hit,
1146 widget_value **hit_return)
1148 widget_value* val;
1149 widget_value* following_item;
1150 window_state* ws;
1151 XPoint where;
1152 int horizontal_p = mw->menu.horizontal && (level == 0);
1153 int highlighted_p;
1154 int no_return = 0;
1155 enum menu_separator separator;
1157 if (level >= mw->menu.old_depth)
1158 abort_gracefully ((Widget) mw);
1160 if (level < mw->menu.old_depth - 1)
1161 following_item = mw->menu.old_stack [level + 1];
1162 else
1163 following_item = NULL;
1165 if (hit)
1166 *hit_return = NULL;
1168 where.x = 0;
1169 where.y = 0;
1171 ws = &mw->menu.windows [level];
1173 if (!just_compute_p)
1174 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1175 0, 0, ws->width, ws->height);
1177 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1179 highlighted_p = val == following_item;
1180 if (highlighted_p && highlighted_pos)
1182 if (horizontal_p)
1183 highlighted_pos->x = where.x;
1184 else
1185 highlighted_pos->y = where.y;
1188 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1189 just_compute_p);
1191 if (highlighted_p && highlighted_pos)
1193 if (horizontal_p)
1194 highlighted_pos->y = where.y;
1195 else
1196 highlighted_pos->x = where.x;
1199 if (hit
1200 && !*hit_return
1201 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1202 && !lw_separator_p (val->name, &separator, 0)
1203 && !no_return)
1205 if (val->enabled)
1206 *hit_return = val;
1207 else
1208 no_return = 1;
1209 if (mw->menu.inside_entry != val)
1211 if (mw->menu.inside_entry)
1212 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1213 (XtPointer) mw->menu.inside_entry);
1214 mw->menu.inside_entry = val;
1215 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1216 (XtPointer) mw->menu.inside_entry);
1220 if (horizontal_p)
1221 where.y = 0;
1222 else
1223 where.x = 0;
1226 if (!just_compute_p)
1228 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1229 False, False);
1230 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1231 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1235 \f/* Motion code */
1236 static void
1237 set_new_state (XlwMenuWidget mw, widget_value *val, int level)
1239 int i;
1241 mw->menu.new_depth = 0;
1242 for (i = 0; i < level; i++)
1243 push_new_stack (mw, mw->menu.old_stack [i]);
1244 push_new_stack (mw, val);
1247 static void
1248 expose_cb (Widget widget,
1249 XtPointer closure,
1250 XEvent* event,
1251 Boolean* continue_to_dispatch)
1253 XlwMenuWidget mw = (XlwMenuWidget) closure;
1254 int i;
1256 *continue_to_dispatch = False;
1257 for (i = 0; i < mw->menu.windows_length; ++i)
1258 if (mw->menu.windows [i].w == widget) break;
1259 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
1260 display_menu (mw, i, False, NULL, NULL, NULL);
1263 static void
1264 set_window_type (Widget w, XlwMenuWidget mw)
1266 int popup_menu_p = mw->menu.top_depth == 1;
1267 Atom type = XInternAtom (XtDisplay (w),
1268 popup_menu_p
1269 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1270 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1271 False);
1273 XChangeProperty (XtDisplay (w), XtWindow (w),
1274 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1275 XA_ATOM, 32, PropModeReplace,
1276 (unsigned char *)&type, 1);
1280 static void
1281 make_windows_if_needed (XlwMenuWidget mw, int n)
1283 int i;
1284 int start_at;
1285 window_state* windows;
1287 if (mw->menu.windows_length >= n)
1288 return;
1290 if (!mw->menu.windows)
1292 mw->menu.windows =
1293 (window_state*)XtMalloc (n * sizeof (window_state));
1294 start_at = 0;
1296 else
1298 mw->menu.windows =
1299 (window_state*)XtRealloc ((char*)mw->menu.windows,
1300 n * sizeof (window_state));
1301 start_at = mw->menu.windows_length;
1303 mw->menu.windows_length = n;
1305 windows = mw->menu.windows;
1307 for (i = start_at; i < n; i++)
1309 Arg av[10];
1310 int ac = 0;
1311 windows [i].x = 0;
1312 windows [i].y = 0;
1313 windows [i].width = 1;
1314 windows [i].height = 1;
1315 windows [i].max_rest_width = 0;
1316 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1317 XtSetArg (av[ac], XtNheight, 1); ++ac;
1318 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1319 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1320 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1321 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1322 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1323 windows [i].w =
1324 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1325 (Widget) mw, av, ac);
1326 XtRealizeWidget (windows [i].w);
1327 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1328 windows [i].window = XtWindow (windows [i].w);
1329 windows [i].pixmap = None;
1330 #ifdef HAVE_XFT
1331 windows [i].xft_draw = 0;
1332 #endif
1333 set_window_type (windows [i].w, mw);
1335 XFlush (XtDisplay (mw));
1338 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1341 xlwmenu_window_p (Widget w, Window window)
1343 XlwMenuWidget mw = (XlwMenuWidget) w;
1344 int i;
1346 for (i = 0; i < mw->menu.windows_length; ++i)
1347 if (window == mw->menu.windows[i].window)
1348 break;
1350 return i < mw->menu.windows_length;
1353 /* Make the window fit in the screen */
1354 static void
1355 fit_to_screen (XlwMenuWidget mw,
1356 window_state *ws,
1357 window_state *previous_ws,
1358 Boolean horizontal_p)
1360 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1361 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1362 /* 1 if we are unable to avoid an overlap between
1363 this menu and the parent menu in the X dimension. */
1364 int horizontal_overlap = 0;
1366 if (ws->x < 0)
1367 ws->x = 0;
1368 else if (ws->x + ws->width > screen_width)
1370 if (!horizontal_p)
1371 /* The addition of shadow-thickness for a sub-menu's position is
1372 to reflect a similar adjustment when the menu is displayed to
1373 the right of the invoking menu-item; it makes the sub-menu
1374 look more `attached' to the menu-item. */
1375 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1376 else
1377 ws->x = screen_width - ws->width;
1378 if (ws->x < 0)
1380 ws->x = 0;
1381 horizontal_overlap = 1;
1384 /* If we overlap in X, try to avoid overlap in Y. */
1385 if (horizontal_overlap
1386 && ws->y < previous_ws->y + previous_ws->height
1387 && previous_ws->y < ws->y + ws->height)
1389 /* Put this menu right below or right above PREVIOUS_WS
1390 if there's room. */
1391 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1392 ws->y = previous_ws->y + previous_ws->height;
1393 else if (previous_ws->y - ws->height > 0)
1394 ws->y = previous_ws->y - ws->height;
1397 if (ws->y < 0)
1398 ws->y = 0;
1399 else if (ws->y + ws->height > screen_height)
1401 if (horizontal_p)
1402 ws->y = previous_ws->y - ws->height;
1403 else
1404 ws->y = screen_height - ws->height;
1405 if (ws->y < 0)
1406 ws->y = 0;
1410 static void
1411 create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1413 if (ws->pixmap != None)
1415 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1416 ws->pixmap = None;
1418 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1419 ws->width, ws->height,
1420 DefaultDepthOfScreen (XtScreen (ws->w)));
1421 #ifdef HAVE_XFT
1422 if (ws->xft_draw)
1423 XftDrawDestroy (ws->xft_draw);
1424 if (mw->menu.xft_font)
1426 int screen = XScreenNumberOfScreen (mw->core.screen);
1427 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1428 ws->pixmap,
1429 DefaultVisual (XtDisplay (ws->w), screen),
1430 mw->core.colormap);
1432 else
1433 ws->xft_draw = 0;
1434 #endif
1437 /* Updates old_stack from new_stack and redisplays. */
1438 static void
1439 remap_menubar (XlwMenuWidget mw)
1441 int i;
1442 int last_same;
1443 XPoint selection_position;
1444 int old_depth = mw->menu.old_depth;
1445 int new_depth = mw->menu.new_depth;
1446 widget_value** old_stack;
1447 widget_value** new_stack;
1448 window_state* windows;
1449 widget_value* old_selection;
1450 widget_value* new_selection;
1452 /* Check that enough windows and old_stack are ready. */
1453 make_windows_if_needed (mw, new_depth);
1454 make_old_stack_space (mw, new_depth);
1455 windows = mw->menu.windows;
1456 old_stack = mw->menu.old_stack;
1457 new_stack = mw->menu.new_stack;
1459 /* compute the last identical different entry */
1460 for (i = 1; i < old_depth && i < new_depth; i++)
1461 if (old_stack [i] != new_stack [i])
1462 break;
1463 last_same = i - 1;
1465 /* Memorize the previously selected item to be able to refresh it */
1466 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1467 if (old_selection && !old_selection->enabled)
1468 old_selection = NULL;
1469 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1470 if (new_selection && !new_selection->enabled)
1471 new_selection = NULL;
1473 /* Call callback when the highlighted item changes. */
1474 if (old_selection || new_selection)
1475 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1476 (XtPointer) new_selection);
1478 /* updates old_state from new_state. It has to be done now because
1479 display_menu (called below) uses the old_stack to know what to display. */
1480 for (i = last_same + 1; i < new_depth; i++)
1482 XtPopdown (mw->menu.windows [i].w);
1483 old_stack [i] = new_stack [i];
1485 mw->menu.old_depth = new_depth;
1487 /* refresh the last selection */
1488 selection_position.x = 0;
1489 selection_position.y = 0;
1490 display_menu (mw, last_same, new_selection == old_selection,
1491 &selection_position, NULL, NULL);
1493 /* Now place the new menus. */
1494 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1496 window_state *previous_ws = &windows[i - 1];
1497 window_state *ws = &windows[i];
1499 ws->x = (previous_ws->x + selection_position.x
1500 + mw->menu.shadow_thickness);
1501 if (mw->menu.horizontal && i == 1)
1502 ws->x += mw->menu.margin;
1504 #if 0
1505 if (!mw->menu.horizontal || i > 1)
1506 ws->x += mw->menu.shadow_thickness;
1507 #endif
1509 ws->y = (previous_ws->y + selection_position.y
1510 + mw->menu.shadow_thickness);
1511 if (mw->menu.horizontal && i == 1)
1512 ws->y += mw->menu.margin;
1514 size_menu (mw, i);
1516 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1518 create_pixmap_for_menu (ws, mw);
1519 XtMoveWidget (ws->w, ws->x, ws->y);
1520 XtPopup (ws->w, XtGrabNone);
1521 XtResizeWidget (ws->w, ws->width, ws->height,
1522 mw->core.border_width);
1523 XtResizeWindow (ws->w);
1524 display_menu (mw, i, False, &selection_position, NULL, NULL);
1527 /* unmap the menus that popped down */
1528 for (i = new_depth - 1; i < old_depth; i++)
1529 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1530 XtPopdown (windows[i].w);
1533 static Boolean
1534 motion_event_is_in_menu (XlwMenuWidget mw,
1535 XMotionEvent *ev,
1536 int level,
1537 XPoint *relative_pos)
1539 window_state* ws = &mw->menu.windows [level];
1540 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1541 int x = ws->x + shadow;
1542 int y = ws->y + shadow;
1543 relative_pos->x = ev->x_root - x;
1544 relative_pos->y = ev->y_root - y;
1545 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1546 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1549 static Boolean
1550 map_event_to_widget_value (XlwMenuWidget mw,
1551 XMotionEvent *ev,
1552 widget_value **val,
1553 int *level)
1555 int i;
1556 XPoint relative_pos;
1557 window_state* ws;
1558 int inside = 0;
1560 *val = NULL;
1562 /* Find the window */
1563 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1565 ws = &mw->menu.windows [i];
1566 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1568 inside = 1;
1569 display_menu (mw, i, True, NULL, &relative_pos, val);
1571 if (*val)
1573 *level = i + 1;
1574 return True;
1579 if (!inside)
1581 if (mw->menu.inside_entry != NULL)
1582 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1583 (XtPointer) mw->menu.inside_entry);
1584 mw->menu.inside_entry = NULL;
1587 return False;
1590 \f/* Procedures */
1591 static void
1592 make_drawing_gcs (XlwMenuWidget mw)
1594 XGCValues xgcv;
1595 float scale;
1596 XtGCMask mask = GCForeground | GCBackground;
1598 #ifdef HAVE_X_I18N
1599 if (!mw->menu.fontSet && mw->menu.font)
1601 xgcv.font = mw->menu.font->fid;
1602 mask |= GCFont;
1604 #else
1605 if (mw->menu.font)
1607 xgcv.font = mw->menu.font->fid;
1608 mask |= GCFont;
1610 #endif
1611 xgcv.foreground = mw->menu.foreground;
1612 xgcv.background = mw->core.background_pixel;
1613 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1615 xgcv.foreground = mw->menu.button_foreground;
1616 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1618 xgcv.background = mw->core.background_pixel;
1620 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1622 /* Allocate color for disabled menu-items. */
1623 mw->menu.disabled_foreground = mw->menu.foreground;
1624 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1625 scale = 2.3;
1626 else
1627 scale = 0.55;
1629 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1630 mw->core.colormap,
1631 &mw->menu.disabled_foreground,
1632 scale,
1633 0x8000);
1635 if (mw->menu.foreground == mw->menu.disabled_foreground
1636 || mw->core.background_pixel == mw->menu.disabled_foreground)
1638 /* Too few colors, use stipple. */
1639 xgcv.foreground = mw->menu.foreground;
1640 xgcv.fill_style = FillStippled;
1641 xgcv.stipple = mw->menu.gray_pixmap;
1642 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1643 | GCFillStyle | GCStipple, &xgcv);
1645 else
1647 /* Many colors available, use disabled pixel. */
1648 xgcv.foreground = mw->menu.disabled_foreground;
1649 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1652 xgcv.foreground = mw->menu.button_foreground;
1653 xgcv.background = mw->core.background_pixel;
1654 xgcv.fill_style = FillStippled;
1655 xgcv.stipple = mw->menu.gray_pixmap;
1656 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1657 | GCFillStyle | GCStipple, &xgcv);
1659 xgcv.foreground = mw->core.background_pixel;
1660 xgcv.background = mw->menu.foreground;
1661 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1664 static void
1665 release_drawing_gcs (XlwMenuWidget mw)
1667 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1668 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1669 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1670 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1671 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1672 /* let's get some segvs if we try to use these... */
1673 mw->menu.foreground_gc = (GC) -1;
1674 mw->menu.button_gc = (GC) -1;
1675 mw->menu.disabled_gc = (GC) -1;
1676 mw->menu.inactive_button_gc = (GC) -1;
1677 mw->menu.background_gc = (GC) -1;
1680 #ifndef emacs
1681 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1682 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1683 #endif
1685 static void
1686 make_shadow_gcs (XlwMenuWidget mw)
1688 XGCValues xgcv;
1689 unsigned long pm = 0;
1690 Display *dpy = XtDisplay ((Widget) mw);
1691 Screen *screen = XtScreen ((Widget) mw);
1692 Colormap cmap = mw->core.colormap;
1693 XColor topc, botc;
1694 int top_frobbed = 0, bottom_frobbed = 0;
1696 mw->menu.free_top_shadow_color_p = 0;
1697 mw->menu.free_bottom_shadow_color_p = 0;
1699 if (mw->menu.top_shadow_color == -1)
1700 mw->menu.top_shadow_color = mw->core.background_pixel;
1701 else
1702 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1704 if (mw->menu.bottom_shadow_color == -1)
1705 mw->menu.bottom_shadow_color = mw->menu.foreground;
1706 else
1707 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1709 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1710 mw->menu.top_shadow_color == mw->menu.foreground)
1712 topc.pixel = mw->core.background_pixel;
1713 #ifdef emacs
1714 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1715 &topc.pixel,
1716 1.2, 0x8000))
1717 #else
1718 XQueryColor (dpy, cmap, &topc);
1719 /* don't overflow/wrap! */
1720 topc.red = MINL (65535, topc.red * 1.2);
1721 topc.green = MINL (65535, topc.green * 1.2);
1722 topc.blue = MINL (65535, topc.blue * 1.2);
1723 if (XAllocColor (dpy, cmap, &topc))
1724 #endif
1726 mw->menu.top_shadow_color = topc.pixel;
1727 mw->menu.free_top_shadow_color_p = 1;
1728 top_frobbed = 1;
1731 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1732 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1734 botc.pixel = mw->core.background_pixel;
1735 #ifdef emacs
1736 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1737 &botc.pixel,
1738 0.6, 0x4000))
1739 #else
1740 XQueryColor (dpy, cmap, &botc);
1741 botc.red *= 0.6;
1742 botc.green *= 0.6;
1743 botc.blue *= 0.6;
1744 if (XAllocColor (dpy, cmap, &botc))
1745 #endif
1747 mw->menu.bottom_shadow_color = botc.pixel;
1748 mw->menu.free_bottom_shadow_color_p = 1;
1749 bottom_frobbed = 1;
1753 if (top_frobbed && bottom_frobbed)
1755 if (topc.pixel == botc.pixel)
1757 if (botc.pixel == mw->menu.foreground)
1759 if (mw->menu.free_top_shadow_color_p)
1761 x_free_dpy_colors (dpy, screen, cmap,
1762 &mw->menu.top_shadow_color, 1);
1763 mw->menu.free_top_shadow_color_p = 0;
1765 mw->menu.top_shadow_color = mw->core.background_pixel;
1767 else
1769 if (mw->menu.free_bottom_shadow_color_p)
1771 x_free_dpy_colors (dpy, screen, cmap,
1772 &mw->menu.bottom_shadow_color, 1);
1773 mw->menu.free_bottom_shadow_color_p = 0;
1775 mw->menu.bottom_shadow_color = mw->menu.foreground;
1780 if (!mw->menu.top_shadow_pixmap &&
1781 mw->menu.top_shadow_color == mw->core.background_pixel)
1783 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1784 if (mw->menu.free_top_shadow_color_p)
1786 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1787 mw->menu.free_top_shadow_color_p = 0;
1789 mw->menu.top_shadow_color = mw->menu.foreground;
1791 if (!mw->menu.bottom_shadow_pixmap &&
1792 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1794 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1795 if (mw->menu.free_bottom_shadow_color_p)
1797 x_free_dpy_colors (dpy, screen, cmap,
1798 &mw->menu.bottom_shadow_color, 1);
1799 mw->menu.free_bottom_shadow_color_p = 0;
1801 mw->menu.bottom_shadow_color = mw->menu.foreground;
1804 xgcv.fill_style = FillStippled;
1805 xgcv.foreground = mw->menu.top_shadow_color;
1806 xgcv.stipple = mw->menu.top_shadow_pixmap;
1807 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1808 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1810 xgcv.foreground = mw->menu.bottom_shadow_color;
1811 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1812 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1813 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1817 static void
1818 release_shadow_gcs (XlwMenuWidget mw)
1820 Display *dpy = XtDisplay ((Widget) mw);
1821 Screen *screen = XtScreen ((Widget) mw);
1822 Colormap cmap = mw->core.colormap;
1823 Pixel px[2];
1824 int i = 0;
1826 if (mw->menu.free_top_shadow_color_p)
1827 px[i++] = mw->menu.top_shadow_color;
1828 if (mw->menu.free_bottom_shadow_color_p)
1829 px[i++] = mw->menu.bottom_shadow_color;
1830 if (i > 0)
1831 x_free_dpy_colors (dpy, screen, cmap, px, i);
1833 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1834 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1837 #ifdef HAVE_XFT
1838 static XftFont *
1839 getDefaultXftFont (XlwMenuWidget mw)
1841 int screen = XScreenNumberOfScreen (mw->core.screen);
1842 return XftFontOpenName (XtDisplay (mw), screen, DEFAULT_FONTNAME);
1845 static int
1846 openXftFont (XlwMenuWidget mw)
1848 char *fname = mw->menu.fontName;
1850 mw->menu.xft_font = 0;
1851 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FONTNAME) == 0;
1853 if (fname && strcmp (fname, "none") != 0)
1855 int screen = XScreenNumberOfScreen (mw->core.screen);
1856 int len = strlen (fname), i = len-1;
1857 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1858 while (i > 0 && '0' <= fname[i] && fname[i] <= '9')
1859 --i;
1860 if (fname[i] == ' ')
1862 fname = xstrdup (mw->menu.fontName);
1863 fname[i] = '-';
1866 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1867 if (!mw->menu.xft_font)
1868 mw->menu.xft_font = getDefaultXftFont (mw);
1871 if (fname != mw->menu.fontName) xfree (fname);
1873 return mw->menu.xft_font != 0;
1875 #endif
1877 static void
1878 XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
1880 /* Get the GCs and the widget size */
1881 XlwMenuWidget mw = (XlwMenuWidget) w;
1882 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1883 Display* display = XtDisplay (mw);
1885 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1886 mw->menu.cursor = mw->menu.cursor_shape;
1888 mw->menu.gray_pixmap
1889 = XCreatePixmapFromBitmapData (display, window, gray_bits,
1890 gray_width, gray_height,
1891 (unsigned long)1, (unsigned long)0, 1);
1893 #ifdef HAVE_XFT
1894 if (openXftFont (mw))
1896 else
1897 #endif
1899 mw->menu.font = XLoadQueryFont (display, mw->menu.fontName);
1900 if (!mw->menu.font)
1902 mw->menu.font = XLoadQueryFont (display, "fixed");
1903 if (!mw->menu.font)
1905 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1906 abort ();
1911 #ifdef HAVE_X_I18N
1912 if (mw->menu.fontSet)
1913 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1914 #endif
1916 make_drawing_gcs (mw);
1917 make_shadow_gcs (mw);
1919 mw->menu.popped_up = False;
1921 mw->menu.old_depth = 1;
1922 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1923 mw->menu.old_stack_length = 1;
1924 mw->menu.old_stack [0] = mw->menu.contents;
1926 mw->menu.new_depth = 0;
1927 mw->menu.new_stack = 0;
1928 mw->menu.new_stack_length = 0;
1929 push_new_stack (mw, mw->menu.contents);
1931 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1932 mw->menu.windows_length = 1;
1933 mw->menu.windows [0].x = 0;
1934 mw->menu.windows [0].y = 0;
1935 mw->menu.windows [0].width = 0;
1936 mw->menu.windows [0].height = 0;
1937 mw->menu.windows [0].max_rest_width = 0;
1938 mw->menu.windows [0].pixmap = None;
1939 #ifdef HAVE_XFT
1940 mw->menu.windows [0].xft_draw = 0;
1941 #endif
1942 size_menu (mw, 0);
1944 mw->core.width = mw->menu.windows [0].width;
1945 mw->core.height = mw->menu.windows [0].height;
1948 static void
1949 XlwMenuClassInitialize (void)
1953 static void
1954 XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
1956 XlwMenuWidget mw = (XlwMenuWidget)w;
1957 XSetWindowAttributes xswa;
1958 int mask;
1960 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1961 (w, valueMask, attributes);
1963 xswa.save_under = True;
1964 xswa.cursor = mw->menu.cursor_shape;
1965 mask = CWSaveUnder | CWCursor;
1966 /* I sometimes get random BadCursor errors while creating the first
1967 frame on a display. I can not find their reason, but they are
1968 annoying so for now let's ignore any errors here. -- lorentey */
1969 #ifdef emacs
1970 x_catch_errors (XtDisplay (w));
1971 #endif
1972 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1973 #ifdef emacs
1974 x_uncatch_errors ();
1975 #endif
1977 mw->menu.windows [0].w = w;
1978 mw->menu.windows [0].window = XtWindow (w);
1979 mw->menu.windows [0].x = w->core.x;
1980 mw->menu.windows [0].y = w->core.y;
1981 mw->menu.windows [0].width = w->core.width;
1982 mw->menu.windows [0].height = w->core.height;
1984 set_window_type (mw->menu.windows [0].w, mw);
1985 create_pixmap_for_menu (&mw->menu.windows [0], mw);
1987 #ifdef HAVE_XFT
1988 if (mw->menu.xft_font)
1990 XColor colors[3];
1991 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
1992 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
1993 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
1994 = mw->menu.disabled_foreground;
1995 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
1996 mw->menu.xft_fg.color.alpha = 0xFFFF;
1997 mw->menu.xft_fg.color.red = colors[0].red;
1998 mw->menu.xft_fg.color.green = colors[0].green;
1999 mw->menu.xft_fg.color.blue = colors[0].blue;
2000 mw->menu.xft_bg.color.alpha = 0xFFFF;
2001 mw->menu.xft_bg.color.red = colors[1].red;
2002 mw->menu.xft_bg.color.green = colors[1].green;
2003 mw->menu.xft_bg.color.blue = colors[1].blue;
2004 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2005 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2006 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2007 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2009 #endif
2012 /* Only the toplevel menubar/popup is a widget so it's the only one that
2013 receives expose events through Xt. So we repaint all the other panes
2014 when receiving an Expose event. */
2015 static void
2016 XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
2018 XlwMenuWidget mw = (XlwMenuWidget)w;
2020 /* If we have a depth beyond 1, it's because a submenu was displayed.
2021 If the submenu has been destroyed, set the depth back to 1. */
2022 if (submenu_destroyed)
2024 mw->menu.old_depth = 1;
2025 submenu_destroyed = 0;
2028 display_menu (mw, 0, False, NULL, NULL, NULL);
2032 /* Part of a hack to make the menu redisplay when a tooltip frame
2033 over a menu item is unmapped. */
2035 void
2036 xlwmenu_redisplay (Widget w)
2038 XlwMenuRedisplay (w, NULL, None);
2041 static void
2042 XlwMenuDestroy (Widget w)
2044 int i;
2045 XlwMenuWidget mw = (XlwMenuWidget) w;
2047 if (pointer_grabbed)
2048 ungrab_all ((Widget)w, CurrentTime);
2049 pointer_grabbed = 0;
2051 submenu_destroyed = 1;
2053 release_drawing_gcs (mw);
2054 release_shadow_gcs (mw);
2056 /* this doesn't come from the resource db but is created explicitly
2057 so we must free it ourselves. */
2058 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2059 mw->menu.gray_pixmap = (Pixmap) -1;
2061 /* Don't free mw->menu.contents because that comes from our creator.
2062 The `*_stack' elements are just pointers into `contents' so leave
2063 that alone too. But free the stacks themselves. */
2064 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2065 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2067 /* Original comment was:
2069 Remember, you can't free anything that came from the resource
2070 database. This includes:
2071 mw->menu.cursor
2072 mw->menu.top_shadow_pixmap
2073 mw->menu.bottom_shadow_pixmap
2074 mw->menu.font
2075 Also the color cells of top_shadow_color, bottom_shadow_color,
2076 foreground, and button_foreground will never be freed until this
2077 client exits. Nice, eh?
2079 But now I can free font without any visible glitches. */
2081 if (mw->menu.font)
2082 XFreeFont (XtDisplay (mw), mw->menu.font);
2084 #ifdef HAVE_XFT
2085 if (mw->menu.windows [0].xft_draw)
2086 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2087 if (mw->menu.xft_font)
2088 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2089 #endif
2091 if (mw->menu.windows [0].pixmap != None)
2092 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
2093 /* start from 1 because the one in slot 0 is w->core.window */
2094 for (i = 1; i < mw->menu.windows_length; i++)
2096 if (mw->menu.windows [i].pixmap != None)
2097 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
2098 #ifdef HAVE_XFT
2099 if (mw->menu.windows [i].xft_draw)
2100 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2101 #endif
2104 if (mw->menu.windows)
2105 XtFree ((char *) mw->menu.windows);
2108 #ifdef HAVE_XFT
2109 static int
2110 fontname_changed (XlwMenuWidget newmw,
2111 XlwMenuWidget oldmw)
2113 /* This will force a new XftFont even if the same string is set.
2114 This is good, as rendering parameters may have changed and
2115 we just want to do a redisplay. */
2116 return newmw->menu.fontName != oldmw->menu.fontName;
2118 #endif
2120 static Boolean
2121 XlwMenuSetValues (Widget current, Widget request, Widget new,
2122 ArgList args, Cardinal *num_args)
2124 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2125 XlwMenuWidget newmw = (XlwMenuWidget)new;
2126 Boolean do_redisplay = False;
2128 if (newmw->menu.contents
2129 && newmw->menu.contents->contents
2130 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2131 do_redisplay = True;
2132 /* Do redisplay if the contents are entirely eliminated. */
2133 if (newmw->menu.contents
2134 && newmw->menu.contents->contents == 0
2135 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2136 do_redisplay = True;
2138 if (newmw->core.background_pixel != oldmw->core.background_pixel
2139 || newmw->menu.foreground != oldmw->menu.foreground
2140 #ifdef HAVE_XFT
2141 || fontname_changed (newmw, oldmw)
2142 #endif
2143 #ifdef HAVE_X_I18N
2144 || newmw->menu.fontSet != oldmw->menu.fontSet
2145 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2146 #else
2147 || newmw->menu.font != oldmw->menu.font
2148 #endif
2151 int i;
2152 release_drawing_gcs (newmw);
2153 make_drawing_gcs (newmw);
2155 release_shadow_gcs (newmw);
2156 /* Cause the shadow colors to be recalculated. */
2157 newmw->menu.top_shadow_color = -1;
2158 newmw->menu.bottom_shadow_color = -1;
2159 make_shadow_gcs (newmw);
2161 do_redisplay = True;
2163 if (XtIsRealized (current))
2164 /* If the menu is currently displayed, change the display. */
2165 for (i = 0; i < oldmw->menu.windows_length; i++)
2167 XSetWindowBackground (XtDisplay (oldmw),
2168 oldmw->menu.windows [i].window,
2169 newmw->core.background_pixel);
2170 /* clear windows and generate expose events */
2171 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2172 0, 0, 0, 0, True);
2176 #ifdef HAVE_XFT
2177 if (fontname_changed (newmw, oldmw))
2179 int i;
2180 int screen = XScreenNumberOfScreen (newmw->core.screen);
2181 if (newmw->menu.xft_font)
2182 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2183 openXftFont (newmw);
2184 for (i = 0; i < newmw->menu.windows_length; i++)
2186 if (newmw->menu.windows [i].xft_draw)
2187 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2188 newmw->menu.windows [i].xft_draw = 0;
2190 if (newmw->menu.xft_font)
2191 for (i = 0; i < newmw->menu.windows_length; i++)
2192 newmw->menu.windows [i].xft_draw
2193 = XftDrawCreate (XtDisplay (newmw),
2194 newmw->menu.windows [i].window,
2195 DefaultVisual (XtDisplay (newmw), screen),
2196 newmw->core.colormap);
2198 #endif
2199 #ifdef HAVE_X_I18N
2200 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2202 do_redisplay = True;
2203 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2205 #endif
2207 return do_redisplay;
2210 static void
2211 XlwMenuResize (Widget w)
2213 XlwMenuWidget mw = (XlwMenuWidget)w;
2215 if (mw->menu.popped_up)
2217 /* Don't allow the popup menu to resize itself. */
2218 mw->core.width = mw->menu.windows [0].width;
2219 mw->core.height = mw->menu.windows [0].height;
2220 mw->core.parent->core.width = mw->core.width;
2221 mw->core.parent->core.height = mw->core.height;
2223 else
2225 mw->menu.windows [0].width = mw->core.width;
2226 mw->menu.windows [0].height = mw->core.height;
2227 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2231 \f/* Action procedures */
2232 static void
2233 handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2235 widget_value* val;
2236 int level;
2238 if (!map_event_to_widget_value (mw, ev, &val, &level))
2239 pop_new_stack_if_no_contents (mw);
2240 else
2241 set_new_state (mw, val, level);
2242 remap_menubar (mw);
2244 /* Sync with the display. Makes it feel better on X terms. */
2245 XSync (XtDisplay (mw), False);
2248 static void
2249 handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2251 int x = ev->x_root;
2252 int y = ev->y_root;
2253 int state = ev->state;
2254 XMotionEvent oldev = *ev;
2256 /* allow motion events to be generated again */
2257 if (ev->is_hint
2258 && XQueryPointer (XtDisplay (mw), ev->window,
2259 &ev->root, &ev->subwindow,
2260 &ev->x_root, &ev->y_root,
2261 &ev->x, &ev->y,
2262 &ev->state)
2263 && ev->state == state
2264 && (ev->x_root != x || ev->y_root != y))
2265 handle_single_motion_event (mw, ev);
2266 else
2267 handle_single_motion_event (mw, &oldev);
2270 static void
2271 Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2273 XlwMenuWidget mw = (XlwMenuWidget)w;
2275 if (!mw->menu.popped_up)
2277 menu_post_event = *ev;
2278 /* If event is set to CurrentTime, get the last known time stamp.
2279 This is for calculating if (popup) menus should stay up after
2280 a fast click. */
2281 if (menu_post_event.xbutton.time == CurrentTime)
2282 menu_post_event.xbutton.time
2283 = XtLastTimestampProcessed (XtDisplay (w));
2285 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2287 else
2289 /* If we push a button while the menu is posted semipermanently,
2290 releasing the button should always pop the menu down. */
2291 next_release_must_exit = 1;
2293 /* notes the absolute position of the menubar window */
2294 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2295 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2297 /* handles the down like a move, slots are compatible */
2298 ev->xmotion.is_hint = 0;
2299 handle_motion_event (mw, &ev->xmotion);
2303 static void
2304 Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2306 XlwMenuWidget mw = (XlwMenuWidget)w;
2307 if (mw->menu.popped_up)
2308 handle_motion_event (mw, &ev->xmotion);
2311 /* Do nothing.
2312 This is how we handle presses and releases of modifier keys. */
2313 static void
2314 Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2318 static widget_value *
2319 find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2321 widget_value *current = item;
2322 enum menu_separator separator;
2324 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2325 || (skip_titles && !current->call_data && !current->contents))
2326 if (current->next)
2327 current=current->next;
2328 else
2329 return NULL;
2331 return current;
2334 static widget_value *
2335 find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2337 widget_value *current = item;
2338 enum menu_separator separator;
2340 while (current->next && (current=current->next) &&
2341 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2342 || (skip_titles && !current->call_data && !current->contents)))
2345 if (current == item)
2347 if (mw->menu.old_depth < 2)
2348 return current;
2349 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2351 while (lw_separator_p (current->name, &separator, 0)
2352 || !current->enabled
2353 || (skip_titles && !current->call_data
2354 && !current->contents))
2356 if (current->next)
2357 current=current->next;
2359 if (current == item)
2360 break;
2365 return current;
2368 static widget_value *
2369 find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2371 widget_value *current = item;
2372 widget_value *prev = item;
2374 while ((current=find_next_selectable (mw, current, skip_titles))
2375 != item)
2377 if (prev == current)
2378 break;
2379 prev=current;
2382 return prev;
2385 static void
2386 Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2388 XlwMenuWidget mw = (XlwMenuWidget) w;
2389 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2390 int popup_menu_p = mw->menu.top_depth == 1;
2392 /* Inside top-level menu-bar? */
2393 if (mw->menu.old_depth == mw->menu.top_depth)
2394 /* When <down> in the menu-bar is pressed, display the corresponding
2395 sub-menu and select the first selectable menu item there.
2396 If this is a popup menu, skip title item of the popup. */
2397 set_new_state (mw,
2398 find_first_selectable (mw,
2399 selected_item->contents,
2400 popup_menu_p),
2401 mw->menu.old_depth);
2402 else
2403 /* Highlight next possible (enabled and not separator) menu item. */
2404 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2405 mw->menu.old_depth - 1);
2407 remap_menubar (mw);
2410 static void
2411 Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2413 XlwMenuWidget mw = (XlwMenuWidget) w;
2414 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2415 int popup_menu_p = mw->menu.top_depth == 1;
2417 /* Inside top-level menu-bar? */
2418 if (mw->menu.old_depth == mw->menu.top_depth)
2420 /* FIXME: this is tricky. <up> in the menu-bar should select the
2421 last selectable item in the list. So we select the first
2422 selectable one and find the previous selectable item. Is there
2423 a better way? */
2424 /* If this is a popup menu, skip title item of the popup. */
2425 set_new_state (mw,
2426 find_first_selectable (mw,
2427 selected_item->contents,
2428 popup_menu_p),
2429 mw->menu.old_depth);
2430 remap_menubar (mw);
2431 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2432 set_new_state (mw,
2433 find_prev_selectable (mw,
2434 selected_item,
2435 popup_menu_p),
2436 mw->menu.old_depth - 1);
2438 else
2439 /* Highlight previous (enabled and not separator) menu item. */
2440 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2441 mw->menu.old_depth - 1);
2443 remap_menubar (mw);
2446 void
2447 Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2449 XlwMenuWidget mw = (XlwMenuWidget) w;
2450 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2452 /* Inside top-level menu-bar? */
2453 if (mw->menu.old_depth == mw->menu.top_depth)
2454 /* When <left> in the menu-bar is pressed, display the previous item on
2455 the menu-bar. If the current item is the first one, highlight the
2456 last item in the menubar (probably Help). */
2457 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2458 mw->menu.old_depth - 1);
2459 else if (mw->menu.old_depth == 1
2460 && selected_item->contents) /* Is this menu item expandable? */
2462 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2463 remap_menubar (mw);
2464 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2465 if (!selected_item->enabled && find_first_selectable (mw,
2466 selected_item,
2468 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2469 mw->menu.old_depth - 1);
2472 else
2474 pop_new_stack_if_no_contents (mw);
2475 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2476 mw->menu.old_depth - 2);
2479 remap_menubar (mw);
2482 void
2483 Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2485 XlwMenuWidget mw = (XlwMenuWidget) w;
2486 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2488 /* Inside top-level menu-bar? */
2489 if (mw->menu.old_depth == mw->menu.top_depth)
2490 /* When <right> in the menu-bar is pressed, display the next item on
2491 the menu-bar. If the current item is the last one, highlight the
2492 first item (probably File). */
2493 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2494 mw->menu.old_depth - 1);
2495 else if (selected_item->contents) /* Is this menu item expandable? */
2497 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2498 remap_menubar (mw);
2499 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2500 if (!selected_item->enabled && find_first_selectable (mw,
2501 selected_item,
2503 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2504 mw->menu.old_depth - 1);
2506 else
2508 pop_new_stack_if_no_contents (mw);
2509 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2510 mw->menu.old_depth - 2);
2513 remap_menubar (mw);
2516 /* Handle key press and release events while menu is popped up.
2517 Our action is to get rid of the menu. */
2518 static void
2519 Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2521 XlwMenuWidget mw = (XlwMenuWidget)w;
2523 /* Pop down everything. */
2524 mw->menu.new_depth = 1;
2525 remap_menubar (mw);
2527 if (mw->menu.popped_up)
2529 mw->menu.popped_up = False;
2530 ungrab_all ((Widget)mw, ev->xmotion.time);
2531 if (XtIsShell (XtParent ((Widget) mw)))
2532 XtPopdown (XtParent ((Widget) mw));
2533 else
2535 XtRemoveGrab ((Widget) mw);
2536 display_menu (mw, 0, False, NULL, NULL, NULL);
2540 /* callback */
2541 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2544 static void
2545 Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2547 XlwMenuWidget mw = (XlwMenuWidget)w;
2548 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2550 /* If user releases the button quickly, without selecting anything,
2551 after the initial down-click that brought the menu up,
2552 do nothing. */
2553 if ((selected_item == 0
2554 || ((widget_value *) selected_item)->call_data == 0)
2555 && !next_release_must_exit
2556 && (ev->xbutton.time - menu_post_event.xbutton.time
2557 < XtGetMultiClickTime (XtDisplay (w))))
2558 return;
2560 /* pop down everything. */
2561 mw->menu.new_depth = 1;
2562 remap_menubar (mw);
2564 if (mw->menu.popped_up)
2566 mw->menu.popped_up = False;
2567 ungrab_all ((Widget)mw, ev->xmotion.time);
2568 if (XtIsShell (XtParent ((Widget) mw)))
2569 XtPopdown (XtParent ((Widget) mw));
2570 else
2572 XtRemoveGrab ((Widget) mw);
2573 display_menu (mw, 0, False, NULL, NULL, NULL);
2577 /* callback */
2578 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2582 \f/* Special code to pop-up a menu */
2583 static void
2584 pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
2586 int x = event->x_root;
2587 int y = event->y_root;
2588 int w;
2589 int h;
2590 int borderwidth = mw->menu.shadow_thickness;
2591 Screen* screen = XtScreen (mw);
2592 Display *display = XtDisplay (mw);
2594 next_release_must_exit = 0;
2596 mw->menu.inside_entry = NULL;
2597 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2599 if (XtIsShell (XtParent ((Widget)mw)))
2600 size_menu (mw, 0);
2602 w = mw->menu.windows [0].width;
2603 h = mw->menu.windows [0].height;
2605 x -= borderwidth;
2606 y -= borderwidth;
2607 if (x < borderwidth)
2608 x = borderwidth;
2609 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2610 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2611 if (y < borderwidth)
2612 y = borderwidth;
2613 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2614 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2616 mw->menu.popped_up = True;
2617 if (XtIsShell (XtParent ((Widget)mw)))
2619 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2620 XtParent ((Widget)mw)->core.border_width);
2621 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2622 display_menu (mw, 0, False, NULL, NULL, NULL);
2623 mw->menu.windows [0].x = x + borderwidth;
2624 mw->menu.windows [0].y = y + borderwidth;
2625 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2627 else
2629 XEvent *ev = (XEvent *) event;
2631 XtAddGrab ((Widget) mw, True, True);
2633 /* notes the absolute position of the menubar window */
2634 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2635 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2636 mw->menu.top_depth = 2;
2639 #ifdef emacs
2640 x_catch_errors (display);
2641 #endif
2642 if (XtGrabPointer ((Widget)mw, False,
2643 (PointerMotionMask
2644 | PointerMotionHintMask
2645 | ButtonReleaseMask
2646 | ButtonPressMask),
2647 GrabModeAsync, GrabModeAsync, None,
2648 mw->menu.cursor_shape,
2649 event->time) == Success)
2651 if (! GRAB_KEYBOARD
2652 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2653 GrabModeAsync, event->time) == Success)
2655 XtSetKeyboardFocus((Widget)mw, None);
2656 pointer_grabbed = 1;
2658 else
2659 XtUngrabPointer ((Widget)mw, event->time);
2662 #ifdef emacs
2663 if (x_had_errors_p (display))
2665 pointer_grabbed = 0;
2666 XtUngrabPointer ((Widget)mw, event->time);
2668 x_uncatch_errors ();
2669 #endif
2671 ((XMotionEvent*)event)->is_hint = 0;
2672 handle_motion_event (mw, (XMotionEvent*)event);