ensure name of a mutex is a string
[emacs.git] / lwlib / xlwmenu.c
blobe8831c37f8fdbb3fb62fcc01167d8ba921c9d6fd
1 /* Implements a lightweight menubar widget.
3 Copyright (C) 1992 Lucid, Inc.
4 Copyright (C) 1994-1995, 1997, 1999-2012 Free Software Foundation, Inc.
6 This file is part of the Lucid Widget Library.
8 The Lucid Widget Library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2, or (at your option)
11 any later version.
13 The Lucid Widget Library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with GNU Emacs; see the file COPYING. If not, write to the
20 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA. */
23 /* Created by devin@lucid.com */
25 #include <config.h>
27 #include <setjmp.h>
28 #include <lisp.h>
30 #include <stdio.h>
31 #include <ctype.h>
33 #include <sys/types.h>
34 #if (defined __sun) && !(defined SUNOS41)
35 #define SUNOS41
36 #include <X11/Xos.h>
37 #undef SUNOS41
38 #else
39 #include <X11/Xos.h>
40 #endif
41 #include <X11/IntrinsicP.h>
42 #include <X11/ObjectP.h>
43 #include <X11/StringDefs.h>
44 #include <X11/cursorfont.h>
45 #include <X11/Shell.h>
46 #include "xlwmenuP.h"
48 #ifdef emacs
50 #include <xterm.h>
51 #include "bitmaps/gray.xbm"
53 #else /* not emacs */
55 #include <X11/bitmaps/gray>
57 #endif /* not emacs */
59 static int pointer_grabbed;
60 static XEvent menu_post_event;
62 static char
63 xlwMenuTranslations [] =
64 "<BtnDown>: start()\n\
65 <Motion>: drag()\n\
66 <BtnUp>: select()\n\
67 <Key>Shift_L: nothing()\n\
68 <Key>Shift_R: nothing()\n\
69 <Key>Meta_L: nothing()\n\
70 <Key>Meta_R: nothing()\n\
71 <Key>Control_L: nothing()\n\
72 <Key>Control_R: nothing()\n\
73 <Key>Hyper_L: nothing()\n\
74 <Key>Hyper_R: nothing()\n\
75 <Key>Super_L: nothing()\n\
76 <Key>Super_R: nothing()\n\
77 <Key>Alt_L: nothing()\n\
78 <Key>Alt_R: nothing()\n\
79 <Key>Caps_Lock: nothing()\n\
80 <Key>Shift_Lock: nothing()\n\
81 <KeyUp>Shift_L: nothing()\n\
82 <KeyUp>Shift_R: nothing()\n\
83 <KeyUp>Meta_L: nothing()\n\
84 <KeyUp>Meta_R: nothing()\n\
85 <KeyUp>Control_L: nothing()\n\
86 <KeyUp>Control_R: nothing()\n\
87 <KeyUp>Hyper_L: nothing()\n\
88 <KeyUp>Hyper_R: nothing()\n\
89 <KeyUp>Super_L: nothing()\n\
90 <KeyUp>Super_R: nothing()\n\
91 <KeyUp>Alt_L: nothing()\n\
92 <KeyUp>Alt_R: nothing()\n\
93 <KeyUp>Caps_Lock: nothing()\n\
94 <KeyUp>Shift_Lock:nothing()\n\
95 <Key>Return: select()\n\
96 <Key>Down: down()\n\
97 <Key>Up: up()\n\
98 <Key>Left: left()\n\
99 <Key>Right: right()\n\
100 <Key>: key()\n\
101 <KeyUp>: key()\n\
104 /* FIXME: Space should toggle togglable menu item but not remove the menu
105 so you can toggle the next one without entering the menu again. */
107 /* FIXME: Should ESC close one level of menu structure or the complete menu? */
109 /* FIXME: F10 should enter the menu, the first one in the menu-bar. */
111 #define offset(field) XtOffset(XlwMenuWidget, field)
112 static XtResource
113 xlwMenuResources[] =
115 #ifdef HAVE_X_I18N
116 {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet),
117 offset(menu.fontSet), XtRFontSet, NULL},
118 #endif
119 #ifdef HAVE_XFT
120 #define DEFAULT_FONTNAME "Sans-10"
121 #else
122 #define DEFAULT_FONTNAME "XtDefaultFont"
123 #endif
124 {XtNfont, XtCFont, XtRString, sizeof(String),
125 offset(menu.fontName), XtRString, DEFAULT_FONTNAME },
126 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
127 offset(menu.foreground), XtRString, "XtDefaultForeground"},
128 {XtNdisabledForeground, XtCDisabledForeground, XtRPixel, sizeof(Pixel),
129 offset(menu.disabled_foreground), XtRString, (XtPointer)NULL},
130 {XtNbuttonForeground, XtCButtonForeground, XtRPixel, sizeof(Pixel),
131 offset(menu.button_foreground), XtRString, "XtDefaultForeground"},
132 {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
133 offset(menu.margin), XtRImmediate, (XtPointer)1},
134 {XtNhorizontalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
135 offset(menu.horizontal_spacing), XtRImmediate, (XtPointer)3},
136 {XtNverticalSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
137 offset(menu.vertical_spacing), XtRImmediate, (XtPointer)2},
138 {XtNarrowSpacing, XtCMargin, XtRDimension, sizeof(Dimension),
139 offset(menu.arrow_spacing), XtRImmediate, (XtPointer)10},
141 {XmNshadowThickness, XmCShadowThickness, XtRDimension,
142 sizeof (Dimension), offset (menu.shadow_thickness),
143 XtRImmediate, (XtPointer)1},
144 {XmNtopShadowColor, XmCTopShadowColor, XtRPixel, sizeof (Pixel),
145 offset (menu.top_shadow_color), XtRImmediate, (XtPointer)-1},
146 {XmNbottomShadowColor, XmCBottomShadowColor, XtRPixel, sizeof (Pixel),
147 offset (menu.bottom_shadow_color), XtRImmediate, (XtPointer)-1},
148 {XmNtopShadowPixmap, XmCTopShadowPixmap, XtRPixmap, sizeof (Pixmap),
149 offset (menu.top_shadow_pixmap), XtRImmediate, (XtPointer)None},
150 {XmNbottomShadowPixmap, XmCBottomShadowPixmap, XtRPixmap, sizeof (Pixmap),
151 offset (menu.bottom_shadow_pixmap), XtRImmediate, (XtPointer)None},
153 {XtNopen, XtCCallback, XtRCallback, sizeof(XtPointer),
154 offset(menu.open), XtRCallback, (XtPointer)NULL},
155 {XtNselect, XtCCallback, XtRCallback, sizeof(XtPointer),
156 offset(menu.select), XtRCallback, (XtPointer)NULL},
157 {XtNhighlightCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
158 offset(menu.highlight), XtRCallback, (XtPointer)NULL},
159 {XtNenterCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
160 offset(menu.enter), XtRCallback, (XtPointer)NULL},
161 {XtNleaveCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
162 offset(menu.leave), XtRCallback, (XtPointer)NULL},
163 {XtNmenu, XtCMenu, XtRPointer, sizeof(XtPointer),
164 offset(menu.contents), XtRImmediate, (XtPointer)NULL},
165 {XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
166 offset(menu.cursor_shape), XtRString, (XtPointer)"right_ptr"},
167 {XtNhorizontal, XtCHorizontal, XtRInt, sizeof(int),
168 offset(menu.horizontal), XtRImmediate, (XtPointer)True},
170 #undef offset
172 static Boolean XlwMenuSetValues(Widget current, Widget request, Widget new,
173 ArgList args, Cardinal *num_args);
174 static void XlwMenuRealize(Widget, Mask *, XSetWindowAttributes *);
175 static void XlwMenuResize(Widget w);
176 static void XlwMenuInitialize(Widget, Widget, ArgList, Cardinal *);
177 static void XlwMenuRedisplay(Widget w, XEvent *ev, Region region);
178 static void XlwMenuDestroy(Widget w);
179 static void XlwMenuClassInitialize(void);
180 static void Start(Widget w, XEvent *ev, String *params, Cardinal *num_params);
181 static void Drag(Widget w, XEvent *ev, String *params, Cardinal *num_params);
182 static void Down(Widget w, XEvent *ev, String *params, Cardinal *num_params);
183 static void Up(Widget w, XEvent *ev, String *params, Cardinal *num_params);
184 static void Left(Widget w, XEvent *ev, String *params, Cardinal *num_params);
185 static void Right(Widget w, XEvent *ev, String *params, Cardinal *num_params);
186 static void Select(Widget w, XEvent *ev, String *params, Cardinal *num_params);
187 static void Key(Widget w, XEvent *ev, String *params, Cardinal *num_params);
188 static void Nothing(Widget w, XEvent *ev, String *params, Cardinal *num_params);
189 static int separator_height (enum menu_separator);
190 static void pop_up_menu (XlwMenuWidget, XButtonPressedEvent *);
192 static XtActionsRec
193 xlwMenuActionsList [] =
195 {"start", Start},
196 {"drag", Drag},
197 {"down", Down},
198 {"up", Up},
199 {"left", Left},
200 {"right", Right},
201 {"select", Select},
202 {"key", Key},
203 {"MenuGadgetEscape", Key}, /* Compatibility with Lesstif/Motif. */
204 {"nothing", Nothing},
207 #define SuperClass ((CoreWidgetClass)&coreClassRec)
209 XlwMenuClassRec xlwMenuClassRec =
211 { /* CoreClass fields initialization */
212 (WidgetClass) SuperClass, /* superclass */
213 "XlwMenu", /* class_name */
214 sizeof(XlwMenuRec), /* size */
215 XlwMenuClassInitialize, /* class_initialize */
216 NULL, /* class_part_initialize */
217 FALSE, /* class_inited */
218 XlwMenuInitialize, /* initialize */
219 NULL, /* initialize_hook */
220 XlwMenuRealize, /* realize */
221 xlwMenuActionsList, /* actions */
222 XtNumber(xlwMenuActionsList), /* num_actions */
223 xlwMenuResources, /* resources */
224 XtNumber(xlwMenuResources), /* resource_count */
225 NULLQUARK, /* xrm_class */
226 TRUE, /* compress_motion */
227 XtExposeCompressMaximal, /* compress_exposure */
228 TRUE, /* compress_enterleave */
229 FALSE, /* visible_interest */
230 XlwMenuDestroy, /* destroy */
231 XlwMenuResize, /* resize */
232 XlwMenuRedisplay, /* expose */
233 XlwMenuSetValues, /* set_values */
234 NULL, /* set_values_hook */
235 XtInheritSetValuesAlmost, /* set_values_almost */
236 NULL, /* get_values_hook */
237 NULL, /* accept_focus */
238 XtVersion, /* version */
239 NULL, /* callback_private */
240 xlwMenuTranslations, /* tm_table */
241 XtInheritQueryGeometry, /* query_geometry */
242 XtInheritDisplayAccelerator, /* display_accelerator */
243 NULL /* extension */
244 }, /* XlwMenuClass fields initialization */
246 0 /* dummy */
250 WidgetClass xlwMenuWidgetClass = (WidgetClass) &xlwMenuClassRec;
252 int submenu_destroyed;
254 /* For debug, if installation-directory is non-nil this is not an installed
255 Emacs. In that case we do not grab the keyboard to make it easier to
256 debug. */
257 #define GRAB_KEYBOARD (EQ (Vinstallation_directory, Qnil))
259 static int next_release_must_exit;
261 \f/* Utilities */
263 /* Ungrab pointer and keyboard */
264 static void
265 ungrab_all (Widget w, Time ungrabtime)
267 XtUngrabPointer (w, ungrabtime);
268 if (GRAB_KEYBOARD) XtUngrabKeyboard (w, ungrabtime);
271 /* Like abort, but remove grabs from widget W before. */
273 static _Noreturn void
274 abort_gracefully (Widget w)
276 if (XtIsShell (XtParent (w)))
277 XtRemoveGrab (w);
278 ungrab_all (w, CurrentTime);
279 abort ();
282 static void
283 push_new_stack (XlwMenuWidget mw, widget_value *val)
285 if (!mw->menu.new_stack)
287 mw->menu.new_stack_length = 10;
288 mw->menu.new_stack =
289 (widget_value**)XtCalloc (mw->menu.new_stack_length,
290 sizeof (widget_value*));
292 else if (mw->menu.new_depth == mw->menu.new_stack_length)
294 mw->menu.new_stack_length *= 2;
295 mw->menu.new_stack =
296 (widget_value**)XtRealloc ((char*)mw->menu.new_stack,
297 mw->menu.new_stack_length * sizeof (widget_value*));
299 mw->menu.new_stack [mw->menu.new_depth++] = val;
302 static void
303 pop_new_stack_if_no_contents (XlwMenuWidget mw)
305 if (mw->menu.new_depth > 1)
307 if (!mw->menu.new_stack [mw->menu.new_depth - 1]->contents)
308 mw->menu.new_depth -= 1;
312 static void
313 make_old_stack_space (XlwMenuWidget mw, int n)
315 if (!mw->menu.old_stack)
317 mw->menu.old_stack_length = 10;
318 mw->menu.old_stack =
319 (widget_value**)XtCalloc (mw->menu.old_stack_length,
320 sizeof (widget_value*));
322 else if (mw->menu.old_stack_length < n)
324 mw->menu.old_stack_length *= 2;
325 mw->menu.old_stack =
326 (widget_value**)XtRealloc ((char*)mw->menu.old_stack,
327 mw->menu.old_stack_length * sizeof (widget_value*));
331 \f/* Size code */
332 static int
333 string_width (XlwMenuWidget mw, char *s)
335 XCharStruct xcs;
336 int drop;
337 #ifdef HAVE_XFT
338 if (mw->menu.xft_font)
340 XGlyphInfo gi;
341 XftTextExtentsUtf8 (XtDisplay (mw), mw->menu.xft_font,
342 (FcChar8 *) s,
343 strlen (s), &gi);
344 return gi.width;
346 #endif
347 #ifdef HAVE_X_I18N
348 if (mw->menu.fontSet)
350 XRectangle ink, logical;
351 XmbTextExtents (mw->menu.fontSet, s, strlen (s), &ink, &logical);
352 return logical.width;
354 #endif
356 XTextExtents (mw->menu.font, s, strlen (s), &drop, &drop, &drop, &xcs);
357 return xcs.width;
361 #ifdef HAVE_XFT
362 #define MENU_FONT_HEIGHT(mw) \
363 ((mw)->menu.xft_font != NULL \
364 ? (mw)->menu.xft_font->height \
365 : ((mw)->menu.fontSet != NULL \
366 ? (mw)->menu.font_extents->max_logical_extent.height \
367 : (mw)->menu.font->ascent + (mw)->menu.font->descent))
368 #define MENU_FONT_ASCENT(mw) \
369 ((mw)->menu.xft_font != NULL \
370 ? (mw)->menu.xft_font->ascent \
371 : ((mw)->menu.fontSet != NULL \
372 ? - (mw)->menu.font_extents->max_logical_extent.y \
373 : (mw)->menu.font->ascent))
374 #else
375 #ifdef HAVE_X_I18N
376 #define MENU_FONT_HEIGHT(mw) \
377 ((mw)->menu.fontSet != NULL \
378 ? (mw)->menu.font_extents->max_logical_extent.height \
379 : (mw)->menu.font->ascent + (mw)->menu.font->descent)
380 #define MENU_FONT_ASCENT(mw) \
381 ((mw)->menu.fontSet != NULL \
382 ? - (mw)->menu.font_extents->max_logical_extent.y \
383 : (mw)->menu.font->ascent)
384 #else
385 #define MENU_FONT_HEIGHT(mw) \
386 ((mw)->menu.font->ascent + (mw)->menu.font->descent)
387 #define MENU_FONT_ASCENT(mw) ((mw)->menu.font->ascent)
388 #endif
389 #endif
391 static int
392 arrow_width (XlwMenuWidget mw)
394 return (MENU_FONT_ASCENT (mw) * 3/4) | 1;
397 /* Return the width of toggle buttons of widget MW. */
399 static int
400 toggle_button_width (XlwMenuWidget mw)
402 return (MENU_FONT_HEIGHT (mw) * 2 / 3) | 1;
406 /* Return the width of radio buttons of widget MW. */
408 static int
409 radio_button_width (XlwMenuWidget mw)
411 return toggle_button_width (mw) * 1.41;
415 static XtResource
416 nameResource[] =
418 {"labelString", "LabelString", XtRString, sizeof(String),
419 0, XtRImmediate, 0},
422 static char*
423 resource_widget_value (XlwMenuWidget mw, widget_value *val)
425 if (!val->toolkit_data)
427 char* resourced_name = NULL;
428 char* complete_name;
429 XtGetSubresources ((Widget) mw,
430 (XtPointer) &resourced_name,
431 val->name, val->name,
432 nameResource, 1, NULL, 0);
433 if (!resourced_name)
434 resourced_name = val->name;
435 if (!val->value)
437 complete_name = (char *) XtMalloc (strlen (resourced_name) + 1);
438 strcpy (complete_name, resourced_name);
440 else
442 int complete_length =
443 strlen (resourced_name) + strlen (val->value) + 2;
444 complete_name = XtMalloc (complete_length);
445 *complete_name = 0;
446 strcat (complete_name, resourced_name);
447 strcat (complete_name, " ");
448 strcat (complete_name, val->value);
451 val->toolkit_data = complete_name;
452 val->free_toolkit_data = True;
454 return (char*)val->toolkit_data;
457 /* Returns the sizes of an item */
458 static void
459 size_menu_item (XlwMenuWidget mw,
460 widget_value* val,
461 int horizontal_p,
462 int* label_width,
463 int* rest_width,
464 int* button_width,
465 int* height)
467 enum menu_separator separator;
469 if (lw_separator_p (val->name, &separator, 0))
471 *height = separator_height (separator);
472 *label_width = 1;
473 *rest_width = 0;
474 *button_width = 0;
476 else
478 *height = MENU_FONT_HEIGHT (mw)
479 + 2 * mw->menu.vertical_spacing + 2 * mw->menu.shadow_thickness;
481 *label_width =
482 string_width (mw, resource_widget_value (mw, val))
483 + mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
485 *rest_width = mw->menu.horizontal_spacing + mw->menu.shadow_thickness;
486 if (!horizontal_p)
488 if (val->contents)
489 /* Add width of the arrow displayed for submenus. */
490 *rest_width += arrow_width (mw) + mw->menu.arrow_spacing;
491 else if (val->key)
492 /* Add width of key equivalent string. */
493 *rest_width += (string_width (mw, val->key)
494 + mw->menu.arrow_spacing);
496 if (val->button_type == BUTTON_TYPE_TOGGLE)
497 *button_width = (toggle_button_width (mw)
498 + mw->menu.horizontal_spacing);
499 else if (val->button_type == BUTTON_TYPE_RADIO)
500 *button_width = (radio_button_width (mw)
501 + mw->menu.horizontal_spacing);
506 static void
507 size_menu (XlwMenuWidget mw, int level)
509 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 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;
560 ws->max_rest_width = max_rest_width;
562 if (horizontal_p)
564 ws->width += 2 * mw->menu.margin;
565 ws->height += 2 * mw->menu.margin;
570 \f/* Display code */
572 static void
573 draw_arrow (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 (XlwMenuWidget mw,
637 Window window,
638 int x,
639 int y,
640 int width,
641 int height,
642 int erase_p,
643 int down_p)
645 Display *dpy = XtDisplay (mw);
646 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
647 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
648 int thickness = mw->menu.shadow_thickness;
649 XPoint points [4];
651 if (!erase_p && down_p)
653 GC temp;
654 temp = top_gc;
655 top_gc = bottom_gc;
656 bottom_gc = temp;
659 points [0].x = x;
660 points [0].y = y;
661 points [1].x = x + width;
662 points [1].y = y;
663 points [2].x = x + width - thickness;
664 points [2].y = y + thickness;
665 points [3].x = x;
666 points [3].y = y + thickness;
667 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
668 points [0].x = x;
669 points [0].y = y + thickness;
670 points [1].x = x;
671 points [1].y = y + height;
672 points [2].x = x + thickness;
673 points [2].y = y + height - thickness;
674 points [3].x = x + thickness;
675 points [3].y = y + thickness;
676 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
677 points [0].x = x + width;
678 points [0].y = y;
679 points [1].x = x + width - thickness;
680 points [1].y = y + thickness;
681 points [2].x = x + width - thickness;
682 points [2].y = y + height - thickness;
683 points [3].x = x + width;
684 points [3].y = y + height - thickness;
685 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
686 points [0].x = x;
687 points [0].y = y + height;
688 points [1].x = x + width;
689 points [1].y = y + height;
690 points [2].x = x + width;
691 points [2].y = y + height - thickness;
692 points [3].x = x + thickness;
693 points [3].y = y + height - thickness;
694 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
698 static void
699 draw_shadow_rhombus (XlwMenuWidget mw,
700 Window window,
701 int x,
702 int y,
703 int width,
704 int height,
705 int erase_p,
706 int down_p)
708 Display *dpy = XtDisplay (mw);
709 GC top_gc = !erase_p ? mw->menu.shadow_top_gc : mw->menu.background_gc;
710 GC bottom_gc = !erase_p ? mw->menu.shadow_bottom_gc : mw->menu.background_gc;
711 int thickness = mw->menu.shadow_thickness;
712 XPoint points [4];
714 if (!erase_p && down_p)
716 GC temp;
717 temp = top_gc;
718 top_gc = bottom_gc;
719 bottom_gc = temp;
722 points [0].x = x;
723 points [0].y = y + height / 2;
724 points [1].x = x + thickness;
725 points [1].y = y + height / 2;
726 points [2].x = x + width / 2;
727 points [2].y = y + thickness;
728 points [3].x = x + width / 2;
729 points [3].y = y;
730 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
731 points [0].x = x + width / 2;
732 points [0].y = y;
733 points [1].x = x + width / 2;
734 points [1].y = y + thickness;
735 points [2].x = x + width - thickness;
736 points [2].y = y + height / 2;
737 points [3].x = x + width;
738 points [3].y = y + height / 2;
739 XFillPolygon (dpy, window, top_gc, points, 4, Convex, CoordModeOrigin);
740 points [0].x = x;
741 points [0].y = y + height / 2;
742 points [1].x = x + thickness;
743 points [1].y = y + height / 2;
744 points [2].x = x + width / 2;
745 points [2].y = y + height - thickness;
746 points [3].x = x + width / 2;
747 points [3].y = y + height;
748 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
749 points [0].x = x + width / 2;
750 points [0].y = y + height;
751 points [1].x = x + width / 2;
752 points [1].y = y + height - thickness;
753 points [2].x = x + width - thickness;
754 points [2].y = y + height / 2;
755 points [3].x = x + width;
756 points [3].y = y + height / 2;
757 XFillPolygon (dpy, window, bottom_gc, points, 4, Convex, CoordModeOrigin);
761 /* Draw a toggle button on widget MW, X window WINDOW. X/Y is the
762 top-left corner of the menu item. SELECTED_P non-zero means the
763 toggle button is selected. */
765 static void
766 draw_toggle (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
768 int width, height;
770 width = toggle_button_width (mw);
771 height = width;
772 x += mw->menu.horizontal_spacing;
773 y += (MENU_FONT_ASCENT (mw) - height) / 2;
774 draw_shadow_rectangle (mw, window, x, y, width, height, False, selected_p);
778 /* Draw a radio button on widget MW, X window WINDOW. X/Y is the
779 top-left corner of the menu item. SELECTED_P non-zero means the
780 toggle button is selected. */
782 static void
783 draw_radio (XlwMenuWidget mw, Window window, int x, int y, int selected_p)
785 int width, height;
787 width = radio_button_width (mw);
788 height = width;
789 x += mw->menu.horizontal_spacing;
790 y += (MENU_FONT_ASCENT (mw) - height) / 2;
791 draw_shadow_rhombus (mw, window, x, y, width, height, False, selected_p);
795 /* Draw a menu separator on widget MW, X window WINDOW. X/Y is the
796 top-left corner of the menu item. WIDTH is the width of the
797 separator to draw. TYPE is the separator type. */
799 static void
800 draw_separator (XlwMenuWidget mw,
801 Window window,
802 int x,
803 int y,
804 int width,
805 enum menu_separator type)
807 Display *dpy = XtDisplay (mw);
808 XGCValues xgcv;
810 switch (type)
812 case SEPARATOR_NO_LINE:
813 break;
815 case SEPARATOR_SINGLE_LINE:
816 XDrawLine (dpy, window, mw->menu.foreground_gc,
817 x, y, x + width, y);
818 break;
820 case SEPARATOR_DOUBLE_LINE:
821 draw_separator (mw, window, x, y, width, SEPARATOR_SINGLE_LINE);
822 draw_separator (mw, window, x, y + 2, width, SEPARATOR_SINGLE_LINE);
823 break;
825 case SEPARATOR_SINGLE_DASHED_LINE:
826 xgcv.line_style = LineOnOffDash;
827 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
828 XDrawLine (dpy, window, mw->menu.foreground_gc,
829 x, y, x + width, y);
830 xgcv.line_style = LineSolid;
831 XChangeGC (dpy, mw->menu.foreground_gc, GCLineStyle, &xgcv);
832 break;
834 case SEPARATOR_DOUBLE_DASHED_LINE:
835 draw_separator (mw, window, x, y, width,
836 SEPARATOR_SINGLE_DASHED_LINE);
837 draw_separator (mw, window, x, y + 2, width,
838 SEPARATOR_SINGLE_DASHED_LINE);
839 break;
841 case SEPARATOR_SHADOW_ETCHED_IN:
842 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
843 x, y, x + width, y);
844 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
845 x, y + 1, x + width, y + 1);
846 break;
848 case SEPARATOR_SHADOW_ETCHED_OUT:
849 XDrawLine (dpy, window, mw->menu.shadow_top_gc,
850 x, y, x + width, y);
851 XDrawLine (dpy, window, mw->menu.shadow_bottom_gc,
852 x, y + 1, x + width, y + 1);
853 break;
855 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
856 xgcv.line_style = LineOnOffDash;
857 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
858 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
859 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
860 xgcv.line_style = LineSolid;
861 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
862 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
863 break;
865 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
866 xgcv.line_style = LineOnOffDash;
867 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
868 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
869 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_OUT);
870 xgcv.line_style = LineSolid;
871 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
872 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
873 break;
875 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
876 draw_separator (mw, window, x, y, width, SEPARATOR_SHADOW_ETCHED_IN);
877 draw_separator (mw, window, x, y + 3, width, SEPARATOR_SHADOW_ETCHED_IN);
878 break;
880 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
881 draw_separator (mw, window, x, y, width,
882 SEPARATOR_SHADOW_ETCHED_OUT);
883 draw_separator (mw, window, x, y + 3, width,
884 SEPARATOR_SHADOW_ETCHED_OUT);
885 break;
887 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
888 xgcv.line_style = LineOnOffDash;
889 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
890 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
891 draw_separator (mw, window, x, y, width,
892 SEPARATOR_SHADOW_DOUBLE_ETCHED_IN);
893 xgcv.line_style = LineSolid;
894 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
895 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
896 break;
898 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
899 xgcv.line_style = LineOnOffDash;
900 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
901 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
902 draw_separator (mw, window, x, y, width,
903 SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT);
904 xgcv.line_style = LineSolid;
905 XChangeGC (dpy, mw->menu.shadow_bottom_gc, GCLineStyle, &xgcv);
906 XChangeGC (dpy, mw->menu.shadow_top_gc, GCLineStyle, &xgcv);
907 break;
909 default:
910 abort ();
915 /* Return the pixel height of menu separator SEPARATOR. */
917 static int
918 separator_height (enum menu_separator separator)
920 switch (separator)
922 case SEPARATOR_NO_LINE:
923 return 2;
925 case SEPARATOR_SINGLE_LINE:
926 case SEPARATOR_SINGLE_DASHED_LINE:
927 return 1;
929 case SEPARATOR_DOUBLE_LINE:
930 case SEPARATOR_DOUBLE_DASHED_LINE:
931 return 3;
933 case SEPARATOR_SHADOW_ETCHED_IN:
934 case SEPARATOR_SHADOW_ETCHED_OUT:
935 case SEPARATOR_SHADOW_ETCHED_IN_DASH:
936 case SEPARATOR_SHADOW_ETCHED_OUT_DASH:
937 return 2;
939 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN:
940 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT:
941 case SEPARATOR_SHADOW_DOUBLE_ETCHED_IN_DASH:
942 case SEPARATOR_SHADOW_DOUBLE_ETCHED_OUT_DASH:
943 return 5;
945 default:
946 abort ();
951 /* Display the menu item and increment where.x and where.y to show how large
952 the menu item was. */
954 static void
955 display_menu_item (XlwMenuWidget mw,
956 widget_value* val,
957 window_state* ws,
958 XPoint* where,
959 Boolean highlighted_p,
960 Boolean horizontal_p,
961 Boolean just_compute_p)
963 GC deco_gc;
964 GC text_gc;
965 int font_height = MENU_FONT_HEIGHT (mw);
966 int font_ascent = MENU_FONT_ASCENT (mw);
967 int shadow = mw->menu.shadow_thickness;
968 int margin = mw->menu.margin;
969 int h_spacing = mw->menu.horizontal_spacing;
970 int v_spacing = mw->menu.vertical_spacing;
971 int label_width;
972 int rest_width;
973 int button_width;
974 int height;
975 int width;
976 enum menu_separator separator;
977 int separator_p = lw_separator_p (val->name, &separator, 0);
978 #ifdef HAVE_XFT
979 XftColor *xftfg;
980 #endif
982 /* compute the sizes of the item */
983 size_menu_item (mw, val, horizontal_p, &label_width, &rest_width,
984 &button_width, &height);
986 if (horizontal_p)
987 width = label_width + rest_width;
988 else
990 label_width = ws->label_width;
991 width = ws->width - 2 * shadow;
994 /* Only highlight an enabled item that has a callback. */
995 if (highlighted_p)
996 if (!val->enabled || !(val->call_data || val->contents))
997 highlighted_p = 0;
999 /* do the drawing. */
1000 if (!just_compute_p)
1002 /* Add the shadow border of the containing menu */
1003 int x = where->x + shadow;
1004 int y = where->y + shadow;
1006 if (horizontal_p)
1008 x += margin;
1009 y += margin;
1012 /* pick the foreground and background GC. */
1013 if (val->enabled)
1014 text_gc = mw->menu.foreground_gc;
1015 else
1016 text_gc = mw->menu.disabled_gc;
1017 deco_gc = mw->menu.foreground_gc;
1018 #ifdef HAVE_XFT
1019 xftfg = val->enabled ? &mw->menu.xft_fg : &mw->menu.xft_disabled_fg;
1020 #endif
1022 if (separator_p)
1024 draw_separator (mw, ws->pixmap, x, y, width, separator);
1026 else
1028 int x_offset = x + h_spacing + shadow;
1029 char* display_string = resource_widget_value (mw, val);
1030 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, True,
1031 False);
1033 /* Deal with centering a menu title. */
1034 if (!horizontal_p && !val->contents && !val->call_data)
1036 int l = string_width (mw, display_string);
1038 if (width > l)
1039 x_offset = (width - l) >> 1;
1041 else if (!horizontal_p && ws->button_width)
1042 x_offset += ws->button_width;
1045 #ifdef HAVE_XFT
1046 if (ws->xft_draw)
1048 int draw_y = y + v_spacing + shadow;
1049 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1050 mw->menu.xft_font,
1051 x_offset, draw_y + font_ascent,
1052 (unsigned char *) display_string,
1053 strlen (display_string));
1055 else
1056 #endif
1057 #ifdef HAVE_X_I18N
1058 if (mw->menu.fontSet)
1059 XmbDrawString (XtDisplay (mw), ws->pixmap, mw->menu.fontSet,
1060 text_gc, x_offset,
1061 y + v_spacing + shadow + font_ascent,
1062 display_string, strlen (display_string));
1063 else
1064 #endif
1065 XDrawString (XtDisplay (mw), ws->pixmap,
1066 text_gc, x_offset,
1067 y + v_spacing + shadow + font_ascent,
1068 display_string, strlen (display_string));
1070 if (!horizontal_p)
1072 if (val->button_type == BUTTON_TYPE_TOGGLE)
1073 draw_toggle (mw, ws->pixmap, x, y + v_spacing + shadow,
1074 val->selected);
1075 else if (val->button_type == BUTTON_TYPE_RADIO)
1076 draw_radio (mw, ws->pixmap, x, y + v_spacing + shadow,
1077 val->selected);
1079 if (val->contents)
1081 int a_w = arrow_width (mw);
1082 draw_arrow (mw, ws->pixmap, deco_gc,
1083 x + width - a_w
1084 - mw->menu.horizontal_spacing
1085 - mw->menu.shadow_thickness,
1086 y + v_spacing + shadow, a_w,
1087 highlighted_p);
1089 else if (val->key)
1091 #ifdef HAVE_XFT
1092 if (ws->xft_draw)
1094 int draw_x = ws->width - ws->max_rest_width
1095 + mw->menu.arrow_spacing;
1096 int draw_y = y + v_spacing + shadow + font_ascent;
1097 XftDrawStringUtf8 (ws->xft_draw, xftfg,
1098 mw->menu.xft_font,
1099 draw_x, draw_y,
1100 (unsigned char *) val->key,
1101 strlen (val->key));
1103 else
1104 #endif
1105 #ifdef HAVE_X_I18N
1106 if (mw->menu.fontSet)
1107 XmbDrawString (XtDisplay (mw), ws->pixmap,
1108 mw->menu.fontSet,
1109 text_gc,
1110 x + label_width + mw->menu.arrow_spacing,
1111 y + v_spacing + shadow + font_ascent,
1112 val->key, strlen (val->key));
1113 else
1114 #endif
1115 XDrawString (XtDisplay (mw), ws->pixmap,
1116 text_gc,
1117 x + label_width + mw->menu.arrow_spacing,
1118 y + v_spacing + shadow + font_ascent,
1119 val->key, strlen (val->key));
1122 else
1124 XDrawRectangle (XtDisplay (mw), ws->pixmap,
1125 mw->menu.background_gc,
1126 x + shadow, y + shadow,
1127 label_width + h_spacing - 1,
1128 font_height + 2 * v_spacing - 1);
1129 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height,
1130 True, False);
1133 if (highlighted_p)
1134 draw_shadow_rectangle (mw, ws->pixmap, x, y, width, height, False,
1135 False);
1139 where->x += width;
1140 where->y += height;
1143 static void
1144 display_menu (XlwMenuWidget mw,
1145 int level,
1146 Boolean just_compute_p,
1147 XPoint *highlighted_pos,
1148 XPoint *hit,
1149 widget_value **hit_return)
1151 widget_value* val;
1152 widget_value* following_item;
1153 window_state* ws;
1154 XPoint where;
1155 int horizontal_p = mw->menu.horizontal && (level == 0);
1156 int highlighted_p;
1157 int no_return = 0;
1158 enum menu_separator separator;
1160 if (level >= mw->menu.old_depth)
1161 abort_gracefully ((Widget) mw);
1163 if (level < mw->menu.old_depth - 1)
1164 following_item = mw->menu.old_stack [level + 1];
1165 else
1166 following_item = NULL;
1168 if (hit)
1169 *hit_return = NULL;
1171 where.x = 0;
1172 where.y = 0;
1174 ws = &mw->menu.windows [level];
1176 if (!just_compute_p)
1177 XFillRectangle (XtDisplay (mw), ws->pixmap, mw->menu.background_gc,
1178 0, 0, ws->width, ws->height);
1180 for (val = mw->menu.old_stack [level]->contents; val; val = val->next)
1182 highlighted_p = val == following_item;
1183 if (highlighted_p && highlighted_pos)
1185 if (horizontal_p)
1186 highlighted_pos->x = where.x;
1187 else
1188 highlighted_pos->y = where.y;
1191 display_menu_item (mw, val, ws, &where, highlighted_p, horizontal_p,
1192 just_compute_p);
1194 if (highlighted_p && highlighted_pos)
1196 if (horizontal_p)
1197 highlighted_pos->y = where.y;
1198 else
1199 highlighted_pos->x = where.x;
1202 if (hit
1203 && !*hit_return
1204 && (horizontal_p ? hit->x < where.x : hit->y < where.y)
1205 && !lw_separator_p (val->name, &separator, 0)
1206 && !no_return)
1208 if (val->enabled)
1209 *hit_return = val;
1210 else
1211 no_return = 1;
1212 if (mw->menu.inside_entry != val)
1214 if (mw->menu.inside_entry)
1215 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1216 (XtPointer) mw->menu.inside_entry);
1217 mw->menu.inside_entry = val;
1218 XtCallCallbackList ((Widget)mw, mw->menu.enter,
1219 (XtPointer) mw->menu.inside_entry);
1223 if (horizontal_p)
1224 where.y = 0;
1225 else
1226 where.x = 0;
1229 if (!just_compute_p)
1231 draw_shadow_rectangle (mw, ws->pixmap, 0, 0, ws->width, ws->height,
1232 False, False);
1233 XCopyArea (XtDisplay (mw), ws->pixmap, ws->window,
1234 mw->menu.foreground_gc, 0, 0, ws->width, ws->height, 0, 0);
1238 \f/* Motion code */
1239 static void
1240 set_new_state (XlwMenuWidget mw, widget_value *val, int level)
1242 int i;
1244 mw->menu.new_depth = 0;
1245 for (i = 0; i < level; i++)
1246 push_new_stack (mw, mw->menu.old_stack [i]);
1247 push_new_stack (mw, val);
1250 static void
1251 expose_cb (Widget widget,
1252 XtPointer closure,
1253 XEvent* event,
1254 Boolean* continue_to_dispatch)
1256 XlwMenuWidget mw = (XlwMenuWidget) closure;
1257 int i;
1259 *continue_to_dispatch = False;
1260 for (i = 0; i < mw->menu.windows_length; ++i)
1261 if (mw->menu.windows [i].w == widget) break;
1262 if (i < mw->menu.windows_length && i < mw->menu.old_depth)
1263 display_menu (mw, i, False, NULL, NULL, NULL);
1266 static void
1267 set_window_type (Widget w, XlwMenuWidget mw)
1269 int popup_menu_p = mw->menu.top_depth == 1;
1270 Atom type = XInternAtom (XtDisplay (w),
1271 popup_menu_p
1272 ? "_NET_WM_WINDOW_TYPE_POPUP_MENU"
1273 : "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
1274 False);
1276 XChangeProperty (XtDisplay (w), XtWindow (w),
1277 XInternAtom (XtDisplay (w), "_NET_WM_WINDOW_TYPE", False),
1278 XA_ATOM, 32, PropModeReplace,
1279 (unsigned char *)&type, 1);
1283 static void
1284 make_windows_if_needed (XlwMenuWidget mw, int n)
1286 int i;
1287 int start_at;
1288 window_state* windows;
1290 if (mw->menu.windows_length >= n)
1291 return;
1293 if (!mw->menu.windows)
1295 mw->menu.windows =
1296 (window_state*)XtMalloc (n * sizeof (window_state));
1297 start_at = 0;
1299 else
1301 mw->menu.windows =
1302 (window_state*)XtRealloc ((char*)mw->menu.windows,
1303 n * sizeof (window_state));
1304 start_at = mw->menu.windows_length;
1306 mw->menu.windows_length = n;
1308 windows = mw->menu.windows;
1310 for (i = start_at; i < n; i++)
1312 Arg av[10];
1313 int ac = 0;
1314 windows [i].x = 0;
1315 windows [i].y = 0;
1316 windows [i].width = 1;
1317 windows [i].height = 1;
1318 windows [i].max_rest_width = 0;
1319 XtSetArg (av[ac], XtNwidth, 1); ++ac;
1320 XtSetArg (av[ac], XtNheight, 1); ++ac;
1321 XtSetArg (av[ac], XtNsaveUnder, True); ++ac;
1322 XtSetArg (av[ac], XtNbackground, mw->core.background_pixel); ++ac;
1323 XtSetArg (av[ac], XtNborderColor, mw->core.border_pixel); ++ac;
1324 XtSetArg (av[ac], XtNborderWidth, mw->core.border_width); ++ac;
1325 XtSetArg (av[ac], XtNcursor, mw->menu.cursor_shape); ++ac;
1326 windows [i].w =
1327 XtCreatePopupShell ("sub", overrideShellWidgetClass,
1328 (Widget) mw, av, ac);
1329 XtRealizeWidget (windows [i].w);
1330 XtAddEventHandler (windows [i].w, ExposureMask, False, expose_cb, mw);
1331 windows [i].window = XtWindow (windows [i].w);
1332 windows [i].pixmap = None;
1333 #ifdef HAVE_XFT
1334 windows [i].xft_draw = 0;
1335 #endif
1336 set_window_type (windows [i].w, mw);
1338 XFlush (XtDisplay (mw));
1341 /* Value is non-zero if WINDOW is part of menu bar widget W. */
1344 xlwmenu_window_p (Widget w, Window window)
1346 XlwMenuWidget mw = (XlwMenuWidget) w;
1347 int i;
1349 for (i = 0; i < mw->menu.windows_length; ++i)
1350 if (window == mw->menu.windows[i].window)
1351 break;
1353 return i < mw->menu.windows_length;
1356 /* Make the window fit in the screen */
1357 static void
1358 fit_to_screen (XlwMenuWidget mw,
1359 window_state *ws,
1360 window_state *previous_ws,
1361 Boolean horizontal_p)
1363 unsigned int screen_width = WidthOfScreen (XtScreen (mw));
1364 unsigned int screen_height = HeightOfScreen (XtScreen (mw));
1365 /* 1 if we are unable to avoid an overlap between
1366 this menu and the parent menu in the X dimension. */
1367 int horizontal_overlap = 0;
1369 if (ws->x < 0)
1370 ws->x = 0;
1371 else if (ws->x + ws->width > screen_width)
1373 if (!horizontal_p)
1374 /* The addition of shadow-thickness for a sub-menu's position is
1375 to reflect a similar adjustment when the menu is displayed to
1376 the right of the invoking menu-item; it makes the sub-menu
1377 look more `attached' to the menu-item. */
1378 ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness;
1379 else
1380 ws->x = screen_width - ws->width;
1381 if (ws->x < 0)
1383 ws->x = 0;
1384 horizontal_overlap = 1;
1387 /* If we overlap in X, try to avoid overlap in Y. */
1388 if (horizontal_overlap
1389 && ws->y < previous_ws->y + previous_ws->height
1390 && previous_ws->y < ws->y + ws->height)
1392 /* Put this menu right below or right above PREVIOUS_WS
1393 if there's room. */
1394 if (previous_ws->y + previous_ws->height + ws->height < screen_height)
1395 ws->y = previous_ws->y + previous_ws->height;
1396 else if (previous_ws->y - ws->height > 0)
1397 ws->y = previous_ws->y - ws->height;
1400 if (ws->y < 0)
1401 ws->y = 0;
1402 else if (ws->y + ws->height > screen_height)
1404 if (horizontal_p)
1405 ws->y = previous_ws->y - ws->height;
1406 else
1407 ws->y = screen_height - ws->height;
1408 if (ws->y < 0)
1409 ws->y = 0;
1413 static void
1414 create_pixmap_for_menu (window_state* ws, XlwMenuWidget mw)
1416 if (ws->pixmap != None)
1418 XFreePixmap (XtDisplay (ws->w), ws->pixmap);
1419 ws->pixmap = None;
1421 ws->pixmap = XCreatePixmap (XtDisplay (ws->w), ws->window,
1422 ws->width, ws->height,
1423 DefaultDepthOfScreen (XtScreen (ws->w)));
1424 #ifdef HAVE_XFT
1425 if (ws->xft_draw)
1426 XftDrawDestroy (ws->xft_draw);
1427 if (mw->menu.xft_font)
1429 int screen = XScreenNumberOfScreen (mw->core.screen);
1430 ws->xft_draw = XftDrawCreate (XtDisplay (ws->w),
1431 ws->pixmap,
1432 DefaultVisual (XtDisplay (ws->w), screen),
1433 mw->core.colormap);
1435 else
1436 ws->xft_draw = 0;
1437 #endif
1440 /* Updates old_stack from new_stack and redisplays. */
1441 static void
1442 remap_menubar (XlwMenuWidget mw)
1444 int i;
1445 int last_same;
1446 XPoint selection_position;
1447 int old_depth = mw->menu.old_depth;
1448 int new_depth = mw->menu.new_depth;
1449 widget_value** old_stack;
1450 widget_value** new_stack;
1451 window_state* windows;
1452 widget_value* old_selection;
1453 widget_value* new_selection;
1455 /* Check that enough windows and old_stack are ready. */
1456 make_windows_if_needed (mw, new_depth);
1457 make_old_stack_space (mw, new_depth);
1458 windows = mw->menu.windows;
1459 old_stack = mw->menu.old_stack;
1460 new_stack = mw->menu.new_stack;
1462 /* compute the last identical different entry */
1463 for (i = 1; i < old_depth && i < new_depth; i++)
1464 if (old_stack [i] != new_stack [i])
1465 break;
1466 last_same = i - 1;
1468 /* Memorize the previously selected item to be able to refresh it */
1469 old_selection = last_same + 1 < old_depth ? old_stack [last_same + 1] : NULL;
1470 if (old_selection && !old_selection->enabled)
1471 old_selection = NULL;
1472 new_selection = last_same + 1 < new_depth ? new_stack [last_same + 1] : NULL;
1473 if (new_selection && !new_selection->enabled)
1474 new_selection = NULL;
1476 /* Call callback when the highlighted item changes. */
1477 if (old_selection || new_selection)
1478 XtCallCallbackList ((Widget)mw, mw->menu.highlight,
1479 (XtPointer) new_selection);
1481 /* updates old_state from new_state. It has to be done now because
1482 display_menu (called below) uses the old_stack to know what to display. */
1483 for (i = last_same + 1; i < new_depth; i++)
1485 XtPopdown (mw->menu.windows [i].w);
1486 old_stack [i] = new_stack [i];
1488 mw->menu.old_depth = new_depth;
1490 /* refresh the last selection */
1491 selection_position.x = 0;
1492 selection_position.y = 0;
1493 display_menu (mw, last_same, new_selection == old_selection,
1494 &selection_position, NULL, NULL);
1496 /* Now place the new menus. */
1497 for (i = last_same + 1; i < new_depth && new_stack[i]->contents; i++)
1499 window_state *previous_ws = &windows[i - 1];
1500 window_state *ws = &windows[i];
1502 ws->x = (previous_ws->x + selection_position.x
1503 + mw->menu.shadow_thickness);
1504 if (mw->menu.horizontal && i == 1)
1505 ws->x += mw->menu.margin;
1507 #if 0
1508 if (!mw->menu.horizontal || i > 1)
1509 ws->x += mw->menu.shadow_thickness;
1510 #endif
1512 ws->y = (previous_ws->y + selection_position.y
1513 + mw->menu.shadow_thickness);
1514 if (mw->menu.horizontal && i == 1)
1515 ws->y += mw->menu.margin;
1517 size_menu (mw, i);
1519 fit_to_screen (mw, ws, previous_ws, mw->menu.horizontal && i == 1);
1521 create_pixmap_for_menu (ws, mw);
1522 XtMoveWidget (ws->w, ws->x, ws->y);
1523 XtPopup (ws->w, XtGrabNone);
1524 XtResizeWidget (ws->w, ws->width, ws->height,
1525 mw->core.border_width);
1526 XtResizeWindow (ws->w);
1527 display_menu (mw, i, False, &selection_position, NULL, NULL);
1530 /* unmap the menus that popped down */
1531 for (i = new_depth - 1; i < old_depth; i++)
1532 if (i >= new_depth || (i > 0 && !new_stack[i]->contents))
1533 XtPopdown (windows[i].w);
1536 static Boolean
1537 motion_event_is_in_menu (XlwMenuWidget mw,
1538 XMotionEvent *ev,
1539 int level,
1540 XPoint *relative_pos)
1542 window_state* ws = &mw->menu.windows [level];
1543 int shadow = level == 0 ? 0 : mw->menu.shadow_thickness;
1544 int x = ws->x + shadow;
1545 int y = ws->y + shadow;
1546 relative_pos->x = ev->x_root - x;
1547 relative_pos->y = ev->y_root - y;
1548 return (x - shadow < ev->x_root && ev->x_root < x + ws->width
1549 && y - shadow < ev->y_root && ev->y_root < y + ws->height);
1552 static Boolean
1553 map_event_to_widget_value (XlwMenuWidget mw,
1554 XMotionEvent *ev,
1555 widget_value **val,
1556 int *level)
1558 int i;
1559 XPoint relative_pos;
1560 window_state* ws;
1561 int inside = 0;
1563 *val = NULL;
1565 /* Find the window */
1566 for (i = mw->menu.old_depth - 1; i >= 0; i--)
1568 ws = &mw->menu.windows [i];
1569 if (ws && motion_event_is_in_menu (mw, ev, i, &relative_pos))
1571 inside = 1;
1572 display_menu (mw, i, True, NULL, &relative_pos, val);
1574 if (*val)
1576 *level = i + 1;
1577 return True;
1582 if (!inside)
1584 if (mw->menu.inside_entry != NULL)
1585 XtCallCallbackList ((Widget)mw, mw->menu.leave,
1586 (XtPointer) mw->menu.inside_entry);
1587 mw->menu.inside_entry = NULL;
1590 return False;
1593 \f/* Procedures */
1594 static void
1595 make_drawing_gcs (XlwMenuWidget mw)
1597 XGCValues xgcv;
1598 float scale;
1599 XtGCMask mask = GCForeground | GCBackground;
1601 #ifdef HAVE_X_I18N
1602 if (!mw->menu.fontSet && mw->menu.font)
1604 xgcv.font = mw->menu.font->fid;
1605 mask |= GCFont;
1607 #else
1608 if (mw->menu.font)
1610 xgcv.font = mw->menu.font->fid;
1611 mask |= GCFont;
1613 #endif
1614 xgcv.foreground = mw->menu.foreground;
1615 xgcv.background = mw->core.background_pixel;
1616 mw->menu.foreground_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1618 xgcv.foreground = mw->menu.button_foreground;
1619 mw->menu.button_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1621 xgcv.background = mw->core.background_pixel;
1623 #define BRIGHTNESS(color) (((color) & 0xff) + (((color) >> 8) & 0xff) + (((color) >> 16) & 0xff))
1625 /* Allocate color for disabled menu-items. */
1626 mw->menu.disabled_foreground = mw->menu.foreground;
1627 if (BRIGHTNESS(mw->menu.foreground) < BRIGHTNESS(mw->core.background_pixel))
1628 scale = 2.3;
1629 else
1630 scale = 0.55;
1632 x_alloc_lighter_color_for_widget ((Widget) mw, XtDisplay ((Widget) mw),
1633 mw->core.colormap,
1634 &mw->menu.disabled_foreground,
1635 scale,
1636 0x8000);
1638 if (mw->menu.foreground == mw->menu.disabled_foreground
1639 || mw->core.background_pixel == mw->menu.disabled_foreground)
1641 /* Too few colors, use stipple. */
1642 xgcv.foreground = mw->menu.foreground;
1643 xgcv.fill_style = FillStippled;
1644 xgcv.stipple = mw->menu.gray_pixmap;
1645 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask
1646 | GCFillStyle | GCStipple, &xgcv);
1648 else
1650 /* Many colors available, use disabled pixel. */
1651 xgcv.foreground = mw->menu.disabled_foreground;
1652 mw->menu.disabled_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1655 xgcv.foreground = mw->menu.button_foreground;
1656 xgcv.background = mw->core.background_pixel;
1657 xgcv.fill_style = FillStippled;
1658 xgcv.stipple = mw->menu.gray_pixmap;
1659 mw->menu.inactive_button_gc = XtGetGC ((Widget)mw, mask
1660 | GCFillStyle | GCStipple, &xgcv);
1662 xgcv.foreground = mw->core.background_pixel;
1663 xgcv.background = mw->menu.foreground;
1664 mw->menu.background_gc = XtGetGC ((Widget)mw, mask, &xgcv);
1667 static void
1668 release_drawing_gcs (XlwMenuWidget mw)
1670 XtReleaseGC ((Widget) mw, mw->menu.foreground_gc);
1671 XtReleaseGC ((Widget) mw, mw->menu.button_gc);
1672 XtReleaseGC ((Widget) mw, mw->menu.disabled_gc);
1673 XtReleaseGC ((Widget) mw, mw->menu.inactive_button_gc);
1674 XtReleaseGC ((Widget) mw, mw->menu.background_gc);
1675 /* let's get some segvs if we try to use these... */
1676 mw->menu.foreground_gc = (GC) -1;
1677 mw->menu.button_gc = (GC) -1;
1678 mw->menu.disabled_gc = (GC) -1;
1679 mw->menu.inactive_button_gc = (GC) -1;
1680 mw->menu.background_gc = (GC) -1;
1683 #ifndef emacs
1684 #define MINL(x,y) ((((unsigned long) (x)) < ((unsigned long) (y))) \
1685 ? ((unsigned long) (x)) : ((unsigned long) (y)))
1686 #endif
1688 static void
1689 make_shadow_gcs (XlwMenuWidget mw)
1691 XGCValues xgcv;
1692 unsigned long pm = 0;
1693 Display *dpy = XtDisplay ((Widget) mw);
1694 Screen *screen = XtScreen ((Widget) mw);
1695 Colormap cmap = mw->core.colormap;
1696 XColor topc, botc;
1697 int top_frobbed = 0, bottom_frobbed = 0;
1699 mw->menu.free_top_shadow_color_p = 0;
1700 mw->menu.free_bottom_shadow_color_p = 0;
1702 if (mw->menu.top_shadow_color == -1)
1703 mw->menu.top_shadow_color = mw->core.background_pixel;
1704 else
1705 mw->menu.top_shadow_color = mw->menu.top_shadow_color;
1707 if (mw->menu.bottom_shadow_color == -1)
1708 mw->menu.bottom_shadow_color = mw->menu.foreground;
1709 else
1710 mw->menu.bottom_shadow_color = mw->menu.bottom_shadow_color;
1712 if (mw->menu.top_shadow_color == mw->core.background_pixel ||
1713 mw->menu.top_shadow_color == mw->menu.foreground)
1715 topc.pixel = mw->core.background_pixel;
1716 #ifdef emacs
1717 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1718 &topc.pixel,
1719 1.2, 0x8000))
1720 #else
1721 XQueryColor (dpy, cmap, &topc);
1722 /* don't overflow/wrap! */
1723 topc.red = MINL (65535, topc.red * 1.2);
1724 topc.green = MINL (65535, topc.green * 1.2);
1725 topc.blue = MINL (65535, topc.blue * 1.2);
1726 if (XAllocColor (dpy, cmap, &topc))
1727 #endif
1729 mw->menu.top_shadow_color = topc.pixel;
1730 mw->menu.free_top_shadow_color_p = 1;
1731 top_frobbed = 1;
1734 if (mw->menu.bottom_shadow_color == mw->menu.foreground ||
1735 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1737 botc.pixel = mw->core.background_pixel;
1738 #ifdef emacs
1739 if (x_alloc_lighter_color_for_widget ((Widget) mw, dpy, cmap,
1740 &botc.pixel,
1741 0.6, 0x4000))
1742 #else
1743 XQueryColor (dpy, cmap, &botc);
1744 botc.red *= 0.6;
1745 botc.green *= 0.6;
1746 botc.blue *= 0.6;
1747 if (XAllocColor (dpy, cmap, &botc))
1748 #endif
1750 mw->menu.bottom_shadow_color = botc.pixel;
1751 mw->menu.free_bottom_shadow_color_p = 1;
1752 bottom_frobbed = 1;
1756 if (top_frobbed && bottom_frobbed)
1758 if (topc.pixel == botc.pixel)
1760 if (botc.pixel == mw->menu.foreground)
1762 if (mw->menu.free_top_shadow_color_p)
1764 x_free_dpy_colors (dpy, screen, cmap,
1765 &mw->menu.top_shadow_color, 1);
1766 mw->menu.free_top_shadow_color_p = 0;
1768 mw->menu.top_shadow_color = mw->core.background_pixel;
1770 else
1772 if (mw->menu.free_bottom_shadow_color_p)
1774 x_free_dpy_colors (dpy, screen, cmap,
1775 &mw->menu.bottom_shadow_color, 1);
1776 mw->menu.free_bottom_shadow_color_p = 0;
1778 mw->menu.bottom_shadow_color = mw->menu.foreground;
1783 if (!mw->menu.top_shadow_pixmap &&
1784 mw->menu.top_shadow_color == mw->core.background_pixel)
1786 mw->menu.top_shadow_pixmap = mw->menu.gray_pixmap;
1787 if (mw->menu.free_top_shadow_color_p)
1789 x_free_dpy_colors (dpy, screen, cmap, &mw->menu.top_shadow_color, 1);
1790 mw->menu.free_top_shadow_color_p = 0;
1792 mw->menu.top_shadow_color = mw->menu.foreground;
1794 if (!mw->menu.bottom_shadow_pixmap &&
1795 mw->menu.bottom_shadow_color == mw->core.background_pixel)
1797 mw->menu.bottom_shadow_pixmap = mw->menu.gray_pixmap;
1798 if (mw->menu.free_bottom_shadow_color_p)
1800 x_free_dpy_colors (dpy, screen, cmap,
1801 &mw->menu.bottom_shadow_color, 1);
1802 mw->menu.free_bottom_shadow_color_p = 0;
1804 mw->menu.bottom_shadow_color = mw->menu.foreground;
1807 xgcv.fill_style = FillStippled;
1808 xgcv.foreground = mw->menu.top_shadow_color;
1809 xgcv.stipple = mw->menu.top_shadow_pixmap;
1810 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1811 mw->menu.shadow_top_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1813 xgcv.foreground = mw->menu.bottom_shadow_color;
1814 xgcv.stipple = mw->menu.bottom_shadow_pixmap;
1815 pm = (xgcv.stipple ? GCStipple|GCFillStyle : 0);
1816 mw->menu.shadow_bottom_gc = XtGetGC ((Widget)mw, GCForeground | pm, &xgcv);
1820 static void
1821 release_shadow_gcs (XlwMenuWidget mw)
1823 Display *dpy = XtDisplay ((Widget) mw);
1824 Screen *screen = XtScreen ((Widget) mw);
1825 Colormap cmap = mw->core.colormap;
1826 Pixel px[2];
1827 int i = 0;
1829 if (mw->menu.free_top_shadow_color_p)
1830 px[i++] = mw->menu.top_shadow_color;
1831 if (mw->menu.free_bottom_shadow_color_p)
1832 px[i++] = mw->menu.bottom_shadow_color;
1833 if (i > 0)
1834 x_free_dpy_colors (dpy, screen, cmap, px, i);
1836 XtReleaseGC ((Widget) mw, mw->menu.shadow_top_gc);
1837 XtReleaseGC ((Widget) mw, mw->menu.shadow_bottom_gc);
1840 #ifdef HAVE_XFT
1841 static XftFont *
1842 getDefaultXftFont (XlwMenuWidget mw)
1844 int screen = XScreenNumberOfScreen (mw->core.screen);
1845 return XftFontOpenName (XtDisplay (mw), screen, DEFAULT_FONTNAME);
1848 static int
1849 openXftFont (XlwMenuWidget mw)
1851 char *fname = mw->menu.fontName;
1853 mw->menu.xft_font = 0;
1854 mw->menu.default_face = fname && strcmp (fname, DEFAULT_FONTNAME) == 0;
1856 if (fname && strcmp (fname, "none") != 0)
1858 int screen = XScreenNumberOfScreen (mw->core.screen);
1859 int len = strlen (fname), i = len-1;
1860 /* Try to convert Gtk-syntax (Sans 9) to Xft syntax Sans-9. */
1861 while (i > 0 && isdigit (fname[i]))
1862 --i;
1863 if (fname[i] == ' ')
1865 fname = xstrdup (mw->menu.fontName);
1866 fname[i] = '-';
1869 mw->menu.font = XLoadQueryFont (XtDisplay (mw), fname);
1870 if (!mw->menu.font)
1872 mw->menu.xft_font = XftFontOpenName (XtDisplay (mw), screen, fname);
1873 if (!mw->menu.xft_font)
1875 fprintf (stderr, "Can't find font '%s'\n", fname);
1876 mw->menu.xft_font = getDefaultXftFont (mw);
1881 if (fname != mw->menu.fontName) xfree (fname);
1883 return mw->menu.xft_font != 0;
1885 #endif
1887 static void
1888 XlwMenuInitialize (Widget request, Widget w, ArgList args, Cardinal *num_args)
1890 /* Get the GCs and the widget size */
1891 XlwMenuWidget mw = (XlwMenuWidget) w;
1892 Window window = RootWindowOfScreen (DefaultScreenOfDisplay (XtDisplay (mw)));
1893 Display* display = XtDisplay (mw);
1895 #if 0
1896 widget_value *tem = (widget_value *) XtMalloc (sizeof (widget_value));
1898 /* _XtCreate is freeing the object that was passed to us,
1899 so make a copy that we will actually keep. */
1900 memcpy (tem, mw->menu.contents, sizeof (widget_value));
1901 mw->menu.contents = tem;
1902 #endif
1904 /* mw->menu.cursor = XCreateFontCursor (display, mw->menu.cursor_shape); */
1905 mw->menu.cursor = mw->menu.cursor_shape;
1907 mw->menu.gray_pixmap
1908 = XCreatePixmapFromBitmapData (display, window, gray_bits,
1909 gray_width, gray_height,
1910 (unsigned long)1, (unsigned long)0, 1);
1912 #ifdef HAVE_XFT
1913 if (openXftFont (mw))
1915 else
1916 #endif
1918 mw->menu.font = XLoadQueryFont (display, mw->menu.fontName);
1919 if (!mw->menu.font)
1921 mw->menu.font = XLoadQueryFont (display, "fixed");
1922 if (!mw->menu.font)
1924 fprintf (stderr, "Menu font fixed not found, can't continue.\n");
1925 abort ();
1930 #ifdef HAVE_X_I18N
1931 if (mw->menu.fontSet)
1932 mw->menu.font_extents = XExtentsOfFontSet (mw->menu.fontSet);
1933 #endif
1935 make_drawing_gcs (mw);
1936 make_shadow_gcs (mw);
1938 mw->menu.popped_up = False;
1940 mw->menu.old_depth = 1;
1941 mw->menu.old_stack = (widget_value**)XtMalloc (sizeof (widget_value*));
1942 mw->menu.old_stack_length = 1;
1943 mw->menu.old_stack [0] = mw->menu.contents;
1945 mw->menu.new_depth = 0;
1946 mw->menu.new_stack = 0;
1947 mw->menu.new_stack_length = 0;
1948 push_new_stack (mw, mw->menu.contents);
1950 mw->menu.windows = (window_state*)XtMalloc (sizeof (window_state));
1951 mw->menu.windows_length = 1;
1952 mw->menu.windows [0].x = 0;
1953 mw->menu.windows [0].y = 0;
1954 mw->menu.windows [0].width = 0;
1955 mw->menu.windows [0].height = 0;
1956 mw->menu.windows [0].max_rest_width = 0;
1957 mw->menu.windows [0].pixmap = None;
1958 #ifdef HAVE_XFT
1959 mw->menu.windows [0].xft_draw = 0;
1960 #endif
1961 size_menu (mw, 0);
1963 mw->core.width = mw->menu.windows [0].width;
1964 mw->core.height = mw->menu.windows [0].height;
1967 static void
1968 XlwMenuClassInitialize (void)
1972 static void
1973 XlwMenuRealize (Widget w, Mask *valueMask, XSetWindowAttributes *attributes)
1975 XlwMenuWidget mw = (XlwMenuWidget)w;
1976 XSetWindowAttributes xswa;
1977 int mask;
1979 (*xlwMenuWidgetClass->core_class.superclass->core_class.realize)
1980 (w, valueMask, attributes);
1982 xswa.save_under = True;
1983 xswa.cursor = mw->menu.cursor_shape;
1984 mask = CWSaveUnder | CWCursor;
1985 /* I sometimes get random BadCursor errors while creating the first
1986 frame on a display. I can not find their reason, but they are
1987 annoying so for now let's ignore any errors here. -- lorentey */
1988 #ifdef emacs
1989 x_catch_errors (XtDisplay (w));
1990 #endif
1991 XChangeWindowAttributes (XtDisplay (w), XtWindow (w), mask, &xswa);
1992 #ifdef emacs
1993 x_uncatch_errors ();
1994 #endif
1996 mw->menu.windows [0].w = w;
1997 mw->menu.windows [0].window = XtWindow (w);
1998 mw->menu.windows [0].x = w->core.x;
1999 mw->menu.windows [0].y = w->core.y;
2000 mw->menu.windows [0].width = w->core.width;
2001 mw->menu.windows [0].height = w->core.height;
2003 set_window_type (mw->menu.windows [0].w, mw);
2004 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2006 #ifdef HAVE_XFT
2007 if (mw->menu.xft_font)
2009 XColor colors[3];
2010 colors[0].pixel = mw->menu.xft_fg.pixel = mw->menu.foreground;
2011 colors[1].pixel = mw->menu.xft_bg.pixel = mw->core.background_pixel;
2012 colors[2].pixel = mw->menu.xft_disabled_fg.pixel
2013 = mw->menu.disabled_foreground;
2014 XQueryColors (XtDisplay (mw), mw->core.colormap, colors, 3);
2015 mw->menu.xft_fg.color.alpha = 0xFFFF;
2016 mw->menu.xft_fg.color.red = colors[0].red;
2017 mw->menu.xft_fg.color.green = colors[0].green;
2018 mw->menu.xft_fg.color.blue = colors[0].blue;
2019 mw->menu.xft_bg.color.alpha = 0xFFFF;
2020 mw->menu.xft_bg.color.red = colors[1].red;
2021 mw->menu.xft_bg.color.green = colors[1].green;
2022 mw->menu.xft_bg.color.blue = colors[1].blue;
2023 mw->menu.xft_disabled_fg.color.alpha = 0xFFFF;
2024 mw->menu.xft_disabled_fg.color.red = colors[2].red;
2025 mw->menu.xft_disabled_fg.color.green = colors[2].green;
2026 mw->menu.xft_disabled_fg.color.blue = colors[2].blue;
2028 #endif
2031 /* Only the toplevel menubar/popup is a widget so it's the only one that
2032 receives expose events through Xt. So we repaint all the other panes
2033 when receiving an Expose event. */
2034 static void
2035 XlwMenuRedisplay (Widget w, XEvent *ev, Region region)
2037 XlwMenuWidget mw = (XlwMenuWidget)w;
2039 /* If we have a depth beyond 1, it's because a submenu was displayed.
2040 If the submenu has been destroyed, set the depth back to 1. */
2041 if (submenu_destroyed)
2043 mw->menu.old_depth = 1;
2044 submenu_destroyed = 0;
2047 display_menu (mw, 0, False, NULL, NULL, NULL);
2051 /* Part of a hack to make the menu redisplay when a tooltip frame
2052 over a menu item is unmapped. */
2054 void
2055 xlwmenu_redisplay (Widget w)
2057 XlwMenuRedisplay (w, NULL, None);
2060 static void
2061 XlwMenuDestroy (Widget w)
2063 int i;
2064 XlwMenuWidget mw = (XlwMenuWidget) w;
2066 if (pointer_grabbed)
2067 ungrab_all ((Widget)w, CurrentTime);
2068 pointer_grabbed = 0;
2070 submenu_destroyed = 1;
2072 release_drawing_gcs (mw);
2073 release_shadow_gcs (mw);
2075 /* this doesn't come from the resource db but is created explicitly
2076 so we must free it ourselves. */
2077 XFreePixmap (XtDisplay (mw), mw->menu.gray_pixmap);
2078 mw->menu.gray_pixmap = (Pixmap) -1;
2080 #if 0
2081 /* Do free mw->menu.contents because nowadays we copy it
2082 during initialization. */
2083 XtFree (mw->menu.contents);
2084 #endif
2086 /* Don't free mw->menu.contents because that comes from our creator.
2087 The `*_stack' elements are just pointers into `contents' so leave
2088 that alone too. But free the stacks themselves. */
2089 if (mw->menu.old_stack) XtFree ((char *) mw->menu.old_stack);
2090 if (mw->menu.new_stack) XtFree ((char *) mw->menu.new_stack);
2092 /* Remember, you can't free anything that came from the resource
2093 database. This includes:
2094 mw->menu.cursor
2095 mw->menu.top_shadow_pixmap
2096 mw->menu.bottom_shadow_pixmap
2097 mw->menu.font
2098 Also the color cells of top_shadow_color, bottom_shadow_color,
2099 foreground, and button_foreground will never be freed until this
2100 client exits. Nice, eh?
2103 #ifdef HAVE_XFT
2104 if (mw->menu.windows [0].xft_draw)
2105 XftDrawDestroy (mw->menu.windows [0].xft_draw);
2106 if (mw->menu.xft_font)
2107 XftFontClose (XtDisplay (mw), mw->menu.xft_font);
2108 #endif
2110 if (mw->menu.windows [0].pixmap != None)
2111 XFreePixmap (XtDisplay (mw), mw->menu.windows [0].pixmap);
2112 /* start from 1 because the one in slot 0 is w->core.window */
2113 for (i = 1; i < mw->menu.windows_length; i++)
2115 if (mw->menu.windows [i].pixmap != None)
2116 XFreePixmap (XtDisplay (mw), mw->menu.windows [i].pixmap);
2117 #ifdef HAVE_XFT
2118 if (mw->menu.windows [i].xft_draw)
2119 XftDrawDestroy (mw->menu.windows [i].xft_draw);
2120 #endif
2123 if (mw->menu.windows)
2124 XtFree ((char *) mw->menu.windows);
2127 #ifdef HAVE_XFT
2128 static int
2129 fontname_changed (XlwMenuWidget newmw,
2130 XlwMenuWidget oldmw)
2132 /* This will force a new XftFont even if the same string is set.
2133 This is good, as rendering parameters may have changed and
2134 we just want to do a redisplay. */
2135 return newmw->menu.fontName != oldmw->menu.fontName;
2137 #endif
2139 static Boolean
2140 XlwMenuSetValues (Widget current, Widget request, Widget new,
2141 ArgList args, Cardinal *num_args)
2143 XlwMenuWidget oldmw = (XlwMenuWidget)current;
2144 XlwMenuWidget newmw = (XlwMenuWidget)new;
2145 Boolean do_redisplay = False;
2147 if (newmw->menu.contents
2148 && newmw->menu.contents->contents
2149 && newmw->menu.contents->contents->change >= VISIBLE_CHANGE)
2150 do_redisplay = True;
2151 /* Do redisplay if the contents are entirely eliminated. */
2152 if (newmw->menu.contents
2153 && newmw->menu.contents->contents == 0
2154 && newmw->menu.contents->change >= VISIBLE_CHANGE)
2155 do_redisplay = True;
2157 if (newmw->core.background_pixel != oldmw->core.background_pixel
2158 || newmw->menu.foreground != oldmw->menu.foreground
2159 #ifdef HAVE_XFT
2160 || fontname_changed (newmw, oldmw)
2161 #endif
2162 #ifdef HAVE_X_I18N
2163 || newmw->menu.fontSet != oldmw->menu.fontSet
2164 || (newmw->menu.fontSet == NULL && newmw->menu.font != oldmw->menu.font)
2165 #else
2166 || newmw->menu.font != oldmw->menu.font
2167 #endif
2170 int i;
2171 release_drawing_gcs (newmw);
2172 make_drawing_gcs (newmw);
2174 release_shadow_gcs (newmw);
2175 /* Cause the shadow colors to be recalculated. */
2176 newmw->menu.top_shadow_color = -1;
2177 newmw->menu.bottom_shadow_color = -1;
2178 make_shadow_gcs (newmw);
2180 do_redisplay = True;
2182 if (XtIsRealized (current))
2183 /* If the menu is currently displayed, change the display. */
2184 for (i = 0; i < oldmw->menu.windows_length; i++)
2186 XSetWindowBackground (XtDisplay (oldmw),
2187 oldmw->menu.windows [i].window,
2188 newmw->core.background_pixel);
2189 /* clear windows and generate expose events */
2190 XClearArea (XtDisplay (oldmw), oldmw->menu.windows[i].window,
2191 0, 0, 0, 0, True);
2195 #ifdef HAVE_XFT
2196 if (fontname_changed (newmw, oldmw))
2198 int i;
2199 int screen = XScreenNumberOfScreen (newmw->core.screen);
2200 if (newmw->menu.xft_font)
2201 XftFontClose (XtDisplay (newmw), newmw->menu.xft_font);
2202 openXftFont (newmw);
2203 for (i = 0; i < newmw->menu.windows_length; i++)
2205 if (newmw->menu.windows [i].xft_draw)
2206 XftDrawDestroy (newmw->menu.windows [i].xft_draw);
2207 newmw->menu.windows [i].xft_draw = 0;
2209 if (newmw->menu.xft_font)
2210 for (i = 0; i < newmw->menu.windows_length; i++)
2211 newmw->menu.windows [i].xft_draw
2212 = XftDrawCreate (XtDisplay (newmw),
2213 newmw->menu.windows [i].window,
2214 DefaultVisual (XtDisplay (newmw), screen),
2215 newmw->core.colormap);
2217 #endif
2218 #ifdef HAVE_X_I18N
2219 if (newmw->menu.fontSet != oldmw->menu.fontSet && newmw->menu.fontSet != NULL)
2221 do_redisplay = True;
2222 newmw->menu.font_extents = XExtentsOfFontSet (newmw->menu.fontSet);
2224 #endif
2226 return do_redisplay;
2229 static void
2230 XlwMenuResize (Widget w)
2232 XlwMenuWidget mw = (XlwMenuWidget)w;
2234 if (mw->menu.popped_up)
2236 /* Don't allow the popup menu to resize itself. */
2237 mw->core.width = mw->menu.windows [0].width;
2238 mw->core.height = mw->menu.windows [0].height;
2239 mw->core.parent->core.width = mw->core.width;
2240 mw->core.parent->core.height = mw->core.height;
2242 else
2244 mw->menu.windows [0].width = mw->core.width;
2245 mw->menu.windows [0].height = mw->core.height;
2246 create_pixmap_for_menu (&mw->menu.windows [0], mw);
2250 \f/* Action procedures */
2251 static void
2252 handle_single_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2254 widget_value* val;
2255 int level;
2257 if (!map_event_to_widget_value (mw, ev, &val, &level))
2258 pop_new_stack_if_no_contents (mw);
2259 else
2260 set_new_state (mw, val, level);
2261 remap_menubar (mw);
2263 /* Sync with the display. Makes it feel better on X terms. */
2264 XSync (XtDisplay (mw), False);
2267 static void
2268 handle_motion_event (XlwMenuWidget mw, XMotionEvent *ev)
2270 int x = ev->x_root;
2271 int y = ev->y_root;
2272 int state = ev->state;
2273 XMotionEvent oldev = *ev;
2275 /* allow motion events to be generated again */
2276 if (ev->is_hint
2277 && XQueryPointer (XtDisplay (mw), ev->window,
2278 &ev->root, &ev->subwindow,
2279 &ev->x_root, &ev->y_root,
2280 &ev->x, &ev->y,
2281 &ev->state)
2282 && ev->state == state
2283 && (ev->x_root != x || ev->y_root != y))
2284 handle_single_motion_event (mw, ev);
2285 else
2286 handle_single_motion_event (mw, &oldev);
2289 static void
2290 Start (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2292 XlwMenuWidget mw = (XlwMenuWidget)w;
2294 if (!mw->menu.popped_up)
2296 menu_post_event = *ev;
2297 /* If event is set to CurrentTime, get the last known time stamp.
2298 This is for calculating if (popup) menus should stay up after
2299 a fast click. */
2300 if (menu_post_event.xbutton.time == CurrentTime)
2301 menu_post_event.xbutton.time
2302 = XtLastTimestampProcessed (XtDisplay (w));
2304 pop_up_menu (mw, (XButtonPressedEvent*) ev);
2306 else
2308 /* If we push a button while the menu is posted semipermanently,
2309 releasing the button should always pop the menu down. */
2310 next_release_must_exit = 1;
2312 /* notes the absolute position of the menubar window */
2313 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2314 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2316 /* handles the down like a move, slots are compatible */
2317 ev->xmotion.is_hint = 0;
2318 handle_motion_event (mw, &ev->xmotion);
2322 static void
2323 Drag (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2325 XlwMenuWidget mw = (XlwMenuWidget)w;
2326 if (mw->menu.popped_up)
2327 handle_motion_event (mw, &ev->xmotion);
2330 /* Do nothing.
2331 This is how we handle presses and releases of modifier keys. */
2332 static void
2333 Nothing (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2337 static widget_value *
2338 find_first_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2340 widget_value *current = item;
2341 enum menu_separator separator;
2343 while (lw_separator_p (current->name, &separator, 0) || !current->enabled
2344 || (skip_titles && !current->call_data && !current->contents))
2345 if (current->next)
2346 current=current->next;
2347 else
2348 return NULL;
2350 return current;
2353 static widget_value *
2354 find_next_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2356 widget_value *current = item;
2357 enum menu_separator separator;
2359 while (current->next && (current=current->next) &&
2360 (lw_separator_p (current->name, &separator, 0) || !current->enabled
2361 || (skip_titles && !current->call_data && !current->contents)))
2364 if (current == item)
2366 if (mw->menu.old_depth < 2)
2367 return current;
2368 current = mw->menu.old_stack [mw->menu.old_depth - 2]->contents;
2370 while (lw_separator_p (current->name, &separator, 0)
2371 || !current->enabled
2372 || (skip_titles && !current->call_data
2373 && !current->contents))
2375 if (current->next)
2376 current=current->next;
2378 if (current == item)
2379 break;
2384 return current;
2387 static widget_value *
2388 find_prev_selectable (XlwMenuWidget mw, widget_value *item, int skip_titles)
2390 widget_value *current = item;
2391 widget_value *prev = item;
2393 while ((current=find_next_selectable (mw, current, skip_titles))
2394 != item)
2396 if (prev == current)
2397 break;
2398 prev=current;
2401 return prev;
2404 static void
2405 Down (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2407 XlwMenuWidget mw = (XlwMenuWidget) w;
2408 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2409 int popup_menu_p = mw->menu.top_depth == 1;
2411 /* Inside top-level menu-bar? */
2412 if (mw->menu.old_depth == mw->menu.top_depth)
2413 /* When <down> in the menu-bar is pressed, display the corresponding
2414 sub-menu and select the first selectable menu item there.
2415 If this is a popup menu, skip title item of the popup. */
2416 set_new_state (mw,
2417 find_first_selectable (mw,
2418 selected_item->contents,
2419 popup_menu_p),
2420 mw->menu.old_depth);
2421 else
2422 /* Highlight next possible (enabled and not separator) menu item. */
2423 set_new_state (mw, find_next_selectable (mw, selected_item, popup_menu_p),
2424 mw->menu.old_depth - 1);
2426 remap_menubar (mw);
2429 static void
2430 Up (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2432 XlwMenuWidget mw = (XlwMenuWidget) w;
2433 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2434 int popup_menu_p = mw->menu.top_depth == 1;
2436 /* Inside top-level menu-bar? */
2437 if (mw->menu.old_depth == mw->menu.top_depth)
2439 /* FIXME: this is tricky. <up> in the menu-bar should select the
2440 last selectable item in the list. So we select the first
2441 selectable one and find the previous selectable item. Is there
2442 a better way? */
2443 /* If this is a popup menu, skip title item of the popup. */
2444 set_new_state (mw,
2445 find_first_selectable (mw,
2446 selected_item->contents,
2447 popup_menu_p),
2448 mw->menu.old_depth);
2449 remap_menubar (mw);
2450 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2451 set_new_state (mw,
2452 find_prev_selectable (mw,
2453 selected_item,
2454 popup_menu_p),
2455 mw->menu.old_depth - 1);
2457 else
2458 /* Highlight previous (enabled and not separator) menu item. */
2459 set_new_state (mw, find_prev_selectable (mw, selected_item, popup_menu_p),
2460 mw->menu.old_depth - 1);
2462 remap_menubar (mw);
2465 void
2466 Left (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2468 XlwMenuWidget mw = (XlwMenuWidget) w;
2469 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2471 /* Inside top-level menu-bar? */
2472 if (mw->menu.old_depth == mw->menu.top_depth)
2473 /* When <left> in the menu-bar is pressed, display the previous item on
2474 the menu-bar. If the current item is the first one, highlight the
2475 last item in the menubar (probably Help). */
2476 set_new_state (mw, find_prev_selectable (mw, selected_item, 0),
2477 mw->menu.old_depth - 1);
2478 else if (mw->menu.old_depth == 1
2479 && selected_item->contents) /* Is this menu item expandable? */
2481 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2482 remap_menubar (mw);
2483 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2484 if (!selected_item->enabled && find_first_selectable (mw,
2485 selected_item,
2487 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2488 mw->menu.old_depth - 1);
2491 else
2493 pop_new_stack_if_no_contents (mw);
2494 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2495 mw->menu.old_depth - 2);
2498 remap_menubar (mw);
2501 void
2502 Right (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2504 XlwMenuWidget mw = (XlwMenuWidget) w;
2505 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2507 /* Inside top-level menu-bar? */
2508 if (mw->menu.old_depth == mw->menu.top_depth)
2509 /* When <right> in the menu-bar is pressed, display the next item on
2510 the menu-bar. If the current item is the last one, highlight the
2511 first item (probably File). */
2512 set_new_state (mw, find_next_selectable (mw, selected_item, 0),
2513 mw->menu.old_depth - 1);
2514 else if (selected_item->contents) /* Is this menu item expandable? */
2516 set_new_state (mw, selected_item->contents, mw->menu.old_depth);
2517 remap_menubar (mw);
2518 selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2519 if (!selected_item->enabled && find_first_selectable (mw,
2520 selected_item,
2522 set_new_state (mw, find_first_selectable (mw, selected_item, 0),
2523 mw->menu.old_depth - 1);
2525 else
2527 pop_new_stack_if_no_contents (mw);
2528 set_new_state (mw, mw->menu.old_stack [mw->menu.old_depth - 2],
2529 mw->menu.old_depth - 2);
2532 remap_menubar (mw);
2535 /* Handle key press and release events while menu is popped up.
2536 Our action is to get rid of the menu. */
2537 static void
2538 Key (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2540 XlwMenuWidget mw = (XlwMenuWidget)w;
2542 /* Pop down everything. */
2543 mw->menu.new_depth = 1;
2544 remap_menubar (mw);
2546 if (mw->menu.popped_up)
2548 mw->menu.popped_up = False;
2549 ungrab_all ((Widget)mw, ev->xmotion.time);
2550 if (XtIsShell (XtParent ((Widget) mw)))
2551 XtPopdown (XtParent ((Widget) mw));
2552 else
2554 XtRemoveGrab ((Widget) mw);
2555 display_menu (mw, 0, False, NULL, NULL, NULL);
2559 /* callback */
2560 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)0);
2563 static void
2564 Select (Widget w, XEvent *ev, String *params, Cardinal *num_params)
2566 XlwMenuWidget mw = (XlwMenuWidget)w;
2567 widget_value* selected_item = mw->menu.old_stack [mw->menu.old_depth - 1];
2569 /* If user releases the button quickly, without selecting anything,
2570 after the initial down-click that brought the menu up,
2571 do nothing. */
2572 if ((selected_item == 0
2573 || ((widget_value *) selected_item)->call_data == 0)
2574 && !next_release_must_exit
2575 && (ev->xbutton.time - menu_post_event.xbutton.time
2576 < XtGetMultiClickTime (XtDisplay (w))))
2577 return;
2579 /* pop down everything. */
2580 mw->menu.new_depth = 1;
2581 remap_menubar (mw);
2583 if (mw->menu.popped_up)
2585 mw->menu.popped_up = False;
2586 ungrab_all ((Widget)mw, ev->xmotion.time);
2587 if (XtIsShell (XtParent ((Widget) mw)))
2588 XtPopdown (XtParent ((Widget) mw));
2589 else
2591 XtRemoveGrab ((Widget) mw);
2592 display_menu (mw, 0, False, NULL, NULL, NULL);
2596 /* callback */
2597 XtCallCallbackList ((Widget)mw, mw->menu.select, (XtPointer)selected_item);
2601 \f/* Special code to pop-up a menu */
2602 static void
2603 pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event)
2605 int x = event->x_root;
2606 int y = event->y_root;
2607 int w;
2608 int h;
2609 int borderwidth = mw->menu.shadow_thickness;
2610 Screen* screen = XtScreen (mw);
2611 Display *display = XtDisplay (mw);
2613 next_release_must_exit = 0;
2615 mw->menu.inside_entry = NULL;
2616 XtCallCallbackList ((Widget)mw, mw->menu.open, NULL);
2618 if (XtIsShell (XtParent ((Widget)mw)))
2619 size_menu (mw, 0);
2621 w = mw->menu.windows [0].width;
2622 h = mw->menu.windows [0].height;
2624 x -= borderwidth;
2625 y -= borderwidth;
2626 if (x < borderwidth)
2627 x = borderwidth;
2628 if (x + w + 2 * borderwidth > WidthOfScreen (screen))
2629 x = WidthOfScreen (screen) - w - 2 * borderwidth;
2630 if (y < borderwidth)
2631 y = borderwidth;
2632 if (y + h + 2 * borderwidth> HeightOfScreen (screen))
2633 y = HeightOfScreen (screen) - h - 2 * borderwidth;
2635 mw->menu.popped_up = True;
2636 if (XtIsShell (XtParent ((Widget)mw)))
2638 XtConfigureWidget (XtParent ((Widget)mw), x, y, w, h,
2639 XtParent ((Widget)mw)->core.border_width);
2640 XtPopup (XtParent ((Widget)mw), XtGrabExclusive);
2641 display_menu (mw, 0, False, NULL, NULL, NULL);
2642 mw->menu.windows [0].x = x + borderwidth;
2643 mw->menu.windows [0].y = y + borderwidth;
2644 mw->menu.top_depth = 1; /* Popup menus don't have a bar so top is 1 */
2646 else
2648 XEvent *ev = (XEvent *) event;
2650 XtAddGrab ((Widget) mw, True, True);
2652 /* notes the absolute position of the menubar window */
2653 mw->menu.windows [0].x = ev->xmotion.x_root - ev->xmotion.x;
2654 mw->menu.windows [0].y = ev->xmotion.y_root - ev->xmotion.y;
2655 mw->menu.top_depth = 2;
2658 #ifdef emacs
2659 x_catch_errors (display);
2660 #endif
2661 if (XtGrabPointer ((Widget)mw, False,
2662 (PointerMotionMask
2663 | PointerMotionHintMask
2664 | ButtonReleaseMask
2665 | ButtonPressMask),
2666 GrabModeAsync, GrabModeAsync, None,
2667 mw->menu.cursor_shape,
2668 event->time) == Success)
2670 if (! GRAB_KEYBOARD
2671 || XtGrabKeyboard ((Widget)mw, False, GrabModeAsync,
2672 GrabModeAsync, event->time) == Success)
2674 XtSetKeyboardFocus((Widget)mw, None);
2675 pointer_grabbed = 1;
2677 else
2678 XtUngrabPointer ((Widget)mw, event->time);
2681 #ifdef emacs
2682 if (x_had_errors_p (display))
2684 pointer_grabbed = 0;
2685 XtUngrabPointer ((Widget)mw, event->time);
2687 x_uncatch_errors ();
2688 #endif
2690 ((XMotionEvent*)event)->is_hint = 0;
2691 handle_motion_event (mw, (XMotionEvent*)event);